LSTM代码初解析,Torch平台

来源:互联网 发布:网络安全员日常工作 编辑:程序博客网 时间:2024/06/07 01:26

cvpr2015,LSTM代码的链接为https://github.com/wojzaremba/lstm,作为一个初学Torch的小白,决定直接从代码入手,再根据Torch的说明包,与网上资料来进一步学习Torch。
不多说,直接上代码。
该代码分为三个lua文件,分别为base.lua,data.lua,main.lua。

先从base.lua

function g_disable_dropout(node)  if type(node) == "table" and node.__typename == nil then    for i = 1, #node do      node[i]:apply(g_disable_dropout)    end    return  end  if string.match(node.__typename, "Dropout") then    node.train = false  endendfunction g_enable_dropout(node)  if type(node) == "table" and node.__typename == nil then    for i = 1, #node do      node[i]:apply(g_enable_dropout)    end    return  end  if string.match(node.__typename, "Dropout") then    node.train = true  endend

上面这两块代码,分别为dropout的取消,与dropout的设定,dropout是在深度学习中,防止过拟合的一种方式,给出其中一个解释链接,我也不是很懂,http://www.aiuxian.com/article/p-1870737.html

function g_cloneManyTimes(net, T)  local clones = {}   --创建克隆副本  local params, gradParams = net:parameters()  --得到原网络中的参数  local mem = torch.MemoryFile("w"):binary()  --将net网络存入文件中,于readObject()相对应  mem:writeObject(net)  for t = 1, T do    -- We need to use a new reader for each clone.    -- We don't want to use the pointers to already read objects.    --打开读文件    local reader = torch.MemoryFile(mem:storage(), "r"):binary()    --创建克隆副本    local clone = reader:readObject()    --关闭文件    reader:close()    --获得参数    local cloneParams, cloneGradParams = clone:parameters()    for i = 1, #params do      --复制参数      cloneParams[i]:set(params[i])      cloneGradParams[i]:set(gradParams[i])    end    --复制    clones[t] = clone    --获得可用内存空间    collectgarbage()  end  --关闭文件  mem:close()  --返回克隆体  return clonesend

这个代码块是用来,copy多份网络。

初始化GPU设置

function g_init_gpu(args)  local gpuidx = args  gpuidx = gpuidx[1] or 1  print(string.format("Using %s-th gpu", gpuidx))  --setDevice(ID)应用与multi-GPU模式,ID为GPU的编号  cutorch.setDevice(gpuidx)  g_make_deterministic(1)end
function g_make_deterministic(seed)  --设置随机种子  torch.manualSeed(seed)  --设置GPUtorch随机种子  cutorch.manualSeed(seed)  --uniform(a,b)为返回一个a-b之间的随机数  torch.zeros(1, 1):cuda():uniform()end

此函数为将to的内容用from来代替

function g_replace_table(to, from)  assert(#to == #from)  for i = 1, #to do    to[i]:copy(from[i])  endend

此函数块为改变字符形式

function g_f3(f)  return string.format("%.3f", f)endfunction g_d(f)--torch.round()为torch的去整函数  return string.format("%d", torch.round(f))end

data.lua代码块

此代码块不做过多解释

local stringx = require('pl.stringx')local file = require('pl.file')--设置路径local ptb_path = "./data/"local vocab_idx = 0local vocab_map = {}

此函数的作用为对数据作预处理

local function replicate(x_inp, batch_size)   --s为样本的容量   local s = x_inp:size(1)   --x是根据batch_size,确定样本组的数量,batch_size个样本一组   local x = torch.zeros(torch.floor(s / batch_size), batch_size)   for i = 1, batch_size do     --     local start = torch.round((i - 1) * s / batch_size) + 1     --x:size(1)为样本组的组数     local finish = start + x:size(1) - 1     --torch:sub()为取部分的函数,     --x_inp:sub(start,finish)意味取x_inp,start到finish数据,这是将数据从x_inp中复制到x中     x:sub(1, x:size(1), i, i):copy(x_inp:sub(start, finish))   end   return xend

这个函数为加载数据函数,fname为文件名,由于pl.stringx,与pl.file这两个包暂时我还没查到,所以有些语句暂时还没理解

local function load_data(fname)   local data = file.read(fname)   data = stringx.replace(data, '\n', '<eos>')   data = stringx.split(data)   print(string.format("Loading %s, size of data = %d", fname, #data))   local x = torch.zeros(#data)   for i = 1, #data do      if vocab_map[data[i]] == nil then         vocab_idx = vocab_idx + 1         vocab_map[data[i]] = vocab_idx      end      x[i] = vocab_map[data[i]]   end   return xend

这个函数块,根据batch_size来预处理原始数据

local function traindataset(batch_size)   local x = load_data(ptb_path .. "ptb.train.txt")   --上个函数已经说明,大致化成x[num][batch_size],num为个数样本组的个数,batch_size为样本组的个数,每个单元为一个数据集。   x = replicate(x, batch_size)   return xend

这个函数块同上面函数块,将test,valid数据进行预处理

local function testdataset(batch_size)   local x = load_data(ptb_path .. "ptb.test.txt")   --这句话稍微与之前有所不同,这里为什么采用这种方式来预处理数据,我暂时还不清楚   x = x:resize(x:size(1), 1):expand(x:size(1), batch_size)   return xendlocal function validdataset(batch_size)   local x = load_data(ptb_path .. "ptb.valid.txt")   x = replicate(x, batch_size)   return xend

该包返回的数据

return {traindataset=traindataset,        testdataset=testdataset,        validdataset=validdataset}

main.lua

pcall(a)函数可以查看a语句有木有执行成功

--fbcunn,这个包这torch帮助文档中暂时没有找到local ok,cunn = pcall(require, 'fbcunn')if not ok then    --如果没有找到暂时先导入cunn这个包    ok,cunn = pcall(require,'cunn')    if ok then        --如果找到了,打印以下话        print("warning: fbcunn not found. Falling back to cunn")        --nn包中LookupTable.lua中的帮助文档于源代码https://github.com/torch/nn/blob/master/LookupTable.lua         LookupTable = nn.LookupTable    else        print("Could not find cunn or fbcunn. Either is required")        os.exit()    endelse    --得到GPU的属性    deviceParams = cutorch.getDeviceProperties(1)    --GPU的计算能力    cudaComputeCapability = deviceParams.major + deviceParams.minor/10    --同上面代码块    LookupTable = nn.LookupTableend--导入nngraph,base,data等工具包require('nngraph')require('base')local ptb = require('data')

这个函数块为LSTM的训练参数

local params = {batch_size=20,   --批处理数据的大小                seq_length=20,   --序列长度                layers=2,                                         --参数的正则权重                decay=2,                rnn_size=200,    --网络中隐藏层的大小                dropout=0,       --dropout率,防止overfitting                init_weight=0.1,                lr=1,              --                 vocab_size=10000,  --词向量的大小                max_epoch=4,            --最大迭代次数                max_max_epoch=13,                max_grad_norm=5}

在GPU上运行

local function transfer_data(x)   --GPU的转换器  return x:cuda()endlocal state_train, state_valid, state_test   --三个数据集local model = {}                  --真正最后的模型local paramx, paramdx            --训练参数
local state_train, state_valid, state_test   --三个数据集local model = {}                  --真正最后的模型local paramx, paramdx            --两种训练参数lstm层local function lstm(x, prev_c, prev_h)    --lstm层  -- Calculate all four gates in one go  local i2h = nn.Linear(params.rnn_size, 4*params.rnn_size)(x)  --x为输入新的输入向量,分别扩展成o层,f层,c层,i层  local h2h = nn.Linear(params.rnn_size, 4*params.rnn_size)(prev_h)  --prev_h是输入向量,上个状态的输入,同x一样扩展成4层  local gates = nn.CAddTable()({i2h, h2h})    --两个向量相加,形成所有gate的未加非线性的状态  -- 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)  --将上面和在一起的gates,重新排列为4个不同的gate,得reshaped_gates  local sliced_gates = nn.SplitTable(2)(reshaped_gates)  --切分reshaped_gates的第二维度,扩展成一个Table{1,2,3,4}  --  -- 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))  -- 下个Cell单元的状态,公式不详细说了,给出一个LSTM简介http://blog.csdn.net/zdy0_2004/article/details/49977423  local next_c           = nn.CAddTable()({      nn.CMulTable()({forget_gate, prev_c}),      nn.CMulTable()({in_gate,     in_transform})  })  --下个hide层的状态  local next_h           = nn.CMulTable()({out_gate, nn.Tanh()(next_c)})  --返回,下个cell层和下个hide层的状态,因为以后的网络可能还会用到  return next_c, next_hend

创建最终的网络

local function create_network()  local x                = nn.Identity()()  --创建网络的输入借口,用来存放输入数据  local y                = nn.Identity()() --目标向量的接口  local prev_s           = nn.Identity()() --暂时认定为,先前输出的结果,应该为cell与hide的结合  local i                = {[0] = LookupTable(params.vocab_size,                                                    params.rnn_size)(x)}  --是输入向量到,词向量空间的一个映射  local next_s           = {}        --认定为下一个结果,同样也是cell层与hide层的结合  local split         = {prev_s:split(2 * params.layers)}  --2*params.layers乘二是因为prev_s包含cell层与hide,所以乘以二  --先认定params。layers为这个网络的层数,每一层都为LSTM  for layer_idx = 1, params.layers do    local prev_c         = split[2 * layer_idx - 1]    local prev_h         = split[2 * layer_idx]    --从prev_s中取出prev_c与prev_h    local dropped        = nn.Dropout(params.dropout)(i[layer_idx - 1])    --得到dropped层    local next_c, next_h = lstm(dropped, prev_c, prev_h)    --经过lstm得到下一个next_c与next_h    --再将next_c与next_h插入到next_s    table.insert(next_s, next_c)    table.insert(next_s, next_h)    --导入输入    i[layer_idx] = next_h  end  local h2y              = nn.Linear(params.rnn_size, params.vocab_size)  --设置隐藏层,最后一层与接口相连的一层  local dropped          = nn.Dropout(params.dropout)(i[params.layers])  local pred             = nn.LogSoftMax()(h2y(dropped))  local err              = nn.ClassNLLCriterion()({pred, y})  local module           = nn.gModule({x, y, prev_s},                                      {err, nn.Identity()(next_s)})  --从这里可以看出形成后的网络输入为x,y,prev_s向量,输出为err,next_s向量  module:getParameters():uniform(-params.init_weight, params.init_weight)  --初始化参数,将参数设定为[-params.init_weight,params.init_weight]之间  return transfer_data(module)  --将网络转化成GPU模式并返回end
local function setup()  print("Creating a RNN LSTM network.")  --得到核心网络  local core_network = create_network()  --获得参数  paramx, paramdx = core_network:getParameters()  --创建最终模型model,s为储存LSTM中的prev_c与prev_h  --ds,start_s为整个序列公用的,暂未读懂干啥  model.s = {}  model.ds = {}  model.start_s = {}  --词向量序列的长度  for j = 0, params.seq_length do    model.s[j] = {}    for d = 1, 2 * params.layers do        --储存的是 s[j][d]是LSTM中每个层的prev_c与prev_h        model.s[j][d] = transfer_data(torch.zeros(params.batch_size, params.rnn_size))    end  end  for d = 1, 2 * params.layers do    --整个序列公用的start_s与ds    model.start_s[d] = transfer_data(torch.zeros(params.batch_size, params.rnn_size))    model.ds[d] = transfer_data(torch.zeros(params.batch_size, params.rnn_size))  end  --得到核心网络  model.core_network = core_network  --按照序列长度clone出相应长度的核心网络,完成整个rnn网路  model.rnns = g_cloneManyTimes(core_network, params.seq_length)  model.norm_dw = 0  --创建序列每个对应的误差  model.err = transfer_data(torch.zeros(params.seq_length))end

重置状态,只重置start_s,与state_pos

local function reset_state(state)  --state.pos标明网络现在所训练序列的位置  state.pos = 1  if model ~= nil and model.start_s ~= nil then    for d = 1, 2 * params.layers do      model.start_s[d]:zero()    end  endend

重置ds

local function reset_ds()  for d = 1, #model.ds do    model.ds[d]:zero()  endend

网络的向前传播

local function fp(state)  --将第一号序列的起始向量定义为start_s,作为开始向量  g_replace_table(model.s[0], model.start_s)  --如果状态位置+序列长度大于整个state的尺寸就重新初state  --最后一组残余数据不参加训练  if state.pos + params.seq_length > state.data:size(1) then    reset_state(state)  end  for i = 1, params.seq_length do    --得到输入x,目标向量y,序列中的隐藏层(cell,hide)情况s    local x = state.data[state.pos]    local y = state.data[state.pos + 1]    --prev_c与prev_h    local s = model.s[i - 1]    --unpack它接受一个数组(table)作为参数,并默认从下标1开始返回数组的所有元素    --第i号core_network,向前传播,{X,Y,S}为输入向量    model.err[i], model.s[i] = unpack(model.rnns[i]:forward({x, y, s}))    --指向下一个词    state.pos = state.pos + 1  end  --将下次进行向前传播的start_s确定  g_replace_table(model.start_s, model.s[params.seq_length])  --返回误差的平均值  return model.err:mean()end

反向传播

local function bp(state)  --初始paramdx与reset_ds  paramdx:zero()  reset_ds()  --由于是反向传播,所以出params.seq_length开始到第一个词  for i = params.seq_length, 1, -1 do    state.pos = state.pos - 1    --x为当前第i层的输入    local x = state.data[state.pos]    --y为当前层的目标输出    local y = state.data[state.pos + 1]    --s为prev_c与prev_h    local s = model.s[i - 1]    --初始derr    local derr = transfer_data(torch.ones(1))    --执行反向传播    local tmp = model.rnns[i]:backward({x, y, s},                                       {derr, model.ds})[3]    g_replace_table(model.ds, tmp)    cutorch.synchronize()  end  --重新复原state.pos  state.pos = state.pos + params.seq_length  --若norm_dw大于最大值,确定放缩因子,缩小梯度  model.norm_dw = paramdx:norm()  if model.norm_dw > params.max_grad_norm then    local shrink_factor = params.max_grad_norm / model.norm_dw    paramdx:mul(shrink_factor)  end  --加上梯度,完成梯度下降  paramx:add(paramdx:mul(-params.lr))end
local function run_valid()    --测试交叉检验集  reset_state(state_valid)  g_disable_dropout(model.rnns)  --取消dropout,重新刷新一遍dropout浇诘?  local len = (state_valid.data:size(1) - 1) / (params.seq_length)  --分为len个seq_length长度来训练  local perp = 0  --perp困惑度暂时不懂  附上链接 http://blog.sina.com.cn/s/blog_4c9dc2a10102vua9.html,用来评判训练后的质量  for i = 1, len do    perp = perp + fp(state_valid)  end  print("Validation set perplexity : " .. g_f3(torch.exp(perp / len)))  --设定dropout  g_enable_dropout(model.rnns)end
local function run_test()     --跑测试集  reset_state(state_test)  --用于测试取消dropout  g_disable_dropout(model.rnns  local perp = 0   --设定困惑度  local len = state_test.data:size(1)  g_replace_table(model.s[0], model.start_s)  for i = 1, (len - 1) do    local x = state_test.data[i]  --入口词向量    local y = state_test.data[i + 1]  --目标词向量    --由于是测试阶段所以只用保存两个s值,为s[0]与s[1]    perp_tmp, model.s[1] = unpack(model.rnns[1]:forward({x, y, model.s[0]}))    perp = perp + perp_tmp[1]    g_replace_table(model.s[0], model.s[1])  end  print("Test set perplexity : " .. g_f3(torch.exp(perp / (len - 1))))  g_enable_dropout(model.rnns)end

最后主函数代码

local function main()  g_init_gpu(arg)  --获得数据  state_train = {data=transfer_data(ptb.traindataset(params.batch_size))}  state_valid =  {data=transfer_data(ptb.validdataset(params.batch_size))}  state_test =  {data=transfer_data(ptb.testdataset(params.batch_size))}  print("Network parameters:")  print(params)  local states = {state_train, state_valid, state_test}  for _, state in pairs(states) do    reset_state(state)  end  setup()  local step = 0  local epoch = 0       --训练次数  local total_cases = 0  local beginning_time = torch.tic()  local start_time = torch.tic()  print("Starting training.")  --words_per_step是一次fp,dp所训练词的数量  local words_per_step = params.seq_length * params.batch_size  --epoch_size是训练样本组的数量,由data.lua可知数据的形式为[a,batch_size]  local epoch_size = torch.floor(state_train.data:size(1) / params.seq_length)  local perps  --进行每次迭代  while epoch < params.max_max_epoch do    --fp函数    local perp = fp(state_train)  --得到结果困惑度    if perps == nil then      perps = torch.zeros(epoch_size):add(perp)    end    perps[step % epoch_size + 1] = perp    step = step + 1    --dp反向传播    bp(state_train)    total_cases = total_cases + params.seq_length * params.batch_size    --epoch表示当前正在进行的迭代    epoch = step / epoch_size    if step % torch.round(epoch_size / 10) == 10 then      local wps = torch.floor(total_cases / torch.toc(start_time))      local since_beginning = g_d(torch.toc(beginning_time) / 60)      print('epoch = ' .. g_f3(epoch) ..            ', train perp. = ' .. g_f3(torch.exp(perps:mean())) ..            ', wps = ' .. wps ..            ', dw:norm() = ' .. g_f3(model.norm_dw) ..            ', lr = ' ..  g_f3(params.lr) ..            ', since beginning = ' .. since_beginning .. ' mins.')    end    if step % epoch_size == 0 then      run_valid()      if epoch > params.max_epoch then          --降低学习率          params.lr = params.lr / params.decay      end    end    if step % 33 == 0 then      cutorch.synchronize()      collectgarbage()    end  end  run_test()  print("Training is over.")end
0 0