背景
Neural Networksの1つにRecurrent Neural Networks(RNNs)という種類がある.時系列データへ主に適用されるモデルで,主に自然言語の解析や生成に用いられるLong short-term memory(LSTM)もRNNの1つである.LSTMは画像についてConvolutional Neural Network(CNN)がそうであったように,モデルの「深層化」によって入念にデザインされたアルゴリズムを駆逐している.最近では「時系列データであればとりあえずLSTMにデータにぶちこんで損失関数を適切に設計すれば結果がでる」などという乱暴な話まで聞く.Neural Networksはその自由さから多様なデータを受け入れられるが故にブラックボックス化しやすい.したがってこのような状況が生まれるのは致し方がないかもしれない.しかし一口にLSTM, RNNといっても日々改良されたモデルが提案される昨今,その中身を知らないまま使い続けるということは良くはないだろう.少なくとも,いくつかのモデルがどのような計算過程を必要とするのかを俯瞰するのは悪いことではないはずだ.
この記事ではいくつかのRNNを簡単にメモしていきたい.はじめに概要を文章で説明し,数式を示したあとで,その数式を図示して,できれば実装例をtensorflowを使って示したい.実装例ではできるだけ高いレベルのAPI(tf.contrib.rnn
など)は使わずに基本的な演算,つまり四則演算,内積,シンプルな関数適用のみを使って書いていきたい.もちろん実際の実装はtf.contrib.rnn
などの他の実装を参考にしている.実装全体はgithubから参照できる.
基本としてのDense Layer
まずはRNNモデルを成す基本的な演算過程を図示方法の説明と共にみていく.もっともシンプルな演算はなにもしないことである.入力ベクトルを$x$,出力ベクトルを$y$とすると式は以下のようになる:
y = x
この演算は以下のように図示することにする:
Neural Networksの基本は線形和に非線形関数を要素ごとに適用するDense Layerである(時折Linear mapとかと呼ばれることもある).つまり,
y = f(W \cdot x + b)
である.$W$は重み行列であり,その大きさは$x$と$y$によって決まる.$W \cdot x$はマトリックスとベクトルの内積を表しており,$+$は要素和である.なお,以後特に断りがない場合は各文字は意味が同じであると思ってもらいたい.つまり($x$は入力,$y$は出力,$W$は重み行列,$b$はバイアスなどといった具合である.
さて,昨今の深層学習技術は高い非線形性を持ったモデルの性能によるものだが,この非線形性は関数$f$による.$f$は要素ごとに適用される非線形な関数であり,主に$tanh$, $sigmoid$, $relu$(とその亜種)などが使われる.この関数は活性化関数(acivation function)と呼ばれることがある.なぜならこの関数はその素子の内部状態から素子がどのように活性化するのかを決める役割をもつからである.内部状態は重み通して受け取った素子への入力に,その素子が持っているエネルギー(バイアス)を加えたものに相当する.
活性化関数を含む一連の演算はNeural Networkモデルの基本を成す単位としてレイヤーとしてまとめられることが多い.ライブラリや論文ではDense Layer,Linear Mappなどと呼ばれる.この演算を以下のように図示することにする:
以後,図中では青い線を重み行列との内積,$+$マークがついた場合は足し算を表し,赤い線は何かしらの関数適用を示すことにする.
Dense Layerの実装をtensorflowで行ってみる.まずはモデルを呼ぶプログラムを書く.ここではランダムのデータを入力するシンプルなコードにする:
import numpy as np
import tensorflow as tf
from layers import *
def get_random_x(shape, vmin=0.0, vmax=1.0):
x_np = np.random.random(shape).astype(np.float32)
return tf.convert_to_tensor(x_np)
def print_tensor(x):
print(x.shape)
def run(sess, x, y):
print_tensor(x)
print_tensor(sess.run(y))
x_2d = get_random_x((1, 2))
dense_layer = Dense(2, 3, tf.sigmoid)
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
run(sess, x_2d, dense_layer(x_2d))
ここで入力$x$はバッチサイズ1の2次元ベクトルである.これを3次元の出力にする
Dense Layerは重みとして$(2,3)$,バイアスとして$(3)$の大きさをもつ変更可能なマトリックス,すなわちtensorflowでいうところのVariable
をもつクラスとして実装することができる.この実装はtest.py
がimportしているlayers.py
に書くことにする:
import tensorflow as tf
class Dense(object):
def __init__(self, in_dim, out_dim, act_func):
self.W = tf.Variable(tf.truncated_normal((in_dim, out_dim)))
self.b = tf.Variable(tf.zeros((out_dim)))
self.f = act_func
def __call__(self, x):
return self.f(tf.matmul(x, self.W) + self.b)
ここまで内積・要素和・関数適用を図で使ってきたが,要素積もここで示しておきたい,入力$x_1$,$x_2$の要素積を出力する演算は
y = x_1x_2
と書き,以下のように図示することにしよう:
次からはようやくRNNの説明に入る.