ろぐれこーど

限界組み込みエンジニアの学習記録とちょっぴりポエム

Recurrent Neural Network(RNN)の基礎を学んだ

ニューラルネットワークが現在盛んに研究されているが、その中でも系列データを扱うRecurrent Neural Network(RNN)について学習した。 CNNは研究でもよく取り扱っていたが、RNNは手付かずだったので。。

教材はこれとか

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

これとか

Deep Learning (Adaptive Computation and Machine Learning series)

Deep Learning (Adaptive Computation and Machine Learning series)

を見た。Bengio先生の方は日本語訳プロジェクトが進行中のようで、暫定版がScribdで公開されている。もう少し待てば製本版が買えるようになるかな。 http://www.deeplearningbook.me/


系列データ

RNNは系列データを対象とする。わかりやすい例だと音声、文章など。このようなデータを一般に{ \textbf{x}_1, \textbf{x}_2, \ldots, \textbf{x}_t, \ldots, \textbf{x}_T  } と表す。{ T }は可変であり、添え字は大体{ t }を使う。

便宜上{ t } を時刻と呼ぶことが多いが、厳密に時刻と対応している必要はなく、順序に意味があるデータならばなんでもいい(画像、動画、ボリュームデータなど)。多次元への拡張はMulti-dimentional Recurrent Neural Networkで取り上げられているが、またおいおい見ていく。とりあえずここでは、音声の波形のような一次元データを考えるものとする。


RNNの仕組み(順伝播)

RNNにも色々な種類があるようだが、最も単純な回帰構造を持つネットワークは以下のような感じ。

f:id:gco46:20170929005812j:plain
RNNの概要図。(左)回帰の矢印で表現 (右)回帰部分を展開

文字通り、時刻tの隠れ層の出力が時刻t+1の隠れ層に影響するような構造をしている。入力がd次元T系列だとする。入力層から隠れ層につながる重みがU, 隠れ層から隠れ層の重みがW, 隠れ層から出力層の重みがVで表されている。Wは各時刻で共通の重みを用いる(ただし時々刻々と更新されていく)。

入力{ \textbf{x}_1}を受け取って{ \textbf{y}_1 }を出力、{ \textbf{x}_2}を受け取って{ \textbf{y}_2 }を出力...といった手順で、入力の系列長と同じ長さの系列{ \textbf{y}_1...\textbf{y}_T}を出力する(CTC, connectionist temporarl classificationという構造を使えばこの限りではない)。

入力層、隠れ層、出力層の添え字をそれぞれi, j, kで表し、時刻tにおけるネットワークの各ユニットへの入力を{ x_{j}^{t} } 、隠れ層の入出力を{ a_{j}^{t}, h_{j}^{t}}、出力層の入出力を{ o_{k}^{t}, y_{k}^{t}}とする。またノードiからjへの重みを{ u_{ij}}、ノードjからj'を{ w_{jj'}}、ノードjからkを{ v_{jk}}と書く。

時刻tの隠れ層への入力は以下で表される。

 {\displaystyle
a_{j}^{t} = \sum_i u_{ij} x_i^t + \sum_{j'} w_{j'j} h_{j'}^{t-1}
}

但しバイアス項は重みと入力ににまとめて省略しているものとする。隠れ層の出力は

{ \displaystyle
h_j^t = f(a_j^t)
}

{ f }は活性化関数。{ h_j^{t-1} } から時刻tにおける出力を随時計算していくわけだが、t=1に関しては初期値を定める必要がある。通常は{ h_j^0 = 0 }とするらしい。まとめると

 {\displaystyle
\textbf{h}^t = \textbf{f} (\textbf{U} \textbf{x}^t + \textbf{W} \textbf{h}^{t-1} )
}

RNNの出力は隠れ層の出力{ \textbf{h}^t }と重み{ \textbf{V} }から以下のように計算される。

{\displaystyle
o_{k}^{t} = \sum_j v_{jk} h_{j}^{t}
}

{\displaystyle
y_{k}^{t} = f(o_k^t)
}

まとめると

{\displaystyle
\textbf{y}^t = \textbf{f}^{(out)} (\textbf{V} \textbf{o}^t )
}

と書ける。この出力を用いて誤差関数を計算する。これは通常のネットワークと同様で、例えば分類問題なら{ \textbf{f}^{(out)} }はsoftmaxになり、誤差関数はcross entropyを計算する。 出力系列{ \textbf{y}^t }に対する教示信号 { \textbf{d}^t }とするとき、cross entropyは以下のようになる

{\displaystyle
E(\theta) = - \sum_n  \sum_t  \sum_k  d_{nk}^t  \log y_k^t (\textbf{x}_n  ;  \theta)
}

この場合、単一のラベルを系列長分だけ推定するネットワークとなる。


逆伝搬

RNNの学習は通常のニューラルネット同様に勾配降下により行われるが、重みによる微分を求める必要がある。主にRTRL法(real time recurrent learning)、BPTT法(back propagation through time)の二つが用いられる。後者のほうが よりシンプルということで、BPTT法についてみていく。

RNNを時刻で展開すると順伝搬型NNとみなすことができるということで、以下のような伝播を行う。

f:id:gco46:20170929210033j:plain
回帰構造の展開図。青は入力、赤は出力、黒は隠れ層を表す。出力層からデルタを計算して逆伝播していく。

展開したネットワークを通常の順伝播ネットワークとみなすことで誤差逆伝播を実現する。ただし気を付けることが二つある。一つは、 展開すると各時刻で別々の層に誤差を伝播しているように見えるが、実際に更新されるのは同じWである。二つ目は、時刻tの隠れ層に伝播される誤差は時刻tの出力層と、時刻t+1の隠れ層からの二つであること。

通常のネットワークの第l+1層から第l層への誤差、入力での微分{ \delta_j^l \equiv \partial E / \partial a_j^l }は、

{\displaystyle
\delta_j^l = \sum_k w_{kj}^{l+1}  \delta_k^{l+1}  f'(a_j^l)
}

と書けることから、RNNについてもこの考えを導入する。時刻tの出力層のユニットkにおけるデルタを


 {\displaystyle
\delta_k^{out, t}  \equiv  \frac{\partial E}{\partial o_k^t}
}

と書き、同時刻の中間層のユニットjのデルタを


{\displaystyle
\delta_j^t  \equiv  \frac{\partial E}{\partial a_j^t}
}

と書くことにする。時刻tにおける隠れ層のユニットは、tでの出力層とt+1の隠れ層のユニットとつながりがあるため、{\delta_j^t}


{ \displaystyle
\delta_j^t  =  \left( \sum_k v_{jk}^{out} \delta_k^{out, t}  +  \sum_{j'} w_{j'j} \delta_{j'}^{t+1}  \right)  f'(u_j^t)
}

のように計算できる。{ t=T }から始め、tを小さくしながら繰り返し計算すると隠れ層の誤差が求められる。

各層のデルタが計算できたため、誤差{ E }の各層の重みによる微分を計算できるようになった。重みはU, W, Vの三つがあるため、それぞれに関してみていく。{ u_{ij} }は各tの隠れ層のユニットjへの入力 { a_{j}^t }にのみ含まれるため、


 {\displaystyle
\frac{\partial E}{\partial u_{ij}} = \sum_{t=1}^T  \frac{\partial E}{\partial a_{j}^{t}}  \frac{\partial a_{j}^{t}}{\partial u_{ij}}  =  \sum_{t=1}^T \delta_j^t x_i^t
}

となる。同様にして、{ w_{j'j} }は、各tの隠れ層のユニットjへの入力{ a_j^t }にのみ含まれるため、


 {\displaystyle
\frac{\partial E}{\partial w_{j'j}}  =  \sum_{t=1}^T  \frac{\partial E}{\partial a_j^t}  \frac{\partial a_j^t}{\partial w_{j'j}}  = \sum_{t=1}^T \delta_j^t  h_{j}^{t-1}
}

となり、{ v_{jk} }は各tの出力層のユニットkへの入力{ o_k^t }にのみ含まれるので、


{ \displaystyle
\frac{\partial E}{\partial v_{jk}}  =  \sum_{t=1}^T  \frac{\partial E}{\partial o_k^t}  \frac{\partial o_k^t}{\partial v_{jk}}  =  \sum_{t=1}^T \delta_j^t h_j^t
}

と求まる。各重みの誤差勾配を計算したあとは、重みを勾配方向に更新する通常の勾配降下法が適用される。


まとめ

以上が最も単純なRNNの順伝搬、逆伝搬の構造である。理論上は時刻tにおける出力はそれまでの系列の入力の影響を受け、データに潜むcontextを学習できる・・・ということになるが、実際は系列が長くなりすぎると勾配消失が起こるため、ごくわずかな期間の情報しか反映できない問題がある。これを解決した手法がGated recurrent unit(GRU)とかLong short-term memory unit(LSTM)とかあり、実際に成果を上げているモデルはこれらを用いている。

余裕があればまた見ていく。 はてブLaTeX記法は癖があってしんどいな。。