1. 程式人生 > >Torch7入門續集補充(2)--- 每一層設定不同的學習率(finetuning有用)

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的方法

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