戳simple-recurrent-network.lua下載源碼。
Torch的重要作者之一,Nicolas Leonard對Torch中的nn庫進行擴展,發佈了rnn庫。這個庫包含RNN, LSTM, GRU, BRNN, BLSTM等能夠處理時序和記憶的網絡。
本文以其中Simple Recurrent Network源碼爲例,講解Torch中使用RNN1的基本方式。x是自然數序列。
概述
這篇代碼實現的功能是:輸入一個序列x,能夠輸出一個序列y。
在真實應用中,輸入輸出序列可能是不同語種的一句話。這裏做了極大簡化:y=x+1 mod 10。
網絡結構
爲了使用RNN,需要包含rnn庫:
require 'rnn'
首先設定關鍵參數。RNN的核心是隱變量
batchSize = 8
rho = 5 -- sequence length
hiddenSize = 7 -- 隱變量維度
nIndex = 10 -- 輸出分類數量
lr = 0.1 --學習率
Recurrent類型
下面可以建立rnn庫中的Recurrent類型(參看Recurrent.lua)建立核心模塊r
:
local r = nn.Recurrent(
hiddenSize, -- start
nn.LookupTable(nIndex, hiddenSize), -- input
nn.Linear(hiddenSize, hiddenSize), -- feedback
nn.Sigmoid(), -- transfer
rho -- rho
)
創建Recurrent的四個參數意義如下:
start
- 指明隱變量維度。或者,指明從input
到transfer
模塊之間的操作。
input
- 指明從輸入到隱變量的操作。此處是個nn庫中的查找表2。
feedback
- 從前一時刻transfer
之前到當前transfer
函數之前的操作,和input
結果逐元素相加。此處是個全連接層。
transfer
- 非線性函數,可以取ReLU, Sigmoid等。
rho
- 反向傳播的步數。最多隻向前考慮這麼多步驟。
參考源碼中的updateOutput
函數,給出了輸入和輸出的關係:
可以用下圖表示:
輸入
核心模塊r
輸出的是隱變量
這裏用一個全連層把7維隱變量變成10類數字得分,最後加一個SoftMax調整爲概率:
local rnn = nn.Sequential()
:add(r)
:add(nn.Linear(hiddenSize, nIndex))
:add(nn.LogSoftMax())
Recursor類型
用Recursor
把rnn
包起來,聲明這是一個循環網絡:
rnn = nn.Recursor(rnn, rho)
最後加上negative log likelihood誤差函數:
criterion = nn.ClassNLLCriterion()
這個誤差函數的輸入爲長度nIndex的概率x
,真值爲一個標量類號class
。誤差爲:loss(x, class) = -x[class]。
數據
下面來準備一些原始數據。
sequence是個長度1000的DoubleTensor,重複1,2…10,1,2..10。
sequence_ = torch.LongTensor():range(1,10) -- 1到10,列向量
sequence = torch.LongTensor(100,10):copy(sequence_:view(1,10):expand(100,10)) -- view函數把列向量變成行向量,sequence爲100*10的矩陣,每行相同
sequence:resize(100*10) -- one long sequence of 1,2,3...,10,1,2,3...10...
offset包含batchSize個偏移量,取值範圍在1-1000之間。
offsets = {}
for i=1,batchSize do
table.insert(offsets, math.ceil(math.random()*sequence:size(1)))
end
offsets = torch.LongTensor(offsets)
在每一次訓練迭代中,從sequence中創建輸入inputs
和真值targets
:
local inputs, targets = {}, {}
for step=1,rho do -- 設定每一行
-- a batch of inputs
inputs[step] = sequence:index(1, offsets)
-- incement indices
offsets:add(1)
for j=1,batchSize do
if offsets[j] > sequence:size(1) then
offsets[j] = 1
end
end
targets[step] = sequence:index(1, offsets)
end
這裏的inputs
是一個尺寸爲rho*batchSize的數據(左),它的每一列是一個自然數序列,每一行對應這批數據中的一步。
targest
(右)結構類似,但是比輸入後錯一位。
每一步輸入,都對應着一步輸出。
前向傳播
首先清理參數和前次迭代記憶:
rnn:zeroGradParameters()
rnn:forget() -- forget all past time-steps
rho
表示反向傳播的步數,也是訓練過程中能夠考慮的序列長度。分rho
步,送入batch中當前步驟的一行數據:
local outputs, err = {}, 0
for step=1,rho do
outputs[step] = rnn:forward(inputs[step])
err = err + criterion:forward(outputs[step], targets[step])
end
inputs[step]
爲一個長度爲batchSize的數組,表示各序列當前步驟類標。
outputs[step]
爲一個長度爲batchSize*nIndex的數組,每一列表示該序列在當前步驟屬於每一類的概率。
後向傳播
同樣要分rho
步執行,但要注意:inputs
和targets
數據按照倒序執行:
local gradOutputs, gradInputs = {}, {}
for step=rho,1,-1 do -- reverse order of forward calls
gradOutputs[step] = criterion:backward(outputs[step], targets[step])
gradInputs[step] = rnn:backward(inputs[step], gradOutputs[step])
end
本次迭代結束之前,利用反向傳播結果調整系統參數:
rnn:updateParameters(lr)
iteration = iteration + 1
總結
我們可以看到,RNN網絡的記憶長度由rho
控制。如果需要記憶較遠的狀態,需要反覆執行很多步前向和後向算法。
- 如果對RNN完全懵逼,可以先從這篇文章入門。 ↩
- 這是一個nn庫中的LookupTable(H,W),從屬於卷積類別。內部權重爲H*W,輸入h時,輸出第h行的權重。 ↩