金融と工学のあいだ

興味関心に関するメモ(機械学習、検索エンジン、プログラミングなど)

ChainerでWaveNetによる音声合成のチュートリアルを書いてみた

Introduction

このチュートリアルではWaveNetを使ったボコーダーにより人の音声を合成します。実行できるチュートリアルは最下部にあります。

ボコーダーとは、音声をパラメータ化した入力を元に音声を合成することです。例えば、ロボットボイスは、人の音声からその発話の特徴量をパラメータとして取得し、それを元にロボットの音色の音声を合成しています。そのため、音声合成で一般的なText-to-Speech(TTS)のように文字列を入力とするのではなく、音声を入力として音声合成します。

f:id:kumechann:20180817213142p:plain

WaveNetは、生の音声波形を生成するDeep Neural Networkです。 上記の画像のように、音声波形はあるサンプリングレートで毎時刻の値で波形が保存されていますが、それをWaveNetは生成します。また、今回はボコーダーということで、ランダムに音声を生成するのではなく、入力した音声と似た発話の音声を合成できるようになっています。どいういうことかというと、音声をパラメータ化してWaveNetの入力として使うことで、生成される音声に条件付けできるようになっています。

このチュートリアルでわかることは以下の通りです。

  1. WaveNetの概要
  2. 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は音声波形 {\bf x} = \{x_1, ..., x_T\}全体の生成確率を、それぞれの音声サンプル x_tの生成確率の積としてモデリングしています。

\begin{align*} p({\bf x}) = \prod_{t=1}^T p(x_t| x_1, ..., x_{t-1}) \end{align*}

ただ、単にPixcelCNNを適用しただけではなく、WaveNetを提案する上で以下のような工夫もされています。

  1. 単なるCausal Convolutionではなく、Dilated Causal Convolutionを使っていること
  2. 回帰問題としてではなく、識別問題としてLoss関数を計算し、最適化すること
  3. Residual層とスキップコネクションを使っていること
  4. 他の補助的な入力を使って、生成される音声に条件を付けられること

Dilated Causal Convolution

WaveNetの大きな違いの1つに、単なるCausal Convolutionではなく、Dilated Causal Convolutionを使っていることが挙げられます。そもそも、Causal Convolutionは以下のようなネットワークで、これを使うことで x_tの生成を行うのに過去のデータ \{x_1, ..., x_T\}にのみ依存するようにできます。

f:id:kumechann:20180817220003p:plain [1]より

ただ、上記のネットワークだと出力に影響を与える入力は5(=#layers + filter size -1)しかありません。そのため、音声のような長期の関係性があるデータを生成するためには隠れ層を相当な数増やさなければなりません。

その解決策として、下記のようなDilated Causal Convolutionを使用します。

f:id:kumechann:20180817221111p:plain [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全体としては、以下のようなネットワークを使用しています。

f:id:kumechann:20180817223447p:plain

スキップコネクションを使うことで収束を高速化します。

Conditional WaveNet

各Residual Blockの入力に補助的な特徴量を追加することができます。

f:id:kumechann:20180817224009p:plain [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 ドキュメント

f:id:kumechann:20180817161133j:plain

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段階で感情を表現しています。

f:id:kumechann:20180524182951p:plain cited from [1]

このチュートリアルでは、感情分析をRecursive Neural Networkで行います。 Recursive Neural Networkとは木構造再帰的なニューラルネットです。NLPでは文章を木構造で表現することが多く、Recursive Neural Networkはよく使用されます。今回は、まず最初にミニバッチ化しないRecursive Neural Networkの学習方法を学んだ後、発展的な話として、ミニバッチ化したRecursive Neural Networkの学習方法を説明しています。

このチュートリアルでわかることは以下の通りです。

  1. Recursive Neural Networkとは
  2. 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では分岐数を固定した木構造をしようします。 二分木の場合、現在のノードの隠れ状態ベクトル {\bf h}_Pは、左と右の子ノードの隠れ状態ベクトル {\bf h}_L {\bf h}_Rから以下のように計算されます。

\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*}

f:id:kumechann:20180524182158p:plain:w300

この演算は木構造の葉ノードからルートノードに向かって順番に計算されます。Recursive Neural Networkは要素数 Tの場合でも、 深さは \log_2(T)で済むため、Recurrent Neural Networkに比べて長距離の要素間の関係を表現しやすいことが期待されます。

Chainerを使ったRecursive Neural Networkによる感情分析の実装

以下のように、Colaboratoryで実装の説明を行っています。ブラウザさえあれば今すぐにGPU環境でチュートリアルを動かすことが可能なので、お試しください。

Sentiment Analisys with Recursive Neural Network — Chainer Colab Notebook 0.0 ドキュメント

f:id:kumechann:20180817214440p:plain

Reference

メモ

作業ログ

f:id:kumechann:20180519103841p:plain:w100

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.

f:id:kumechann:20180414233532g:plain

In the tutorial, you will learn the following things:

  1. The basic idea of generative model
  2. The difference among GAN and other generative models
  3. Generative Adversarial Networks (GAN)
  4. 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  f from an input x to an output  y

\begin{align*} f: x \mapsto y \end{align*}

Therefore, the purpose of model training is obtaining the map f from training data. In the case of unsupervised learning, we use the dataset of inputs {s^{(n)}}={d_1, d_2, \cdots, d_N} as the training data, and create the model f. In supervised learning, we use the dataset of inputs and their outputs {s^{(n)}}={(d_1, c_1), \cdots, (d_N, c_N)}. 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 d_1, d_2, \cdots, d_N and their labels c_1={\rm cat}, c_2={\rm dog}, \cdots, c_N={\rm cat}.

1.2 What is the Generative Model?

When we think about the generative model, it models the probability distribution p: s \mapsto p(s) which generates the training data s. The one of most simple ideas is that the generative model models the probability distribution p with the map f. We assign each x and y of f: x \mapsto y as follows.

  • x : the training data s
  • y : the likelihood of generating the training data s

In the case, because we model the probability distribution p explicitly, we can calculate the likelihood p(s). In this situation, we can often optimize the parameters of p 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 s \sim p(s) according to the distribution in practice. The likelihood p(s) is used only for model training. In the case, we sometimes do not model the probability distribution p(s) directly, but other targets to facilitate sampling.

There are two approaches to model the probability distribution p(s). The first case is to model the probability distributions p(z) and p(s|z) by introducing the latent variable z. The VAE, which is described later, belongs to this category. Second, we introduce the latent variable z and model the sample generator ]s = G(z)] which fits the distribution p(s). The GAN belongs to this category. These models can generate the training data s \sim p(s) by generating the latent variable z 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:

f:id:kumechann:20180414235717p:plain

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 p({\bf s}) 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 s_2, \cdots, s_n, excluding the first dimension s_1, 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 p({\bf s}) 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*}

p_{\mathrm {model}}({\bf s}) is modeled by the two probability distributions p_{\mathrm {model}}({\bf z}) and p_{\mathrm {model}}({\bf s} | {\bf z}) using the latent variable \bf z.

When training a model using the maximum likelihood estimation on {{\bf s}^{(n)}}, we should calculate p_{\mathrm {model}}({\bf s}) or p_{\mathrm {model}}({\bf z}|{\bf s}) explicitly. However, because p_{\mathrm {model}}({\bf s}) includes integral operator as shown in above equation, it is difficult to calculate p_{\mathrm {model}}({\bf s}) and p_{\mathrm {model}}({\bf z}|{\bf s}).

So, VAE approximates p_{\mathrm {model}}({\bf z}|{\bf s}) with q_{\mathrm {model}}({\bf z}|{\bf s}). 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 q_{\mathrm {model}}({\bf z}|{\bf s}). The disadvantage is that calculation of the probability distribution p_{\mathrm {model}}({\bf s}) 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 p({\bf s}) that generates the training data. Instead, we model a generator G: {\bf z} \mapsto {\bf s}. The generator G samples {\bf s} \sim p({\bf s}) from the latent variable \bf z. Apart from the generator G, we create a discriminator D({\bf x}) which identified the samples from the generator G and the true samples from training data. While training the discriminator D, the generator G 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 p_{\mathrm {model}}({\bf s}) because we do not model any probability distribution, and we cannot infer the latent variable \bf z 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 {\bf s} \sim p({\bf s}) generated from the true distribution with the distribution of the samples {\bf s} = G ({\bf z}) generated from the generator.

f:id:kumechann:20180415000459p:plain

The generator G learns the target distribution on the idea of Nash equilibrium [3] of game theory. In detail, while training the discriminator D, the generator G is also trained so that the discriminator D 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 D({\bf s}) is a probability that the sample \bf s 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 {\bf s} \sim p({\bf s}) generated from true distribution and the samples {\bf s} \sim p_{\mathrm{model}}({\bf s}) generated from the generator G, it means that we should minimize the dissimilarity between the two distributions. It is common to use Jensen-Shannon Divergence D_{\mathrm{JS}} to measure the dissimilarity between the distributions[4].

The D_{\mathrm{JS}} of p_{\mathrm{model}}({\bf s}) and p({\bf s}) can be written as follows by using D({\bf s}):

\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 D_{\mathrm{JS}} will be maximized by the discriminator D and minimized by the generator G, or p_{\mathrm{model}}. And the distribution p_{\mathrm model}({\bf s}) generated by G({\bf x}) can match the true distribution p({\bf s}).

\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 D({\bf s}) and the generator G({\bf x}) [5].

f:id:kumechann:20180415000034p:plain

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.

f:id:kumechann:20180415000048p:plain

cited from [6]

In addition, although GAN is known for its difficulty in learning, this paper introduces various techniques for successful learning:

  1. Convert max-pooling layers to convolution layers
  2. Convert fully connected layers to global average pooling layers in the discriminator
  3. Use batch normalization layers in the generator and the discriminator
  4. 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:

  1. Define a network class which inherits chainer.Chain.
  2. Make chainer.links 's instances in the init_scope(): of constructor __init__.
  3. Concatenate chainer.links 's instances with chainer.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:

  1. Use chainer.functions.leaky_relu as activation functions
  2. Deeper than Generator
  3. 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.

f:id:kumechann:20180415000324p:plain

5. Reference

ChainerでGANのチュートリアルを書いてみた

Intoroduction

このチュートリアルではgenerarive adversarial network(GAN)を使って画像を生成します。ご存じの方も多いと思いますが、GANはdeep learningを用いた生成モデル(generative model)の一種で、よくある応用例に画像生成があります。昨今有名な線画自動着色サービスPaintsChainerもGANの技術が応用されています。

このチュートリアルでわかることは以下の通りです。

  1. 生成モデル(generative model)とは
  2. GANと他の生成モデルとの違い
  3. GANの仕組み
  4. Chainerを使ったDCGANの実装

生成モデル (generative model)とは

数理モデルといった場合、対象のふるまいを数式で記述することである。 特に、機械学習の文脈の場合、対象のふるまいを xから yの変換  fで表現し、モデルとする。

\begin{eqnarray*} \mathrm{model} \ \ \ f : x \mapsto y \end{eqnarray*}

したがって、モデルの学習とは、与えられた訓練データを元に変換 fを得ることである。教師なし学習の場合、訓練データは入力データ  dのセット \left\{s^{(n)} \right\} = \left\{ d_1, d_2, \cdots, d_N \right\}を元に変換 fを得る。教師あり学習の場合、訓練データは入力データ  dと出力データ  cのペアのセット \left\{s^{(n)}  \right\} = \left\{ (d_1, c_1), (d_2, c_2), \cdots, (d_N, c_N) \right\}を元に変換 fを得る。例えば、画像を犬か猫かに分類するといった分類問題の教師あり学習考えてみよう。訓練データは入力画像  d_1, d_2, \cdots, d_nとそれに対応する出力ラベル c_1='cat', c_2='dog', \cdots, c_n='cat'となる。

生成モデルと言った時、モデル化の対象は訓練データ sを生成する確率分布   p : s \mapsto p(s) である。最も単純な生成モデルは、訓練データ sを生成する確率分布   p : s \mapsto p(s) を変換 fでモデル化することである。その場合、 f : x \mapsto y x yにそれぞれ以下を割り当てる。

  •  x:訓練データ s
  •  y:訓練データ sを生成する尤度  p(s)

上記設定では、確率分布  p(s) 自体を陽にモデル化しているので、尤度 p(s)を計算することができる。尤度を計算できればその最大化もできるので、モデルの学習工程が単純という利点がある。しかし、尤度を計算できるだけではサンプリングを行うことはできないため、別途サンプリングをするための工程が必要であるという欠点がある。

そもそも、尤度 p(s)は単にモデルの学習に必要なだけで、実用では s \sim p(s)に従うサンプリングをしたいだけの場合も多い。その場合、確率分布 p(s)を直接モデル化せず、サンプリングしやすいように他の対象をモデル化する場合がある。1つ目は、潜在変数 zを導入して確率分布 p(z) p(s|z)をモデル化する場合である。後述するVAEはこれに属する。2つ目は、潜在変数 zを導入して s \sim p(s)に従うサンプル生成器 s = G(z)をモデル化する。GANはこれに属する。 これらのモデルは、分布潜在変数 zを乱数に基いて生成することで、確率分布  p(s)を満たす訓練データ sをサンプリングできる。例えば、画像生成の場合、潜在変数 zから画像を生成する確率分布  p(s)を計算し、尤もらしい画像 sを生成結果として出力する。

このように定義された生成モデルをどのように使うかというと、以下の用途を挙げることができる。

  • 創作活動の補助(線画着色など)
  • 人に対するインターフェイスの提供(自然文生成など)
  • データ作成コストの削減(シミュレータとしての利用など)

注意

生成モデルといったとき、分類問題において判別モデルと対比して説明する場合がある[1]。しかし、GANにおいて生成モデルといったとき、単に訓練データを生成する確率分布のモデルと定義したほうが自然である。

GANと他の生成モデルとの違い

NIPS2016でのGANのチュートリアル[2]に説明があるように、以下の図のように主な生成モデルを分類することができる。f:id:kumechann:20180103225446p:plain

GANの他にも有名な生成モデルとして、Fully visible belief networks (FVBNs)、Variational autoencoder (VAE)などがある。

Fully Visible Belief Networks (FVBNs)

FVBNsは以下の式のように、訓練データを生成する確率分布  p(s)ベイズの定理を使って1次元の確率分布に分解する。

\begin{eqnarray*} p_{\mathrm{model}}({\bf s}) = \prod^{n}_{i=1}p_{\mathrm{model}} (s_i | s_1, \cdots, s_{i-1}) \end{eqnarray*}

最初の1次元  s_1を除く他の次元 s_2, \cdots, s_nは、それ以前に生成された自身のデータを条件に生成しているので、自己回帰モデルと言うこともできる。 FVBNsの一種であるPixcelRNN、PixcelCNNは1次元の分布関数をそれぞれRecurrent Neural Networks(RNN)、Convolutional Neural Networks(CNN)でモデル化している。FVBNsの長所は、陽に計算可能な尤度を用いて学習できることだ。短所は、各次元をシーケンシャルにしか生成できないのでサンプリングが高コストなことだ。

Variational Auto-Encoder (VAE)

VAEは以下の式のように、訓練データを生成する確率分布  p(s)をモデル化する。この時、 p(s)は潜在変数 zを使って2つの確率分布 p(z) p(s|z)をモデル化している。

\begin{eqnarray*} p_{\mathrm{model}}(s) = \int p_{\mathrm{model}} (s|z) p_{\mathrm{model}} (z) dz \end{eqnarray*}

 \left\{s^{(n)}  \right\}だけを用いてモデルを学習する場合、

 p_{\mathrm{model}}(z|s)を計算できる必要があるが、 上記式に積分が含まれるため計算困難である。そのため、

 p_{\mathrm{model}}(z|s)

 q_{\mathrm{model}}(z|s)で近似する。

その結果、尤度の下限を計算が可能になり、尤度の下限を最大化することで学習を行う。VAEの長所は、サンプリングが低コストなこと、 q_{\mathrm{model}}(z|s)を使用して潜在変数の推定ができることだ。短所は、確率分布  p(s)の計算が困難なこと、学習に近似値を用いていることだ。

Generarive Adversarial Networks (GAN)

GANは、FVBNsやVAEと異なり、訓練データを生成する確率分布  p(s)を陽にモデル化しない。その代わりに、潜在変数 zを使って  s \sim p(s)に従う生成器 s = G(z)をモデル化する。それとは別に、生成器によるサンプル s = G(z)と真のデータ s \sim p(s)の識別器 D(x)を作る。識別器 Dを学習しつつ、識別できないように生成器 Gも学習する。 GANの長所は、サンプリングが低コストなこと、画像生成で state-of-the-artであること。短所は、そもそも確率分布を一切モデル化していないので確率分布  p(s)の計算ができないこと、潜在変数の推論ができないことだ。

GANについて詳しく

GANの仕組み

上記で説明したように、GANは潜在変数 zを使って s \sim p(s)に従う生成器 s = G(z)自体をモデル化する。そして、生成器によるサンプル s = G(z)と真のデータ s \sim p(s)の識別器 D(x)を作る。つまり、生成器と判別器という2つのネットワークが登場する。

学習時にすべきことは、真の分布から生成されたサンプル s \sim p(s)と生成器から生成されたサンプル s = G(z), s \sim p(z)の分布をマッチングすることである。

画像

GANでは、ゲーム理論ナッシュ均衡のアイデア元に、分布がマッチするような生成器 Gを学習する。具体的には、識別器 Dを学習しつつ、識別できないように生成器 Gも学習していく。

上記を直感的に説明する時の例として、紙幣の偽造者と警察の関係が頻繁に使用される。偽造者は本物の紙幣とできるだけ似ている偽造紙幣を造る。警察は本物の紙幣と偽造紙幣を見分けようとする。次第に警察の能力が上がり、本物の紙幣と偽造紙幣をうまく見分けられるようになったとする。その時、偽造者は偽造紙幣を使えなくなってしまうため、更に本物に近い偽造紙幣を造るようになる。警察は本物と偽造紙幣を見分けられるようにさらに改善し…という風に繰り返していくと、最終的には偽造者は本物と区別が付かない偽造紙幣を製造できるようになる。

数式を用いて学習工程を説明すると以下のとおりである。 まず、識別器 D(x)は、真のモデルか識別する確率であるので以下のように表記できる。

\begin{eqnarray*} D(x) = \frac{p(x)}{p(x) + p_{\mathrm{model}}(x)} \end{eqnarray*}

この時、真の分布から生成されたサンプル s \sim p(s)

生成器から生成されたサンプル s \sim p_{\mathrm model}(s)

( s = G(z), s \sim p(z)のこと)の分布をマッチングするということは、2つの分布の相違度を最小化するということである。 よく使用される分布間の相違度の定義として、Jensen-Shannon Divergence

 D_{\mathrm{JS}}が存在する。

 s \sim p_{\mathrm{model}}(s)

 s \sim p(s)

 D_{\mathrm{JS}}は識別器 D(x)を使って以下のように書ける。

\begin{eqnarray*} 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)} \frac{2p(x)}{p(x) + p_{\mathrm{model}}(x)} + \mathbb{E}_{p_{\mathrm{model}}} \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{eqnarray*}

上記  D_{\mathrm{JS}}を識別器 D(x)に対しては最大化し、生成器 G(x)に対しては最小化すれば、

真の分布から生成されたサンプル s \sim p(s)

生成器から生成されたサンプル s \sim p_{\mathrm model}(s)

のマッチングができる。

\begin{eqnarray*} \min_{G} \max_{D} \mathbb{E}_{p(x)} \log D(x) + \mathbb{E}_{p_{\mathrm{model}}} \log (1-D(x)) \end{eqnarray*}

実際に学習を行うときは、識別器 D(x)と生成器 G(x)を交互に更新することで上記min-max問題をときます。

f:id:kumechann:20180108184120p:plain

DCGANとは

このあたりがわかりやすい 。 はじめてのGAN

Chainerを使ったDCGANの実装

- chainer/examples/dcgan at master · chainer/chainer · GitHub

参考文献

作業ログ

作業用参考文献

ひとまず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

    f:id:kumechann:20171231131008p:plain

    • 10000 epoch

    f:id:kumechann:20171231131014p:plain

  • 1000epochから10000epochで生成される画像に変化があるので学習はしてそう。夢に出てきそうな謎画像が生成された。

関連論文を読んで見る

ブログを読む

golang設定

やりたいこと

設定のメモ

wantedlyの資料

やっぱり1から書いてみる

まずは簡単にサーバを立ててみよう

ファイル構成はこんな感じ

$ 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

ブラウザから見た感じ f:id:kumechann:20170812002319p:plain

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)
}

最終型

github.com

  • つけた機能
    • メモリ上に配置した仮想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が変わることがわかると思います。
  • f:id:kumechann:20170628152453p:plain

主なアルゴリズム

  • word2vecと呼ばれる手法は実はSkip-gramとCBoWという2つの手法の総称です。
  • 以下で図を使って説明しますが、その時の記号の意味をここで説明します。
    • N:ボキャブラリ数。
    • D:分散表現のベクトルのサイズ。
    • {v_t} :Target Word。サイズは[N,1]。
    • {v_{t+c}}:Context Word。サイズは[N,1]。
    • {L_H}:隠れ層。サイズは[D,1]。
    • {L_O}:出力層。サイズは[N,1]。
    • {W_H}:隠れ層重み行列。サイズは[N,D]。
    • {W_O}:出力層重み行列。サイズは[D,N]。

Skip-gram

  • Target Word {v_t}が与えられた時、Context Word {v_{t+c}}が出現することを予測するように学習する
  • この時、隠れ層重み行列W_Hの各行がそれぞれの単語の分散表現になる。

  • f:id:kumechann:20170628153848p:plain

Continuous Bag of Words (CBoW)

  • Context Word {v_{t+c}}が与えられた時、Target Word {v_t}が出現することを予測するように学習する
  • この時、出力層重み行列{W_O}の各列がそれぞれの単語の分散表現になる。

  • f:id:kumechann:20170628153915p:plain

Skip-gramの詳細

  • チュートリアルでは、以下の観点からSkip-gramをメインで扱います。
    1. 学習アルゴリズムがCBoWに比べて理解しやすい
    2. 単語数が増えても精度が落ちにくく、スケールしやすい

具体例を使った説明

  • 上の例と同じように、ボキャブラリ数Nは10、分散表現のベクトルのサイズDは2とします。
  • 犬という単語をTarget Word、動物という単語をContext Wordとし学習する様子を説明します。
  • Context Wordは複数あるはずなので下記工程をContext Word分繰り返します。

    1. 犬という単語の局所表現は[0 0 1 0 0 0 0 0 0 0]であり、それをTarget Wordとして入力します。
    2. この時、隠れ層重み行列{W_H}の3行目が隠れ層{L_H}になります。
      • ちなみに、ここの値が学習後には犬という単語の分散表現になります。
    3. 出力層重み行列{W_O}と隠れ層{L_H}をかけた結果が出力層{L_O}となります。
    4. 出力層の各要素の値を制限するために、出力層{L_O}にsoftmax関数を適用し{W_O}を計算する
      • 最終的には出力層とContext Wordの誤差を出し、その誤差をネットワークに逆伝播することでパラメータの更新を行う必要があります。
      • しかし、出力層の各要素の値は範囲が制限されていないため-∞〜+∞までの値をとります。Context Wordの局所表現は、[1 0 0 0 0 0 0 0 0 0]のように各要素は0か1の値しか取りません。
      • 出力層の各要素の値を0〜1に制限するため、各要素の値を0〜1の範囲に制限する関数softmaxを適用します。
    5. {W_O}とanimalの局所表現[1 0 0 0 0 0 0 0 0 0]の誤差を計算し、その誤差をネットワークに逆伝播させてパラメータを更新する
  • f:id:kumechann:20170628155336p:plain

Chainerによる実装方法

実装方法

  • 基本的にchainerを使用する場合にはこのような形でimportをします。
    • functionsをF、linksをLのような形でimportすると使いやすいです。
  • 次にskip-gramのネットワーク構造の定義です。
    • コンストラクタ__init__に、ボキャブラリ数n_vocab、分散ベクトルのサイズn_units、損失関数loss_funcを渡すようになっています。
      • init_scope()内でParameterの初期化を行っています。
      • ここで、self.embed内の重み行列Wが隠れ層重み行列{W_H}にあたります。
      • 注意してもらいたいのが、Skip-gramの場合、Target WordとContext Wordを1対1で対応するため、Context WordとTarget Wordを入れ替えても問題がなく、Context WordとTarget Wordを入れ替えて学習させています。(CBoWモデルとコードの整合性が取りやすいからです。)
    • 関数呼び出し__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のネットワーク構造の定義をしています。
    • xに対して重み行列による線形写像self.out(x) (self.out:= L.Linear(n_in, n_out, initialW=0)(x))を計算した後、F.softmax_cross_entropyを計算します。
    • ここで、線形写像self.out(x)は出力層重み行列{W_O}F.softmax_cross_entropyはsoftmax関数と損失計算部分に該当します。
  • Iteratorの定義
    • コンストラクタ__init__に、単語idのリストによる文書データセットdataset、ウインドウサイズwindow、ミニバッチサイズbatch_sizeを渡すようになっています。
      • この中で、文書中の単語の位置をシャッフルしたリストself.orderを作成しています。それは学習する時に、文書の最初から最後まで順番に学習するのではなく、文書からランダムに単語を選択し学習するようにするためです。ウィンドウサイズ分だけ最初と最後を切り取った単語の位置がシャッフルされて入っています。
      • 例:文書データセットdataset中の単語数が100個、ウインドウサイズwindowが5だった場合、self.orderは5から94までの数字がシャッフルされたnumpy.arrayになる。
    • イテレータの定義__next__は、コンストラクタのパラメータに従ってミニバッチサイズ個のTarget Word centerとContext Word contextを返します。
      • position = self.order[i:i_end]で、単語の位置をシャッフルしたリストself.orderからbatch_size分のTarget Wordのインデックスpositionを生成します。(positionは後でself.dataset.takeによってTarget Word centerに変換されます。)
      • 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 Word contextに変換されます。)
  • main関数
    • データ取得
      • trainvalにはそれぞれ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作成、実行を下記コードで行っています。
      • 注意してほしいのは、CPUでもGPUでも動くように、GPUの場合にだけGPU上のメモリにデータを転送する関数convertが引数converterとして渡されています。
      • また、trainer.extendによってログ出力などの機能拡張が行われています。

実行方法

$ 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

リファレンス

単語の分散表現のチュートリアル

単語の意味の表現

なぜ単語の意味を表現する必要があるのか

  1. 表現することで機械に処理させることができるから
    • 機械に処理を行わせるためには、処理したい対象を数値列として表現しなくてはなりません。例えば、私たちが普段見ている画像も各画素のRGBの値として表現しており、その数値列を用いて記録を行ったり、描画したりしています。
    • 自然言語においても、同様のことが言えます。自然言語の意味を機械に処理させたい場合には、その意味を数値列で表現しなくてはなりません。
  2. 単語が意味を成す最小の構成単位だから
    • 自然言語の場合、意味を成す最小の構成単位は「単語」です。意味の最小構成単位である単語を表現できれば、その組み合わせである文やパラグラフ、文章の意味を表現することはそこまで難しくないのでは?(本当は難しいのですが…)と思えるでしょう。
    • 上記の理由から、単語の意味を表現するモチベーションは高いです。

機械による単語と意味の表現方法

  • ここで今すぐに単語の意味の表現の話にうつるべきですが、そもそもどのように単語を機械的に扱ってきたか振り返ろうと思います。
  • 例として、「動物」、「食べ物」、「犬」、「猫」、「猿」、「人間」、「トマト」、「りんご」、「バナナ」、「肉」の計10単語を扱います。
    1. 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. 1つの数字(id)でどの単語か表現ができる
        2. 機械としても、1つの数値で単語を表現できるのでメモリ効率が良い
      • しかし、この方法には大きな問題があります。それはここで単語に割り当てられるidと単語の意味には何の関係もないことです。
        • 例:「動物(id=0)」に対して「食べ物(id=1)」と「犬(id=2)」だと食べ物の方がidが近いが、それは動物という単語に対して「食べ物」の方が「犬」より意味が近いことを表現しているわけではない
      • 特にこの問題は機械学習などの数値解析的手法を適用するときに明らかになります。(例えば、数直線状に上記単語がidの位置に配置されている図を想像して、それを意味のまとまりごとに分断する線を引けるか、考えてみるといいかもしれません。)
    2. 局所表現(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]
          
      • この方法のメリットは以下になります。

        1. idによる表現よりやや複雑になったが、全単語数を数えた後、順に1つずつone-hot表現を単語に割り当てるだけで良いので単純に生成できる
        2. 全ての単語が空間上で等距離に配置され、単語の距離が近い・遠いという状態がなくなり、idによる表現で問題になっていたことが無視できる
      • しかし、この方法も依然として大きな問題を抱えています。
        1. 全ての単語を等距離に配置してしまったため、似たような意味の単語も全く異なった単語として扱われる
          • 例:動物は[1 0 0 0 0 0 0 0 0 0]、犬は[0 0 1 0 0 0 0 0 0 0]であるが、この2単語の距離は他の単語と較べても変わらず、その他の単語と同様完全に数値的に独立した単語として扱われてしまう。
        2. 全単語数に応じてone-hot表現の次元数が線形に増えてしまう
        3. あらかじめ全単語数を知っていないとone-hot表現を作成できない
          • 例:1度one-hot表現を作った後、単語を追加すると新たにone-hot表現を全単語に対してつくりなおさないといけない
    3. 分散表現(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()
          
      • f:id:kumechann:20170626182224p:plain

      • この方法のメリットは以下が挙げられます
        1. もし単語の意味にしたがって正しく実数ベクトルが割り当てられていた場合、別々の単語でも意味が近い単語であれば近い実数ベクトルを持つことになり、数値計算機械学習アルゴリズムが同じように扱ってくれることです。
          • 例:グラフを見てみるとおそらく簡単に意味ごとのグループを作ることができるでしょう。k-means等のアルゴリズムを使えば機械も同様にグループを作ることができます。
        2. 実ベクトルの次元数は全単語数とは関係ないので、全単語数に対して線形に実ベクトルの次元数が増えることもないし、あとから単語を追加しても実ベクトルの次元数が増えることもない
      • しかし、この方法にも実は決定的な問題があります。
        • それは単語の意味にしたがって実数ベクトルを単語に割り当てることです。
        • 一見するとそんな方法は簡単に思いつかないような気がしますし、最近まで実応用ができるほど精度が高い、かつ高速な方法はありませんでした。
      • そこをブレイクスルーしたのがword2vecです。次の記事からはword2vecの詳細について扱います。
      • Chainerによる単語の分散表現のチュートリアル - 金融と工学のあいだ