#深層学習後編1
再帰型ニューラルネットワーク(RNN)
- RNNとは時系列データに対応可能なニューラルネットワーク
- 時系列データとは時間的順序を追って一定間隔ごとに観察され、統計的依存家計が認められるようなデータの系列。データの順序に意味があるデータ
- 例)音声データ、文章、動画
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))
- RNNには3種類の重みがある。1つは入力から現在の中間層にかけられる重み、2つ目は中間層から出力にかけられる重み、3つ目は時系列的に1つ前の中間層から現在の中間層にかけられる重みである。
- RNNは過去の状態(t-1)を保持し、次の時間の状態(t)を再帰的に求める再帰構造を持っている
実装演習
- 重み初期値の分散を0.5、学習率を0.01、隠れ層のサイズを10にした場合
考察
- 初期状態では学習はうまくいっていたようだが、重みの分散、学習率、隠れ層のノード数を変えると学習がうまくいかなかった。ほかのニューラルネットワークと同様にパラメータによって結果が大きく変わる
- 初期状態での結果が良かったからか、xavierとHeを使った場合は学習が遅くなった
- 活性化関数をシグモイド関数からReLU関数にすると、勾配爆発が起きて学習が進まなくなった。
- 活性化関数をシグモイド関数から双曲線正接関数にすると、勾配爆発が起きず、学習がより早く進んだ。
BPTT
- BPTTとは誤差逆伝播法の一種で、RNNにおけるパラメータ調整方法である
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))
u[:,t+1] = np.dot(X, W_in) + np.dot(z[:,t].reshape(1, -1), W)
z[:,t+1] = functions.sigmoid(u[:,t+1])
np.dot(z[:,t+1].reshape(1, -1), W_out)
y[:,t] = np.tanh(np.dot(z[:,t+1].reshape(1, -1), W_out))
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 -= learning_rate * W_in_grad
W_out -= learning_rate * W_out_grad
W -= learning_rate * W_grad
LSTM
- RNNは時系列を遡れば遡るほど勾配が消失していくため、時系列が長いと学習が困難となる
- 層の数が多いと勾配消失と逆の現象である、勾配爆発が起きる可能性もある。勾配爆発の対策として勾配クリッピングがあげられる
# 勾配のノルムがしきい値より大きい場合、勾配のノルムをしきい値に正規化する
def gradient_clipping(grad,threshold):
norm = np.linalg.norm(grad)
rate = threshold / norm
if rate < 1:
return gradient * rate
return grad
- 活性化関数や重みの初期値を変えることでも勾配消失を解決できるが、構造自体を変えて解決する方法がLSTMである
- LSTMは基本的なRNNの中間層をメモリユニットと呼ぶ要素で置き換えた構造を持つ。
CEC
- 上の図のCECと書かれている部分にあたる。勾配を1とすることで勾配消失及び勾配爆発を防ぐ手法
- CECには時間依存度に関係なく重みが一律になってしまうことから学習できなくなるという課題が存在する。入力層から隠れ層への重みが一律になることを入力重み衝突といい、隠れ層から出力層の重みが一律になることを出力重み衝突という。
- この課題を解決する方法として、入力ゲートと出力ゲートが導入されている
入力ゲートと出力ゲート
- 入力ゲートと出力ゲートはそれぞれ入力データ、出力データに対して作用することで、次のユニットに渡す値を増減させる役割を持つ。(必要な値であれば値を大きくし、必要ない値ならば小さくするように作用する)
- 入力ゲートと出力ゲートに対する重みを変えることでCECの持つ課題を解決することができる
忘却ゲート
- 上記までのLSTMだと過去の情報がすべて保管されてしまう。そのため全く新しいパターンのデータが入力された場合、過去のデータを削除することができないという課題があった
- この課題を解決するために忘却ゲートが導入された
- 忘却ゲートは入力ゲート、出力ゲートと同様にゲートに入力された値を増減させることで値の保持と削除を切り替える役割を持つ
- 例)「映画おもしろかったね。ところで、とてもお腹がすいたから何か(予測対象)」という文で最後の単語を予測するとき、忘却ゲートの作用で「とても」という言葉を予測に影響させないようすることができる
覗き穴結合
- 上記の忘却ゲートを導入したLSTMにも課題があった。そもそも入力ゲート、出力ゲート、忘却ゲートはCECの値を変化させることが役割だが、CECの値自体は3つのゲートの動きに影響を与えることができないという点である。
- そこで覗き穴結合と呼ばれる、CECの値を3つのゲートに渡す構造を導入した
GRU
- LSTMには構造自体に、パラメータが多く計算負荷が高くなるという課題があった
- GRUとはパラメータの数を大幅に削減し、同等かそれ以上の精度を実現できるようにしたRNNの構造である
- LSTMに比べて計算負荷が低いというメリットがある
def gru(x,h,W_r,U_r,W_z,U_z,W,U):
r = _sigmoid(x.dot(X_r.T) + h.dot(U_r.T))
z = _sigmoid(x.dot(X_z.T) + h.dot(U_z.T))
h_bar = np.tanh(x.dot(W.T) + (r * h).dot(U.T))
h_new = (1-z) * h + z * h_bar
return h_new
双方向RNN
- 過去の情報だけでなく、未来の情報も利用することで予測の精度を向上させることができるRNNを双方向RNNと呼ぶ。文章の作成や翻訳などに利用される
def bidirectional_rnn_net(xs,W_f,U_f,W_b,U_b,V):
xs_f = np.zeros_like(xs)
xs_b = np.zeros_like(xs)
for i, x in enumerate(xs):
xs_f[i] = x
xs_b[i] = x[::-1]
hs_f = _rnn(xs_f,W_f,U_f)
hs_b = _rnn(xs_b,W_b,U_b)
hs = [np.concatenate([h_f, h_b[::-1]], axis=1) for h_f,h_b in zip(hs_f,hs_b)]
ys = hs.dot(V.T)
return ys
seq2seq
- Encoder-Decoderモデルの一種で、機械対話や機械翻訳に使用されている。
Encoder RNN
- インプットしたテキストデータを単語などの細かい単位に分けてベクトルに変換する構造
- 以下の手順で処理を行う
- toking
- 文章をトークンと呼ばれる単語などの単位に分割する
- Embedding
- トークンを分散表現ベクトルと呼ばれるone-hotベクトルに変換する
- Encoder RNN
- ベクトルと次のトークンをRNNに入力する
- この3つをトークンごとに繰り返す。最後まで繰り返したときにできたベクトルが、入力した文章を表すベクトルとなる
- toking
Decoder RNN
- Encoder RNNで作成したベクトルを文章に変換する構造
- 以下の手順で処理を行う
- sampling
- 入力されたベクトルからトークンの生成確率を計算し、その確率に基づいてトークンを選ぶ
- Embedding
- 選ばれたトークンをEmbeddingでベクトルに変換し、再びDecoder RNNに入力する
- Detokenize
- 以上を繰り返し、Embeddingで得られたトークンを文章にする
- sampling
HRED(Hierarchical Recurrent Encoder-Decoder)
- seq2seqには一問一答しかできないという課題があり、文脈を無視した文章しかできなかった。これを解消する手段としてHREDがある
- HREDとは過去のすべての文を参考に次の文を生成できる。これによりseq2seqより人間らしい文章を作成することができる
- seq2seqはEncoderRNNとDecoderRNNで構成されているのに対し、HREDはEncoderRNNとDecoderRNNに加えContext RNNという構造を使う
- Context RNNとはEncoderRNNが生成した各文のベクトルをまとめて、すべての文を表すベクトルに変換する役割を持つ
- HREDには2つの課題がある。1つ目は会話の流れに多様性が生まれないこと。2つ目は短く情報量に乏しい答えが多くなってしまうこと
VHRED
- VHREDとはHREDにVAEの潜在変数の概念を追加することで、HREDの課題を解決する構造
VAE
- オートエンコーダ
- オートエンコーダとは入力データと同じ値を出力するニューラルネットワークのことを言う
- 入力データが3ノード、中間層が2ノード、出力層が3ノードノードのオートエンコーダだと特徴量を3つから2つに圧縮することができる。つまり次元削減をすることができる。この時の中間層のノードを潜在変数と呼ぶ
- VAE
- 通常のオートエンコーダだと、潜在変数がどのような構造なのかを把握できない
- そこで圧縮する際に潜在変数の分布を正規分布と仮定することで、構造を把握できるようにしたものがVAEである
- VAEを利用したVREDにより正規分布内で単語を選ぶため、文脈から外れずに表現の異なる文章を作成することができる
Word2vec
- 通常のRNNでは文章や単語のような可変長のデータを使って学習することはできない。そのためそれらのデータを固定長形式で表す必要がある。その方法をword2vecという
- 入力には辞書にある単語数分の長さを持つone-hotベクトルが与えられる。そして入力データのone-hotベクトルと(辞書の単語数)×(単語ベクトルの次元数)の重み行列を積が最終的なベクトルとなる。(通常のRNNでは辞書の単語数×辞書の単語数の重み行列で計算を行う)
- こうすることで大規模データの分散表現の学習に必要な計算量が少なくなった
- また、単語同士の意味の近さ計算することができる
- 例)日本 - 東京 + ロンドン ≒ イギリス
AttentionMechanism
- seq2seqは2単語も100単語も固定長のベクトルで表現するので、長い文章を扱うのが難しい
- この問題を解決するのがAttention Mechanismで、入力データと出力データの単語がどのように関連しているかを学習するものである
- 例) 入力「I have a pen」と出力「私はペンを持っている」は「I」と「私」、「have」と「持っている」、「pen」と「ペン」が関連しており、「a」は関連が低い
- AttentionMechanismによって関連を学習することで、長い文章でも学習がうまくいきやすくなる