金融と工学のあいだ

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

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による単語の分散表現のチュートリアル - 金融と工学のあいだ

CS224n「Lecture2 Word Vector Representations: word2vec」


Lecture 2 | Word Vector Representations: word2vec

Word meaning

離散表現の問題

  • 細かいニュアンスが消えてしまう
  • 人手で作らないといけない
  • 単語種長のone-hot vectorが必要になる
  • 近い意味の単語が全く異なる語として扱われる

分散表現

  • ある単語に近い単語はその単語の意味を表現する f:id:kumechann:20170507000225p:plain
  • 単語の意味が固定長のみつベクトルで表現される
    • 意味の近い単語は類似度が高くなるように学習されている f:id:kumechann:20170507000509p:plain

word2vecとは

単語tからのprediction

f:id:kumechann:20170507001215p:plain - 全ての単語t=1,…,Tに対して、距離mにあるた単語の確率が最大化されるように学習を行う

objective functionとは

  • objective function = loss function = cost function

詳細

  • 確率のモデリングにはSoftmax関数を用いる
    • Softmaxは実数を0~1の値(確率)にマップできるf:id:kumechann:20170507002142p:plain

参考文献

CS224n「Lecture1 Intro to NLP and Deep Learning」


Lecture 1 | Natural Language Processing with Deep Learning

  • CS224nのvideoが公開されたのでこちらにする。

自然言語処理とは?

自然言語のレベル

  • 音声 → 音素
  • 文書 → token化
  • 構文解析 → 意味解析

自然言語の特徴

  1. 離散
    • 脳内では連続的な信号である
  2. シンボリック
  3. 分類的

DL

DLと他の機械学習の違い

  • 自動で適切な特徴量を取得する

なぜDLをつかうの?

  • 特徴量を設計しなくて良い
  • ユニバーサル
  • 現在進行系で改善されている

なぜ成功したの?

なぜNLPは難しいの?

  • 複雑
  • プログラミングと違ってあいまい
  • コンテキストによって意味が異なる

Deep NLP

  • Deep LearningとNLPをくみあわせたもの
  • 以下で大きな成果を出した
    1. レベル:speech, words, syntax, semantics
    2. ツール:speech, entities, parsing
    3. アプリ:機械翻訳、意味解析、会話エージェント、QAシステム

リンク

CS224n「Lecture1 Intro to NLP and Deep Learning」


CS224D Lecture 1 - 29th Mar 2016 - Introduction.mp4

Deep Learningを研究する理由

  • 下記によりパフォーマンスが上がった

DLが効果を発揮した分野

Phonology・Morphology・Word・Syntax・Semantics

  • Phonology:ある言語の音の体系およびその音素の分析と分類の研究
  • Morphology:形態。容認可能な単語を形成するための規則の研究。
  • word
  • syntax
  • semantics
  • DLは上記すべてvectorとして表現する。
  • その他にも
    • Sentiment Analysis
    • Question Answering
    • Machine Translation

リンク