【循环网络】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行的权重。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章