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
- LSTM代码初解析,Torch平台
- Theano LSTM代码解析
- junhyukoh的lstm代码解析
- Theano中的LSTM代码解析
- junhyukoh的lstm代码解析
- 使用Torch nngraph实现LSTM
- 使用Torch nngraph实现LSTM
- theano官方lstm代码解析(1)
- Theano官方LSTM代码解析(2)
- Theano:LSTM源码解析
- Theano:LSTM源码解析
- 代码-Torch7-学习torch-tutorials
- theano lstm代码(lstm.py)理解
- torch
- Torch
- Torch
- Torch
- Torch
- 块级元素与内联元素的区别
- 隐藏tabwidgt 用radiogroup来实现tab的切换
- JAVA Arrays.binarySearch
- CVS SVN Git
- TCP Socket
- LSTM代码初解析,Torch平台
- struts的logic标签库
- android 仿花椒直播中星星从底部往上移动
- Java通过while循环实现输入异常重新输入功能
- hibernate 多对一注解
- windows下如何下载并安装Python 3.5.1
- PHP中如何使用socket进行通信?
- 标签的使用(四):表单标签的使用汇总
- phpstorm配置xdebug(win)