咋说来着,有时候你真的很想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,这就难办了。
最简单的就是直接用包,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