tensorflow で rnn を動かしてみる

tensorflow の rnn のチュートリアルはちょっと複雑で何をやってるのかパッとはわからないので、簡単なやつを作って動かしてみたい

そもそもRNNとは?

出力をまた入力に使うらしい。よくこんな図を見るけど具体的に何が起きているのか?

深層学習による自然言語処理 という本だと、

ということなので、1層で入力も出力も2個のときは、下図っぽい感じになるはず。

任意の長さの入力 (今回は入力素子が2個なので長さ4なら [ [3,2], [4,4], [1,0], [9,9] ] みたいな感じ) について、
最後の入力(上の例では [9,9])が渡されたときの出力(にsoftmaxとかかけたもの)で誤差関数の値を計算して、それを最小化するようにWを調整していくことで学習していく。
(必要なら各入力に対する出力を保存しておいてそれらで誤差関数を計算してもいいみたい)

入力の素子数が1つで活性化関数がreluとかなら、Wを全部1にしてbを全部0にすれば任意の長さの正の数の和が計算できそうなので、それを実装してみようと思う。

実装してみる


とりあえず、セルというものが必要らしいのでこれを作成する。
今回は1層だけだけどL層に重ねたりするときは、各層でWとかが存在するので、それぞれの層をひとまとめで表すものがセルなんだと思う。
今回は BasicRNNCell を利用する。

In [1]: import tensorflow as tf

In [2]: sess = tf.Session()

In [3]: size = 1

In [4]: rnn_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=size, activation=tf.nn.relu)

BasicRNNCell は上で図示した感じの最も単純なネットワークのセル。
出力の素子数(num_units)と、利用する活性化関数(activation)を指定できる。

dynamic_rnn を使うとこれより先の処理を自分で書かなくてもよくなるっぽいけど、何が起こっているのか知りたいのでまずはdynamic_rnnを使わずに実装してみる。

各層の出力を入力として使うんだけど、一番最初の入力を渡すときは各層の出力(初期状態)は無いので何らかの値を指定しないと計算ができない。

セルの設定ごとに必要な初期状態のデータの形は変わるけどセル自体に初期状態のデータを出力する機能があるので、これを利用する

In [5]: n_batch = 1

In [6]: initial_state = rnn_cell.zero_state(n_batch, tf.float32)

In [7]: type(initial_state)
Out[7]: tensorflow.python.framework.ops.Tensor


ここまで来ると、任意の入力に対して出力を計算できるようになる

In [32]: x = tf.placeholder(tf.float32, shape=[None, n_batch, size])

In [47]: def inference(x, max_len):
    ...:     state = initial_state
    ...:     for i in range(max_len):
    ...:         (output, state) = rnn_cell(x[i], state)
    ...:     return tf.squeeze(output)
    ...: 

ほんとはxの長さでループを回したいんだけど上手いやり方がわからなかったので、とりあえず長さ固定でやることにする...

次に、誤差の計算をする

In [82]: max_len = 3

In [83]: output = inference(x, max_len)

In [84]: y = tf.placeholder(tf.float32, shape=[])    

In [85]: loss = tf.reduce_mean(tf.square(y - output))

誤差関数を最小化するための、optimizerを作る

In [86]: optimizer = tf.train.GradientDescentOptimizer(0.01)

In [88]: train_step = optimizer.minimize(loss)
In [89]: xs = [ [[[1]],[[2]],[[3]]],
    ...:        [[[2]],[[2]],[[2]]],
    ...:        [[[5]],[[4]],[[3]]],
    ...:        [[[7]],[[7]],[[7]]],
    ...:        [[[9]],[[8]],[[7]]] ]
    ...:        

In [95]: ys = [6, 6, 12, 21, 24]

In [96]: init = tf.global_variables_initializer()

In [97]: sess.run(init)

In [106]: for i in range(len(ys)):
     ...:     sess.run(train_step, feed_dict={x: xs[i], y: ys[i]})
     ...:     


やったー 学習させることに成功しました。

一応結果を確認してみます。

In [111]: print(sess.run(rnn_cell.variables[0])) # RNNのウェイトを表示してみる
[[-4.94734144]
 [-8.36993599]]

In [112]: loss.eval(session=sess, feed_dict={x:[[[1]],[[1]],[[1]]], y:3})
Out[112]: 9.0

学習データが大分少ないので全然誤差がでかいですが、とりあえず動かすことができたのでここまで