您的位置:首页 > 其它

Torch7入门续集(八)---终结篇----不再写Torch博客了,反正就是难受

2017-04-20 17:14 267 查看
这是终篇,记录一些坑吧以及易错点之类的吧,以前也记录了一点儿。

Torch代码书写时可能碰到的一些问题

有些重要的值得注意的地方,会慢慢加到这里,作为参考。

第一次更新:2017-4-20

第二次更新:2017-4-21

第三次更新:2017-4-24 :增加mask操作的三维示例

第四次更新: 2017-10-31: 增加“attempt to index field ‘THNN’ (a nil value)”解决。

1. 自定义层必须return self.output或是self.gradInput形式。

function CustomizedLayer:updateOutput(input)
print('updateGradInput'..self.cls)
-- other code and get tmp
self.output = tmp
return self.output
end

function CustomizedLayer:updateGradInput(input, gradOutput)
print('updateGradInput'..self.cls)
self.gradInput = gradOutput
return self.gradInput
end


输出

updateOutput..output 208554.015625
#然而在net:forward(input)后,得到的
..latent after customizedLayer: 0


因此不能直接return tmp 或是return gradOutput。否则网络的输出实际上是0!即使是在updateGradInput直接返回gradOutput,或是updateOutput直接输出input, 都必须要转换为self.gradInput 和 self.output。值得注意的是,返回值也可以是多个的。比如

...
function cLayer:updateOutput(input)
...
return self.ouput, self.k
end


2. lua的三元运算符

lua默认参数当然很简单

function myFun(a)
p = a or 1 -- 用or来
end


但是有时候你看见

k = a and b or c


这就是三元运算符,类似C中的 k = a ? b : c

a = 1
b = a == 1 and 2 or 0  --输出2

a = 3
b = a == 1 and 2 or 0  --输出0


[]
进行Tensor取值补充

a = torch.Tensor(6,3,4,5)
--下面4种写法等价
b = a[{{1},{1,3},{1,4},{1,5}] -- 1*3*4*5
b = a[{{1},{},{},{}] -- 1*3*4*5
b = a[1] -- 取第一行 3*4*5 会减维


[ByteTensor]
进行mask操作

有时候我们希望在tensor的某些位置提取出来,这些位置不要求是矩形,可以是任意形状。一般是用让一个mask。要求这个mask必须只能是填充
0或1
的ByteTensor类型。mask的某个位置为1,那么被mask的tensor的对应位置的值被置成自定义数字.

-- 用bernoulli()可以设置只有0和1的tensor
-- b = torch.ByteTensor(3,4):bernoulli()
b = torch.Tensor(3,4):apply(function()
if i > 0 then
i = i - 1
else
i = i + 1
end
return i
end)
--[[
b
1  0  1  0
1  0  1  0
1  0  1  0
[torch.DoubleTensor of size 3x4]
]]
b = b:byte()
a = torch.Tensor(2,3,4)
-- 用来进行mask
a[1][b] = 3

--[[
th>a
(1,.,.) =
3.0000e+00  6.9521e-310   3.0000e+00   0.0000e+00
3.0000e+00  7.8762e-114   3.0000e+00  3.6017e+227
3.0000e+00  4.6197e+281   3.0000e+00  8.2678e+140

(2,.,.) =
7.0981e+194  7.4861e-114   4.0622e-66  7.5656e-307
1.2946e+214  1.0740e-152  9.0870e+223  2.1724e-153
1.3085e+180   2.2462e-57  2.1724e-153  1.3085e+180
[torch.DoubleTensor of size 2x3x4]
]]

-- 三维示例
a = torch.rand(2,3,4):mul(4):floor():int()
th> a
(1,.,.) =
0  0  3  3
3  2  2  0
3  0  3  2

(2,.,.) =
1  2  3  3
3  3  3  3
0  3  2  3
[torch.IntTensor of size 2x3x4]
mask = torch.Tensor(2,3,4):bernoulli():byte()
th> mask
(1,.,.) =
0  1  0  1
1  0  1  1
0  0  1  1

(2,.,.) =
0  1  0  1
0  1  0  1
0  0  1  1
[torch.ByteTensor of size 2x3x4]
th> a[mask] = 0  -- 相应位置置为0
[0.0000s]
th> a
(1,.,.) =
0  0  3  0
0  2  0  0
3  0  0  0

(2,.,.) =
1  0  3  0
3  0  3  0
0  3  0  0
[torch.DoubleTensor of size 2x3x4]


一些小细节

Tensor构造时,“赋值操作”最好放在最后

local input = torch.Tensor(3,4)
local output2 = torch.Tensor():zero():typeAs(input):resizeAs(input)


此时output2的值又变成随机的了

output2
6.9316e-310  6.9316e-310   0.0000e+00   0.0000e+00
0.0000e+00  2.1724e-153   5.4104e-67  8.0109e-307
8.4880e-314  1.0748e+160  2.1724e-153  9.5896e-308
[torch.DoubleTensor of size 3x4]


所以正确写法:

local input = torch.Tensor(3,4)
local output2 = torch.Tensor():typeAs(input):resizeAs(input):zero()


获得网络的某一层的信息

其实module类中有两个状态变量:
output
gradInput
, 所以

net.modules[i].output
net.modules[i].gradInput


另外,还有就是这一层的权值信息

net.modules[i].weight
net.modules[i].bias
net.modules[i].gradWeight
net.modules[i].gradBias


attempt to index field ‘THNN’ (a nil value)

我不想说什么。。其实就是你忘了加

require 'cunn'


再次说说网络更新权值的方式

常用函数

自定义层一般是重载
__init__
,
updateOuput
updateGradInput
三个。调用网络forward函数时,其会自动调用每一层的
updateOutput
函数。

[b]调用网络backward函数时,也会自动调用每一层的
updateGradInput(input,gradOutput)
accGradParameters(input,gradOuput,scale)


function Module:backward(input, gradOutput, scale)
scale = scale or 1
self:updateGradInput(input, gradOutput)
self:accGradParameters(input, gradOutput, scale)
return self.gradInput
end


显然,每一层不就是主要三个功能:“更新”输出(updateOutput), “更新”输入的梯度(updateGradInput),“计算”本层的参数梯度(accGradParameters)

其实就是按照英文来翻译,就是最精确的意思。如果我们自定义的层没有参数或是只是简单的用已有的层组合形成的,没有什么特殊的要求,那么就没必要
accGradParameters
了,会自动调用内部的这个函数实现计算。

updateParameters(learningRate)

accGradParameters
计算好梯度后,我们需要更新一下本层的参数,此时调用
updateParameters(learningRate)
就可以了。可以看到updateParameters(learningRate)就是先用
parameters()
获得内部的参数以及参数的梯度。然后对每个参数进行更新。

function Module:updateParameters(learningRate)
local params, gradParams = self:parameters()
if params then
for i=1,#params do
params[i]:add(-learningRate, gradParams[i])
end
end
end


不过一般我们用的是
getParameters
,返回的是经过扁平的参数。

function Module:getParameters()
-- get parameters
local parameters,gradParameters = self:parameters()
local p, g = Module.flatten(parameters), Module.flatten(gradParameters)
assert(p:nElement() == g:nElement(),
'check that you are sharing parameters and gradParameters')
if parameters then
for i=1,#parameters do
assert(parameters[i]:storageOffset() == gradParameters[i]:storageOffset(),
'misaligned parameter at ' .. tostring(i))
end
end
return p, g
end


另外,
zeroGradParameters()
可以置零参数的梯度。

function Module:zeroGradParameters()
local _,gradParams = self:parameters()
if gradParams then
for i=1,#gradParams do
gradParams[i]:zero()
end
end
end


总结:只有调用updateParameters后,权值才更新。调用backward只是计算每一层的gradInput和每层参数的梯度,当然如今大家都用optim包了,所以一般也不会手动调用
updateParameters
。,至于 optim包如何使用,参照 http://blog.csdn.net/hungryof/article/details/66970563

再次强调:backward只是调用每一层的 updateGradInput以及accGradParameters,并不会更新权值参数,只是计算参数的梯度,以及更新每一层的输入的梯度。

local fDx = function(x)
netD:apply(function(m) if torch.type(m):find('Convolution') then m.bias:zero() end end)
netG:apply(function(m) if torch.type(m):find('Convolution') then m.bias:zero() end end)

gradParametersD:zero()

-- Real
-- train netD with (real, real_label)
local output = netD:forward(real_AB)
local label = torch.FloatTensor(output:size()):fill(real_label)
if opt.gpu>0 then
label = label:cuda()
end

local errD_real = criterion:forward(output, label)
local df_do = criterion:backward(output, label)
-- 这里对针对real的图,计算netD的网络的输入的梯度,以及网络参数的梯度。
netD:backward(real_AB, df_do)

-- Fake
-- train netD with (fake_AB, fake_label)
local output = netD:forward(fake_AB)
label:fill(fake_label)
local errD_fake = criterion:forward(output, label)
local df_do = criterion:backward(output, label)
-- 这里的netD的参数没有置0,所以梯度是累加!
-- 当netD经过real和fake的计算后,梯度“中和”后,再将我们需要优化
-- 的网络的参数的梯度 gradParametersD输出。
netD:backward(fake_AB, df_do)

errD = (errD_real + errD_fake)/2

return errD, gradParametersD
end


local fGx = function(x)
netD:apply(function(m) if torch.type(m):find('Convolution') then m.bias:zero() end end)
netG:apply(function(m) if torch.type(m):find('Convolution') then m.bias:zero() end end)

gradParametersG:zero()

-- GAN loss
local df_dg = torch.zeros(fake_B:size())
if opt.gpu>0 then
df_dg = df_dg:cuda();
end

if opt.use_GAN==1 then
local output = netD.output -- netD:forward{input_A,input_B} was already executed in fDx, so save computation
local label = torch.FloatTensor(output:size()):fill(real_label) -- fake labels are real for generator cost
if opt.gpu>0 then
label = label:cuda();
end
errG = criterion:forward(output, label)
local df_do = criterion:backward(output, label)
-- 你可能要问,为什么这里用的是updateGradInput而不是backward呢?
df_dg = netD:updateGradInput(fake_AB, df_do):narrow(2,fake_AB:size(2)-output_nc+1, output_nc)
else
errG = 0
end

-- unary loss
local df_do_AE = torch.zeros(fake_B:size())
if opt.gpu>0 then
df_do_AE = df_do_AE:cuda();
end
if opt.use_L1==1 then
errL1 = criterionAE:forward(fake_B, real_B)
df_do_AE = criterionAE:backward(fake_B, real_B)
else
errL1 = 0
end

netG:backward(real_A, df_dg + df_do_AE:mul(opt.lambda))

return errG, gradParametersG
end


回答:– 你可能要问,为什么这里用的是updateGradInput而不是backward呢?

这是因为,这里的G生成的假图的梯度是通过,让D尽量认为假图是真图,这种梯度来更新G。这是D对G的一种作用。但是我们要获得这个梯度,如果调用backward, 那么就会不仅调用

updateGradInput还会调用accGradParameters, 那么更改了D的约束,这就会出现问题。所以只用updateGradInput会更加符合。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: