总说
这个有难度。一般来说都是最后一层的输入进行构建loss的,得到loss的梯度,然后一层一层进行反传。
但是有时候我们希望在中间层也加入监督,即feature matching。
这里以Torch7代码为例。
net(input)_layerX
表示将input输入到net中,net的layerX层的feature maps。
大概总结了一下,有三种类型吧。
1. feature matching的 target不变的类型—neural style是一个很好的例子
2. 类似RCF(richer convolutional features for edge detection)的中间层利用反卷积直接到1个通道或者3个通道的。
3. 真正的feature matching。让 net(input)_layerX
和net(target)_layerX
尽量一样的。
类似neural style的最简单的feature matching类型
这个算做feature matching有点勉强,不过现在看这个代码可以认为是最简单的一种feature matching。
Torch7学习(七)——Neural-Style代码解析 中,文章的最后有一个问题,当时我还不知道怎么回答。现在想想,其实这就是一种最简单的feature matching!
因为neural style中首先要不断改变输入x,进而让VGG(x)_layerX
和VGG(content)_layerX
尽量一样,如果有多层,就是多层的feature matching。
neural style代码中大多数初学者不懂的一个问题:为啥在自定义的updateGradInput中self.gradInput:add(gradOutput)呢?
在layerX
的更新梯度时,有两个梯度来源:一个是后一层往这一层传入的gradOutput
,另一个是本层进行feature matching产生的梯度。
function ContentLoss:updateGradInput(input, gradOutput)
if input:nElement() == self.target:nElement() then
self.gradInput = self.crit:backward(input, self.target)
end
if self.normalize then
self.gradInput:div(torch.norm(self.gradInput, 1) + 1e-8)
end
self.gradInput:mul(self.strength)
-- 上面self.gradInput正是本层进行feature matching得到的梯度,
-- 当然还要加上其后一层传过来的梯度,相加后,才能往前传。
self.gradInput:add(gradOutput)
return self.gradInput
end
或是以一个“结点”的思维去理解。比如网络的某一层开始分成两道,进行传播,那么反向时,该层往前传的梯度,肯定是两道的梯度进行相加。所以,如果你把这种层内计算的梯度当做是一个分支,该分支直接将本层的输入将VGG(content)_layerX
比较,因此得到的梯度要加上另一道的梯度(该层后一层反传过来的梯度)。
neural style的一个小细节就是,它全部的loss都是来自feature matching, 这些loss分别代表着不同层次语义特征的差异,需要在不同层次上对整个网路的更新起作用。因此最后一层反传时的gradOutput
就是dy
设置成全0的tensor!