再帰型ニューラルネットワーク(RNN, Recurrent NN)
再帰型ニューラルネットワークの概念
- 時系列データに対応可能なニューラルネットワーク
- 時系列データとは、時間的順序を追って一定間隔ごとに観察され、しかも相互に統計的依存関係が認められるようなデータの系列
- 例えば、音声データ、株価データやテキストデータ
- テキストデータは単語が前の単語とつながっていると考える
- 時系列データとは、時間的順序を追って一定間隔ごとに観察され、しかも相互に統計的依存関係が認められるようなデータの系列
- 入力層〜中間層〜出力層というニューラルネットワークの基本構造は変わらないが、中間層同士に時間的なつながり(数珠つながり)を持つ
- 時系列モデルを扱うには、初期の状態と過去の時間$ t-1 $の状態を保持し、そこから次の時間での$ t $を再帰的に求める再帰構造が必要となる
- 中間層の出力を次の時点の中間層の入力として活用する
- エルマンネットワーク
- 出力層を戻すジョーダンネットワークというものも存在する
- 入力$ x_n $と前の時点の中間層 $ z_{n-1} $から、新しい中間層 $ z_n $が得られ、そこから出力$ y_n $を得る
- 重みは入力の重み$ W_{(in)} $と出力の重み$ W_{(out)} $に加え、前の中間層からの重み$ W $がある
- エルマンネットワーク
- 数学的な記述
- $ u^t = W_{(in)}x^t + Wz^{t-1} + b $
- $x^t$はt時点の入力、$z^{t-1}$は前の中間層の出力
- $ z^t = f(W_{(in)}x^t + Wz^{t-1} + b) $
- $f$は中間層の活性化関数
- $ v^t = W_{(out)}z^t + c $
- $ y^t = g(W_{(out)}z^t + c) $
- $g$は出力層の活性化関数
- $ u^t = W_{(in)}x^t + Wz^{t-1} + b $
- 実装(バイアスb, cは省略)
- 数式上は現在が$ t $、前が$ t-1 $だが、Pythonでの実装では$ t $が0から始まるため、現在が$ t+1 $、前が$ t $とする
u[:,t+1] = np.dot(X, W_in ) + np.dot(z[:,t].reshape(1, -1), W)
z[:,t+1] = functions.sigmoid (u[:,t+1])
y[:,t] = functions.sigmoid(np.dot(z[:,t+1].reshape(1, -1), W_out))
BPTT(Back Propagation Through Time)
- RNNにおけるパラメーター調整方法の一種
- 誤差逆伝播法の一種
- 数学的な記述
- Wが3つあるので、3つの偏微分を行う
- $ \dfrac{∂E}{∂W_{(in)}} = \dfrac{∂E}{∂u^t} \lbrack \dfrac{∂u^t}{∂W_{(in)}} \rbrack ^T = δ^t[x^t]^T$
- $ δ $は、$E$から$u$までの微分をひとまとめにしたもの
- RNNにおいて、$ \lbrack \rbrack ^T $は時間を遡って全て微分することを表す
- $ \dfrac{∂E}{∂W_{(out)}} = \dfrac{∂E}{∂v^t} \lbrack \dfrac{∂v^t}{∂W_{(out)}} \rbrack ^T = δ^{out,t}[z^t]^T$
- $ \dfrac{∂E}{∂W} = \dfrac{∂E}{∂u^t} \lbrack \dfrac{∂u^t}{∂W} \rbrack ^T = δ^t[z^{t-1}]^T$
- バイアスは2つ
- $ \dfrac{∂E}{∂b} = \dfrac{∂E}{∂u^t} \dfrac{∂u^t}{∂b} = δ^t as \dfrac{∂u^t}{∂b} = 1 $
- $ \dfrac{∂E}{∂c} = \dfrac{∂E}{∂v^t} \dfrac{∂v^t}{∂v} = δ^{out,t} as \dfrac{∂v^t}{∂c} = 1 $
- コードでの記述(バイアスb, cは省略)
np.dot(X.T, delta[:,t].reshape(1, -1))
np.dot(z[:,t+1].reshape(-1, 1), delta_out[:,t].reshape(-1,1))
np.dot(z[:,t].reshape(-1, 1), delta[:,t].reshape(1, -1))
- $δ^t$は、$E$から$u$までの微分をひとまとめにしたもの
- $ δ^{t-n-1} = δ^{t-n} \cdots $ は一つ前の時点とつながっていることを示す
delta_out[:,t] = functions.d_mean_squared_error(dd, y[:,t]) * functions.d_sigmoid(y[:,t])
delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) * functions.d_sigmoid(u[:,t+1])
- パラメーターの更新式
# 勾配更新
W_in_grad += np.dot(X.T, delta[:,t].reshape(1,-1))
W_out_grad += np.dot(z[:,t+1].reshape(-1,1), delta_out[:,t].reshape(-1,1))
W_grad += np.dot(z[:,t].reshape(-1,1), delta[:,t].reshape(1,-1))
# 勾配適用
W_in -= learning_rate * W_in_grad
W_out -= learning_rate * W_out_grad
W -= learning_rate * W_grad
- 時間的なつながりを辿る部分は$ \sum $がつく
- BPTTの全体像
- $ z^t $を開くと$ z^{t-1} $が出てきて、$ z^{t-1} $を開くと$ z^{t-2} $が出てきてと、zが数珠つながりになっている(=再帰)
LSTM(Long Short-Term Memory)
-
RNNの課題
- 時系列を遡れば遡るほど、勾配が消失していく
- 結果的に長い時系列の学習が困難
- 逆の勾配爆発という現象もある
- 勾配が層を逆伝搬するごとに指数関数的に大きくなってしまい、結果的に学習がうまく行かない
- 時系列を遡れば遡るほど、勾配が消失していく
-
LSTMは構造自体を変えて勾配消失問題を解決するRNNの一種
-
CEC: Constant Error Carousel(記憶セル)
- CECは記憶機能しかない(考える機能は持たない)
- 勾配消失および勾配爆発の解決方法として、理屈上、勾配が1であれば解決できる
- 勾配を1にするために導入されたのがCEC
- 逆に勾配が1ということは、ニューラルネットワークの学習特性がないということ(覚えるが、学習しない)
- CECの周りに学習機能を配置する
-
入力ゲートと出力ゲート
- CECは記憶機能しか持たず、学習機能を持たないが、入力ゲートと出力ゲートを追加することにより、全体として学習機能を持たせる
- 入力・出力ゲートを追加することで、それぞれのゲートへの入力値の重みを、重み行列$ \boldsymbol{W_x}, \boldsymbol{U_h} $で可変可能とする
- 入力ゲートは入力された情報をどのように記憶するかをCECに指示する
- 出力ゲートはCECが保管している情報(記憶)をどのように使うかを指示する
- 今回の入力値と前回の出力値(隠れ層)をもとに、入力ゲート、出力ゲート共に学習する
- 入力ゲートの重み$ \boldsymbol{W_x}^{(i)} $(今回の入力値に対する重み), $ \boldsymbol{U_h}^{(i)} $(前回の出力値に対する重み)を学習する
- 入力ゲートの出力(どれだけCECに記憶させるか)$ \boldsymbol{I} $
- 入力ゲートの重み$ \boldsymbol{W_x}^{(i)} $(今回の入力値に対する重み), $ \boldsymbol{U_h}^{(i)} $(前回の出力値に対する重み)を学習する
- 出力ゲートの重み$ \boldsymbol{W_x}^{(o)} $(今回の入力値に対する重み), $ \boldsymbol{U_h}^{(o)} $(前回の出力値に対する重み)を学習する
- 出力ゲートの出力(CECの記憶をどう使うか)$ \boldsymbol{O} $
- CECは記憶機能しか持たず、学習機能を持たないが、入力ゲートと出力ゲートを追加することにより、全体として学習機能を持たせる
-
忘却ゲート
- CECには過去の情報が全て保管されているが、過去の情報が入らなくなっても、削除することができず、保管され続ける
- 忘却ゲートは、過去の情報が要らなくなった時点で、CECからその情報を忘却させる機能を持つ
- 忘却ゲートの重み$ \boldsymbol{W_x}^{(f)} $(今回の入力値に対する重み), $ \boldsymbol{U_h}^{(f)} $(前回の出力値に対する重み)を学習する
- 忘却ゲートの出力(どれだけCECに忘却させるか)$ \boldsymbol{F} $
- CECには過去の情報が全て保管されているが、過去の情報が入らなくなっても、削除することができず、保管され続ける
-
処理の流れ
- 学習されるLSTMのパラメーターは、入力に対する重み行列である$ \boldsymbol{W_x} = [\boldsymbol{W_x}^{(f)}, \boldsymbol{W_x}^{(g)}, \boldsymbol{W_x}^{(i)}, \boldsymbol{W_x}^{(o)}] $、前の時刻からの隠れ状態に対する重み行列である$ \boldsymbol{U_h} = [\boldsymbol{U_h}^{(f)}, \boldsymbol{U_h}^{(g)}, \boldsymbol{U_h}^{(i)}, \boldsymbol{U_h}^{(o)}] $、バイアスベクトルである$ \boldsymbol{B} = [\boldsymbol{b}^{(f)}, \boldsymbol{b}^{(g)}, \boldsymbol{b}^{(i)}, \boldsymbol{b}^{(o)}] $からなる
- 入力と前の時刻からの隠れ状態から、忘却ゲートの値を計算する
- $ \boldsymbol{F} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(f)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(f)} + \boldsymbol{b}^{(f)}) $
- ゲートは度合い(忘却ゲートの場合は、忘れる度合い)を調整するので、シグモイド関数で0〜1とする
- $ \boldsymbol{F} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(f)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(f)} + \boldsymbol{b}^{(f)}) $
- 入力と前の時刻からの隠れ状態から、入力の値を計算する
- $ \boldsymbol{G} = tanh(\boldsymbol{X_t} \boldsymbol{W_x}^{(g)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(g)} + \boldsymbol{b}^{(g)}) $
- 入力の値は情報量を一定程度維持しつつ勾配爆発を避けるために、tanh関数で-1〜1とする
- $ \boldsymbol{G} = tanh(\boldsymbol{X_t} \boldsymbol{W_x}^{(g)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(g)} + \boldsymbol{b}^{(g)}) $
- 入力と前の時刻からの隠れ状態から、入力ゲートの値を計算する
- $ \boldsymbol{I} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(i)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(i)} + \boldsymbol{b}^{(i)}) $
- ゲートは度合い(入力ゲートの場合は、入力を加える度合い)を調整するので、シグモイド関数で0〜1とする
- $ \boldsymbol{I} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(i)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(i)} + \boldsymbol{b}^{(i)}) $
- 入力と前の時刻からの隠れ状態から、出力ゲートの値を計算する
- $ \boldsymbol{O} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(o)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(o)} + \boldsymbol{b}^{(o)}) $
- ゲートは度合い(出力ゲートの場合は、出力を伝える度合い)を調整するので、シグモイド関数で0〜1とする
- $ \boldsymbol{O} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(o)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(o)} + \boldsymbol{b}^{(o)}) $
- 前の時刻の記憶$ C_{t-1} $に対し、忘却ゲートの値(度合い)$ F $を作用させ、続いて入力の値$ G $に対し入力ゲートの値(度合い)$ I $を作用させたものを加え、現時点での記憶$ C_t $を得る
- $ \boldsymbol{C_t} = \boldsymbol{F} \odot \boldsymbol{C_{t-1}} + \boldsymbol{I} \odot \boldsymbol{G} $
- ゲートによる作用は、アダマール積であることに注意
- $ \boldsymbol{C_t} = \boldsymbol{F} \odot \boldsymbol{C_{t-1}} + \boldsymbol{I} \odot \boldsymbol{G} $
- 現時点での記憶$ C_t $をtanh関数に通した上で、出力ゲートの値(度合い)$ O $を作用させることで、現時点での隠れ状態 $ H_t $を得る
- $ \boldsymbol{H_t} = \boldsymbol{O} \odot tanh(\boldsymbol{C_t}) $
- 入力と同様に記憶も情報量を維持するためにtanh関数を使用する
- ゲートによる作用は、アダマール積であることに注意
- $ \boldsymbol{H_t} = \boldsymbol{O} \odot tanh(\boldsymbol{C_t}) $
- 実装に際しては、入力に対する重み行列である$ \boldsymbol{W_x} $、前の時刻からの隠れ状態に対する重み行列である$ \boldsymbol{U_h} $、バイアスベクトルである$ \boldsymbol{B} $それぞれを一まとまりの行列として$ \boldsymbol{X_t} \boldsymbol{W_x} + \boldsymbol{H_{t-1}} \boldsymbol{U_h} + \boldsymbol{b} $の計算を行い、結果を$ f, g, i, o $に分割する処理とすることが一般的
- 前述のように、ゲート$ f, i, o $にはシグモイド関数を、$ g $にはtanh関数を適用し、$ F, G, I, O $を得る
-
覗き穴(peep hole)結合
- CEC自体の値は、ゲート(入力/出力/忘却)制御に影響を与えていないが、与えることはできないか
- 覗き穴結合はCEC自体の値を重み行列を介してゲートに伝播可能にした構造
- 例えば、入力と前の時刻からの隠れ状態と、前の時刻の記憶から、忘却ゲートの値を計算する
- $ \boldsymbol{F} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(f)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(f)} + \boldsymbol{C_{t-1}} \odot \boldsymbol{V_c}^{(f)} + \boldsymbol{b}^{(f)}) $
- 覗き穴の作用は、アダマール積であることに注意
- $ \boldsymbol{F} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(f)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(f)} + \boldsymbol{C_{t-1}} \odot \boldsymbol{V_c}^{(f)} + \boldsymbol{b}^{(f)}) $
- ただし出力ゲートの計算においては、出力ゲートがCECより後ろに存在するので、現在の記憶($ C_t $)を作用させる
- $ \boldsymbol{O} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(o)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(o)} + \boldsymbol{C_{t}} \odot \boldsymbol{V_c}^{(o)} + \boldsymbol{b}^{(o)}) $
- ただ実際にはあまり大きな効果は得られていない
- CEC自体の値は、ゲート(入力/出力/忘却)制御に影響を与えていないが、与えることはできないか
-
LSTMでは、勾配爆発を避けるために、勾配クリッピングを使うことがある
- 大きい勾配に対し、ノルムを閾値以下に正規化する
GRU(Gated Recurrent Unit)
-
LSTMの改良版
- LSTMではパラメーター数が多く、計算負荷が高くなる
- GRUはパラメーターを減らすことによって、計算負荷が低い
-
CEC、入力ゲート、出力ゲート、忘却ゲートがなく、その代わりに、リセットゲートと更新ゲートがある
-
処理の流れ
- 学習されるGRUのパラメーターは、入力に対する重み行列である$ \boldsymbol{W_x} = [\boldsymbol{W_x}^{(r)}, \boldsymbol{W_x}^{(z)}, \boldsymbol{W_x}^{(\tilde{h})}] $、前の時刻からの隠れ状態に対する重み行列である$ \boldsymbol{U_h} = [\boldsymbol{U_h}^{(r)}, \boldsymbol{U_h}^{(z)}, \boldsymbol{U_h}^{(\tilde{h})}] $、バイアスベクトルである$ \boldsymbol{B} = [\boldsymbol{b}^{(r)}, \boldsymbol{b}^{(z)}, \boldsymbol{b}^{(\tilde{h})}] $からなる
- 入力と前の時刻からの隠れ状態から、リセットゲートの値を計算する
- $ \boldsymbol{R} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(r)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(r)} + \boldsymbol{b}^{(r)}) $
- ゲートは度合い(リセットゲートの場合は、前の隠れ状態を忘れる度合い)を調整するので、シグモイド関数で0〜1とする
- $ \boldsymbol{R} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(r)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(r)} + \boldsymbol{b}^{(r)}) $
- 入力と前の時刻からの隠れ状態から、更新ゲートの値を計算する
- $ \boldsymbol{Z} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(z)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(z)} + \boldsymbol{b}^{(z)}) $
- ゲートは度合い(更新ゲートの場合は、前の隠れ状態を混ぜる度合い)を調整するので、シグモイド関数で0〜1とする
- $ \boldsymbol{Z} = sigmoid(\boldsymbol{X_t} \boldsymbol{W_x}^{(z)} + \boldsymbol{H_{t-1}} \boldsymbol{U_h}^{(z)} + \boldsymbol{b}^{(z)}) $
- 入力と前の時刻からの隠れ状態、およびリセットゲートの値から、仮の隠れ状態の値$ \boldsymbol{\tilde{H}} $を計算する
- $ \boldsymbol{\tilde{H}} = tanh \lbrace \boldsymbol{X_t} \boldsymbol{W_x}^{(\tilde{h})} + (\boldsymbol{R} \odot \boldsymbol{H_{t-1}}) \boldsymbol{U_h}^{(\tilde{h})} + \boldsymbol{b}^{(\tilde{h})} \rbrace $
- 入力の値は情報量を一定程度維持しつつ勾配爆発を避けるために、tanh関数で-1〜1とする
- ゲートによる作用は、アダマール積であることに注意
- $ \boldsymbol{\tilde{H}} = tanh \lbrace \boldsymbol{X_t} \boldsymbol{W_x}^{(\tilde{h})} + (\boldsymbol{R} \odot \boldsymbol{H_{t-1}}) \boldsymbol{U_h}^{(\tilde{h})} + \boldsymbol{b}^{(\tilde{h})} \rbrace $
- 最後に、(1- 更新ゲートの値$ \boldsymbol{Z}$)と$\boldsymbol{H_{t-1}} $、 $ \boldsymbol{Z} $と$ \boldsymbol{\tilde{H}} $から、新たな隠れ状態の値$ \boldsymbol{H_t} $を計算する
- $ \boldsymbol{H_t} = (1 - \boldsymbol{Z}) \odot \boldsymbol{H_{t-1}} + \boldsymbol{Z} \odot \boldsymbol{\tilde{H}} $
- ゲートによる作用は、アダマール積であることに注意
- なお、$ 1- \boldsymbol{Z} $と$ \boldsymbol{Z} $をひっくり返した数式で記載されていることもあるので、注意が必要(基本的には上記が正しいと思われる)
- $ \boldsymbol{H_t} = (1 - \boldsymbol{Z}) \odot \boldsymbol{H_{t-1}} + \boldsymbol{Z} \odot \boldsymbol{\tilde{H}} $
その他の派生RNNモデル
エコーステートネットワーク
- 入力の重み$ \boldsymbol{U} $と、隠れ層の重み$ \boldsymbol{V} $をランダムな値で固定し、出力の重み$ \boldsymbol{W} $のみを学習する
- シンプルな線形回帰の問題となり、勾配消失や勾配爆発が発生しなくなり、また、学習が速くなる
スキップ接続
- 長期依存性の課題への対策の一つとして、隠れ層を1時刻以上スキップした接続を追加する
Leaky unit
- 隠れ層に線型結合を導入して移動平均の効果を得る
- $ h_t = (1 - \frac{1}{τ})h_{t-1} + \frac{1}{τ} f( \boldsymbol{W_h} h_{t-1} + \boldsymbol{W}_{in}x_t) $
双方向RNN
- 過去の情報だけでなく、未来の情報を加味することで、精度を向上させるためのモデル
- 文章の推敲や、機械翻訳等に活用される
- 文章は過去(文の前段)も未来(文の後段)も同時に情報が得られるので、過去から未来への情報の伝達だけでなく、未来から過去への情報の伝達も活用できる
- 時間に対し順方向に隠れ状態を伝播させる層と、逆方向に隠れ状態を伝播させる層は直接的には結合していない
RNNの応用例
seq2seq
- 2つのネットワークがつながれている自然言語処理用のネットワーク
- 系列変換モデル: 単語列などの系列(sequence)を受け取り、別の系列へ変換するモデル
- 時系列の入力をもとに、出力を時系列で予測する
- 一つ目のネットワーク(Encoder)で文の意味(=文脈)を抽出・保持し、それを二つ目のネットワーク(Decoder)に渡す
- seq2seqはEncoder-Decoderモデルの一種で、機械対話や機械翻訳などに使用される
- 系列変換モデル: 単語列などの系列(sequence)を受け取り、別の系列へ変換するモデル
- Encoder RNN
- ユーザーがインプットしたテキストデータを、単語等のトークンに区切って渡す構造
- Tokening: 文章を単語等のトークン毎に分割し、トークンごとのIDに分割する
- IDはone hot vectorで表現できる
- Embedding: IDから、そのトークンを表す分散表現ベクトルに変換
- one hot vectorは数万列(単語数だけ)あるが、それを数百列の数字行列に圧縮する
- 意味が似ている単語が似た行列になるようにする
- 結果的に単語の意味を抽出していることになる
- one hot vectorは数万列(単語数だけ)あるが、それを数百列の数字行列に圧縮する
- Encoder RNN: ベクトルを順番にRNNに入力していく
- 処理手順
- vec1をRNNに入力し、hidden stateを出力する
- このhidden stateと次の入力vec2をまたRNNに入力し、結果のhidden stateを出力という流れを繰り返す
- 最後のvecを入れたときのhidden stateをfinal stateとしてとっておく
- このfinal stateがthought vectorと呼ばれ、入力した文の意味を表すベクトルとなる
- 意味が似ている単語が似た行列になるようにする(=特徴量の抽出)という処理が難しい
- GoogleのBERTはMLM(Masked Language Model)で
- 単語に分解した上で、ある単語をマスク、前後の単語から予測させる
- それによって、似た意味の単語が似たような意味ベクトルになるようになる
- 人間が学習データを作る必要がないので、大量のデータを学習できる
- GoogleのBERTはMLM(Masked Language Model)で
- 処理手順
- Decoder RNN
- システムがアウトプットデータを単語等のトークンごとに生成する構造
- 処理手順
- Decoder RNN: Encoder RNNのfinal state(thought vector)から、各tokenの生成確率を出力
- Sampling: 生成確率にもとづいてtokenをランダムに選ぶ
- Embedding: 2で選ばれたtokenをEmbeddingして Decoder RNNへの次の入力とする
- Detokenize: 1~3を繰り返し、2で得られたtokenを文字列に直す
HRED
- seq2seqでは一問一答しかできない(それまでの文脈が活かされない)
- HREDでは、過去n-1個の発話から次の発話を生成する
- 一文一文も単語と同様にseq2seqにかける
- 文の意味ベクトルを次に引き継ぐ
- seq2seqでは単語の意味ベクトルだったが、その上にさらに文レベルでのRNNを構築する
- つまり、HREDとは、seq2seq + Context RNN
- Context RNN: Encoderのまとめた各文章の系列をまとめて、これまでの会話コンテキスト全体を表すベクトルに変換する構造
- これによって、過去の発話の履歴を加味した返答をできる
- Context RNN: Encoderのまとめた各文章の系列をまとめて、これまでの会話コンテキスト全体を表すベクトルに変換する構造
- HREDの課題
- HREDは確率的な多様性が字面にしかなく、会話の「流れ」のような多様性が無い
- HREDは「うん」「そうだね」など、短く情報量に乏しい答えをしがちである
- VHRED
- HREDに、VAE(Variational Auto Encoder)の潜在変数の概念を追加したもの
- HREDの課題をVAEの潜在変数の概念を追加することで解決した構造
- VAEについては、生成モデルを参照
- HREDに、VAE(Variational Auto Encoder)の潜在変数の概念を追加したもの
word2vec
- RNNでは、単語のような可変長の文字列をNNに与えることができず、固定長形式で単語を表す必要がある
- word2vecでは、学習データからボキャブラリーを作成
- 辞書の単語数だけone-hot vectorができる
- これにボキャブラリ数 × 単語ベクトルの次元数の重み行列を導入することで、現実的な計算速度とメモリ量で大規模データの分散表現の学習を実現可能にした
- 埋め込み行列を獲得するための方法としてContinuous Bag-of-Words(CBOW)と、skip-gramの二種類のニューラルネットワークがある
- CBOWは前後の単語からある単語を予測するNN
- skip-gramはある単語が与えられた時に、その周辺の単語を予測するNN
- word2vecで巨大なコーパスを扱う場合、通常の他クラス分類を行うと計算速度が問題になるため、負例サンプリング(negative sampling)という手法で、損失関数の計算時間の短縮を図る
- 通常のCBOWまたはskip-gramにおける出力層では、すべての語彙を対象として、ソフトマックス関数と交差エントロピー誤差関数の計算を行うが、これは非常に重くなる
- 負例サンプリングを適用した場合の出力層では、1個の正例とk個の負例だけを対象として、シグモイド関数と交差エントロピー誤差関数の計算を行う
- つまり膨大な多クラス分類問題をk+1個の2クラス分類問題としている
- k個の負例は、コーパスの中から単語の出現頻度に基づいてサンプリングする
- kはハイパーパラメーター
Attention Mechanism
- seq2seqは、文章が長くなるほどそのシーケンスの内部表現の次元も大きくなっていくため、長い文章への対応が難しい
- Attention Mechanismは、「入力と出力のどの単語が関連しているのか」という関連性の重みを学習する仕組み
- 一文の中で特に重要な単語を自力で見出す