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

这是终篇,记录一些坑吧以及易错点之类的吧,以前也记录了一点儿。
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)
-- 用[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]

一些小细节

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类中有两个状态变量:outputgradInput, 所以

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__,updateOuputupdateGradInput三个。调用网络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了,会自动调用内部的这个函数实现计算。

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会更加符合。

你可能感兴趣的:(Deep,Learning,Lua,Torch7入门教程)