找到关于输入的Caffe conv滤波器的梯度

pir*_*pir 36 c++ python neural-network deep-learning caffe

我需要在卷积神经网络(CNN)中找到关于单个卷积滤波器的输入层的梯度,作为可视化滤波器的方法.
给定Caffe的Python接口中经过训练的网络(例如本示例中的网络),如何根据输入层中的数据找到conv-filter的渐变?

编辑:

根据cesans回答,我添加了以下代码.我输入图层的尺寸是[8, 8, 7, 96].我的第一个转换层conv1有11个过滤器,大小为1x5,导致尺寸[8, 11, 7, 92].

net = solver.net
diffs = net.backward(diffs=['data', 'conv1'])
print diffs.keys() # >> ['conv1', 'data']
print diffs['data'].shape # >> (8, 8, 7, 96)
print diffs['conv1'].shape # >> (8, 11, 7, 92)
Run Code Online (Sandbox Code Playgroud)

从输出中可以看出,返回的数组net.backward()的尺寸等于Caffe中我的图层的尺寸.经过一些测试后,我发现这个输出分别是data层和conv1层的损耗梯度.

但是,我的问题是如何根据输入层中的数据找到单个转换滤波器的梯度,这是另外的.我怎样才能做到这一点?

Sha*_*hai 29

Caffe net耍弄两个数字"流".
第一个是数据"流":通过网络推送的图像和标签.当这些输入通过网络进行时,它们被转换为高级表示并最终转换为类概率向量(在分类任务中).
第二个"流"保存不同层的参数,卷积的权重,偏差等.这些数字/权重在网络的训练阶段期间被改变和学习.

尽管这两个"流"的作用根本不同,但是caffe仍然使用相同的数据结构blob来存储和管理它们.
但是,对于每个层,每个流有两个不同的 blob矢量.

这是一个我希望澄清的例子:

import caffe
solver = caffe.SGDSolver( PATH_TO_SOLVER_PROTOTXT )
net = solver.net
Run Code Online (Sandbox Code Playgroud)

如果你现在看看

net.blobs
Run Code Online (Sandbox Code Playgroud)

您将看到一个字典,为网络中的每个图层存储"caffe blob"对象.每个blob都有数据和渐变的存储空间

net.blobs['data'].data.shape    # >> (32, 3, 224, 224)
net.blobs['data'].diff.shape    # >> (32, 3, 224, 224)
Run Code Online (Sandbox Code Playgroud)

对于卷积层:

net.blobs['conv1/7x7_s2'].data.shape    # >> (32, 64, 112, 112)
net.blobs['conv1/7x7_s2'].diff.shape    # >> (32, 64, 112, 112)
Run Code Online (Sandbox Code Playgroud)

net.blobs 保存第一个数据流,它的形状与输入图像的形状相匹配,直到得到的类概率向量.

另一方面,你可以看到另一个成员 net

net.layers
Run Code Online (Sandbox Code Playgroud)

这是一个存储不同层参数的caffe矢量.
看第一层('data'图层):

len(net.layers[0].blobs)    # >> 0
Run Code Online (Sandbox Code Playgroud)

没有用于存储输入图层的参数.
另一方面,对于第一卷积层

len(net.layers[1].blobs)    # >> 2
Run Code Online (Sandbox Code Playgroud)

网络为滤波器权重存储一个blob,为恒定偏置存储另一个blob.他们来了

net.layers[1].blobs[0].data.shape  # >> (64, 3, 7, 7)
net.layers[1].blobs[1].data.shape  # >> (64,)
Run Code Online (Sandbox Code Playgroud)

如您所见,该层在3通道输入图像上执行7x7卷积,并具有64个这样的滤波器.

现在,如何获得渐变?好吧,正如你所说的那样

diffs = net.backward(diffs=['data','conv1/7x7_s2'])
Run Code Online (Sandbox Code Playgroud)

返回数据流的渐变.我们可以通过验证

np.all( diffs['data'] == net.blobs['data'].diff )  # >> True
np.all( diffs['conv1/7x7_s2'] == net.blobs['conv1/7x7_s2'].diff )  # >> True
Run Code Online (Sandbox Code Playgroud)

(TL; DR)您想要参数的渐变,这些参数存储在net.layers参数中:

net.layers[1].blobs[0].diff.shape # >> (64, 3, 7, 7)
net.layers[1].blobs[1].diff.shape # >> (64,)
Run Code Online (Sandbox Code Playgroud)

为了帮助您将图层的名称及其索引映射到net.layers矢量,您可以使用net._layer_names.


更新关于使用梯度来可视化滤波器响应:
梯度通常用于定义标量函数.损耗是一个标量,因此你可以说像素/滤波器重量相对于标量损失的梯度.该梯度是每个像素/滤波器权重的单个数字.
如果您希望得到最大程度激活特定内部隐藏节点的输入,则需要一个"辅助"网络,其中损失只是对要显示的特定隐藏节点的激活的度量.一旦有了这个辅助网络,就可以从任意输入开始,并根据输入层的辅助损耗梯度更改此输入:

update = prev_in + lr * net.blobs['data'].diff
Run Code Online (Sandbox Code Playgroud)


小智 10

运行backward()传递时,可以根据任何图层获得渐变.只需在调用函数时指定图层列表.要根据数据层显示渐变:

net.forward()
diffs = net.backward(diffs=['data', 'conv1'])`
data_point = 16
plt.imshow(diffs['data'][data_point].squeeze())
Run Code Online (Sandbox Code Playgroud)

在某些情况下,您可能希望强制所有图层向后执行,查看force_backward模型的参数.

https://github.com/BVLC/caffe/blob/master/src/caffe/proto/caffe.proto