リカレントネットワーク
リカレントネットワークとは
リカレントネットワークとは、ある層の出力がもう一度その層へ入力される「回帰結合」を持つニューラルネットワーク全般のことを指します。
このような結合を持つことで、ニューラルネットは過去の情報を保持することができるようになります。実際には回帰結合の部分には何らかの変換が準備されており、情報を保持するための工夫がなされております。
LSTMやGRU
LSTMやGRUなどは、この回帰結合をどのように表現するのかの具体的な策であり、いわばリカレントネットワークを達成する一手段ということになります。
LSTMやGRUではゲートという概念を導入することで、現在の入力と過去の出力を、それぞれどの程度今回の出力に反映するかを調整することが可能になっています。
今回の記事について
今回はリカレントネットワーク(回帰結合を有するニューラルネットワーク)の基本について紹介します。リカレントネットワークが具体的にLSTMやGRUあるいは単純な回帰結合のいずれで実現されているのか、その詳細には立ち入りません(しかし基本的な考え方全て同じ)。
リカレントネットワークの都合
リカレントネットワークは、実際には回帰結合部分で、過去全ての情報をぐるぐる流し続けているわけではありません。学習の都合上、どこかで打ち切らなければなりません。
過去、いくつまでの情報を保持するかを予め決めてしまえば、リカレントネットワークはあたかも通常のニューラルネットワークのように展開図にすることができます。
普通のニューラルネットワークの展開図
普通のニューラルネットワーク
入力層のベクトル
隠れ層のベクトル
出力層のベクトル
とした場合には、下記の図のような形で表せます。これは展開図も何も、普通のニューラルネットワークの話で、変わったことは1つもありません。
ここではそれぞれ何らかの変換であり、通常はアフィン変換の後に活性化関数によって非線形変換が行われますが、それらをまとめて文字で表しておきます。
そうすると、このニューラルネットワークの方程式は
と2つの式で表され、隠れ層のベクトルを消去することで
と合成関数の形で表現することができます。
ニューラルネットワークの学習において広く持ちいられている誤差逆伝搬法は、入力から出力に掛けて合成関数の形式で表現されていることを利用し、微分の連鎖規則を使ったエレガントな微分の計算手法です。
言い換えれば、微分の連鎖規則が上手く使えることが誤差逆伝搬法の(強いてはニューラルネットワークの)条件になってくるわけです。実をいうと、回帰結合があるケースでは、微分の連鎖規則で上手く勾配を求めることができません(=学習のアルゴリズムが書けない)。
乱暴に言ってしまえば、誤差逆伝搬法が使える条件はフィードフォワード型の結合のみでネットワークが構成されていることになってきます。
ちょっとだけ変わったニューラルネットワーク
例えば以下の形のニューラルネットワークでも誤差逆伝搬法を適用することができます。
方程式は、例えば以下のような形になるでしょう。
を消去すれば
[tex:{\bf y} = g(f({\bf x}))+i(h({\bf x}))]
という形にすることができます。変換にパラメータが含まれている場合に、微分を求めるのは容易そうです。
入力が複数あっても良い
入力ベクトルが複数存在しており、しかもそれが途中から合流する形になっていても構いません。問題なく微分の連鎖規則を適用できます。
層を飛んだ結合があっても良い
以下のような構造でも構いません(むしろを経由した勾配が伝搬できなくなった場合にも、直接飛び越えた結合の勾配が上手く伝搬することが期待できる)。
このような構成を持ったものを「1つの層」と考えて、更にこの構造を多層化していったものをLasNetと呼びます(変換は畳み込み+ReLUが用いられる)。
リカレントネットワークの展開図
上記のニューラルネットワークに対して、回帰結合をくっつけます。
この回帰結合は変換を行って、隠れ層の出力を再び隠れ層の入力に用います。
リカレントネットワークの方程式
普通のニューラルネットワークでは意識する必要がありませんでしたが、今回は、「時間」を考えなければなりません。1つ前の出力が、現在の入力に用いられることを考慮し、リカレントネットワークの方程式は
という形になります。実際には、隠れ層の回帰結合はもっと複雑であり、更に変換が準備されて、
のようになっている場合が多いです。要するに、とを混ぜてしまうということです。このような混ぜてしまう変換を1つにまとめて、と置いてしまいましょう。
がリカレントネットワークの方程式になっています。特に難しいことはありません。現在の隠れ層の値は、2つの値、を引数にした何らかの関数で決定されるというだけのことです。
(通常、上記の方程式を「状態方程式」などと言います。隠れマルコフモデルなどでは、これを上手にモデル化しておくことで、最適化問題を上手く解ける形式に落とし込んでいます)
合成関数へ持っていけるか
さて、ここから、隠れ層のを消去して合成関数の形にしてみましょう。
お分かりだと思われますが、これはどこかで打ち切らない限り無限に続いてしまいます。仮に、過去まで考慮した場合、式を以下のように展開することになります。
:
となっており、順にを消去していくことで
となります。
非常にややこしい形をしていますが、引数に注意すれば、必要なのはの過去個分と、だけです。ここで、は初期値として適当に与えてしまえば、事実上だけで表現できることになります。
もちろん本来ははもっと前のやに依存するところを、それをハッキリ求めるのを諦めて、適当に設定しちゃえということになります。
ここでを「sequence length」などと言います。(これによってを何個入力することになるか決まるため)
リカレントネットワークの展開図
こうすることで、リカレントネットワークは、あたかも普通のニューラルネットワークかのように記述することが可能になります(入力が複数ある構造が許されているのを思い出してください)。
入力が複数あり、途中で合流する形でも微分の連鎖規則を使える
仮にとして、を求めるケースを考えます。
方程式は以下で表されます。
一旦、時系列データであることを忘れてしまい、入力ベクトルが複数個あるニューラルネットワークだと思ってしまえば、以下のような展開図を得ることができます。(枠の色と数式の色が対応)
便宜的に入力はと考えます。そして隠れ層としてが計算され、出力が計算されるというモデルです。
多くのフレームワークでは、は初期値として内部に与えておいて、実際に引数に取るのはsequence lengthのデータ(この場合、3つの)だけです。
添字には時間という認識が「人間」にはありますが、ニューラルネットワークからしたら、そんなこと知ったことではないので、単に複数の入力を持ち、それが途中で合流するフィードフォワードニューラルネットワークとして、リカレントネットワークを実装することが出来るというわけです。
無理矢理、合成関数の形にするならば、を随時消去していき
という形を取ることになります。
最後に
フレームワークではLSTMやGRUなどが既に実装されており、使う側は「どのようなデータを受け取ってどのようなデータを出力するのか」だけを知っていれば使うことができます。
TensorFlow
今回は、その最低限を把握できるように記事を書いたつもりです。
TensorFlowは非常に分かりやすく、使うときには今回説明したようなフィードフォワードニューラルネットの構造が内部で準備されていると思えば、上手く扱うことができるでしょう。
本来ならばというベクトル(1階のテンソル)で1つのデータなのですが、sequence lengthを予め「T」と決めてしまうことで、(2階のテンソル)を1つのデータだと思って一気に入力するイメージです。そしてこれをバッチサイズ分だけ準備しておくことになります。
TensorFlowでは入力の配列は(batch_size, sequence length, vector dim)という形(3階のテンソル)になります。(placeholderは(None, sequence length, vector dim)としておく)
(逆伝搬に関する計算の都合が、打ち切りを行う目的であったにも関わらず、順伝搬もsequence lengthで打ち切ってしまっていることに注意してください。これがどれほど学習に影響を及ぼすかは分かりませんが)
Chainer
一方でChainerの場合はもう少し複雑で、計算グラフ(すなわちニューラルネットワークの構造)が決定されるのが順伝搬が行われた後です。すなわち、どれだけの長さのデータを与えて、計算をどれだけ回すことになるかかによって、構築されるニューラルネットワークが決まるということです。
本来のリカレントニューラルネットワークは、sequence lengthなんてものを決めることはなく、無限に値を反映すべきです。そもそも勝手にsequence lengthを決めてしまうのは、誤差逆伝搬時の計算の都合上であって、特にデータを流す順伝搬の際に必要な処理ではありません。
なぜなら順伝搬時には「時間をどれだけ遡るか」という問題になんて直面しないからです。ただただちゃんと時系列順にデータを順番与えることだけ守れば、その順番通りに計算を行うだけです。
そしていざ、逆伝搬をしたいときになって初めて、どこまで過去の入力に遡るべきかを(計算の都合のために)決めなければならないということです。その点Define by Runという概念は上手く働いており、順伝搬時には時系列データをひたすら順番通りに与えておき、逆伝搬時にlossの影響を「Truncated」するということが出来ます。
LSTMのソースに関する解説は以下の記事を参考に。
本来の在り方
実はこの違いに関して、どれほどの差が学習で出てしまうのか、私はしっかりと認識できていません。「TensorFlowのリカレントネットはどこか限定的すぎる」ということをなんとなく感じていました。
Chainerをメインで使いつつ、TensorFlowの人気に嫉妬し始めて、最近真面目に勉強するようになってきたためか、以下のような記事を見つけました。
Styles of Truncated Backpropagation - R2RT
なんか、実験データ見る限り問題なさ気な感じもします。