Caffe的神经网络处理两种数字“流”。第一种是数据“流”,即图像和标签通过网络传输。当这些输入通过网络时,它们被转换成高级表示,并最终转换为类概率向量(在分类任务中)。第二个“流”保存不同层的参数,卷积的权重、偏差等。这些数字/权重在网络训练阶段中进行更改和学习。
尽管这两个“流”的作用完全不同,但Caffe仍然使用相同的数据结构“blob”来存储和管理它们。但是,对于每个层,有两个不同的blob向量,分别针对每个流。下面是一个希望能够澄清问题的示例:
import caffe
solver = caffe.SGDSolver( PATH_TO_SOLVER_PROTOTXT )
net = solver.net
如果您现在看一下
net.blobs
您会看到一个字典,其中存储了网络中每个层的“caffe blob”对象。每个blob都有储存数据和梯度的空间。
net.blobs['data'].data.shape
net.blobs['data'].diff.shape
对于卷积层:
net.blobs['conv1/7x7_s2'].data.shape
net.blobs['conv1/7x7_s2'].diff.shape
net.blobs
保存了第一个数据流,其形状与输入图像相匹配,直到得到分类概率向量。
另一方面,您可以看到net
的另一个成员。
net.layers
这是一个存储不同层参数的Caffe向量。
看第一层(
'data'
层):
len(net.layers[0].blobs)
对于输入层,没有需要存储的参数。
另一方面,第一个卷积层需要存储以下参数
len(net.layers[1].blobs)
网络存储一个用于过滤权重的blob,另一个用于常量偏置。它们在这里。
net.layers[1].blobs[0].data.shape
net.layers[1].blobs[1].data.shape
正如您所看到的,这一层对3通道输入图像进行7x7卷积,并具有64个这样的滤波器。
那么,如何得到梯度?好吧,正如您注意到的那样。
diffs = net.backward(diffs=['data','conv1/7x7_s2'])
返回数据流的梯度。我们可以通过验证此操作。
np.all( diffs['data'] == net.blobs['data'].diff ) # >> True
np.all( diffs['conv1/7x7_s2'] == net.blobs['conv1/7x7_s2'].diff ) # >> True
(简要概述) 您想获取参数的梯度,这些梯度存储在net.layers
和参数中:
net.layers[1].blobs[0].diff.shape
net.layers[1].blobs[1].diff.shape
为了帮助你将层的名称与它们在 net.layers
向量中的索引相互映射,你可以使用 net._layer_names
。
关于使用梯度可视化滤波器响应的更新:
梯度通常是针对一个标量函数定义的。损失是一个标量,因此您可以根据标量损失来计算像素/滤波器权重的梯度。这个梯度是每个像素/滤波器权重的单个数字。
如果您想获取导致特定内部隐藏节点最大激活的输入,则需要一个“辅助”网络,其损失恰好是要可视化的特定隐藏节点的激活度的度量。一旦您拥有了这个辅助网络,您可以从任意输入开始,并基于辅助损失到输入层的梯度更改该输入:
update = prev_in + lr * net.blobs['data'].diff