https://github.com/wojzaremba/lstmに実装されている LSTM 言語モデルを使用しています
次の lstm 関数を使用します
local function lstm(x, prev_c, prev_h)
-- Calculate all four gates in one go
local i2h = nn.Linear(params.rnn_size, 4*params.rnn_size)(x)
local h2h = nn.Linear(params.rnn_size, 4*params.rnn_size)(prev_h)
local gates = nn.CAddTable()({i2h, h2h})
-- Reshape to (batch_size, n_gates, hid_size)
-- Then slize the n_gates dimension, i.e dimension 2
local reshaped_gates = nn.Reshape(4,params.rnn_size)(gates)
local sliced_gates = nn.SplitTable(2)(reshaped_gates)
-- Use select gate to fetch each gate and apply nonlinearity
local in_gate = nn.Sigmoid()(nn.SelectTable(1)(sliced_gates))
local in_transform = nn.Tanh()(nn.SelectTable(2)(sliced_gates))
local forget_gate = nn.Sigmoid()(nn.SelectTable(3)(sliced_gates))
local out_gate = nn.Sigmoid()(nn.SelectTable(4)(sliced_gates))
local next_c = nn.CAddTable()({
nn.CMulTable()({forget_gate, prev_c}),
nn.CMulTable()({in_gate, in_transform})
})
local next_h = nn.CMulTable()({out_gate, nn.Tanh()(next_c)})
return next_c, next_h
end
これは次のネットワークで使用されます (ソフトマックス層と基準層を削除し、コードの別の場所に別々に追加しました)
local function create_network()
local x = nn.Identity()()
local prev_s = nn.Identity()()
local i = {[0] = x}
local next_s = {}
local split = {prev_s:split(2 * params.layers)}
for layer_idx = 1, params.layers do
local prev_c = split[2 * layer_idx - 1]
local prev_h = split[2 * layer_idx]
local dropped = nn.Dropout(params.dropout)(i[layer_idx - 1])
local next_c, next_h = lstm(dropped, prev_c, prev_h)
table.insert(next_s, next_c)
table.insert(next_s, next_h)
i[layer_idx] = next_h
end
local res = nn.Identity()(i[params.layers])
local module = nn.gModule({x, prev_s},
{res, nn.Identity()(next_s)})
return module
end
上記のネットワークは、ネットワークの出力と、次の反復で使用される lstm 層の状態を返します。状態は、2 層 lstm ネットワークの次の順序でテーブルに保存されます { cell_1, output_1, cell_2, output_2 }。ネットワーク出力と output_2 は同じです。
2 つの質問があります: (1) このネットワークに順伝播と逆伝播を適用すると、状態の勾配はどのように配置されますか? それらは上の表と同じ順序になりますか、それとも次のように逆になりますか: {grad_cell_2, grad_output_2, grad_cell_1, grad_output_1}
最初は出力テーブルと同じ順序になると思っていましたが、順序が逆になっているのではないかと疑う理由があります (反復ごとに勾配を手動で設定したいくつかのテストに基づいています)。確かなことはわかりませんが、このコードをデバッグして何が起こっているのかを正確に知る方法もわかりません。
(2) バックワード ステップで、出力のみの勾配 (状態テーブルの最後のエントリと同じ) がわかっている場合、出力 (res) または状態テーブル (next_s) の勾配を渡す必要があります。両方?出力は単にテーブルの最後のエントリであるため、勾配を出力のみに渡すか、状態テーブルの最後のエントリのみに渡すと、まったく同じ結果が得られると思います。ただし、両方の方法で試してみると、異なる結果が得られます。