【问题标题】:Finding gradient of a Caffe conv-filter with regards to input查找 Caffe 卷积滤波器相对于输入的梯度
【发布时间】:2015-09-28 06:09:37
【问题描述】:

我需要找到关于卷积神经网络 (CNN) 中单个卷积滤波器的输入层的梯度,作为visualize the filters 的一种方式。
给定在Caffe 的 Python 接口中训练有素的网络,例如this example 中的网络,我如何才能找到卷积滤波器相对于输入层数据的梯度?

编辑:

基于answer by cesans,我添加了下面的代码。我的输入层的尺寸是[8, 8, 7, 96]。我的第一个 conv 层 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)

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

但是,我的问题是如何找到单个 conv-filter 相对于输入层中的数据的梯度,这是另一回事。我怎样才能做到这一点?

【问题讨论】:

    标签: python c++ deep-learning conv-neural-network caffe


    【解决方案1】:

    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
    

    【讨论】:

    • 感谢您的详细回答,这让我对 Blob 的工作方式有了更深入的了解。但是,您还没有得到关于损失的梯度吗?过滤器可视化是通过优化输入来完成的,以最大化过滤器激活。为此,我需要获取关于输入的梯度,而不是关于损失的梯度。基于此,您的答案似乎并没有真正反映问题,而是与 cesans 给出的答案类似。
    • 所以数据流的梯度本质上是神经元中所有权重的累积梯度?
    • @Alex 根据chain rule计算梯度
    • 是的,我明白,每个 dE/dW 都是重量的偏导数。但是 net.backward(diffs=['data','conv1/7x7_s2']) 返回的是层中所有神经元的值,而不是内核中的所有权重,我觉得有点混乱
    • 没问题,等我把所有的细节都整理好了
    【解决方案2】:

    当你运行backward() pass 时,你可以得到任何层的渐变。调用函数时只需指定层列表。要根据数据层显示梯度:

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

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

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

    【讨论】:

    • 完美,谢谢!我如何获得与 SGD 用于调整的完全相同的梯度,例如conv1 中过滤器的参数? diffs = net.backward(diffs=['loss', 'conv1']) 会准确地告诉我,还是 caffe 会在降低误差表面之前对梯度进行某种操作?
    • 如何计算权重更新取决于 Solver。对于 SGD,如果动量不为零,则此时它包括先前的更新以及学习率和权重衰减。但是,根据这里的信息:caffe.berkeleyvision.org/tutorial/solver.html(更新参数)和这里的代码:github.com/BVLC/caffe/blob/master/src/caffe/solver.cpp我猜diff中存储的值是最终的权重更新。
    • 我目前正在使用带有 solver.step(1) 的循环训练我的 CNN。我想在每次迭代期间找到梯度,我想我可以简单地将diffs = net.backward(diffs=['loss', 'conv1']) 添加到该循环中,因为求解器步骤会自动执行前向传递。您认为它可能会干扰培训的任何原因吗?
    • 运行net.backward() 只是计算梯度并存储它们,但不更新参数,所以这应该不是问题。尽管您将向后运行两次。
    • 是的,所以我猜训练会慢一些。你知道我如何在训练时只用 1 次后向传递来提取梯度吗?我找不到任何允许这样做的选项。
    猜你喜欢
    • 1970-01-01
    • 2021-01-03
    • 2020-03-16
    • 2019-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-10
    • 1970-01-01
    相关资源
    最近更新 更多