たにしきんぐダム

プログラミングやったりゲームしてます

Coursera / Sequential Model (week1) 受講メモ (RNN/LSTM/GRU/BiRNN/DeepRNN)

Deep Learning Specialization 5つ目のコース www.coursera.org

CNN より RNN のほうが興味あったので先にこっちを先にやった。分量が多くなりそうなので分けておく。やっとこさRNNだよ。

RNN

feed forward neural network が得意でないタスク

これまで見てきたようなニューラルネットには以下の課題がある

  • 入力次元が一定
  • 各要素の値がそれぞれ独立しているという仮定がある

しかし自然言語や音声など時系列方向に文脈が存在するため、これらをfeed forward neural network学習することは難しい。 そこで、neural network をいくつも並べて過去のneural netの隠れ層からの出力を、未来のneural netへの入力に渡していくことで、時系列的な文脈を解するneural networkであるRecurrent Neural Networkが登場する。

まず最も基本的なRNNの構造を見ていく。

概要

時刻 t = 1...T とするとsimpleなRNNは、2層ニューラルネット(1つの隠れ層と出力層)がT個連なる形をしており、一つ前の時刻のニューラルネットの隠れ層からの出力を次の時刻のニューラルネットの隠れ層の入力に使われる形をとっている。(2層ニューラルネットの代わりに多層(3層以上)のニューラルネットを Deep RNN というが、それは後半で学ぶ)。

forward propagation

  • $ x^{<t>} $ : 時刻tでの入力層からの出力
  • $ a^{<t>} $ : 時刻tでの隠れ層からの出力
    • $ p^{<t>} $ 隠れ層の活性化前の値
  • $ y^{<t>} $ : 時刻tでの出力層からの出力
    • $ q^{<t>} $ 出力層の活性化前の値
  • 次元
    • $ h $: 隠れ層の素子数
    • $ n_x $: 入力データの次元
  • 結合係数、バイアス項
    • $ W_{aa} $ : 過去の隠れ層から現在の隠れ層への結合係数 (h, h) 行列
    • $ W_{ax} $ : 入力から隠れ層への結合係数 (h, n_x) 行列
    • $ W_{ya} $ : 隠れ層 - 出力層の結合係数
    • $ b_a $ : 隠れ層に入るバイアス項
    • $ b_y $ : 出力層に入るバイアス項
  • 活性化関数
    • $ f $ : 隠れ層の活性化関数
    • $ g $ : 出力層の活性化関数

まず、隠れ・出力層の活性化前の値p,qはそれぞれ

f:id:tanishiking24:20201014235413p:plain

f:id:tanishiking24:20201014235416p:plain

隠れ層・出力層からの出力は以下のようになる。

f:id:tanishiking24:20201014235410p:plain

f:id:tanishiking24:20201014235418p:plain

隠れ層の活性化前の値を見るとわかるように、ひとつ前の時刻の隠れ層からの出力が、現時刻の隠れ層に入ってきている。

back propagation through time

d2l.ai

コスト関数

これまで見てきた feed forward neural network では各バッチでコスト関数と、そのパラメータによる微分を計算してパラメータを更新していた。

BPTT では出力層からの誤差伝播に加えて、未来のRNN cellからの誤差も伝搬してくる。

  • 時刻tにおけるコスト関数を $ L^{<t>}(\hat{y^{<t>}}, y^{<t>}) $
  • 全体でのコスト関数を $ L(\hat{y}, y) = \sum_{t=1}^{T_x}(L^{<t>}(\hat{y^{<t>}}, y^{<t>})) $

時刻tにおける微分

まずはそれぞれのパラメータに対して、時刻tにおける誤差関数を計算する。$ \frac{\partial L}{\partial p^{<t>}} $ と $ \frac{\partial L}{\partial q^{<t>}} $ については根幹部分なので後で考える。

f:id:tanishiking24:20201015010415p:plain

f:id:tanishiking24:20201015010432p:plain

f:id:tanishiking24:20201015010443p:plain

f:id:tanishiking24:20201015010457p:plain

f:id:tanishiking24:20201015010500p:plain

時間方向の逆伝搬

  • $ \frac{\partial L}{\partial p^{<t>}} $ を計算する。RNNではネットワークのforward-propagationで時刻t-1での隠れ層の出力 $ a^{<t-1>} $ を次の時刻の隠れ層に入力していたので、逆伝搬の際も誤差伝搬を考える必要がある。
  • やるべきことは $ \frac{\partial L}{\partial p^{<t-1>}} $ を $ \frac{\partial L}{\partial p^{<t>}} $ を使って表現すること

再帰的な関係式を求めると以下のようになる (* は element-wise な掛け算)

f:id:tanishiking24:20201015012323p:plain

(これ間違えてる!pをqと読み替えてください)

つまり

f:id:tanishiking24:20201015012334p:plain

(こっちも間違えてる!pをqと読み替えてください)

この結果すべての時間でのパラメータに対する微分が求められることになったし、それは時間方向の逆伝搬の影響を受けているということが分かった。めでたしめでたし

更新式

求めた微分を利用して、いくつかのstepの過去にまで遡って勾配を積み重ねて更新を行う。τはどの程度のタイムステップを遡って勾配を計算するか。

f:id:tanishiking24:20201015022320p:plain

f:id:tanishiking24:20201015022351p:plain

f:id:tanishiking24:20201015022402p:plain

f:id:tanishiking24:20201015022417p:plain

f:id:tanishiking24:20201015022425p:plain

vanishing / exploding gradient problem

initialization / activation function

weight initialization f:id:tanishiking24:20201014235410p:plain にあるように、同じ重み行列が何度も掛け合わせられることになる。例えば $ a^{<3>} $ には

f:id:tanishiking24:20201015235759p:plain

というような項が出現するはず、簡単のため activation function f が恒等関数の場合などを考えるとわかりやすいが、重み $ W $ が何度もかけられている。その結果計算の途中でオーバーフローを起こしてしまったりする原因になる。

これを防ぐため、パラメータの行列を直交行列で初期化する。f=idの場合を考えると $ WWT = I $ となるのでオーバーフローしなくなる。もちろん実際はfは恒等関数ではなく非線形関数だし、学習を重ねていくとWが直交行列から形を変えていくことになるが、それでも適当に初期化するよりもオーバーフローを防ぐ助けとなる。

activation function gradient vanishing problem の対応としてこれまでの feed forward network で使われていたのは、activation function に ReLU を使うという方法が取られていましたが、RNNでReLUを使ってしまうと上に書いたような a の値が時間を重ねるごとに爆発的に増えてしまいオーバーフローを起こしてしまうことになる。そのためRNNではtanhがよく使われる。

上で見た時間方向での微分の式を見ると

f:id:tanishiking24:20201015012334p:plain

なので、以下の値が1なら誤差が定常化できるが、これが1より小さいとgradient vanishingになるし、大きいと exploding gradient になる。重みはともかく、fはtanhとなるとその微分はかなり小さくなるよねぇ。

f:id:tanishiking24:20201020171506p:plain

この後学ぶLSTMやGRUでは gradient vanishing problem 対策が成されている。

LSTM

colah.github.io

qiita.com

(わかりやすいんだけど難しいのでやっぱわからない!!!LSTMの図あんまわかり易くないね????)

LSTM は RNN で課題となっていた vanishing gradient problem を解決するために時系列方向の誤差を定常化するための機構に加えて、いくつかのゲートがくっついて高性能(パラメータは増えた)になったもの。

元論文読んでみたりしたけどボトムアップ的に(課題->解決方法)理解するのは全然できなかったので、トップダウン的によく使われてるやつを眺めて確かに問題解決できてますねという感じで理解しようと思う...

  • RNN では隠れ層からの出力を次の RNN cell に渡していた
  • LSTM ではそれに加えて、 cell state という過去からの入力を溜め込んでいく機構を cell に追加する。
  • 過去からの入力をまるまるすべて溜め込んでいるといくつか課題があるので、入力/出力/忘却ゲートを加えて入力値や逆伝搬してくる値を絞る
    • 過去のデータが新しいデータに上書きされて消えてしまったり
    • stochastic gradient descent みたいに過去のパラメータ更新と矛盾するパラメータ更新がどんどん発生して学習が上手く進まなかったり
    • 文脈が完全に変わって過去データをまっさらにしたくなったときに対応できない

早速式を眺めてみる

  • $ tanh $ : hyperbolic tangent
  • $ \sigma $: sigmoid 関数
  • $ f $: 出力層の活性化関数(softmaxとか)
  • $ a^{<t>} $: 時刻tのcellからの隠れ層からの出力、次の時刻のcellへの入力になる
  • $ c^{<t>} $: 時刻tのcellからのcell state、次のcellへの入力になる
  • $ x^{<t>} $: 時刻tのcellへの入力データ

f:id:tanishiking24:20201020164901p:plain
新しいcell stateの候補(candidate value)。この後導入するinput gateによってどの程度この値を採用するかが決まるので"候補"。

f:id:tanishiking24:20201020165110p:plain
input gate。candidate value のうちどの程度をcell stateとして採用するかを絞り込むゲート。

時刻tのcell stateは上で決めた candidate value * input gate と、次に定義する forget gate * 前のcell state により決まる。

f:id:tanishiking24:20201020164557p:plain
forget gate、0~1をとる。0なら過去のcell stateはすべて忘れ、1なら過去のcell stateの値をすべてキープする

f:id:tanishiking24:20201020165248p:plain
時刻tのcell state

時刻tのcell stateが定まったら、後は次の層に与えるoutputと、outputから時刻tの予測値を計算する

f:id:tanishiking24:20201020165432p:plain
output gate

f:id:tanishiking24:20201020165600p:plain
出力値を-1~1で抑えるためにtanhをかけてoutput gateを通す。(tanhで抑えないと overflow の原因となる)

f:id:tanishiking24:20201020165541p:plain
時刻tの予測値

これに加えて peephole connection というものを加えたバージョンもある

LSTM は gradient vanishing を対策できとるんか?

LSTMではcell stateのgradientが定常化できれいればよいのであるが、どうなったかな。back propagationを計算してみる。大変なので cell state にだけ注目

f:id:tanishiking24:20201020175248p:plain
時刻tの出力からの誤差伝搬

f:id:tanishiking24:20201020172055p:plain
時間方向の誤差伝搬

となるので、forget gate からの出力値によってどの程度誤差を逆伝搬させるかがコントロールされていることが分かる。forgate gate が 1 なら基本的に誤差はキレイに伝搬するし、0に近くなると時系列的な依存はなくなったとみなしてその時点で時間方向の誤差は消えてなくなる。

うーん分かったような、なんか狐につままれた気分が拭えない。

gradient clipping

計算した gradient をそのまま適用すると、更新値が大きすぎて安定して学習が進まない場合がある。そのため gradient を計算した後それを適用する前に適当な値に切り詰めることを gradient clipping という。

numpy.clip で切り詰めたり、tf optimizer の option になってたり

numpy.org

www.tensorflow.org

よくわからなかったポイント

  • 1997年の論文見てて思ったけど、RNNの課題感からCECを導入しましょうってなるロジックがよくわからなかった、dL(t-1) = dL(t) とするためにCECを導入するしかなかった?他に方法はなかった?
  • 入力重み衝突とかってstochastic gradient descentでも起こり得る問題と同じだよね? batch gradient は的な時系列方向では当然できないから 入力ゲートと出力ゲートをつけてどの程度重みを絞ったって理解で良い?

GRU (Gated Recurrent Unit)

LSTMはかなり効果的だが、パラメータの数が多いため計算に時間がかかる問題がある。GRUはLSTMを簡単にしてパラメータの数を減らしつつもLSTMと同等以上の性能を保持するモデルとして提案された。

  • LSTMの構成要素
    • cell state
    • input gate
    • output gate
    • forget gate
  • GRUの構成要素
    • reset gate
    • update gate (LSTM の input gate + forget gate な役割)

f:id:tanishiking24:20201020183537p:plain
update gate

f:id:tanishiking24:20201020183547p:plain
reset gate

f:id:tanishiking24:20201020183604p:plain
candidate value

f:id:tanishiking24:20201020183618p:plain
output

Bidirectional RNN

d2l.ai

  • これまでのRNNモデルは過去の状態から未来の状態を予測するというモデルだった
  • 時系列データのある点を予測する時、過去のデータした得られない場合は仕方ないが、すべてのデータが得られるならば未来の状態もある時点での状態の推論に役立つだろうと考えられたのが Bidirectional RNN

Bidirectional RNN では過去から未来に伝播する層と、未来から過去に伝搬する層を別々に考えられる。

f:id:tanishiking24:20201020232555p:plain

f:id:tanishiking24:20201020232606p:plain

それぞれで隠れ層からの出力を計算し、concatして、活性化関数を通して出力する。

f:id:tanishiking24:20201020232813p:plain

f:id:tanishiking24:20201020232824p:plain

Deep RNN

  • これまでの議論で登場したRNNでは、それぞれの時刻でのcellはすべて2層ニューラルネットだった。
  • 各時刻でのneural netに複数のLSTM cellなどを重ねることで表現力を高めようという試み
  • 層を重ねることで、それぞれの層で異なる長さの時系列的な相関をモデルに組み込むことができるようになる。

machinelearningmastery.com