のんびりしているエンジニアの日記

ソフトウェアなどのエンジニア的な何かを書きます。

Recurrent Neural Networkについて調査してみた

Sponsored Links

皆さんこんにちは
お元気ですか。私は自由に元気です。

今日はRecurrent Neural Networkについて見て行きたいと思います。

Recurrent Neural Network

Recurrent Neural Networkは通常のネットワークと異なり、時系列が考慮されているニューラルネットワークです。
解ける問題としては、翻訳、文書生成、音声生成などに使えると思います。
最近では画像を説明する文章を生成する問題にも活用されていて、今後、特に使われる
ことになると考えています。

言語表現

各ワードは次のようなベクトルで表現できるとします。
f:id:tereka:20150705160909p:plain

入力の時には対応するワードのベクトルを入力します。
{犬,と,猫,と,狐,が,好き}といった文章があるとすると、
ワードベクトルのインデックスで表現するとなると{0,1,2,1,3,4,5}と表すことができる。

ネットワーク構成の概要

f:id:tereka:20150705154908p:plain

画像はhttp://aclweb.org/anthology//D/D13/D13-1169.pdfより
上記の図を数式にすると以下の通りです。

{ \displaystyle
s_t= Uw_{i} + Ws_{t-1} (1)\\
y(t) = Vs_{t} (2)
}

y(t)は現在の言葉の次の言葉の確率である。

(1)は現在のワードベクトルに重みUと前回の隠れ層の出力に重みWを掛けたベクトルを合計している
(2)は(1)のベクトルに重みを掛けて、今の言葉の次の言葉を予測します。
これを繰り返すことにより、次から次へと言葉を生成し、文章を作成していきます。

BPTT(Back Propagation Through Time)

f:id:tereka:20150705171305p:plain

通常のBack Propagationと異なり、最後の出力から何回か前の単語の誤差についても誤差を計算することになります。
数式とかは、他のサイトを参考にすると良いでしょう。

Source Code

ChainerにExampleが掲載されています。
ちょうど良いので、こちらを解説していきましょう。github.com

Model

Chainerにちょうど良い例があります。Long Short Term Memoryが含まれていますが、まぁ良いでしょう。
EmbedIDは言語へベクトルを変換する。

model = chainer.FunctionSet(embed=F.EmbedID(len(vocab), n_units),
                            l1_x=F.Linear(n_units, 4 * n_units),
                            l1_h=F.Linear(n_units, 4 * n_units),
                            l2_x=F.Linear(n_units, 4 * n_units),
                            l2_h=F.Linear(n_units, 4 * n_units),
                            l3=F.Linear(n_units, len(vocab)))

1Stepの学習

def forward_one_step(x_data, y_data, state, train=True):
    x = chainer.Variable(x_data, volatile=not train)
    t = chainer.Variable(y_data, volatile=not train)
    h0 = model.embed(x)
    h1_in = model.l1_x(F.dropout(h0, train=train)) + model.l1_h(state['h1'])
    c1, h1 = F.lstm(state['c1'], h1_in)
    h2_in = model.l2_x(F.dropout(h1, train=train)) + model.l2_h(state['h2'])
    c2, h2 = F.lstm(state['c2'], h2_in)
    y = model.l3(F.dropout(h2, train=train))
    state = {'c1': c1, 'h1': h1, 'c2': c2, 'h2': h2}
    return state, F.softmax_cross_entropy(y, t)


def make_initial_state(batchsize=batchsize, train=True):
    return {name: chainer.Variable(mod.zeros((batchsize, n_units),
                                             dtype=np.float32),
                                   volatile=not train)
            for name in ('c1', 'h1', 'c2', 'h2')}

forward_one_stepの内部は少し通常のネットワークと変わっています。

h1_in = model.l1_x(F.dropout(h0, train=train)) + model.l1_h(state['h1'])

上記のコードは{ \displaystyle s_t= Uw_{i} + Ws_{t-1}}の数式と同一である。
state['h1']には、前回の隠れ層の出力を保存しています。
lstmについてはまた後日、解説したいと思います。

学習部分

for i in six.moves.range(jump * n_epoch):
    x_batch = np.array([train_data[(jump * j + i) % whole_len]
                        for j in six.moves.range(batchsize)])
    y_batch = np.array([train_data[(jump * j + i + 1) % whole_len]
                        for j in six.moves.range(batchsize)])
    state, loss_i = forward_one_step(x_batch, y_batch, state)
    accum_loss += loss_i
    cur_log_perp += loss_i.data.reshape(())

    if (i + 1) % bprop_len == 0: 
        optimizer.zero_grads()
        accum_loss.backward()
        accum_loss.unchain_backward()  # truncate
        accum_loss = chainer.Variable(mod.zeros(()))

        optimizer.clip_grads(grad_clip)
        optimizer.update()

最低限の箇所だけを記載します。
基本的にシーケンスをバッチで入力していき、ある一定の入力があった場合に
BPTTを実行するとなっています。

感想

Chainer勉強するのにも結構いいよね。