这是终篇,记录一些坑吧以及易错点之类的吧,以前也记录了一点儿。
Torch代码书写时可能碰到的一些问题
有些重要的值得注意的地方,会慢慢加到这里,作为参考。
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
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)
-- 用[b]来进行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]
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
我不想说什么。。其实就是你忘了加
require 'cunn'
自定义层一般是重载__init__
,updateOuput
和updateGradInput
三个。调用网络forward函数时,其会自动调用每一层的updateOutput
函数。
调用网络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
了,会自动调用内部的这个函数实现计算。
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
这是因为,这里的G生成的假图的梯度是通过,让D尽量认为假图是真图,这种梯度来更新G。这是D对G的一种作用。但是我们要获得这个梯度,如果调用backward, 那么就会不仅调用
updateGradInput还会调用accGradParameters, 那么更改了D的约束,这就会出现问题。所以只用updateGradInput会更加符合。