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 # >> (32, 3, 224, 224)
net.blobs['data'].diff.shape # >> (32, 3, 224, 224)
对于卷积层:
net.blobs['conv1/7x7_s2'].data.shape # >> (32, 64, 112, 112)
net.blobs['conv1/7x7_s2'].diff.shape # >> (32, 64, 112, 112)
net.blobs 保存第一个数据流,它的形状与输入图像的形状匹配,直至生成的类概率向量。
另一方面,你可以看到net的另一个成员
net.layers
这是一个存储不同层参数的caffe向量。
看第一层('data'层):
len(net.layers[0].blobs) # >> 0
没有要为输入层存储的参数。
另一方面,对于第一个卷积层
len(net.layers[1].blobs) # >> 2
网络存储一个 blob 用于过滤器权重,另一个用于恒定偏差。他们来了
net.layers[1].blobs[0].data.shape # >> (64, 3, 7, 7)
net.layers[1].blobs[1].data.shape # >> (64,)
如您所见,该层对 3 通道输入图像执行 7x7 卷积,并有 64 个这样的过滤器。
现在,如何获得渐变?好吧,正如你所说的
diffs = net.backward(diffs=['data','conv1/7x7_s2'])
返回 data 流的梯度。我们可以通过
来验证这一点
np.all( diffs['data'] == net.blobs['data'].diff ) # >> True
np.all( diffs['conv1/7x7_s2'] == net.blobs['conv1/7x7_s2'].diff ) # >> True
(TL;DR) 你想要参数的梯度,这些参数存储在net.layers 中:
net.layers[1].blobs[0].diff.shape # >> (64, 3, 7, 7)
net.layers[1].blobs[1].diff.shape # >> (64,)
为了帮助您将图层名称及其索引映射到net.layers 向量,您可以使用net._layer_names。
更新关于使用渐变来可视化过滤器响应:
梯度通常是为 标量 函数定义的。损失是一个标量,因此您可以说像素/滤波器权重相对于标量损失的梯度。此梯度是每个像素/过滤器权重的单个数字。
如果你想获得最大激活特定内部隐藏节点的输入,你需要一个“辅助”网络,它的损失正是你想要的特定隐藏节点激活的度量形象化。一旦你有了这个辅助网络,你就可以从任意输入开始,并根据输入层的辅助损失梯度改变这个输入:
update = prev_in + lr * net.blobs['data'].diff