LSTM

囲み枠c

はじめに

今回は、ChainerによるLSTMの実装例を示し、Fibonacci数列から作られる周期関数に適用した結果を示す。

LSTM(Long Short Term Memory)

LSTMは、時系列データ\left(\vec{x}(1),\cdots,\vec{x}(T)\right)が与えられた時、その並びの中に存在する規則性を見い出し、\vec{x}(T+1)を予測するネットワークである。LSTMの構造を以下に示す。

ここで、\vec{x}(t),\vec{h}(t),\vec{y}(t)は、それぞれ時刻tにおける入力値、隠れ層の値、出力値である。U,W,Vは、これらのベクトルに乗算される行列である。現在の入力値と直前の時刻での隠れ層の値が足し合わされ、現在の隠れ層の値になる。

(1)    \begin{equation*} \vec{h}(t)=f\left(U\vec{x}(t)+W\vec{h}(t-1)\right) \end{equation*}

ここで、f(\cdot)は活性化関数である。上式からわかる通り、隠れ層は過去のデータを現在のデータに反映させるための層である。隠れ層の各ユニット(黄色い丸)はLSTMブロックと呼ばれ、以下4つの構造をその内部に持つ(黄色い丸を入出力と同じユニットに置き換えると通常のRNNになる)。

  1. CEC(Constant Error Carousel)
  2. 入力ゲート
  3. 出力ゲート
  4. 忘却ゲート

CECは、長周期データを扱う際に問題となる勾配消失を抑制するために追加されたユニットである。入力・出力ゲートは、過去の値が必要なときは開き、そうでない場合は閉じる仕組みである。これを導入することにより、長周期の規則性を検出することが可能となる。しかし、入力・出力ゲートは、過去のデータをLSTMブロック内に留めておく機構であるため、時系列データが急激に変化するような場合、その変化に対応できない。これに対処するのが忘却ゲートである。忘却ゲートは、内部に記憶しておく必要がなくなった情報を忘れるための仕組みである。

\left(\vec{x}(1),\cdots,\vec{x}(T)\right)から\vec{x}(T+1)を予測する様子を示すため、上の図を時間方向にさらに接続したものを次に示す。

図中の青い矩形で囲んだデータが入力値、赤い矩形で囲んだ値が出力値である。赤い矩形と緑の矩形の間の2乗誤差の和

(2)    \begin{equation*} E=\sum_{i=1}^T\;\left\|\vec{y}(i)-\vec{x}(i+1)\right\|^2 \end{equation*}

が最小となるようにネットワークを訓練する。

教師データの構成

教師データの作成手順は以下の通りである。

  1. 十分に長い時系列データを用意する。
  2. 始点を1つずつずらながら、適当な長さLで時系列データを分割する(下図参照)。
  3. 長さL内の各値は直後の値を予測するための入力値となる(下図参照)。

長さLのシーケンス内で2乗誤差の和を計算し、これを最小にする。この手順を切り出したシーケンスの数だけ繰り返す。

周期関数の生成

Fibonacci数列は次の漸化式を満たす数列である。

(3)    \begin{equation*} a_n=a_{n-1}+a_{n-2}\;\;(a_1=a_2=1) \end{equation*}

この数列を任意の自然数で割った余りは、次の表に示すような周期を持つ。

5で割った時の数字の並びは以下のようになる。

(4)    \begin{equation*} {\bf 1,1,2,3,0,3,3,1,4,0,4,4,3,2,0,2,2,4,1,0},1,1,2,3,\cdots \end{equation*}

太字で示した部分が1つの周期である。今回はこの周期20の数列をLSTMで学習する。

動作環境

  1. macOS Sierra
  2. Python 3.6.2
  3. Chainer 3.0.0

Chainerによる実装例(訓練)

ここまでのロジックを実装したものが以下である。

実行時のエポック数と誤差値の関係は以下の通りである。

ここで、lossは訓練データに対する誤差(上の式(2)に相当するもの)、val_lossはテストデータに対する誤差を表す。

Chainerによる実装例(予測)

実装例を示す前に、予測時のデータの与え方を以下に示す(下図参照)。

  1. 訓練時の時系列データを用意する。
  2. 訓練時と同じ長さLで最初のシーケンスを取り出す。
  3. このシーケンスを用いて、L+1個目の値を予測する。
  4. シーケンスの初項を切り捨て、末尾に、いま予測した値を追加する。新たな長さLのシーケンスが出来上がる。
  5. 同じことを繰り返す。
  6. シーケンスに占める予測値の割合が増えていき、Lステップ目で全ての値が予測値に置き換わる。

予測時に使用するプログラムは以下の通りである。

Lの長さを色々変えて予測した結果を以下に示す。L以外のパラメータは同じである。

黒線が予測値、赤い点線が正解値である。横軸の長さは4Lとした。横軸最初のLステップは、予測値ではないので0としてある。L\geq 20で波形をほぼ予測できていることが分かる。元の関数が周期20を持つことから、Lとして少なくとも1周期分必要であるということである。

まとめ

今回は、ChainerによるLSTMの実装を行った。ネットを検索するとChainerを用いたLSTMの実装例はたくさん見つかるが、疑問の余地なく理解できるサンプルは少ないのではないか。Chainerのソースに含まれるサンプルを読み解きながら、自分なりに納得できる形に仕上げたものが今回の実装例である。chainer.training.Trainerは敢えて使用していない。ロジックの流れがわかり難くなるためである。
今回の実装では、chainer.initializersが結果に与える影響が大変大きかった。chainer.initializers.Normalで安定した結果が得られるようになった。

補足

今回取り上げた例では時系列データのパラメータを時刻としているが、時刻である必要はない。値の並びに意味があるデータは全て時系列データである。
KerasとTensorflowによるLSTMの実装が、例えば「詳解ディープラーニング TensorFlow・Kerasによる時系列データ処理」(著者:巣籠悠輔)にも記載されている。純粋にコード量で比較すると、Tensorflow > Chainer > Kerasとなる(この本は良書です)。

Kumada Seiya

Kumada Seiya

仕事であろうとなかろうと勉強し続ける、その結果”中身”を知ったエンジニアになれる

最近の記事

  • 関連記事
  • おすすめ記事
  • 特集記事

アーカイブ

カテゴリー

PAGE TOP