ディープラーニングブログ

Mine is deeper than yours!

深層学習による自然言語処理 - RNN, LSTM, ニューラル機械翻訳の理論

本稿ではニューラルネットワーク誤差逆伝播法,言語モデル,RNN,LSTM,ニューラル機械翻訳の一連の手法について数理的に解説する.

前編の目次

ニューラルネットワーク

リカレントニューラルネットワーク (RNN)

ニューラル機械翻訳 (NMT)

評価手法

arXiv で追う最新のニューラル機械翻訳

ニューラルネットワーク

まったくニューラルネットワークの順伝播・逆伝播を知らない方は,まず 誤差逆伝播法のノート もしくは 逆伝播の仕組み を先に通読することを勧める.手前味噌だが前著 機械学習と深層学習の数理 でも初歩からニューラルネットワークを解説している.

順伝播 (Forwardpropagation)

目次に戻る ↩︎

ウォーミングアップに {5} ユニットの入力層 {\boldsymbol x} (青),{3} ユニットの隠れ層 {\boldsymbol h} (紫),{5} ユニットの出力層 {\boldsymbol y} (赤) の {3} 層のニューラルネットで順伝播を見てみる.{\boldsymbol x}, {\boldsymbol h} 間の重み行列を {\boldsymbol{w^1}},バイアスベクトルを {\boldsymbol{b^1}}{\boldsymbol h}, {\boldsymbol y} 間の重み行列を {\boldsymbol{w^2}},バイアスベクトルを {\boldsymbol{b^2}} とおく.{\boldsymbol{\bar{h}}}, {\boldsymbol{\bar{y}}} は活性 (重み付き総和) で,活性化関数 {f(\cdot)} はすべてシグモイド関数とする.

下図のネットワークとグラフは同義である.バイアスベクトルは省略.

ネットワーク グラフ
f:id:Ryobot:20170216140238p:plain f:id:Ryobot:20170216140236p:plain

{h_j} は次式によって表される.

{\displaystyle h_j = f(\bar{h}_j) = f \left( \sum_{k=1}^{5} w_{jk}^{1} x_k + b_j^1 \right)}

{y_i} は次式によって表される.

$\begin{eqnarray} \displaystyle y_i &=& f(\bar{y}_i) = f \left( \sum_{j=1}^{3} w_{ij}^{2} h_j + b_i^2\right) \\ & & \\ &=& f \left( \sum_{j=1}^{3} w_{ij}^{2} \, \, f \left( \sum_{k=1}^{5} w_{jk}^{1} x_k + b_j^1 \right) + b_i^2\right) \end{eqnarray}$

深層になっても同様の手続きである.

逆伝播 (Backpropagation)

目次に戻る ↩︎

誤差逆伝播 (Back-prop) とは,損失関数を各パラメータで微分して,各パラメータ (Data) における勾配 (Grad) を求め,損失関数が小さくなる方向へパラメータ更新を行うことをいう.ここで勾配は各パラメータに付随する変数と捉えられる.Chainer 内部でも,Variable インスタンスにパラメータ (重み行列とバイアスベクトル) を保持する Data と,各パラメータの勾配を保持する Grad の2つがあり,それらに forward メソッド や backward メソッドを適応して Variable を更新している.

Back-prop における連鎖律とは,多変数関数の微分である.一変数関数なら「合成関数の微分」だけで良いが,多変数関数の場合は「合成関数の微分の和」,言い換えれば「ネットワークの各経路から求まる勾配の総和」となる.言葉では説明しづらいので,{5} 層のニューラルネットで各層間の重み行列の {1} 成分に付随する勾配を求め,層の深さと勾配の関係を追ってみたい.

損失関数は二乗誤差関数を使う.ただし {t_i} は教師信号.

{\displaystyle E =\displaystyle \frac{1}{2} \sum_{i=1}^{5} \left( t_i - y_i \right)^2}偏微分{\displaystyle \frac{\partial E}{\partial y_i} = y_i - t_i}

活性化関数はシグモイド関数を使う.

{\displaystyle f(z) = \frac{1}{1+e^{-z}}}偏微分{\displaystyle \frac{\partial f(z)}{\partial z} =  f(z) \left( 1 - f(z) \right)}

f:id:Ryobot:20170216140235p:plain:w512

{w_{ij}^{4}} の勾配を求める.Back-prop は,{E} => {y_i}{1} 経路.

$\begin{eqnarray} \displaystyle \frac{\partial E}{\partial w_{ij}^{4}} &=& \frac{\partial E}{\partial y_i} \frac{\partial y_i}{\partial \bar{y}_i} \frac{\partial \bar{y}_i}{\partial w_{ij}^{4}} \\ & & \\ &=& \left( y_i - t_i \right) \left( y_i ( 1 - y_i ) \right) h_j^3 \end{eqnarray}$

{w_{jk}^{3}} の勾配を求める.Back-prop は,{E} => {y_i} => {h_j^3}{5} 経路.よって勾配は {5} 経路分の偏微分の総和となる.

$\begin{eqnarray} \displaystyle \frac{\partial E}{\partial w_{jk}^{3}} &=& \sum_{i=1}^{5} \Bigl\{ \frac{\partial E}{\partial y_i} \frac{\partial y_i}{\partial \bar{y}_i} \frac{\partial \bar{y}_i}{\partial h_j^3} \Bigr\} \frac{\partial h_j^3}{\partial \bar{h}_j^3} \frac{\partial \bar{h}_j^3}{\partial w_{jk}^{3}} \\ & & \\ &=& \sum_{i=1}^{5} \Bigl\{ ( y_i - t_i ) \left( y_i \left( 1 - y_i \right) \right) w_{ij}^{4} \Bigr\} \left( h_j^3 ( 1 - h_j^3 ) \right) h_k^2 \end{eqnarray}$

{w_{kl}^{2}} の勾配を求める.Back-prop は,{E} => {y_i} => {h_j^3} => {h_k^2}{5 \times 3} 経路.よって勾配は {15} 経路分の偏微分の総和となる.

$\begin{eqnarray} \displaystyle \frac{\partial E}{\partial w_{kl}^{2}} &=& \sum_{j=1}^{3} \Bigl\{ \sum_{i=1}^{5} \Bigl\{ \frac{\partial E}{\partial y_i} \frac{\partial y_i}{\partial \bar{y}_i} \frac{\partial \bar{y}_i}{\partial h_j^3} \Bigr\} \frac{\partial h_j^3}{\partial \bar{h}_j^3} \frac{\partial \bar{h}_j^3}{\partial h_k^2} \Bigr\} \frac{\partial h_k^2}{\partial \bar{h}_k^2} \frac{\partial \bar{h}_k^2}{\partial w_{kl}^{2}} \\ & & \\ &=& \sum_{j=1}^{3} \Bigl\{ \sum_{i=1}^{5} \Bigl\{ ( y_i - t_i ) \left( y_i \left( 1 - y_i \right) \right) w_{ij}^{4} \Bigr\} \left( h_j^3 ( 1 - h_j^3 ) \right) w_{jk}^{3} \Bigr\} \left( h_k^2 ( 1 - h_k^2 ) \right) h_l^1 \end{eqnarray}$

{w_{lm}^{1}} の勾配を求める.Back-prop は,{E} => {y_i} => {h_j^3} => {h_k^2} => {h_l^1}{5 \times 3 \times 3} 経路.よって勾配は {45} 経路分の偏微分の総和となる.

$\begin{eqnarray} \displaystyle \frac{\partial E}{\partial w_{lm}^{1}} &=& \sum_{k=1}^{3} \Bigl\{ \sum_{j=1}^{3} \Bigl\{ \sum_{i=1}^{5} \Bigl\{ \frac{\partial E}{\partial y_i} \frac{\partial y_i}{\partial \bar{y}_i} \frac{\partial \bar{y}_i}{\partial h_j^3} \Bigr\} \frac{\partial h_j^3}{\partial \bar{h}_j^3} \frac{\partial \bar{h}_j^3}{\partial h_k^2} \Bigr\} \frac{\partial h_k^2}{\partial \bar{h}_k^2} \frac{\partial \bar{h}_k^2}{\partial h_l^1} \Bigr\} \frac{\partial h_l^1}{\partial \bar{h}_l^1} \frac{\partial \bar{h}_l^1}{\partial w_{lm}^{1}} \\ & & \\ &=& \sum_{k=1}^{3} \Bigl\{ \sum_{j=1}^{3} \Bigl\{ \sum_{i=1}^{5} \Bigl\{ ( y_i - t_i ) \left( y_i \left( 1 - y_i \right) \right) w_{ij}^{4} \Bigr\} \left( h_j^3 ( 1 - h_j^3 ) \right) w_{jk}^{3} \Bigr\} \left( h_k^2 ( 1 - h_k^2 ) \right) w_{kl}^{2} \Bigr\} \left( h_l^1 ( 1 - h_l^1 ) \right) x_m \end{eqnarray}$

直感的には1層増えるごとに「重み」と「活性化関数の微分」と「シグマ (経路数の和)」が 1つずつ増えることがわかる.後述のリカレントニューラルネットの Backpropagation Through Time では,各層における誤差を次式によって表すが,{\boldsymbol{e}_h(\cdot)} の漸化式として着目すると,遡行する層が1つ深くなるごとに「重み{W}」と「活性化関数の微分{f'(\cdot)}」が加わっていることがわかる (ベクトル表記なのでシグマはない).

{\displaystyle \boldsymbol{e}_h(t - \tau - 1) = \boldsymbol{e}_h(t - \tau) W \odot f'\left( \boldsymbol{u}(t - \tau - 1) \right)}

リカレントニューラルネットワーク (RNN)

Recurrent Neural Network (RNN) は再帰的な構造を持ったニューラルネットワークである.主に音声認識自然言語処理のような系列データの処理で扱われる.数理的な解説は数式で書き下すリカレントニューラルネットワークが良い.いきなり数式は面食らうという人はRecurrent Neural Network Language Modelの話を先に勧める.これは RNN をニューラル言語モデル (Neural Language Model) に応用した RNNLM の解説で明快である.

ニューラル言語モデルでは次式によって文章の確率を求める.

{\displaystyle P(W) = \prod_{i = 1}^{|W|} P(w_i | w_1 \cdots w_{i-1})}

過去に入力した {(n-1)} 個の単語から次の単語を生起する条件付き確率 {P(w_i | w_0 \cdots w_{i-1})} を求めたいが,最尤推定をベースに幾つかの方法がある.1-gram モデルでは履歴 (過去の単語) を参照せず,次式のように単語の出現回数をカウントする.{\tilde{w}}コーパスの全単語.

{\displaystyle P(w_i | w_1 \cdots w_{i-1}) \approx P(w_i) = \frac{Count(w_i)}{\sum_{\tilde{w}} Count(\tilde{w})}}

本稿で扱う n-gram言語モデルでは次式のように履歴を参照し,文脈を考慮する.

{\displaystyle P(w_i | w_1 \cdots w_{i-1}) \approx P(w_i | w_{i-n+1} \cdots w_{i-1}) = \frac{Count(w_{i-n+1} \cdots w_{i})}{Count(w_{i-n+1} \cdots w_{i-1})}}

Recurrent Neural Network Language Model (RNNLM)

目次に戻る ↩︎

現在〜過去に入力した単語からもっともらしい次の単語を予測するタスクを考える.三層の RNN は試行 {t} における入力層 {w(t)}, 隠れ層 {w(t)}, 出力層 {y(t)} と,試行 {t-1} における隠れ層 {s(t-1)} によって表される.過去の試行における隠れ層を次の試行の入力層に入力することで,過去の情報を考慮している.
モデルは下図の通り.RNNLM開発者のスライドより抜粋.

f:id:Ryobot:20170216140247p:plain:w512

入力層~隠れ層の順伝播は次式で表される.バイアスベクトルは省略.

{\displaystyle \boldsymbol{s}(t) = f\left( U \boldsymbol{w}(t) + W \boldsymbol{s}(t-1) \right)}

{\boldsymbol{u}(t) = U \boldsymbol{w}(t) + W \boldsymbol{s}(t-1)} とおくと,{\boldsymbol{s}(t) = f\left( \boldsymbol{u}(t) \right)}

ただし,{\displaystyle f(z) = \frac{1}{1+e^{-z}}}微分{\displaystyle \frac{\partial f(z)}{\partial z} =  f(z) \left( 1 - f(z) \right)} となる.

  • {w(t)} は one-hot ( {1} 成分だけ {1}, 他の成分は全て {0}) な {D} 次元単語ベクトル.{D} (単語数) は {10,000} ~ {200,000}
  • {U} は重み行列 (単語の辞書).行数 = Word Embeddings (単語の特徴を表現したベクトル) の次元数 {h},列数 = 辞書の単語数 {D}
  • {s(t-1)} は試行 {t-1} における {h} 次元隠れ層ベクトル
  • {W} は重み行列.行数 = 列数 = 隠れ層ベクトルの次元数 {h}
  • {f(\cdot)}シグモイド関数 (各成分を {0} ~ {1} の値に非線形変換).普通は{\tanh(\cdot)} を使うが計算を簡略化
  • {s(t)} は試行 {t} における {h} 次元隠れ層ベクトル.h は {50} ~ {1,000}

隠れ層~出力層の順伝播は次式で表される.バイアスベクトルは省略.

{\displaystyle \boldsymbol{y}(t) = g\left( V \boldsymbol{s}(t) \right)}

{\boldsymbol{v}(t) = V \boldsymbol{s}(t) } とおくと,{\boldsymbol{y}(t) = g\left( \boldsymbol{v}(t) \right)}

ただし,{\displaystyle g(z_m) = \frac{\exp(z_m)}{\sum_{k}\exp(z_k)}}

微分{\displaystyle \frac{\partial g(z_{m'})}{\partial z_m} = \left\{ \begin{array}{ll} g(z_{m'}) ( 1- g(z_{m'})) & (m'=m) \\ -g(z_{m'}) g(z_m)  & (m'\neq m) \end{array} \right.} となる.

  • {V} は重み行列.各行は辞書の各単語に対応しており,各行と {s(t)}内積によって各単語のスコアを決定する.行数 = 辞書の単語数 {D}, 列数 = 隠れ層ベクトルの次元数 {h}
  • {g(\cdot)} はソフトマックス関数 (各成分を {0} ~ {1} の値,合計が {1}非線形変換)
  • {y(t)}{D} 次元ベクトルの確率分布.各単語の生起確率を予測する.

誤差逆伝播 (Back-prop) として,二乗誤差関数を使うことを考える.{n} はデータセットのインデックス,{\boldsymbol t} は教師データのラベルを表す.

{\displaystyle E_{se} =\displaystyle \frac{1}{2} \sum_{n=1}||\boldsymbol t_n - \boldsymbol y_n||^2}

この偏微分{\displaystyle \frac{\partial E_{se}}{\partial \boldsymbol y_n} = \boldsymbol y_n - \boldsymbol t_n} である.

求めたいモデルのパラメータは {U, V, W} なので,各パラメータ {U, V, W}{E}偏微分し,勾配を求め,二乗誤差関数を最小化する方向へパラメータを更新していく.

{\displaystyle \frac{\partial E_{se}}{\partial V} = \frac{\partial E_{se}}{\partial \boldsymbol{v}(t)} \frac{\partial \boldsymbol{v}(t)}{\partial V} = \boldsymbol{s}(t) \boldsymbol{e}_o(t)^{T}}

{\displaystyle \frac{\partial E_{se}}{\partial U} = \frac{\partial E_{se}}{\partial \boldsymbol{u}(t)} \frac{\partial \boldsymbol{u}(t)}{\partial U} = \boldsymbol{x}(t) \boldsymbol{e}_h(t)^{T}}

{\displaystyle \frac{\partial E_{se}}{\partial W} = \frac{\partial E_{se}}{\partial \boldsymbol{u}(t)} \frac{\partial \boldsymbol{u}(t)}{\partial W} = \boldsymbol{s}(t-1) \boldsymbol{e}_h(t)^{T}}

ただし,{ \displaystyle \boldsymbol{e}_o(t) = \frac{\partial E}{\partial \boldsymbol{v}(t)} }{ \displaystyle \boldsymbol{e}_h(t) = \frac{\partial E}{\partial \boldsymbol{u}(t)} }

{ \boldsymbol{e}_o(t) }{ \boldsymbol{e}_h(t) } は Back-prop において誤差と呼ばれる.これは次式によって求めることができる.

$\begin{eqnarray}\displaystyle \boldsymbol{e}_o(t) &=& \frac{\partial E_{se}}{\partial \boldsymbol{y}(t)} \frac{\partial \boldsymbol{y}(t)}{\partial \boldsymbol{v}(t)} \\ & & \\ &=& \left( \boldsymbol{y}(t) - \boldsymbol{t}(t) \right) \odot g'\left(\boldsymbol{v}(t)\right)\end{eqnarray}$

$\begin{eqnarray}\displaystyle \boldsymbol{e}_h(t) &=& \frac{\partial E_{se}}{\partial \boldsymbol{y}(t)} \frac{\partial \boldsymbol{y}(t)}{\partial \boldsymbol{v}(t)} \frac{\partial \boldsymbol{v}(t)}{\partial \boldsymbol{s}(t)} \frac{\partial \boldsymbol{s}(t)}{\partial \boldsymbol{u}(t)} \\ & & \\ &=& \boldsymbol{e}_o(t)^T V \odot f'\left(\boldsymbol{u}(t)\right)\end{eqnarray}$

ただし,{\odot}アダマール積 (成分ごとの積).

以上より,次式によってパラメータを更新する.{\eta} は学習率.

{\displaystyle V(t+1) = V(t) - \eta \boldsymbol{s}(t) \boldsymbol{e}_o(t)^{T}}

{\displaystyle U(t+1) = U(t) - \eta \boldsymbol{x}(t) \boldsymbol{e}_h(t)^{T}}

{\displaystyle W(t+1) = W(t) - \eta \boldsymbol{s}(t-1) \boldsymbol{e}_h(t)^{T}}

これでうまくいくかと思いきやそうではない.二乗誤差関数を使うと誤差の計算が煩雑になってしまうので代替として使われるのが,クラス分類でおなじみの交差エントロピー誤差関数である.{n} はデータセットのインデックス,{k} は出力層のユニットを表す.

{\displaystyle E_{cross} = - \sum_{n} \sum_{k} t_{nk} \log y_{nk}}

これを損失関数に使うと,{ \boldsymbol{e}_o(t) } は簡潔に表される.途中式のソフトマックス関数は出力層の成分同士に依存関係があるので,ベクトルではなく {1} 成分で考える.

{\begin{eqnarray}\displaystyle e_o(t) = \frac{\partial E_{cross}}{\partial v_k(t)} = \sum_{k'} \frac{\partial E_{cross}}{\partial y_{k'}(t)} \frac{\partial y_{k'}(t)}{\partial v_k(t)}  = - \sum_{k'} \frac{t_{k'}(t)}{y_{k'}(t)} \frac{\partial y_{k'}(t)}{\partial v_k(t)} \end{eqnarray}}

ここで {\displaystyle y_{k'}(t)} はソフトマックス関数で,

微分{\displaystyle \frac{\partial y_{k'}(t)}{\partial v_k(t)} = \left\{ \begin{array}{ll} y_k(t) ( 1- y_k(t)) & (k'=k) \\ -y_{k'}(t) y_k(t)  & (k'\neq k) \end{array} \right.}

{k'=k}{k'\neq k} で場合分けして計算すると,

$\begin{eqnarray}\displaystyle e_o(t) &=& - \Bigl\{ \frac{t_{k}(t)}{y_{k}(t)} \frac{\partial y_{k}(t)}{\partial v_k(t)} + \sum_{k'\neq k} \frac{t_{k'}(t)}{y_{k'}(t)} \frac{\partial y_{k'}(t)}{\partial v_k(t)} \Bigr\} \\ & & \\ &=& - \Bigl\{ \frac{t_k(t)}{y_k(t)} y_k(t) ( 1- y_k(t)) + \sum_{k'\neq k} \frac{t_{k'}(t)}{y_{k'}(t)} ( -y_{k'}(t) y_k(t) ) \Bigr\} \\ & & \\ &=& - t_k(t) ( 1- y_k(t)) + \sum_{k'\neq k} t_{k'}(t) y_k(t) \\ & & \\ &=& - t_k(t) + y_k(t) \Bigl\{ t_k(t) + \sum_{k'\neq k} t_{k'}(t) \Bigr\} \\ & & \\ &=& - t_k(t) + y_k(t) \sum_{k'} t_{k'}(t) \end{eqnarray}$

ここで教師信号は,正解の {k}{t_k(t) = 1},不正解の {k}{t_k(t) = 0} なので {\displaystyle \sum_{k} t_{k}(t) = 1}

{e_o(t) = y_k(t) - t_k(t)}.よって,{ \boldsymbol{e}_o(t) = \boldsymbol{y}(t) - \boldsymbol{t}(t)}

Backpropagation Through Time (BPTT)

目次に戻る ↩︎

ここまでの式は,{t+1} からみて直近の過去である {t} の誤差までしか Back-prop に反映していない.そこで,より過去に遡って誤差を反映させる手法として BPTT が使われる.
下図では, 過去の状態 {t-1,t-2} を保持して過去の誤差も加える truncated BPTT を使っている.

f:id:Ryobot:20170216140220p:plain:w512

ひとまず 1 ステップだけ遡る (誤差 {\boldsymbol{e}_h(t-1)}{\boldsymbol{e}_h(t)} の式で表す) と,

$\begin{eqnarray}\displaystyle \boldsymbol{e}_h(t-1) &=& \frac{\partial E}{\partial \boldsymbol{u}(t-1)} \\ & & \\ &=& \frac{\partial E}{\partial \boldsymbol{u}(t)} \frac{\partial \boldsymbol{u}(t)}{\partial \boldsymbol{u}(t-1)} \\ & & \\ &=& \boldsymbol{e}_h(t) \frac{\partial \boldsymbol{u}(t)}{\partial \boldsymbol{s}(t-1)} \frac{\partial \boldsymbol{s}(t-1)}{\partial \boldsymbol{u}(t-1)} \\ & & \\ &=& \boldsymbol{e}_h(t) W \odot f'\left(\boldsymbol{u}(t-1)\right)\end{eqnarray}$

これを一般化すると次式のような再帰的な漸化式として表される.{\tau} は遡行数 (どれくらい過去に遡るか) を表すパラメータ.一般的に {\tau=3} が使われる.

{\displaystyle \boldsymbol{e}_h(t - \tau - 1) = \boldsymbol{e}_h(t - \tau) W \odot f'\left( \boldsymbol{u}(t - \tau - 1) \right)}

よって,次式によってパラメータを更新する.

{\displaystyle V(t+1) = V(t) - \eta \boldsymbol{s}(t) \boldsymbol{e}_o(t)^{T}}

{\displaystyle U(t+1) = U(t) - \eta \sum_{z=0}^{\tau} \boldsymbol{x}(t-z) \boldsymbol{e}_h(t - z)^{T}}

{\displaystyle W(t+1) = W(t) - \eta \sum_{z=0}^{\tau} \boldsymbol{s}(t-z-1) \boldsymbol{e}_h(t - z)^{T}}

余談だが,RNN の Back-prop には,BPTT 以外にも Real Time Recurrent Learning (RTRL) など多数ある.

また,Recurrent NN と混同されがちな Recursive NN (RNN)⾃然⾔語処理分野におけるディープラーニングの現状 が良い解説だった.

近況

Long Short-Term Memory (LSTM)

目次に戻る ↩︎

f:id:Ryobot:20170216140234p:plain

RNN の隠れ層を LSTM block に置き換えた亜種に LSTM がある.RNN が苦手とする系列データの長期依存を学習できるため自然言語処理でよく使われる.LSTM block は,メモリと3つのゲート (入力ゲート, 出力ゲート, 忘却ゲート) から成る.メモリは,Constant Error Carousel というベクトルに過去の入力を溜め込む役割がある.各ゲートは関数なので,LSTM block 自体,関数を合成した関数といえる.このあたりは,以下の資料が参考になる.

LSTM block

入力層からの入力を {embed_t}, LSTM block の出力を {y_t}, 前ステップ {t-1} における LSTM block の出力を {hidden_{t-1}} とおく.また,{W}{R} は重み行列,{b} はバイアスベクトル,{\tanh(\cdot)}tanh 関数,{\sigma(\cdot)}シグモイド関数を表す.

入力 (右) と入力ゲート (左) 忘却ゲート
f:id:Ryobot:20170216140231p:plain f:id:Ryobot:20170216140230p:plain

LSTM block への入力 (素の RNN と同じ) は,次式で表される.

{\bar{a_t} = W_a embed_t + R_a hidden_{t-1} + b_a}

{a_t = \tanh(\bar{a_t})}

入力ゲート (Input Gate) の変換は次式で表される.

{\bar{i_t} = W_i embed_t + R_i hidden_{t-1} + b_i}

{i_t = \sigma(\bar{i_t})}

忘却ゲート (Forget Gate) の変換は次式で表される.

{\bar{f_t} = W_f embed_t + R_f hidden_{t-1} + b_f}

{f_t = \sigma(\bar{f_t})}

メモリセル 出力ゲート (左) と出力 (右)
f:id:Ryobot:20170216140232p:plain f:id:Ryobot:20170216140233p:plain

メモリセルの変換は次式で表される.{cell_t} はメモリの出力,{\odot}アダマール積 (成分ごとの積) を表す. ちなみに,このメモリが勾配消失問題の回避に寄与している.勾配消失は多層でシグモイド関数が重なることで起きる (シグモイド関数微分の値域は {0} ~ {\frac{1}{4}} なので掛ける回数が増えると勾配が {0} になる) ので,非線形変換を持たないメモリは勾配を残しやすい.他の勾配消失に対するアプローチとして,正値の入力を恒等写像する ReLU (Rectifier Linear Unit) や入力をそのまま出力に加算する Residual Block も深層まで勾配を残しやすい.

{cell_t = i_t \odot a_t + f_t \odot cell_{t-1}}

出力ゲート (Output Gate) の変換は次式で表される.

{\bar{o_t} = W_o embed_t + R_o hidden_{t-1} + b_o}

{o_t = \sigma(\bar{o_t})}

LSTM block の出力は次式で表される.

{y_t = hidden_t = o_t \odot \tanh(cell_t)}

以上の関数の合成関数が LSTM block となる.{cell_t}{hidden_t} が次ステップの LSTM block に引き継がれる.

各パラメータの次元数を確認すると,入力 {embed_t}{m} 次元ベクトル (Word Enbeddings), 出力 {y_t}{n} 次元ベクトル ({hidden_t}{n} 次元ベクトル) とおくと,{W_a, W_i, W_f, W_o}{m \times n} 重み行列,{R_a, R_i, R_f, R_o}{n \times n} 重み行列, バイアス項 {b_a, b_i, b_f, b_o}{n} 次元ベクトルになる.

覗き穴結合 (peephole connections) GRU block
f:id:Ryobot:20170216140243p:plain:w512 f:id:Ryobot:20170216140227p:plain:w512

LSTM には現在までに様々なバージョンが提案されている.

覗き穴結合 (peephole connections) は,メモリセルの内部状態を3つのゲートの制御に直接利用する仕組みである.入力ゲート・忘却ゲートに {cell_{t-1}},出力ゲートに {cell_{t}} を peephole重みとアダマール積して加算している.

{\bar{i_t} = W_i embed_t + R_i hidden_{t-1} + p_i \odot cell_{t-1} + b_i}

{\bar{f_t} = W_f embed_t + R_f hidden_{t-1} + p_f \odot cell_{t-1}  + b_f}

{\bar{o_t} = W_o embed_t + R_o hidden_{t-1} + p_o \odot cell_{t}  + b_o}

peephole重み {p_i, p_f, p_o}{n} 次元ベクトルになる.

Gated Recurrent Unit (GRU)

目次に戻る ↩︎

LSTM とよく比較される GRU は,LSTM block に幾つかの変更を加えてシンプルな GRU block にしたモデルである.まず入力ゲートと忘却ゲートを組み合わせて更新ゲートをつくり,また {cell}{hidden} はマージして,それを初期化するリセットゲートを導入する.

更新ゲート (Update Gate) の変換は次式で表される.

{z_t = \sigma \left( W_z embed_t + U_z hidden_{t-1} \right)}

リセットゲート (Reset Gate) の変換は次式で表される.

{r_t = \sigma \left( W_r embed_t + U_r hidden_{t-1} \right)}

隠れ状態の変換は次式で表される.{\odot}アダマール積.

{\tilde{h_t} = \tanh \left( W embed_t + U (r_t \odot hidden_{t-1}) \right)}

{hidden_t = z_t  \odot \tilde{h_t} + (1 - z_t) \odot hidden_{t-1}}

LSTM や GRU,後述の seq2seq はいわゆる内部メモリを持ったニューラルネットで,情報の保持と処理を同時に行う (e.g., メモリセル).これに対し,Neural Turing Machine (arXiv, 2014/10) や Memory Networks (arXiv, 2015/3) は外部メモリ (External Memory) を持ったニューラルネットワークで,情報の保持と処理を切り離すことで RAM メモリのような長期記憶を可能にした.Encoder の中間層を保持する注意 (Attention) はこの中間といえる.

Chainer 実装

言語モデル (次にくる単語の予測) を例に LSTM のフォワード計算をベタに記述した.無論 Chainer 提供の関数 links.LSTM や functions.LSTM を使えば数行で表せる.あとでパープレキシティの算出でも使う.

class ChainerLSTM(Chain):
    def __init__(self):
        super(ChainerLSTM, self).__init__(
            U = L.EmbedID(VocabSize, hiddenSize),
            Wa = L.Linear(hiddenSize, hiddenSize),
            #省略 (Wi, Wf, Wo, Ra, Ri, Rf, Ro も Wa と同様)
            V = L.Linear(hiddenSize, VocabSize),
        )
    def reset(self):
        self.zerograds()
        self.cell = Variable(np.zeros((1, hiddenSize), dtype=np.float32))
        self.hidden = Variable(np.zeros((1, hiddenSize), dtype=np.float32))

def forward(model, sentence):
    model.reset()
    loss = Variable(np.zeros((), dtype=np.float32))
    for i in range(len(sentence)):
        wid = sentence[i]
        embed = model.U(Variable(np.array([wid], dtype=np.int32)))
        a = F.tanh(model.Wa(embed) + model.Ra(model.hidden))
        inputGate = F.sigmoid(model.Wi(embed) +  model.Ri(model.hidden))
        forgetGate = F.sigmoid(model.Wf(embed) +  model.Rf(model.hidden))
        model.cell =  inputGate * a + forgetGate * model.cell
        outputGate = F.sigmoid(model.Wo(embed) +  model.Ro(model.hidden))
        model.hidden = outputGate * F.tanh(model.cell)
        y = model.V(model.hidden)
        nextwid = sentence[i + 1] if (i != len(sentence) - 1) else eosID
        target = Variable(np.array([nextwid], dtype=np.int32))
        loss += F.softmax_cross_entropy(y, target)
    return loss

近況

Quasi-Recurrent Neural Network (QRNN) (arXiv, 2016/11) では,既存の LSTM と同等かそれ以上でありながら,16倍高速に学習できる手法が提案された.最近,自然言語処理で Convolution を使う研究が増えている.New neural network building block allows faster and more accurate text understanding を参照されたい.

f:id:Ryobot:20170216140244j:plain

Neural Architecture Search with Reinforcement Learning (OpenReview, 2016/11) は狂気じみた姿である.RNN に限らず,最適なネットワーク構成を強化学習で探索する試みで,画像認識のエラー率では近年 SOTA を叩き出した DenseNet や Wide ResNet と良い勝負だし,パープレキシティでは SOTA の LSTM を凌駕している.800 ネットワークを 800 GPU で常時訓練.やっぱり狂気.下図では LSTM block っぽい何かを生成している.

f:id:Ryobot:20170216140225p:plain:w512

RNN のドロップアウトとバッチ正規化

目次に戻る ↩︎

RNN の学習を改善する手法を幾つか紹介したい.

ドロップアウト

ドロップアウト (Dropout) は隠れユニットをベルヌーイ分布に従った確率 (50%など) でランダムに無視する手法で,アンサンブル学習に似た役割を果たす.Dropout には正則化の効果があり,訓練データへの過学習を抑えバリデーションデータ (テストデータ) への汎化性能を改善すると言われている.テスト時はパラメータに Dropout で使った確率をかける.

RNN 系譜への単純な Dropout 適応はメモリセルの働きを妨害しないようにするために,時間方向のリカレント接続 {hidden_{t-1} \to hidden_{t}} に使用できない.下図のように非リカレント接続に使用できる.

f:id:Ryobot:20170216140222p:plain

入力と3つのゲートの線形変換におけるドロップアウト {\displaystyle D(\cdot)} の適応は次式のように表される.

{\displaystyle \begin{pmatrix} \bar{\boldsymbol{a}_t} \ \bar{\boldsymbol{i}_t} \ \bar{\boldsymbol{f}_t} \ \bar{\boldsymbol{o}_t} \end{pmatrix} = \boldsymbol{W} D( \boldsymbol{x}_t) + \boldsymbol{R} \boldsymbol{h}_{t - 1} + \boldsymbol{b}}

変分RNN (Variational RNN) では各時間ステップで同じ Dropout をマスクする手法である.下図の異なる矢印の色は異なる Dropout のマスクである.変分 RNN ではパラメータを共有するレイヤーには同じ Dropout を適応しているのがわかる.変分RNN によるパープレキシティの改善は若干なので非リカレント接続の Dropout でだけ十分かもしれない.

f:id:Ryobot:20170216140249p:plain

バッチ正規化

各層の入力データの分布はそれより下層のパラメータ更新によって変化する.各層の勾配はミニバッチ内の平均をとって推定するが,この分布の変化によってミニバッチごとに異なるバイアスが乗ってしまい学習が不安定になるという問題がある.これを内部共変量シフト (Internal Covariate Shift) という.

ところで正規化 (Normalization) とは学習データの各サンプルを平均 {0},分散 {1} に揃える前処理のことで,例えば正規化された画像は明るさのばらつきが小さくなる.同様の塩梅で,バッチ正規化 (Batch Normalization) では各層の入力データの分布を各ユニットともミニバッチごとに平均 {0},分散 {1} に揃える手法のことで内部共変量シフトを抑制できる.またバッチ正規化は正則化の効果もあるので,Dropout や L1, L2 正則化の代用となる.

ミニバッチ {B = { x_{1 \ldots m} }}に含まれる {x} の変換は次式のように表される.このとき学習パラメータを {\gamma , \beta} とおく (スケールとシフトの役割でネットワークの表現力をあげる).

{\displaystyle \mu_{B} \leftarrow \frac{1}{m} \sum_{i=1}^{m} x_i}

{\displaystyle \sigma^2_{B} \leftarrow \frac{1}{m} \sum_{i=1}^{m} (x_i - \mu_{B})^2}

{\displaystyle \hat{x}_i \leftarrow \frac{x_i - \mu_{B}}{\sqrt{\sigma^2_{B} + \epsilon}}}

{\displaystyle y_i \leftarrow \gamma \hat{x}_i + \beta \equiv BN_{\gamma , \beta} (x_i)}

左下図はバッチ正規化の比較.Inception はバッチ正規化なしで,BN はバッチ正規化ありである.BN- のあとの数字は学習率の倍率である.Baseline の学習率は {0.0015} で,x5 なら {0.0075},x30なら {0.045} となる.バッチ正規化すると学習率を上げても学習が安定するので学習速度が早くなるといえる.

バッチ正規化 リカレントバッチ正規化
f:id:Ryobot:20170216140219p:plain:w600 f:id:Ryobot:20170216140245p:plain:w450

リカレントバッチ正規化 (Recurrent Batch Normalization) は,このバッチ正規化を LSTM の内部で行う手法である.論文では MNIST の画像 ({784} ピクセル) を 1ステップに 1ピクセルずつ入力し正解のラベルを予測する Sequential MNIST (pixel by pixel MNIST) というタスクで実験を行っている.結果は右上図で Batch-normalized LSTM の方が早く収束している.

Batch-Normalized LSTM では,入力〜隠れ層と隠れ層〜隠れ層の2箇所に適応している.簡略化のためにバッチ正規化 {\displaystyle BN(\, \cdot \, ; \gamma , \beta)} を次式のように定義する.

{\displaystyle BN(\boldsymbol{h} ; \gamma , \beta) = \beta + \gamma \frac{\boldsymbol{h} - \hat{E} (\boldsymbol{h})}{\sqrt{ \hat{Var}(\boldsymbol{h})+ \epsilon}}}

リカレントバッチ正規化は次式のように表される.ただし,入力と3つのゲートの線形変換は1つにまとめて表記している.

{\displaystyle \begin{pmatrix} \bar{\boldsymbol{a}_t} \ \bar{\boldsymbol{i}_t} \ \bar{\boldsymbol{f}_t} \ \bar{\boldsymbol{o}_t} \end{pmatrix} = BN(\boldsymbol{W} \, \boldsymbol{x}_t ;  \gamma_x , \beta_x) + BN(\boldsymbol{R} \, \boldsymbol{h}_{t - 1} ;  \gamma_h , \beta_h) + \boldsymbol{b}}

{\boldsymbol{c}_t = \sigma( \bar{\boldsymbol{i}_t}) \odot \tanh(\bar{\boldsymbol{a}_t}) +  \sigma(\bar{\boldsymbol{f}_t}) \odot \boldsymbol{c}_{t - 1}}

{\boldsymbol{h}_t = \sigma( \bar{\boldsymbol{o}_t}) \odot \tanh(BN(\boldsymbol{c}_t ; \gamma_c , \beta_c))}

余談だが,バッチ正規化以降,重みを正規化する Weight Normalization,各ユニットとも層ごとに正規化する Layer Normalization,バッチ正規化をベースに各ミニバッチの統計量から求めたスケール {r} とシフト {d} を追加した Batch Renormalization など様々な正規化手法が提案されている.

ニューラル機械翻訳 (NMT)

機械翻訳の主流である統計的機械翻訳 (SMT, Statistical Machine Translation) は,原言語を与えた時に対訳の尤度が最大となる確率モデルを学習して目的言語に翻訳するシステムを指す.

Google 翻訳のアップグレードでも話題になったニューラル機械翻訳 (NMT, Neural Machine Translation) は,統計的機械翻訳で学習する確率モデルとしてニューラルネットワークを使った手法である.中でもエンコーダーデコーダー翻訳モデル (Encoder-Decoder Model) はニューラルネットワークのみで翻訳可能で大きな注目を集めている.

Sequence to Sequence (seq2seq)

目次に戻る ↩︎

seq2seq (Encoder-Decoder Model と同義) は LSTM block を 2つ用いて,入力を処理するエンコーダーと出力を生成するデコーダーを組み合わせたモデルである (原著論文では GRU block も使用).一般的にエンコーダーデコーダーの LSTM block は異なるパラメータだが,パラメータを共有しても (LSTM block 1つでも) 問題なく,もしくはそれぞれ多層の LSTM block でも良い.系列データから系列データを生成するモデルなのでニューラル機械翻訳のほか,ニューラル対話モデル (NCM) や文章要約にも使われる.

f:id:Ryobot:20170216140241p:plain

学習は対訳のペア {X, T} によって行う.
Encoder への入力文を {x_1 \, x_2 \, x_3 \, \cdots \, x_m},Decoder からの出力文を {y_1 \, y_2 \, y_3 \, \cdots \, y_n \, y_{n + 1}},対訳文 (教師データ) を {t_1 \, t_2 \, t_3 \, \cdots \, t_n \, t_{n + 1}} とおく.
ただし,{y_{n + 1}, t_{n + 1}} は文末記号 EOS (End of Sentence) である.

まず,{x_1 x_2 x_3 \cdots x_m} を Encoder の各ステップの LSTM block {f(\cdot)} に順に入力していく.このとき LSTM block には出力層への出力はなく,LSTM block の {cell}{hidden} を次ステップの LSTM block に渡すだけである.これは次式のように表される.

{\boldsymbol{c}_t, \boldsymbol{h}_t = f(x_t, \boldsymbol{c}_{t - 1}, \boldsymbol{h}_{t - 1})}

最後に {x_m} を入力した時の Encoder の {cell}{hidden} を Thought Vector {S} として,Decoder の LSTM block {f(\cdot)} に渡す.そして,Decoder の最初の LSTM block に GO を入力し,その時の出力 {y_1} と対訳 {t_1} の誤差が損失となる.次ステップの LSTM block への入力は学習では {t_1}, 本番では {y_1} となり,その時の出力 {y_2} と対訳 {t_2} の誤差が損失となる.これらは次式のように表される.

{\boldsymbol{c}_t, \boldsymbol{h}_t = f(t_{t-1}, \boldsymbol{c}_{t - 1}, \boldsymbol{h}_{t - 1})}

{\boldsymbol{c}_t, \boldsymbol{h}_t = f(y_{t-1}, \boldsymbol{c}_{t - 1}, \boldsymbol{h}_{t - 1})}

これを繰り返して {y_{n + 1}}{t_{n + 1}} の損失まで累積する.この累積損失を Back-Prop してパラメータ更新を行う.モデルの学習は条件付き確率分布を {p(y_t | \, y_{t - 1} , y_{t - 2} , \cdots , y_1, \boldsymbol{c}) = g(y_{t-1}, \boldsymbol{h}_t, \boldsymbol{c}_t)} とおいた時,次式のような条件付き対数尤度の最大化として表される.

{\displaystyle \max_{\theta} \frac{1}{N} \sum_{n = 1}^{N} \log p_{\theta} (\boldsymbol{y}_n | \, \boldsymbol{x}_n)}

バケツとパディング

TensorFlow では,バケツ (bucketing) とパディングが用意されている.
ミニバッチ学習では入出力長を揃えなければならない.パディングはあらかじめモデルの入出力長を固定値 (例えば,入力長: 5, 出力長: 10) に定め,入出力文は空白を PAD で埋める.ちなみに入力文は反転して入力した方が精度が良くなる.

["I", "go", "."] => ["Je", "vais", "."]
[PAD PAD "." "go" "I"] => [GO "Je" "vais" "." EOS PAD PAD PAD PAD PAD]

文が短いときに不必要に多くの PAD 埋めを防ぎたい.バケツは入出力長を数種類に固定 (例えば,{[(5, 10), (10, 15), (20, 25), (40, 50)]}) して,種類数分の seq2seq モデルを用意する.

詳細は TensorFlow Tutorials - Sequence-to-Sequence Modelsを参照されたい.

語彙数増大への対応

データセットの全語彙を入出力層に使うと,データセットの対訳文数が増えるほど語彙数も増えるので入出力層が大きくなる.これは計算時間の増大やクラス分類の精度低下を招くので,簡易には語彙数を制限するのが望ましい.語彙数に上限を設け,出現頻度が低い語彙を UNK で埋めてしまえば良い.

大規模に語彙を使う場合,計算時間のボトルネックは次式のようなソフトマックス関数の分母の計算量に起因する.

{\displaystyle g(z_m) = \frac{\exp(z_m)}{\sum_{k \in Vocab}\exp(z_k)}}

そこでサンプリングなどでソフトマックス関数の分母を近似する手法などが幾つか提案されている.

余談

文章要約は FaceBook (arXiv, 2015/9), IBM (Watson) (arXiv, 2016/2), Google Brain (project page, 2016/8) から主要な論文が出ている.また,チャットボットは りんなを徹底解剖対話botの技術 あたりが参考になる.近年は seq2seq を対話モデルに応用したニューラル対話モデル (Neural Conversation Model) が目立つようになった.これは教師データを対訳文から返答文に変えただけで,モデル自体はニューラル機械翻訳と同じ.

Encoder-Decoder モデルで作られた中間層を word2vec のような枠組みで文章の分散表現を求める手法に Skip-Thought Vectors がある.

注意 (Attention)

目次に戻る ↩︎

左下図は通常の Encoder-Decoder Model の入出力長と後述の BLUE スコアとの関係で,スコアが高いほど翻訳精度が良いことを表す.入出力長が 20 を超えたあたりから翻訳精度が落ちているが,数百次元の {cell}{hidden} だけで長文を記憶させるのは無理があろう.無論,それらの次元数を増やせば今度は重み行列が {n^2} オーダーで膨れ上がり計算コストが大きくなってしまう.右下図は注意 (Attention) モデルの入出力長と BLUE スコアとの関係である.RNNsearch が Attention Model であり,その後の数字は学習データの長さを表す.学習データが長文の場合,Attention Model では長文の翻訳でもスコアが落ちないのがわかる.

Vanilla Model (non-Attention) Attention Model
f:id:Ryobot:20170216140242p:plain:w450 f:id:Ryobot:20170216140250p:plain:w600

注意 (Attention) は,入出力のソフトなアライメントを学習し,翻訳に関係する部分に着目しながら翻訳を行う手法である.Encoder 側の各ステップの中間層をすべて記録し,「彼女が her に対応する」といった単語アライメントや文脈情報を Decoder 側に考慮させることでエンコーダーデコーダー翻訳モデルの弱点であった長文の翻訳を改善できる.

f:id:Ryobot:20170216140240p:plain

まず Encoder 側の中間層 {\bar{\boldsymbol{h}}_s} をすべて記録し,それぞれ現時点の Decoder の中間層 {\boldsymbol{h}_t} との内積 {{\boldsymbol{h}_t}^{\mathrm{T}} \bar{\boldsymbol{h}}_s} (スコアという) を求めソフトマックス関数で正規化し Alignment Weight Vector {\boldsymbol{a}_t} をつくる (下図参照).

$\begin{eqnarray}\displaystyle \boldsymbol{a}_t(s) &=& \mathrm{align} \left( \boldsymbol{h}_t , \boldsymbol{\bar{h}}_s \right) \\ & & \\ &=& \frac{ \exp \left( \mathrm{score} \left( \boldsymbol{h}_t , \boldsymbol{\bar{h}}_s \right) \right) }{ \sum_{s'} \exp \left( \mathrm{score} \left( \boldsymbol{h}_t , \boldsymbol{\bar{h}}_{s'} \right) \right) } \end{eqnarray}$

ちなみにスコアは一般的に内積 (dot積) だが論文中では以下のような general や concat も提案されている.{\boldsymbol{W}_a}{\boldsymbol{v}_a} はモデルのパラメータである.

{\mathrm{score} \left( \boldsymbol{h}_t , \boldsymbol{\bar{h}}_s \right) = \left \{\begin{array}{ll}{\boldsymbol{h}_t}^{\mathrm{T}}\bar{\boldsymbol{h}}_s & dot \\{\boldsymbol{h}_t}^{\mathrm{T}}\boldsymbol{W}_a\bar{\boldsymbol{h}}_s & general \\{\boldsymbol{v}_a}^{\mathrm{T}} \tanh \left( \boldsymbol{W}_a [\boldsymbol{h}_t ; \boldsymbol{\bar{h}}_s] \right) & concat\end{array}\right.}

この {\boldsymbol{a}_t} を Encoder 側の中間層にそれぞれ掛けたベクトルの総和を Context Vector {\boldsymbol{c}_t} といい,これと現時点の Decoder 中間層 {\boldsymbol{h}_t} をスタックに連結 (concat) させ,重み行列 {\boldsymbol{W}_c} をかけて活性化関数 {\tanh} に通したベクトル {\tilde{\boldsymbol{h}}_t} を最終的な中間層の出力とする.

{\boldsymbol{c}_t = \sum_{s = 1}^{s'} \boldsymbol{a}_t(s) \boldsymbol{\bar{h}}_s}

{\boldsymbol{\tilde{h}}_t = \tanh \left( \boldsymbol{W}_c [\boldsymbol{c}_t ; \boldsymbol{h}_t] \right)}

畢竟,現時点の中間層 (Text な情報 {\boldsymbol{h}_t}) に注意すべき中間層の情報 (Context な情報 {\boldsymbol{c}_t}) を合体させる.単純な Attention 導入によって追加で学習すべきパラメータは最後の重み行列 {\boldsymbol{W}_c} だけである.以上の説明は Global Attentional Model と呼ばれるタイプである.

Global Attentional Model Local Attentional Model
f:id:Ryobot:20170216140226p:plain f:id:Ryobot:20170216140229p:plain

Local Attentional Model ではこれに Aligned Position {p_t} を追加する.ただし,{\boldsymbol{W}_p}{\boldsymbol{v}_p} はモデルのパラメータで,{S} は入力文の長さである.よって,{p_t \in [0, S]} となる.

{\displaystyle p_t = S \cdot \mathrm{sigmoid}  \left( \boldsymbol{v}_p^{\mathrm{T}} \tanh(\boldsymbol{W}_p \boldsymbol{h}_t) \right)}

次に Aligned Position {p_t} を優先するために {p_t} を中心としたガウス分布を配置し,次式のように Alignment Weight Vector {\boldsymbol{a}_t} をつくる.

{\displaystyle \boldsymbol{a}_t(s) = \mathrm{align} \left( \boldsymbol{h}_t , \boldsymbol{\bar{h}}_s \right) \exp  \left( - \frac{(s - p_t)^2}{2 \sigma^2} \right)}

その後は Global Attentional Model と同様の手続きである.

解説は Yuta Kikuchi さんの 最近のDeep Learning (NLP) 界隈におけるAttention事情 が詳しい.
ビジュアルで理解するなら Attention and Augmented Recurrent Neural Networks もおすすめである.

双方向エンコーダー・多層LSTM

目次に戻る ↩︎

双方向エンコーダー (Bi-directional Encoder) は,通常のエンコーダー (RNN) の中間層に入力文を逆向きに入力するエンコーダー (RNN) の中間層を連結 (concat) する.これによってエンコーダーの初期において将来の単語に関する情報を追加できる.

f:id:Ryobot:20170216140218p:plain

逆方向の RNN と順方向の RNN は次式のように表される.{f(\cdot)} は LSTM block や GRU block を表す.

{\overleftarrow{\boldsymbol{h}}_t = f(\boldsymbol{x}_t, \overleftarrow{\boldsymbol{h}}_{t + 1})}, {\overrightarrow{\boldsymbol{h}}_t = f(\boldsymbol{x}_t, \overrightarrow{\boldsymbol{h}}_{t - 1})}

連結は次式のように表される.

{\boldsymbol{h}_t = \Bigl[ \, \overleftarrow{\boldsymbol{h}}_t ; \overrightarrow{\boldsymbol{h}}_t \, \Bigr]}

Google 翻訳の双方向エンコーダーでは,連結後もとのサイズに戻るように {\overleftarrow{\boldsymbol{h}}_t , \overrightarrow{\boldsymbol{h}}_t} を半分のサイズにしている.

多層 LSTM (Stacked LSTM) は LSTM block を積み重ねて深層化したモデルである.MNIST で使う多層パーセプトロンのように,各層で異なるサイズの情報を表現できる.seq2seq の原著 では 4層の Stacked LSTM を使用している.また TensorFlow や Keras のチュートリアルにも掲載されている.

TensorFlow チュートリアル Keras チュートリアル
f:id:Ryobot:20170216140248p:plain f:id:Ryobot:20170216140228p:plain

言語モデルの評価 パープレキシティ

目次に戻る ↩︎

言語モデルの評価尺度には幾つかあるが,尤度, 対数尤度, エントロピー, パープレキシティあたりが代表的である.

尤度は,モデル {M} を与えられた時のテストデータ {W_{test}} の確率で次式のとおり.

{\displaystyle P(W_{test} | M) = \prod_{w \in W_{test}} P(w | M)}

exp は桁数が多すぎると値がとぶ.例えば math.exp(710.0)math range error になる.尤度も負の値爆発を起こすので対数尤度が使われる.

{\displaystyle \log P(W_{test} | M) = \sum_{w \in W_{test}} \log P(w | M)}

エントロピー {H}は,底 {2} の負の対数尤度を単語数 {|W_{test}|} で割った値で次式で表される.エントロピーが大きいほど次の単語の予測が困難(不確実)である.

{\displaystyle H(W_{test} | M) = \frac{1}{|W_{test}|} \sum_{w \in W_{test}} -\log_2 P(w | M)}

パープレキシティ (Perplexity) {PPL} は, {2}エントロピー乗.この値が小さいほど優れたモデルだといえる.

{\displaystyle PPL = 2^{H}}

例えば,RNNLM を評価するにはモデル {M} から {w_i} が生起される確率 {P(w_i|M)} を計算して,パープレキシティを求めればよい.RNNLM の出力 {y} は,施行 {t} における辞書の全単語の確率分布 {y(t)=(p_1, p_2, \cdots, p_D)} (ただし {D} は辞書の単語数) であり,単語 {w_{i}(t)} の生起確率 {p_{i}(t)}{p_{i}(t)=P(w_{i}(t) | w(1) w(2) \cdots w(t - 1))} と表される.

前述の LSTM の Chainer 実装からパープレキシティを求めるには次のとおり.

def PPL(model, sentence):
    sum = 0.0
    # 省略 (cell, hidden の Variable 化)
    for  i in range(len(sentence) - 1):
        pred, target = sentence[i], sentence[i + 1]
        embed = model.U(Variable(np.array([pred], dtype=np.int32)))
        # 省略 (フォワード計算を記述)
        y = F.softmax(model.V(hidden))
        p = y.data[0][target]
        sum -= math.log(p, 2) # math.log(x, y) は真数 x, 底 y
    return sum

f = 0.0
w = 0
for sentence in range(len(testData)):
    f += PPL(model, sentence)
    w += len(sentence)
ppl = math.pow(2, f / w) # math.pow(x, y) は x の y 乗

ちなみに言語モデルの解説は Graham Neubig 氏の 1-gram 言語モデルn-gram 言語モデル や松本研の 言語モデル が良い.特に後者ではエントロピーの最小化を Kullback-Leibler ダイバージェンス {D(p || q)} の最小化問題に置き換えて説明している.

機械翻訳の評価 BLEU

目次に戻る ↩︎

機械翻訳の評価手法はパープレキシティのほか,n-gram 適合率によってエントロピーを求める BLEU が一般的に用いられる.NMT 系の論文ではたいてい BLEU の評価を載せてる.BLEU は,局所的に流暢な文や正解文と表現法が一致する文に高評価を与えるが,意味的な妥当性との相関が低いという問題がある.

モデルの出力文を {C'},正解文 (参照文) を {C} とおく.このとき n-gram 適合率 {p_n(C',C)} は次式によって表される.(n-gram のハイフンでなぜかMathJaxがバグる...)

$\begin{eqnarray} \displaystyle p_n &=& \frac{\displaystyle \sum_{C \in \{ Candidates\}} \sum_{ \mbox{ngram} \in C} Count_{clip} ( \mbox{ngram})}{\displaystyle \sum_{C' \in \{ Candidates\}} \sum_{ \mbox{ngram}' \in C'} Count ( \mbox{ngram}')} \\ & & \\ &=& \frac{\displaystyle \sum_{C \in \{ Candidates\}} \mbox{出力文C'と正解文Cで一致したngram数}}{\displaystyle \sum_{C' \in \{ Candidates\}} \mbox{出力文C'の全ngram数}} \end{eqnarray}$

{P_4} なら n-gram の部分は 4-gram になる.
また n-gram は長さnの単語列なので,{Count (\mbox{n-gram'}) = |C'| - n + 1} と表される.

{C'} に含まれる n-gram{C} にも含まれる数を {Count} とおくと,n-gram 一致数 {Count_{clip} ( \mbox{n-gram})} は次式のように表される.{MaxRefCount} は正解文 (参照文) の n-gram の最大値.

{\displaystyle Count_{clip} ( \mbox{n-gram}) = \min(Count, MaxRefCount)}

Brevity Penalty (BP) は,翻訳の出力文の長さ {c} が正解文の長さ {r} よりも短い場合のペナルティである.短文で n-gram 適合率を最適化するのを回避する.

$\begin{equation} BP = \left \{ \begin{array}{l} 1 & if c > r \\ e^{(1-r/c)} & if c \leq r \\ \end{array} \right. \end{equation}$

このとき,BLEU は次式のように定義される.値域は {0} ~ {1} でスコアが大きいほど評価が高い.%表記も多いので,その場合の値域は {0} ~ {100} となる.

{\displaystyle BLEU = BP \cdot \exp \left( \sum_{n=1}^{N} w_n \log p_n \right)}

この対数は次式のとおり.

{\displaystyle \log BLEU = \min(1 - \frac{r}{c}, 0) + \sum_{n=1}^{N} w_n \log p_n}

ただし,{\displaystyle w_n = \frac{1}{N}}

{P_n \neq 0} であるような最大の {n}{N} として使用する.元論文のベースラインでは {N = 4}

arXiv で追う最新のニューラル機械翻訳

目次に戻る ↩︎

派手でなければ機械学習じゃない.ニューラル機械翻訳は火力だぜ.

というわけで最近 (2016年9月以降) で派手だとおもった手法をいくつか紹介したい.

1: Google ニューラル機械翻訳 (GNMT)

ベースは Attention 付きの Encoder-Decoder Model + 双方向エンコーダー,8層の多層LSTMという派手さが特徴.単純な積層では4層が良く,8層では勾配消失発散するので Residual connection (入力の恒等な加算.ツイート図の Sum層) を取り入れている.Attention は多層の Encoder の各層に対して行っている.

非同期 (Async) のデータ並列 (12分割) と各層に GPU を 1つ割り振ったモデル並列 (8モデル) で学習している (いわゆるパイプライン).softmax層も語彙ごとに異なる GPU を割り振っている.ちなみに Async とは各ワーカーがパラメータサーバーからモデルを受け取り,勾配の計算が終わり次第各ワーカーが各々にパラメータサーバーに勾配を渡して非同期にモデルを更新する手法をいう.Google は Async が好きなようで TensorFlow の雛形である DistBelief も Async である.

2: Zero-Shot 翻訳可能な多言語版 GNMT

GNMT の続編でいわゆる「Google翻訳」に 11月から使われ始めた手法.普通の機械翻訳では対訳の2言語間で1つのモデルを学習するが,多言語版 GNMT ではモデルのパラメータを共有して多言語間で1つのモデルを学習し,普遍的な翻訳知識を獲得したらしい.入力文の先頭にターゲット言語のトークン (e.g., <2es> ならスペイン語に翻訳) を指定しれば,未学習の言語間でも翻訳が可能 (Zero-Shot 翻訳).例に漏れず 100GPU で 3週間の学習.機械学習はパワーだぜ!

3: 文字レベルの畳み込みによるニューラル機械翻訳

f:id:Ryobot:20170216140223p:plain:w512

ベースは Attention 付きの Encoder-Decoder モデルだが,Encoder 部分 (上図) が特殊で,Character-Aware Neural Language Models (arXiv, 2015/8) にインスパイアされている.これは CNN のような手続きで言語モデルを構築している.

まず入力文すべてを文字レベルの分散表現に変換し,余白をパディングで埋めてから複数チャネルのカーネルを移動させて畳み込みを行う.次に等間隔の幅で最大値プーリングを行い Segment Enbeddings を求める.これを 4層の Highway Network (arXiv, 2015/5) に入力する.Highway network は次式のようなゲートで入力 {\boldsymbol{x}} を出力 {\boldsymbol{y}} に変換する手法である.

{\displaystyle \boldsymbol{y} = g \odot \mathrm{ReLU}(\boldsymbol{W} \boldsymbol{x} + \boldsymbol{b}) + (1 - g) \odot \boldsymbol{x}}

{g = \sigma(\boldsymbol{W}_t \boldsymbol{x} + \boldsymbol{b}_t)}

その後,双方向 RGU に入力し,出力を Encoder の中間層として扱う.

文字レベルの畳み込みは幾つか先行研究がある.詳細は自然言語処理における畳み込みニューラルネットワークを理解するを参照されたい.

4: ByteNet (Dilated Convolutions を使った畳み込み)

f:id:Ryobot:20170216140221p:plain:w512

DeepMind 製.Encoder (図の下側のソースネットワーク) と Decoder (図の上側のターゲットネットワーク) に Dilated CNN を使ったモデルで,文字レベルの言語モデルで SOTA である.それぞれネットワークの接続を系列方向に移動させて系列データ (もしくは中間層) を生成する.ニューラル機械翻訳のセオリーを踏襲していて,Decoder の学習時は入力 {t} に正解データを入れ,テスト時は前ステップの出力を入れる.また Encoder の中間層を保持して Attention に利用している.

Dilated Convolutions は自己回帰モデルの一種である WaveNet (arXiv, 2016/9) が有名で,系列データをたたみ込むのに DeepMind が好んで使っている.

WaveNet は Dilated Convolutions による計算コストの増大を抑制するために,Residual Block をうまく使っている.musyoku氏も解説ブログで次のように指摘をしている.

ブロック内の畳み込み層を増やしてdilationを大きくすると受容野の幅は指数関数的に大きくなり、ブロックを積み重ねると線形に大きくなります。

それでも Dilated Convolutions の計算コストは大きいので,Fast Wavenet のように過去に計算したニューロンを保存して再利用することでスピードアップを図るのが望ましい.

5: 再構築 (再翻訳) によるニューラル機械翻訳

f:id:Ryobot:20170216140246p:plain:w512

例えば「日本語→英語→日本語」と再翻訳して元の文章を復元できれば良い翻訳モデルだと言えそう,という発想である.この論文では Attention 付きの Encoder-Decoder モデルをベースに Decoder の隠れ層 {s} から Encoder の隠れ層 {\hat{h}} を再構築する.

まず Decoder の隠れ層 {s_j} から weight {\hat{\alpha}_{j, i}} を求め (これは通常の Attention で Encoder の中間層から Alignment Weight Vector を求めるのと同じ手続き),次式のように Inverse Context Vector {\hat{c}_j} を求める.

{\displaystyle \hat{c}_j = \sum_{i = 1}^{I} \hat{\alpha}_{j, i} \cdot s_i}

隠れ層 {\hat{h}_j} の再構築は次式によって表される.ただし,{f_r (\cdot)} は活性化関数.

{\displaystyle \hat{h}_j = f_r (x_{j - 1}, \hat{h}_{j - 1}, \hat{c}_j)}

よって再構築の分布は次式のとおり.ただし,{g_r (\cdot)} はソフトマックス関数.

$\begin{eqnarray}\displaystyle R (\boldsymbol{x} | \boldsymbol{s}) &=& \prod_{j = 1}^{J} R (x_j | x_{< j}, \boldsymbol{s}) \\ & & \\ &=& \prod_{j = 1}^{J} g_r (x_{j - 1}, \hat{h}_j, \hat{c}_j) \end{eqnarray}$

モデルの学習は「翻訳 (Encoder-Decoder) の目的言語の尤度 {P (\boldsymbol{y} | \, \boldsymbol{x} ; \theta)}」と「再構築 (Reconstructor) の元言語の尤度 {R(\boldsymbol{x} | \, \boldsymbol{s} ; \gamma)}」の和が最大化するような目的関数を次式のように設定して行う.{\lambda} は翻訳と再構築の割合を決めるハイパパラメータ.

$\begin{equation} \DeclareMathOperator*{\argmin}{arg\,min} \DeclareMathOperator*{\argmax}{arg\,max} \displaystyle J(\theta, \gamma) = \argmax_{\theta, \, \gamma} \sum_{n = 1}^{N} \Bigl\{ \log P (\boldsymbol{y}^n | \, \boldsymbol{x}^n ; \theta) + \lambda \log R(\boldsymbol{x}^n | \, \boldsymbol{s}^n ; \gamma) \Bigr\} \end{equation}$

6: Convolution と Gated Linear Units (GLU) を多層にした GCNN

f:id:Ryobot:20170216140224j:plain

パープレキシティにおいて WikiText-103 の SOTA かつ Google Billion Word ベンチマーク (1GPU) の最高点を叩き出し,RNN 系譜よりも計算が速い CNN 系譜のすごいモデルである.上図の Convolution 層と Gating 層のブロックは L層 (実験では 8層と 13層) を積層している.また各ブロックは bottleneck 構造の Residual Block (各 Res block は最大 5層持つ) で入力を出力に加算されている.

語彙数を {|V|},埋め込みサイズを {m},Word Embeddings のテーブルを {\mathrm{\boldsymbol{D}}^{|V| \times m}} とおくと,隠れ層 {h_l} は次式によって表される.ただし,{\boldsymbol{X} \in \mathrm{\boldsymbol{R}}^{N \times m}}{h_l} への入力 (Word Embeddings もしくは前の隠れ層の出力),{\boldsymbol{W} \in \mathrm{\boldsymbol{R}}^{k \times m \times n}}{\boldsymbol{b} \in \mathrm{\boldsymbol{R}}^{n}}{\boldsymbol{V} \in \mathrm{\boldsymbol{R}}^{k \times m \times n}}{\boldsymbol{c} \in \mathrm{\boldsymbol{R}}^{n}} は学習パラメータ,{\sigma}シグモイド関数{\otimes} は行列間のアダマール積である.カーネルは将来の単語を参照しないように畳み込みへの入力をシフトさせる.また {k}カーネルの幅として,{k / 2} 要素で先頭をゼロパディングしている.

{\displaystyle h_l (\boldsymbol{X}) = (\boldsymbol{X} * \boldsymbol{W} + \boldsymbol{b}) \otimes \sigma (\boldsymbol{X} * \boldsymbol{V} + \boldsymbol{c})}

各層の出力はゲート {\boldsymbol{X} * \boldsymbol{V} + \boldsymbol{c}} によって渡される情報を制御された線形写像 {\boldsymbol{X} * \boldsymbol{W} + \boldsymbol{b}} となる.よってこのゲートを Gated Linear Units (GLU) という.

実験結果はおおむね以下のとおり.

  • WikiText-103 での PPL は LSTM-1024 (score 48.7) < GCNN-8 (score 44.9)
  • GPU での毎秒あたりの処理数は LSTM-2048 (2282 tokens) <<< GCNN-22 (45878 tokens)
  • ネットワークが深いほど,また文脈長が長いほど,PPL は単純減少する
  • 重み正規化 << 勾配クリッピング < 重み正規化 + 勾配クリッピング

「RNNは衰退しました」は冗談である.半分は.
系列データを順に入力するタイプのネットワークは並列性が弱く計算速度に時間がかかるので「改良→実験」のサイクルが遅くなってしまう.残念ながら RNN 系譜はミニバッチ学習 (マルチシーケンスな並列化) でも GPU の恩恵をほとんど得られないうえに,オンライン学習では CPU の方が早い.

7: 疎なゲートでエキスパートのサブネットワークを選択

f:id:Ryobot:20170216140239p:plain:w512

last author が Geoffrey Hinton, Jeff Dean の Google Brain 製.ゲートによる制御で数千単位のフィードフォワードのサブネットワークの幾つかを選択し疎結合する Mixture-of-Experts layer (MoE) を提案している.実験では MoE を Stacked LSTM 間に適応して,大規模な言語モデル機械翻訳のタスクでは SOTA のモデル (e.g., Google ニューラル機械翻訳) よりパープレキシティと BLEU score で優れている.