【循環網絡】simple recurrent network源碼解讀

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的核心是隱變量h ,記錄了系統當前時刻的狀態。

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 - 指明隱變量維度。或者,指明從inputtransfer模塊之間的操作。
input - 指明從輸入到隱變量的操作。此處是個nn庫中的查找表2
feedback - 從前一時刻transfer之前到當前transfer函數之前的操作,和input結果逐元素相加。此處是個全連接層。
transfer - 非線性函數,可以取ReLU, Sigmoid等。
rho - 反向傳播的步數。最多隻向前考慮這麼多步驟。

參考源碼中的updateOutput函數,給出了輸入和輸出的關係:

outputt=transfer(feedback(ht1)+input(xt))

可以用下圖表示:
這裏寫圖片描述

輸入x 是1-10之間的整數。
核心模塊r輸出的是隱變量h ,還需要做一點後處理,才能輸出各類得分。
這裏用一個全連層把7維隱變量變成10類數字得分,最後加一個SoftMax調整爲概率:

local rnn = nn.Sequential()
   :add(r)
   :add(nn.Linear(hiddenSize, nIndex))
   :add(nn.LogSoftMax())

Recursor類型

Recursorrnn包起來,聲明這是一個循環網絡:

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步執行,但要注意:inputstargets數據按照倒序執行:

   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控制。如果需要記憶較遠的狀態,需要反覆執行很多步前向和後向算法。


  1. 如果對RNN完全懵逼,可以先從這篇文章入門。
  2. 這是一個nn庫中的LookupTable(H,W),從屬於卷積類別。內部權重爲H*W,輸入h時,輸出第h行的權重。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章