Torch7入门续集补充(2)--- 每一层设置不同的学习率(finetuning有用)

总说

咋说来着,有时候你真的很想finetuning一个网络,想让网络前面固定或是学习率很小,但是你会发现,完全弄不来啊!虽然你可能发现了有一个叫nn.ZeroGrad的layer,看看代码:

local ZeroGrad, parent = torch.class('nn.ZeroGrad', 'nn.Module')

function ZeroGrad:updateOutput(input)
    self.output:set(input)
    return self.output
 end

 -- the gradient is simply zeroed.
 -- useful when you don't want to backpropgate through certain paths.
 function ZeroGrad:updateGradInput(input, gradOutput)
    self.gradInput = nn.utils.recursiveResizeAs(self.gradInput, input)
    self.gradInput = nn.utils.recursiveFill(self.gradInput, 0)
    return self.gradInput
 end

就是直接将gradInput填0就行。这样,在 nn.ZeroGrad前面的层的梯度都是0,从而使得前面固定。
但是,你如果想要让前面的网络学习率不是0,这就难办了。

两种方法

第一种:针对使用 SGD的方法

最简单的就是直接用包,https://github.com/gpleiss/nnlr 。
这种是自己设置lrs来进行:
我们先看sgd的代码:

lrs是learningRates, 其实在SGD中,可以为每个参数设置不同的学习率的。见下面代码:

function optim.sgd(opfunc, x, config, state)
   -- (0) get/update state
   local config = config or {}
   local state = state or config
   local lr = config.learningRate or 1e-3
   local lrd = config.learningRateDecay or 0
   local wd = config.weightDecay or 0
   local mom = config.momentum or 0
   local damp = config.dampening or mom
   local nesterov = config.nesterov or false
   local lrs = config.learningRates
   local wds = config.weightDecays
   state.evalCounter = state.evalCounter or 0
   local nevals = state.evalCounter
   assert(not nesterov or (mom > 0 and damp == 0), "Nesterov momentum requires a momentum and zero dampening")

   -- (1) evaluate f(x) and df/dx
   local fx,dfdx = opfunc(x)

   -- (2) weight decay with single or individual parameters
   if wd ~= 0 then
      dfdx:add(wd, x)
   elseif wds then
      if not state.decayParameters then
         state.decayParameters = torch.Tensor():typeAs(x):resizeAs(dfdx)
      end
      state.decayParameters:copy(wds):cmul(x)
      dfdx:add(state.decayParameters)
   end

   -- (3) apply momentum
   if mom ~= 0 then
      if not state.dfdx then
         state.dfdx = torch.Tensor():typeAs(dfdx):resizeAs(dfdx):copy(dfdx)
      else
         state.dfdx:mul(mom):add(1-damp, dfdx)
      end
      if nesterov then
         dfdx:add(mom, state.dfdx)
      else
         dfdx = state.dfdx
      end
   end

   -- (4) learning rate decay (annealing)
   local clr = lr / (1 + nevals*lrd)

   -- (5) parameter update with single or individual learning rates
   if lrs then
      if not state.deltaParameters then
         state.deltaParameters = torch.Tensor():typeAs(x):resizeAs(dfdx)
      end
      -- deltaParameters是 梯度*每个参数的学习率
      state.deltaParameters:copy(lrs):cmul(dfdx)
      -- 然后进行更新参数x
      x:add(-clr, state.deltaParameters)
   else
      x:add(-clr, dfdx)
   end

   -- (6) update evaluation counter
   state.evalCounter = state.evalCounter + 1

   -- return x*, f(x) before optimization
   return x,{fx}
end

关键看这个:

   -- (5) parameter update with single or individual learning rates
   if lrs then
      if not state.deltaParameters then
         state.deltaParameters = torch.Tensor():typeAs(x):resizeAs(dfdx)
      end
      -- deltaParameters是 梯度*每个参数的学习率
      state.deltaParameters:copy(lrs):cmul(dfdx)
      -- 然后进行更新参数x
      x:add(-clr, state.deltaParameters)
   else
      x:add(-clr, dfdx)
   end

可以看到,如果设置了lrs(我们在外面一般是传入optmState.learningRates, 注意是”learningRates”不是”learningRate”),那么就先将lrs乘以梯度,然后在将这个已经对每个参数的梯度乘了一个特定系数(这个系数是每个参数的学习率),再乘以一个总体的学习率 -clr,然后加上x,x参数就这样更新了。

      -- deltaParameters是 梯度*每个参数的学习率
      state.deltaParameters:copy(lrs):cmul(dfdx)

因此:我们可以通过改变optimState.learningRates的值来逐层设置学习率(weight和bias都可以单独设置),看例子:

-- suppose you have a model called model
lrs_model = model:clone()
lrs = lrs_model:getParameters() -- 为了让lrs的长度是所有参数的长度。并且是严格按照逐层逐参数展开的。因此lrs严格对应每个参数。
lrs:fill(1) -- setting the base learning rate to 1

-- 这里设置第5层的bias的学习率是2(都是相对值)
lrs_model:get(5).bias:fill(2)
-- 设置第2层的weight的相对学习率是3
lrs_model:get(2).weight:fill(3)

-- now pass lrs_model to optimState, which was created previously
optimState.learningRates = lrs

由于getParameters是对于每层的参数进行flaten, 所以一层的参数先展开weight, 再展开bias。因此这里
lrs_model:get(5).bias:fill(2)就是将lrs的某些值由1变成2,同理解释下面一行。最后我们将
lrs(按照上面的代码来讲是,大部分为1,有些变成了2,有些变成了3)作为 optimState.learningRates即可。

通用算法的学习率设置

核心:利用parameters而不是getParameters来获得参数!
这就有意思了,因为几乎所有的代码都是通过 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

这里可以看到调用了 parameters函数,然后再进行“压平”flatten。再看parameters:

function Module:parameters()
   if self.weight and self.bias then
      return {self.weight, self.bias}, {self.gradWeight, self.gradBias}
   elseif self.weight then
      return {self.weight}, {self.gradWeight}
   elseif self.bias then
      return {self.bias}, {self.gradBias}
   else
      return
   end
end

可以看到这里直接返回 table 类型。因此如果我们使用 parameters函数,则返回一个table,table中的每个tensor对应每层的weight/bias。因此我们需要对于每层都弄一个optimState就可以了。

local params, gradParams = model:parameters() 

-- 设置每层的学习率为0.01
local learningRates = torch.Tensor(#params):fill(0.01)
-- 将第2层的设置为0.01
learningRates[2] = 0.001

optimState = {}
for i = 1, #params do
  table.insert(optimState, {
    learningRate = learningRates[i],
    learningRateDecay = 0.0001,
    momentum = 0.9,
    dampening = 0.0,
    weightDecay = 5e-4
  })
end

for e = 1, epochs do
  -- Get MNIST batch
  X, Y = get_mnist_batch(batch_size)

  -- forward -> backward (outside of feval)
  model:zeroGradParameters()
  out = model:forward(X)
  err = criterion:forward(out, Y)
  gradOutputs = criterion:backward(out, Y)
  model:backward(X, gradOutputs)

  -- 对每一层的参数 params
  for i = 1, #params do
    local feval = function(x)
      return err, gradParams[i]
    end

    optim.sgd(feval, params[i], optimState[i])
  end

end

附加参考链接:
https://gist.github.com/szagoruyko/1e994e713fce4a41773e (第五问)
http://www.thedataware.com/post/the-torch-adventures-setting-layer-wise-training-parameters
https://gist.github.com/farrajota/f1b4616d67c07b17d817a7baf2659646

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