RecurrentShop について調べる


RecurrentShop: Keras 上で RNN をうまく使うためのフレームワーク。
Decoder や Readout を簡単に実装できるようにしたよというやつ。

誤りなどありましたらご指摘いただけますと幸いです。

RNNCell

RecurrentShop での RNN は下図のような構成になっているようです。
参考: https://github.com/datalogai/recurrentshop/blob/master/recurrentshop/basic_cells.py

(正確には、入力:[x, h_tm] 出力:[h, h] となっているのですが、入出力の各 index:1 の要素(隠れ層入出力)は RecurrentSequential 内でよしなにぐるぐる回されるので外から見えるのは、入力:x, 出力:h となるようです。)

私が見慣れた RNN だと、出力が次の入力に再入力される、つまり出力と入力は次元数が同じネットワークとなるのですが、そのようなネットワークを作る場合後述する readout を使う必要がありそうです。

LSTM や GRU の場合もちゃんと読んでいないですがおそらく同様です。

RecurrentSequential

rnn = RecurrentSequential(unroll=False, return_sequences=False)
rnn.add(SimpleRNNCell(10, input_dim=5))
rnn.add(LSTMCell(12))
rnn.add(Dense(5))
rnn.add(GRU(8))

# rnn can now be used as regular Keras Recurrent layer.

RNN の Cell もしくは通常の Cell をつなぎ合わせて RNN を作れます。

https://github.com/datalogai/recurrentshop/blob/master/recurrentshop/engine.py#L987 あたりのコードを読むと、下図のようにつながるようです。

(各セルの中のベクトルの流れは不正確です。http://qiita.com/KojiOhki/items/89cd7b69a8a6239d67ca あたりを読んで下さい。)

計算はある時刻tの中で下から上まですべて計算されます。
(各 RNN のセルは、下から渡ってきた入力と、時刻t-1の隠れ層の状態から出力を決定し上のノードにそれを伝えます)
つまり、破線で囲われた多層 RNN が一つの RNN セルのように振る舞います。

ネストした RNN

ちなみに上の「」は実はその通りのことができて、 RecurrentSequential を一つのセルに変換できます。
それによってネストした RNN を作ることができます。

rnn1 = RecurrentSequential()
rnn1.add(....)
rnn1.add(....)

rnn1_cell = rnn1.get_cell()  ## ここ!

rnn2 = RecurrentSequential()
rnn2.add(rnn1_cell)
rnn2.add(...)

Decoder

https://github.com/datalogai/recurrentshop/blob/master/docs/decoder.md
単一のベクトルから時系列を生成する RecurrentNetwork のことです。
RecurrentShop では Attention などに対応するため時系列を入力する方法もあるそうですがちゃんと見てません。

RecurrentSequential に decode=True を与えることで Decoder にできます。この場合出力する時系列は無限につづいてしまうので output_length を指定する必要があります。

rnn = RecurrentSequential(decode=True, output_length=10)
rnn.add(SimpleRNNCell(25, input_dim=20))

x = Input((20,))
y = rnn(x)

print(K.int_shape(y))  # >> (None, 10, 25)

上の例では、(20次元) でバッチサイズは任意の入力を受け取って、 (バッチサイズ任意, 長さ10, 25次元) の時系列を出力します。

Decoder への入力

コードを読んだ感じ、状態だけをぐるぐる回しているようにみえるけどいまいちわからない。要調査

最初の状態・インプット定義
        preprocessed_input = self.preprocess_input(inputs, training=None)
        constants = self.get_constants(inputs, training=None)
        if self.decode:
            initial_states.insert(0, inputs)
            preprocessed_input = K.zeros((1, self.output_length, 1))
            input_length = self.output_length
        else:
            input_length = input_shape[1]

各ステップでの計算
        if self.decode:
            model_input = states
        else:
            model_input = [inputs] + states

Readout

https://github.com/datalogai/recurrentshop/blob/master/docs/readout.md
RNN の出力を次時刻の入力に入れます。

rnn = RecurrentSequential(readout='add')
rnn.add(LSTMCell(10, input_dim=10))
rnn.add(GRUCell(10))
rnn.add(SimpleRNNCell(10))

上のコードの場合下のようになります。

時刻tの LSTM への入力は、外から与えられた入力と時刻t-1のRNNの出力の和になるということです。

(readout の Input と本来の Input の計算(add, mull, avg..)は element-wise に行われます。 add([1,2,3], [1,1,1])[2,3,4] となります)

ちなみに https://github.com/farizrahman4u/seq2seq の seq2seq は readout を使っていないのでいわゆる巷でいう seq2seq とやや違う構造になっている気がします。

teacher_force

https://github.com/datalogai/recurrentshop/blob/master/docs/teacher_force.md
学習時に readout の入力を ground_truth に差し替えることができるようです。

rnn = RecurrentSequential(readout='add', teacher_force=True, return_sequences=True)
rnn.add(LSTMCell(10, input_dim=10))
rnn.add(LSTMCell(10))
rnn.add(LSTMCell(10))
rnn.add(Activation('softmax'))


x = Input((7, 10))
y_true = Input((7, 10))  # This is where you feed the ground truth values

y = rnn(x, ground_truth=y_true)

model = Model([x, y_true], y)

model.compile(loss='categorical_crossentropy', optimizer='sgd')

# Training

X_true = np.random.random((32, 7, 10))
Y_true = np.random.random((32, 7, 10))

model.fit([X_true, Y_true], Y_true)  # Note that Y_true is part of both input and output


# Prediction

X = np.random.random((32, 7, 10))

model.predict(X)  # >> Error! the graph still has an input for ground truth.. 

zeros = np.zeros((32, 7, 10)) # Need not be zeros.. any array of same shape would do

model.predict([X, zeros])

生成時に適当な行列を与えます。
生成時は下記のコードの通り ground_truth は使われないようなっているようです。

readout = K.in_train_phase(K.switch(counter[0], ground_truth_slice, readout), readout)

unroll

https://keras.io/ja/layers/recurrent/
Keras に標準である機能なようです。

unroll: 真理値(デフォルトはFalse).Trueなら,ネットワークは展開され, そうでなければシンボリックループが使われます. 展開はよりメモリ集中傾向になりますが,RNNをスピードアップできます. 展開は短い系列にのみ適しています.