ChainerでWaveNetによる音声合成のチュートリアルを書いてみた
Introduction
このチュートリアルではWaveNetを使ったボコーダーにより人の音声を合成します。実行できるチュートリアルは最下部にあります。
ボコーダーとは、音声をパラメータ化した入力を元に音声を合成することです。例えば、ロボットボイスは、人の音声からその発話の特徴量をパラメータとして取得し、それを元にロボットの音色の音声を合成しています。そのため、音声合成で一般的なText-to-Speech(TTS)のように文字列を入力とするのではなく、音声を入力として音声合成します。
WaveNetは、生の音声波形を生成するDeep Neural Networkです。 上記の画像のように、音声波形はあるサンプリングレートで毎時刻の値で波形が保存されていますが、それをWaveNetは生成します。また、今回はボコーダーということで、ランダムに音声を生成するのではなく、入力した音声と似た発話の音声を合成できるようになっています。どいういうことかというと、音声をパラメータ化してWaveNetの入力として使うことで、生成される音声に条件付けできるようになっています。
このチュートリアルでわかることは以下の通りです。
- WaveNetの概要
- Chainerを使ったWaveNetによる音声合成の実装
- 音声データの前処理方法
- 独自前処理を含んだDatasetの定義方法
- WaveNetの実装方法
WaveNetとは
WaveNet[1]は、van den Oordらによって2016年に提案された手法で、生の音声波形を生成するDeep Neural Networkです。同じくvan den Oordらによって提案されたPixcelCNN[2]は、数千ものの画素数の画像を精緻に生成できる手法ですが、これを音声に応用したものです。音声は以下のような特有の特性を持ちますが、PixcelCNNを応用したWaveNetは当時のstate of the artな精度を発揮しています。
- 音声は、最低でも毎秒16,000以上のサンプルを含む
- 音声は、長短含め様々な時間スケールで重要な特徴を有する
- 音声は、2次元ではなく、1次元の時系列データである
PixcelCNNと同じように、WaveNetは音声波形全体の生成確率を、それぞれの音声サンプルの生成確率の積としてモデリングしています。
\begin{align*} p({\bf x}) = \prod_{t=1}^T p(x_t| x_1, ..., x_{t-1}) \end{align*}
ただ、単にPixcelCNNを適用しただけではなく、WaveNetを提案する上で以下のような工夫もされています。
- 単なるCausal Convolutionではなく、Dilated Causal Convolutionを使っていること
- 回帰問題としてではなく、識別問題としてLoss関数を計算し、最適化すること
- Residual層とスキップコネクションを使っていること
- 他の補助的な入力を使って、生成される音声に条件を付けられること
Dilated Causal Convolution
WaveNetの大きな違いの1つに、単なるCausal Convolutionではなく、Dilated Causal Convolutionを使っていることが挙げられます。そもそも、Causal Convolutionは以下のようなネットワークで、これを使うことでの生成を行うのに過去のデータにのみ依存するようにできます。
[1]より
ただ、上記のネットワークだと出力に影響を与える入力は5(=#layers + filter size -1)しかありません。そのため、音声のような長期の関係性があるデータを生成するためには隠れ層を相当な数増やさなければなりません。
その解決策として、下記のようなDilated Causal Convolutionを使用します。
[1]より
Dilated Causal Convolutionはフィルタをあてるときに、入力をいくつかスキップします。その結果、出力に影響を与える入力を効果的に過去までさかのぼって使うことができます。
識別問題として最適化
実数連続値を出力とする問題の場合、通常は回帰問題として解くことが多いですが、WaveNetでは識別問題として解いています。 なぜなら、識別問題で使用されるsoftmax分布はその分布の形に一切の仮定がないため任意の分布をモデルでき、結果として精度が良い場合があるからです。
ただ、音声の場合各サンプルが16bitで表現されることが多く、そのままだと16bit=65,536クラスの識別問題として解かなければなりません。 さすがにこの数の識別問題は解けそうもないため、以下のようなµ-law変換[3]を用いて8bit=256に量子化します。
\begin{align*} F(x)=sgn(x) \frac{\log(1+\mu |x|)}{\log(1+\mu)} \end{align*}
これなら画像程度の量子化数になるので扱いやすい問題になります。
Residual層とスキップコネクション
WaveNet全体としては、以下のようなネットワークを使用しています。
スキップコネクションを使うことで収束を高速化します。
Conditional WaveNet
各Residual Blockの入力に補助的な特徴量を追加することができます。
[4]より
すべてのResidual Blockに同じ値を入力する場合、global conditioningといいます。例えば、発話者のidを入力することで、その発話者独特の音声を生成することができます。また、Residual Blockに時系列データを入力する場合、local conditioningと言います。今回のチュートリアルでは発話者のスペクトログラムを入力することで、発話者の細かい音色や内容を生成することができます。
Chainerを使ったWaveNetによる音声合成の実装
以下のように、Colaboratoryで実装の説明を行っています。ブラウザさえあれば今すぐにGPU環境でチュートリアルを動かすことが可能なので、お試しください。「Show on Colaboratory」のリンクをを押すとColaboratoryで開くことができます。
Synthesize Human Speech with WaveNet — Chainer Colab Notebook 0.0 ドキュメント
Reference
ChainerでRecursive Neural Networkによる感情分析のチュートリアルを書いてみた
Introduction
このチュートリアルではRecursive Neural Networkを使って文章の感情分析を行います。
感情分析とは、自然言語処理(Natural Language Processing:NLP)のタスクの1つで、文章に対して書き手の感情(sentiment)を識別する問題です。感情を表現する場合、基本的には肯定的か、否定的かを段階に分けて表現します。例えば、今回使用するデータセットの場合、1(really negative)、2(negative)、3(neutral)、4(positive)、5(really positive)のように5段階で感情を表現しています。
cited from [1]
このチュートリアルでは、感情分析をRecursive Neural Networkで行います。 Recursive Neural Networkとは木構造の再帰的なニューラルネットです。NLPでは文章を木構造で表現することが多く、Recursive Neural Networkはよく使用されます。今回は、まず最初にミニバッチ化しないRecursive Neural Networkの学習方法を学んだ後、発展的な話として、ミニバッチ化したRecursive Neural Networkの学習方法を説明しています。
このチュートリアルでわかることは以下の通りです。
- Recursive Neural Networkとは
- Chainerを使ったRecursive Neural Networkによる感情分析の実装
- ミニバッチ化しないRecursive Neural Networkの学習方法
- ミニバッチ化したRecursive Neural Networkの学習方法
Recursive Neural Networkとは[2]
Recursive Neural Networkとは、再帰ニューラルネット(Recurrent Neural Network)を木構造に拡張したものです。 どちらも頭文字で省略するとRNNと表記することが多いので、どちらを表現しているか注意が必要です。多くの場合、Recurrent Neural Networkを指している事が多いですが、自然言語処理の場合Recursive Neural Networkを指していることもあります。
Recursive Neural Networkでは分岐数を固定した木構造をしようします。 二分木の場合、現在のノードの隠れ状態ベクトルは、左と右の子ノードの隠れ状態ベクトル、から以下のように計算されます。
\begin{align*} {\bf h}_P = a \left( {\bf W} \left[ \begin{array}{l} {\bf h}_L \\ {\bf h}_R \\ \end{array} \right] + {\bf b} \right) \end{align*}
この演算は木構造の葉ノードからルートノードに向かって順番に計算されます。Recursive Neural Networkは要素数の場合でも、 深さはで済むため、Recurrent Neural Networkに比べて長距離の要素間の関係を表現しやすいことが期待されます。
Chainerを使ったRecursive Neural Networkによる感情分析の実装
以下のように、Colaboratoryで実装の説明を行っています。ブラウザさえあれば今すぐにGPU環境でチュートリアルを動かすことが可能なので、お試しください。
Sentiment Analisys with Recursive Neural Network — Chainer Colab Notebook 0.0 ドキュメント
Reference
- [1] Recursive Deep Models for Semantic Compositionality Over a Sentiment Treebank
- [2] 深層学習による自然言語処理 (機械学習プロフェッショナルシリーズ)
深層学習による自然言語処理 (機械学習プロフェッショナルシリーズ)
- 作者: 坪井祐太,海野裕也,鈴木潤
- 出版社/メーカー: 講談社
- 発売日: 2017/05/25
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
メモ
作業ログ
Tutorial for DCGAN with Chainer
DCGAN: Generate the images with Deep Convolutional GAN
0. Introduction
In this tutorial, we generate images with generative adversarial network (GAN). It is a kind of generative model with deep neural network, and often applied to the image generation. The GAN technique is also applied to PaintsChainer a famous automatic coloring service.
In the tutorial, you will learn the following things:
- The basic idea of generative model
- The difference among GAN and other generative models
- Generative Adversarial Networks (GAN)
- Implementation of DCGAN in Chainer
1. The basic idea of generative model
1.1 What is the Model?
In the field of science and engineering, we describe a system using mathematical concepts and language. The description is called as a mathematical model, and the process of developing a mathematical model is mathematical modeling. Especially, in the context of machine learning, we explain a target model by a map from an input to an output
\begin{align*} f: x \mapsto y \end{align*}
Therefore, the purpose of model training is obtaining the map from training data. In the case of unsupervised learning, we use the dataset of inputs as the training data, and create the model . In supervised learning, we use the dataset of inputs and their outputs . As a simple example, let's consider a supervised learning problem such as classifying images into dogs or cats. Then, the training dataset consist of input images and their labels .
1.2 What is the Generative Model?
When we think about the generative model, it models the probability distribution which generates the training data . The one of most simple ideas is that the generative model models the probability distribution with the map . We assign each and of as follows.
- : the training data
- : the likelihood of generating the training data
In the case, because we model the probability distribution explicitly, we can calculate the likelihood . In this situation, we can often optimize the parameters of to maximize the likelihood, and train the model with maximum likelihood estimation. So, there is an advantage that the training process is simple because you can just maximize the likelihood by optimizing the parameters. However, there is a disadvantage that we have to make a mechanism for sampling because we have only the way of calculating the likelihood.
In the first place, we often just want to sample according to the distribution in practice. The likelihood is used only for model training. In the case, we sometimes do not model the probability distribution directly, but other targets to facilitate sampling.
There are two approaches to model the probability distribution . The first case is to model the probability distributions and by introducing the latent variable . The VAE, which is described later, belongs to this category. Second, we introduce the latent variable and model the sample generator ]s = G(z)] which fits the distribution . The GAN belongs to this category. These models can generate the training data by generating the latent variable based on random numbers.
These generative models can be used for the following purposes:
- Assistance for creative activities (e.g. coloring of line drawings)
- Providing interfaces to people (e.g. generating natural sentences)
- Reduction of data creation cost (e.g. use as a simulator)
Note
When we talk about generative model, we sometimes explain it against discrimination
model in classification problem [1]. However, when we talk about generative
model in GAN, it is natural to define it as a model of probability distribution
that generates training data.
2. The difference among GAN and other generative models
As explained in the GAN tutorial in NIPS 2016 [2], the generative models can be classified into the categories as shown in the following figure:
cited from [1]
Besides GAN, other famous generative models are Fully visible belief networks (FVBNs) and Variational autoencoder (VAE).
2.1 Fully Visible Belief Networks (FVBNs)
FVBNs decomposes the probability distribution into one-dimensional probability distributions using the Bayes' theorem as shown in the following equation:
\begin{align*} p_{\mathrm{model}}({\bf s}) = \prod^{n}_{i=1}p_{\mathrm{model}} (s_i | s_1, \cdots, s_{i-1}) \end{align*}
Since the dimensions from , excluding the first dimension , are generated based on the dimensions previously generated, FVBNs can be said to be an autoregressive model. PixcelRNN and PixcelCNN, which are categorized as FVBNs, model one-dimensional distribution functions with Recurrent Neural Networks(RNN) and Convolutional Neural Networks(CNN), respectively. The advantage of FVBNs is that the model can learn with explicitly computable likelihood. The disadvantage is that the sampling cost can be expensive because each dimension can only be generated sequentially.
2.2 Variational Auto-Encoder (VAE)
The VAE models the probability distribution that generates the training data as follows:
\begin{align*} p_{\mathrm {model}}({\bf s}) = \int p_{\mathrm {model}}({\bf s}|{\bf z}) p_{\mathrm {model}}({\bf z}) d{\bf z} \end{align*}
is modeled by the two probability distributions and using the latent variable .
When training a model using the maximum likelihood estimation on , we should calculate or explicitly. However, because includes integral operator as shown in above equation, it is difficult to calculate and .
So, VAE approximates with . As a result, we can calculate the lower bound of the likelihood, and train the model by maximizing the lower bound of the likelihood. The advantage of VAE is that sampling is low cost, and you can estimate latent variables by . The disadvantage is that calculation of the probability distribution is difficult, and approximate values are used for training model.
3. Generarive Adversarial Networks (GAN)
3.1 What is the GAN?
Unlike FVBNs and VAE, GAN does not explicitly models the probability distribution that generates the training data. Instead, we model a generator . The generator samples from the latent variable . Apart from the generator , we create a discriminator which identified the samples from the generator and the true samples from training data. While training the discriminator , the generator is also trained so that th generated samples cannot be identified by the discriminator. The advantages of GAN are low sampling cost and state-of-the-art in image generation. The disadvantage is that we cannot calculate the probability distribution because we do not model any probability distribution, and we cannot infer the latent variable from a sample.
3.2 How the GAN works?
As explained above, GAN uses the two models, the generator and the discriminator. In other words, we set up two neural networks for GAN.
When training the networks, we should match the distribution of the samples generated from the true distribution with the distribution of the samples generated from the generator.
The generator learns the target distribution on the idea of Nash equilibrium [3] of game theory. In detail, while training the discriminator , the generator is also trained so that the discriminator cannot identify the generated samples.
As an intuitive example, the relationship between counterfeiters of banknotes and police is frequently used. The counterfeiters try to make counterfeit notes that are similar to real banknotes. The police try to distinguish real bank notes from counterfeit notes. It is supposed that the ability of the police gradually rises, so that real banknotes and counterfeit notes can be recognized well. Then, the counterfeiters will not be able to use counterfeit banknotes, so they will build more similar counterfeit banknotes to real. As the police improve the skill further, so that they can distinguish real and counterfeit notes... Eventually, the counterfeiter will be able to produce as similar banknote as genuine.
The training process is explained by mathematical expressions as follows. First, since the discriminator is a probability that the sample is generated from the true distribution, it can be expressed as follows:
\begin{align*} D({\bf s}) = \frac{p({\bf s})}{p({\bf s}) + p_{\mathrm{model}}({\bf s})} \end{align*}
Then, when we match the distributions of the samples generated from true distribution and the samples generated from the generator , it means that we should minimize the dissimilarity between the two distributions. It is common to use Jensen-Shannon Divergence to measure the dissimilarity between the distributions[4].
The of and can be written as follows by using :
\begin{align*} 2 D_{\mathrm{JS}} &=& D_{\mathrm{KL}}(p(x)||\bar{p}(x)) + D_{\mathrm{KL}}(p_{\mathrm{model}}(x)||\bar{p}(x)) \\ &=& \mathbb{E}_{p(x)} \log \frac{2p(x)}{p(x) + p_{\mathrm{model}}(x)} + \mathbb{E}_{p_{\mathrm{model}}} \log \frac{2p_{\mathrm{model}}(x)}{p(x) + p_{\mathrm{model}}(x)} \\ &=& \mathbb{E}_{p(x)} \log D(x) + \mathbb{E}_{p_{\mathrm{model}}} \log (1-D(x)) + \log 4 \end{align*}
The will be maximized by the discriminator and minimized by the generator , or . And the distribution generated by can match the true distribution .
\begin{align*} \min_{G} \max_{D} \mathbb{E}_{p(x)} \log D(x) + \mathbb{E}_{p_{\mathrm{model}}} \log (1-D(x)) \end{align*}
When training actually, the above min-max problem is solved by alternately updating the discriminator and the generator [5].
cited from [5]
3.3 What is DCGAN?
In this section, we will introduce the model called DCGAN(Deep Convolutional GAN) proposed by Radford et al.[6]. As shown below, it is a model using CNN(Convolutional Neural Network) as its name suggests.
cited from [6]
In addition, although GAN is known for its difficulty in learning, this paper introduces various techniques for successful learning:
- Convert max-pooling layers to convolution layers
- Convert fully connected layers to global average pooling layers in the discriminator
- Use batch normalization layers in the generator and the discriminator
- Use leaky ReLU activation functions in the discriminator
4. Implementation of DCGAN in Chainer
There is an example of DCGAN in the official repository of Chainer, so we will explain how to implement DCGAN based on this: chainer/examples/dcgan
4.1 Define the generator model
First, let's define a network for the generator.
When we make a network in Chainer, we should follow some rules:
- Define a network class which inherits
chainer.Chain
. - Make
chainer.links
's instances in theinit_scope():
of constructor__init__
. - Concatenate
chainer.links
's instances withchainer.functions
to make whole network.
If you are not familiar with constructing a new network, you can read this tutorial.
As we can see from the constructor __init__
, the Generator
uses the deconvolution layer chainer.links.Deconvolution2D
and the batch normalization chainer.links.BatchNormalization
.
In _call__
, each layer is concatenated by chainer.functions.relu
except the last layer.
Because the first argument of L.Deconvolution
is the channel size of input and
the second is the channel size of output, we can find that each layer halve the
channel size. When we construct Generator
with ch=1024
, the network
is same with the image above.
Note
Be careful when you concatenate a fully connected layer's output and
a convolutional layer's input. As we can see the 1st line of ``__call__ ``,
the output and input have to be concatenated with reshaping by
``chainer.functions.reshape``.
4.2 Define the discriminator model
In addtion, let's define a network for the discriminator.
The Discriminator
network is almost same with the transposed network
of the Generator
. However, there are minor different points:
- Use
chainer.functions.leaky_relu
as activation functions - Deeper than
Generator
- Add some noise when concatenating layers
4.3 Prepare dataset and iterator
Let's retrieve the CIFAR-10 dataset by using Chainer's dataset utility function
chainer.datasets.get_cifar10
. CIFAR-10 is a set of small natural images.
Each example is an RGB color image of size 32x32. In the original images,
each component of pixels is represented by one-byte unsigned integer.
This function scales the components to floating point values in
the interval [0, scale]
.
4.4 Prepare model and optimizer
4.5 Prepare updater
4.6 Prepare trainer and run
4.7 Start training
We can run the exemple as follows.
$ pwd
/root2chainer/chainer/examples/dcgan
$ python train_dcgan.py --gpu 0
GPU: 0
# Minibatch-size: 50
# n_hidden: 100
# epoch: 1000
epoch iteration gen/loss dis/loss ................] 0.01%
0 100 1.2292 1.76914
total [..................................................] 0.02%
this epoch [#########.........................................] 19.00%
190 iter, 0 epoch / 1000 epochs
10.121 iters/sec. Estimated time to finish: 1 day, 3:26:26.372445.
The results will be saved in the director /root2chainer/chainer/examples/dcgan/result/
.
The image is generated by the generator trained with 1000 epochs, and the GIF image
on the top of this page shows generated images at the each 10 epoch.
5. Reference
ChainerでGANのチュートリアルを書いてみた
Intoroduction
このチュートリアルではgenerarive adversarial network(GAN)を使って画像を生成します。ご存じの方も多いと思いますが、GANはdeep learningを用いた生成モデル(generative model)の一種で、よくある応用例に画像生成があります。昨今有名な線画自動着色サービスPaintsChainerもGANの技術が応用されています。
このチュートリアルでわかることは以下の通りです。
- 生成モデル(generative model)とは
- GANと他の生成モデルとの違い
- GANの仕組み
- Chainerを使ったDCGANの実装
生成モデル (generative model)とは
数理モデルといった場合、対象のふるまいを数式で記述することである。 特に、機械学習の文脈の場合、対象のふるまいをからの変換 で表現し、モデルとする。
したがって、モデルの学習とは、与えられた訓練データを元に変換を得ることである。教師なし学習の場合、訓練データは入力データのセットを元に変換を得る。教師あり学習の場合、訓練データは入力データと出力データのペアのセットを元に変換を得る。例えば、画像を犬か猫かに分類するといった分類問題の教師あり学習考えてみよう。訓練データは入力画像とそれに対応する出力ラベルとなる。
生成モデルと言った時、モデル化の対象は訓練データを生成する確率分布 である。最も単純な生成モデルは、訓練データを生成する確率分布 を変換でモデル化することである。その場合、のとにそれぞれ以下を割り当てる。
- :訓練データ
- :訓練データを生成する尤度
上記設定では、確率分布 自体を陽にモデル化しているので、尤度を計算することができる。尤度を計算できればその最大化もできるので、モデルの学習工程が単純という利点がある。しかし、尤度を計算できるだけではサンプリングを行うことはできないため、別途サンプリングをするための工程が必要であるという欠点がある。
そもそも、尤度は単にモデルの学習に必要なだけで、実用ではに従うサンプリングをしたいだけの場合も多い。その場合、確率分布を直接モデル化せず、サンプリングしやすいように他の対象をモデル化する場合がある。1つ目は、潜在変数を導入して確率分布、をモデル化する場合である。後述するVAEはこれに属する。2つ目は、潜在変数を導入してに従うサンプル生成器をモデル化する。GANはこれに属する。 これらのモデルは、分布潜在変数を乱数に基いて生成することで、確率分布 を満たす訓練データをサンプリングできる。例えば、画像生成の場合、潜在変数から画像を生成する確率分布 を計算し、尤もらしい画像を生成結果として出力する。
このように定義された生成モデルをどのように使うかというと、以下の用途を挙げることができる。
- 創作活動の補助(線画着色など)
- 人に対するインターフェイスの提供(自然文生成など)
- データ作成コストの削減(シミュレータとしての利用など)
注意
GANと他の生成モデルとの違い
NIPS2016でのGANのチュートリアル[2]に説明があるように、以下の図のように主な生成モデルを分類することができる。
GANの他にも有名な生成モデルとして、Fully visible belief networks (FVBNs)、Variational autoencoder (VAE)などがある。
Fully Visible Belief Networks (FVBNs)
FVBNsは以下の式のように、訓練データを生成する確率分布 をベイズの定理を使って1次元の確率分布に分解する。
最初の1次元 を除く他の次元は、それ以前に生成された自身のデータを条件に生成しているので、自己回帰モデルと言うこともできる。 FVBNsの一種であるPixcelRNN、PixcelCNNは1次元の分布関数をそれぞれRecurrent Neural Networks(RNN)、Convolutional Neural Networks(CNN)でモデル化している。FVBNsの長所は、陽に計算可能な尤度を用いて学習できることだ。短所は、各次元をシーケンシャルにしか生成できないのでサンプリングが高コストなことだ。
Variational Auto-Encoder (VAE)
VAEは以下の式のように、訓練データを生成する確率分布 をモデル化する。この時、は潜在変数を使って2つの確率分布、をモデル化している。
だけを用いてモデルを学習する場合、
を計算できる必要があるが、 上記式に積分が含まれるため計算困難である。そのため、
を
で近似する。
その結果、尤度の下限を計算が可能になり、尤度の下限を最大化することで学習を行う。VAEの長所は、サンプリングが低コストなこと、を使用して潜在変数の推定ができることだ。短所は、確率分布 の計算が困難なこと、学習に近似値を用いていることだ。
Generarive Adversarial Networks (GAN)
GANは、FVBNsやVAEと異なり、訓練データを生成する確率分布 を陽にモデル化しない。その代わりに、潜在変数を使って に従う生成器をモデル化する。それとは別に、生成器によるサンプルと真のデータの識別器を作る。識別器を学習しつつ、識別できないように生成器も学習する。 GANの長所は、サンプリングが低コストなこと、画像生成で state-of-the-artであること。短所は、そもそも確率分布を一切モデル化していないので確率分布 の計算ができないこと、潜在変数の推論ができないことだ。
GANについて詳しく
GANの仕組み
上記で説明したように、GANは潜在変数を使ってに従う生成器自体をモデル化する。そして、生成器によるサンプルと真のデータの識別器を作る。つまり、生成器と判別器という2つのネットワークが登場する。
学習時にすべきことは、真の分布から生成されたサンプルと生成器から生成されたサンプルの分布をマッチングすることである。
画像
GANでは、ゲーム理論のナッシュ均衡のアイデア元に、分布がマッチするような生成器を学習する。具体的には、識別器を学習しつつ、識別できないように生成器も学習していく。
上記を直感的に説明する時の例として、紙幣の偽造者と警察の関係が頻繁に使用される。偽造者は本物の紙幣とできるだけ似ている偽造紙幣を造る。警察は本物の紙幣と偽造紙幣を見分けようとする。次第に警察の能力が上がり、本物の紙幣と偽造紙幣をうまく見分けられるようになったとする。その時、偽造者は偽造紙幣を使えなくなってしまうため、更に本物に近い偽造紙幣を造るようになる。警察は本物と偽造紙幣を見分けられるようにさらに改善し…という風に繰り返していくと、最終的には偽造者は本物と区別が付かない偽造紙幣を製造できるようになる。
数式を用いて学習工程を説明すると以下のとおりである。 まず、識別器は、真のモデルか識別する確率であるので以下のように表記できる。
この時、真の分布から生成されたサンプルと
生成器から生成されたサンプル
(のこと)の分布をマッチングするということは、2つの分布の相違度を最小化するということである。 よく使用される分布間の相違度の定義として、Jensen-Shannon Divergence
が存在する。
と
の
は識別器を使って以下のように書ける。
上記 を識別器に対しては最大化し、生成器に対しては最小化すれば、
真の分布から生成されたサンプルと
生成器から生成されたサンプル
のマッチングができる。
実際に学習を行うときは、識別器と生成器を交互に更新することで上記min-max問題をときます。
DCGANとは
このあたりがわかりやすい 。 はじめてのGAN
Chainerを使ったDCGANの実装
- chainer/examples/dcgan at master · chainer/chainer · GitHub
参考文献
- [1] Generative model - Wikipedia
- [2] [1701.00160] NIPS 2016 Tutorial: Generative Adversarial Networks
作業ログ
作業用参考文献
- Generative Adversarial Networks (GAN) の学習方法進展・画像生成・教師なし画像変換
- はじめてのGAN
- ご注文は機械学習ですか?
- Generative Models
- http://cs231n.stanford.edu/slides/2017/cs231n_2017_lecture13.pdf
- 生成モデルの Deep Learning
- 論文紹介 Pixel Recurrent Neural Networks
ひとまずchainer-gan-libを動かしてみよう
- これ pfnet-research/chainer-gan-lib
- なんか久しぶりにcupy, chainerをupdateしてみたら動かなかったので再install。
pip install -e . --no-cache-dir
とかやったらうまくいった。今まで使っていたpython setup.py develop
ではうまくinstallできなかった。 - あとちゃんとpyenv virtualenvをつかってみた 参考
動かした結果
- 1000 epoch
- 10000 epoch
1000epochから10000epochで生成される画像に変化があるので学習はしてそう。夢に出てきそうな謎画像が生成された。
関連論文を読んで見る
- [1406.2661] Generative Adversarial Networks
- [1701.00160] NIPS 2016 Tutorial: Generative Adversarial Networks
- チュートリアルには、他のgenerative modelとの比較があるなど詳しい。
- Generative Models
- この図がわかりやすい
ブログを読む
golang設定
やりたいこと
設定のメモ
wantedlyの資料
やっぱり1から書いてみる
- 結局これをやる thenewstack.io
まずは簡単にサーバを立ててみよう
ファイル構成はこんな感じ
$ tree ../ ../ └── goweb ├── LICENSE ├── README.md └── main.go
main.goの中身はこんな感じ。
package main import ( "fmt" "html" "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) }) log.Fatal(http.ListenAndServe(":8080", nil)) }
実行してみた
$ go run main.go
ブラウザから見た感じ
routerを使ってみた
- routerは"github.com/gorilla/mux"と言うものが有名らしい
$ go get
すると勝手に関連モジュールがdownloadされる
package main import ( "fmt" "net/http" "github.com/gorilla/mux" ) func main() { r := mux.NewRouter() // 単純なハンドラ r.HandleFunc("/", Index) // パスに変数を埋め込み r.HandleFunc("/users", UserIndex) // パスに変数を埋め込み r.HandleFunc("/users/{userId}", UserIndexId) // パス変数で正規表現を使用 // マッチするパスがない場合のハンドラ r.NotFoundHandler = http.HandlerFunc(NotFoundHandler) // http://localhost:8080 でサービスを行う http.ListenAndServe(":8080", r) } func Index(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Index!\n")) } func UserIndex(w http.ResponseWriter, r *http.Request) { w.Write([]byte("UserIndex!\n")) } func UserIndexId(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) fmt.Fprintf(w, "%s user index\n", vars["userId"]) } func NotFoundHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Gorilla!\nNot Found 404\n")) }
JSONを返却する
user.goにuser型の定義を書く
package main type User struct { Id string Name string } type Users []User
UserIndexをJSONを返すように書き直す
func UserIndex(w http.ResponseWriter, r *http.Request) { users := Users{ User{Id: "00000001", Name: "keisuke"}, User{Id: "00000002", Name: "yusuke"}, } json.NewEncoder(w).Encode(users) }
最終型
- つけた機能
- メモリ上に配置した仮想DB
- データのPOST機能
- error機能
Chainerによるword2vecのチュートリアル(1)
word2vecとは
導入
- word2vecは単語の分散表現を生成する手法です。単語の意味が近いほど類似度が大きくなるように、各単語に実ベクトルを割り当てる手法です。
- そもそも単語の意味とはなんでしょうか。
- 人であれば動物と犬という単語が似ているというのはなんとなく分かります。
- しかし、word2vecは何の情報を元に、動物と犬は似ているとか、食べ物と犬は似ていないといった意味の類似度を学習すれば良いのでしょうか。
基本的なアイデア
- word2vecは単語の意味の類似度を単純な情報から学習します。
- それは文章における単語の並びです。ある単語の意味は、その単語の周囲の単語で決まるというアイデアです。
- 学習対象の単語をTarget Word、その周囲の単語をContext Wordと呼びます。ウィンドウサイズcに応じてContex Wordの数は変わります。
- 例として、
The cute cat jumps over the lazy dog.
という文で説明を行います。- 以下の図は全てTarget Wordをcatとした場合のものです。
- ウィンドウサイズcに応じて、catを学習する際に使用するContext Wordが変わることがわかると思います。
主なアルゴリズム
- word2vecと呼ばれる手法は実はSkip-gramとCBoWという2つの手法の総称です。
- 以下で図を使って説明しますが、その時の記号の意味をここで説明します。
- N:ボキャブラリ数。
- D:分散表現のベクトルのサイズ。
- :Target Word。サイズは[N,1]。
- :Context Word。サイズは[N,1]。
- :隠れ層。サイズは[D,1]。
- :出力層。サイズは[N,1]。
- :隠れ層重み行列。サイズは[N,D]。
- :出力層重み行列。サイズは[D,N]。
Skip-gram
- Target Word が与えられた時、Context Word が出現することを予測するように学習する
この時、隠れ層重み行列W_Hの各行がそれぞれの単語の分散表現になる。
Continuous Bag of Words (CBoW)
- Context Word が与えられた時、Target Word が出現することを予測するように学習する
この時、出力層重み行列の各列がそれぞれの単語の分散表現になる。
Skip-gramの詳細
具体例を使った説明
- 上の例と同じように、ボキャブラリ数Nは10、分散表現のベクトルのサイズDは2とします。
- 犬という単語をTarget Word、動物という単語をContext Wordとし学習する様子を説明します。
Context Wordは複数あるはずなので下記工程をContext Word分繰り返します。
- 犬という単語の局所表現は
[0 0 1 0 0 0 0 0 0 0]
であり、それをTarget Wordとして入力します。 - この時、隠れ層重み行列の3行目が隠れ層になります。
- ちなみに、ここの値が学習後には犬という単語の分散表現になります。
- 出力層重み行列と隠れ層をかけた結果が出力層となります。
- 出力層の各要素の値を制限するために、出力層にsoftmax関数を適用しを計算する
- 最終的には出力層とContext Wordの誤差を出し、その誤差をネットワークに逆伝播することでパラメータの更新を行う必要があります。
- しかし、出力層の各要素の値は範囲が制限されていないため-∞〜+∞までの値をとります。Context Wordの局所表現は、
[1 0 0 0 0 0 0 0 0 0]
のように各要素は0か1の値しか取りません。 - 出力層の各要素の値を0〜1に制限するため、各要素の値を0〜1の範囲に制限する関数softmaxを適用します。
- とanimalの局所表現
[1 0 0 0 0 0 0 0 0 0]
の誤差を計算し、その誤差をネットワークに逆伝播させてパラメータを更新する
- 犬という単語の局所表現は
Chainerによる実装方法
- GitHubレポジトリ上のexamples内にword2vecに関するコードがあるので、それに基づいて説明をしていきます。
実装方法
- 基本的にchainerを使用する場合にはこのような形でimportをします。
- functionsをF、linksをLのような形でimportすると使いやすいです。
- 次にskip-gramのネットワーク構造の定義です。
- コンストラクタ
__init__
に、ボキャブラリ数n_vocab
、分散ベクトルのサイズn_units
、損失関数loss_func
を渡すようになっています。- init_scope()内でParameterの初期化を行っています。
- ここでParameterの初期化を行うことが推奨されています。
- PrameterをLinkのattributeとして設定してくれるので、IDEでコードを追いやすくなるなどの効果があります。
- 詳しくはここ Upgrade Guide — Chainer 6.3.0 documentation
- ここで、
self.embed
内の重み行列W
が隠れ層重み行列にあたります。 - 注意してもらいたいのが、Skip-gramの場合、Target WordとContext Wordを1対1で対応するため、Context WordとTarget Wordを入れ替えても問題がなく、Context WordとTarget Wordを入れ替えて学習させています。(CBoWモデルとコードの整合性が取りやすいからです。)
- init_scope()内でParameterの初期化を行っています。
- 関数呼び出し
__call__
は、Target WordのIDx
、Context WordのIDcontext
を受取り損失関数loss_func
で誤差を返します。-
e = self.embed(context)
でcontextに対応する分散表現を取得しています。 batch_size
分のTarget WordのIDx
をContext Wordの数だけbroad castします。x
は[batch_size * n_context,]
,e
は[batch_size * n_context, n_units]
になります。
-
- コンストラクタ
- 損失関数の定義です。実質的には、skip-gramのネットワーク構造の定義をしています。
- Iteratorの定義
- コンストラクタ
__init__
に、単語idのリストによる文書データセットdataset
、ウインドウサイズwindow
、ミニバッチサイズbatch_size
を渡すようになっています。- この中で、文書中の単語の位置をシャッフルしたリスト
self.order
を作成しています。それは学習する時に、文書の最初から最後まで順番に学習するのではなく、文書からランダムに単語を選択し学習するようにするためです。ウィンドウサイズ分だけ最初と最後を切り取った単語の位置がシャッフルされて入っています。 - 例:文書データセット
dataset
中の単語数が100個、ウインドウサイズwindow
が5だった場合、self.order
は5から94までの数字がシャッフルされたnumpy.arrayになる。
- この中で、文書中の単語の位置をシャッフルしたリスト
- イテレータの定義
__next__
は、コンストラクタのパラメータに従ってミニバッチサイズ個のTarget Wordcenter
とContext Wordcontext
を返します。position = self.order[i:i_end]
で、単語の位置をシャッフルしたリストself.order
からbatch_size
分のTarget Wordのインデックスposition
を生成します。(position
は後でself.dataset.take
によってTarget Wordcenter
に変換されます。)offset = np.concatenate([np.arange(-w, 0), np.arange(1, w + 1)])
で、ウインドウを表現するオフセットoffset
を作成しています。pos = position[:, None] + offset[None, :]
によって、それぞれのTarget Wordに対するContext Wordのインデックスpos
を生成します。(pos
は後でself.dataset.take
によってContext Wordcontext
に変換されます。)
- コンストラクタ
- main関数
- データ取得
train
、val
にはそれぞれtrainingデータ、validationデータが入っています。単語のidの列で文書を表現しています。>>> train array([ 0, 1, 2, ..., 39, 26, 24], dtype=int32) >>> val array([2211, 396, 1129, ..., 108, 27, 24], dtype=int32)
trainingデータ
train
に含まれる最大のid+1の値がボキャブラリ数n_vocab
になります。
- 損失関数作成
- モデル作成
- Optimizer作成、Trainer作成、実行を下記コードで行っています。
- データ取得
実行方法
$ pwd /root2chainer/chainer/examples/word2vec $ $ python train_word2vec.py --test # test modeで実行。全データで学習したいときは--testを消去 GPU: -1 # unit: 100 Window: 5 Minibatch-size: 1000 # epoch: 20 Training model: skipgram Output type: hsm n_vocab: 10000 data length: 100 epoch main/loss validation/main/loss 1 4233.75 2495.33 2 1411.14 4990.66 3 4233.11 1247.66 4 2821.66 4990.65 5 4231.94 1247.66 6 5642.04 2495.3 7 5640.82 4990.64 8 5639.31 2495.28 9 2817.89 4990.62 10 1408.03 3742.94 11 5633.11 1247.62 12 4221.71 2495.21 13 4219.3 4990.56 14 4216.57 2495.16 15 4213.52 2495.12 16 5616.03 1247.55 17 5611.34 3742.78 18 2800.31 3742.74 19 1397.79 2494.95 20 2794.1 3742.66
リファレンス
単語の分散表現のチュートリアル
単語の意味の表現
なぜ単語の意味を表現する必要があるのか
- 表現することで機械に処理させることができるから
- 単語が意味を成す最小の構成単位だから
- 自然言語の場合、意味を成す最小の構成単位は「単語」です。意味の最小構成単位である単語を表現できれば、その組み合わせである文やパラグラフ、文章の意味を表現することはそこまで難しくないのでは?(本当は難しいのですが…)と思えるでしょう。
- 上記の理由から、単語の意味を表現するモチベーションは高いです。
機械による単語と意味の表現方法
- ここで今すぐに単語の意味の表現の話にうつるべきですが、そもそもどのように単語を機械的に扱ってきたか振り返ろうと思います。
- 例として、「動物」、「食べ物」、「犬」、「猫」、「猿」、「人間」、「トマト」、「りんご」、「バナナ」、「肉」の計10単語を扱います。
- idによる表現
これは最もシンプルな方法であり、単純に単語それぞれにユニークな番号を割り当てていくだけです。
例:左から右にidを0から割り当てていくと、id=0は動物、id=1は食べ物、・・・、id=9は肉となる。
words = [ "animal", "food", "dog", "cat", "monkey", "human", "tomato", "apple", "banana", "meat" ] print(words[0]) # animal print(words[2]) # dog
この方法のメリットは、誰でもすぐに思いつく方法であり、とても単純の方法であることです。
- 1つの数字(id)でどの単語か表現ができる
- 機械としても、1つの数値で単語を表現できるのでメモリ効率が良い
- しかし、この方法には大きな問題があります。それはここで単語に割り当てられるidと単語の意味には何の関係もないことです。
- 例:「動物(id=0)」に対して「食べ物(id=1)」と「犬(id=2)」だと食べ物の方がidが近いが、それは動物という単語に対して「食べ物」の方が「犬」より意味が近いことを表現しているわけではない
- 特にこの問題は機械学習などの数値解析的手法を適用するときに明らかになります。(例えば、数直線状に上記単語がidの位置に配置されている図を想像して、それを意味のまとまりごとに分断する線を引けるか、考えてみるといいかもしれません。)
- 局所表現(local representation)
- idによる表現は確かに単純ですが、大きな問題を抱えています。この問題を解決するために局所表現、特にその代表的な表現方法「one-hot表現」があります。
全単語数の長さのvectorをそれぞれの単語に用意し、idの位置の値だけ1にし、残りを0にします。
例:
[1 0 0 0 0 0 0 0 0 0]
は動物、[0 1 0 0 0 0 0 0 0 0]
は食べ物、・・・、[0 0 0 0 0 0 0 0 0 1]
は肉となります。words = [ "animal", "food", "dog", "cat", "monkey", "human", "tomato", "apple", "banana", "meat" ] representations = { "animal":[1, 0, 0, 0, 0, 0, 0, 0, 0, 0], "food":[0, 1, 0, 0, 0, 0, 0, 0, 0, 0], "dog":[0, 0, 1, 0, 0, 0, 0, 0, 0, 0], "cat":[0, 0, 0, 1, 0, 0, 0, 0, 0, 0], "monkey":[0, 0, 0, 0, 1, 0, 0, 0, 0, 0], "human":[0, 0, 0, 0, 0, 1, 0, 0, 0, 0], "tomato":[0, 0, 0, 0, 0, 1, 0, 0, 0, 0], "apple":[0, 0, 0, 0, 0, 0, 0, 1, 0, 0], "banana":[0, 0, 0, 0, 0, 0, 0, 0, 1, 0], "meat":[0, 0, 0, 0, 0, 0, 0, 0, 0, 1] } print(representations[words[0]]) # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] print(representations[words[2]]) # [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
この方法のメリットは以下になります。
- idによる表現よりやや複雑になったが、全単語数を数えた後、順に1つずつone-hot表現を単語に割り当てるだけで良いので単純に生成できる
- 全ての単語が空間上で等距離に配置され、単語の距離が近い・遠いという状態がなくなり、idによる表現で問題になっていたことが無視できる
- しかし、この方法も依然として大きな問題を抱えています。
- 全ての単語を等距離に配置してしまったため、似たような意味の単語も全く異なった単語として扱われる
- 例:動物は
[1 0 0 0 0 0 0 0 0 0]
、犬は[0 0 1 0 0 0 0 0 0 0]
であるが、この2単語の距離は他の単語と較べても変わらず、その他の単語と同様完全に数値的に独立した単語として扱われてしまう。
- 例:動物は
- 全単語数に応じてone-hot表現の次元数が線形に増えてしまう
- あらかじめ全単語数を知っていないとone-hot表現を作成できない
- 例:1度one-hot表現を作った後、単語を追加すると新たにone-hot表現を全単語に対してつくりなおさないといけない
- 全ての単語を等距離に配置してしまったため、似たような意味の単語も全く異なった単語として扱われる
- 分散表現(distributed representation)、またな単語埋め込み(word embeddings)
- idによる表現も局所表現も問題を抱えています。その共通する問題として、単語の意味を適切に表現できていないということが挙げられると思います。
- この問題に対応するため、分散表現、もしくは単語埋め込みと呼ばれる手法が生まれました。
分散表現は、単語を高次元の実数ベクトルで表現する手法です。この時、それぞれの単語は意味が近いほど距離が近くなるように実数ベクトルが割り当てられていきます。
例:今回はグラフ化できるように各単語の次元数を2にしました。
import matplotlib.pyplot as plt words = [ "animal", "food", "dog", "cat", "monkey", "human", "tomato", "apple", "banana", "meat" ] representations = { "animal":[-0.3, 0.2], "food":[0.4, -0.1], "dog":[-0.6, 0.4], "cat":[-0.5, 0.5], "monkey":[-0.5, 0.1], "human":[0.0, 0.2], "tomato":[0.6, -0.3], "apple":[0.5, -0.5], "banana":[0.3, -0.5], "meat":[0.1, -0.6] } print(representations[words[0]]) # [-0.3, 0.2] print(representations[words[2]]) # [-0.6, 0.4] for w in words: plt.scatter(representations[w][0], representations[w][1]) plt.annotate(w, xy=(representations[w][0], representations[w][1])) plt.show()
- この方法のメリットは以下が挙げられます
- しかし、この方法にも実は決定的な問題があります。
- それは単語の意味にしたがって実数ベクトルを単語に割り当てることです。
- 一見するとそんな方法は簡単に思いつかないような気がしますし、最近まで実応用ができるほど精度が高い、かつ高速な方法はありませんでした。
- そこをブレイクスルーしたのがword2vecです。次の記事からはword2vecの詳細について扱います。
- Chainerによる単語の分散表現のチュートリアル - 金融と工学のあいだ
- idによる表現