在caffe中,“Python”层是什么?

21
Caffe有一个名为"Python"的层类型。例如,此层类型可用作损失层。在其他情况下,它被用作输入层。这个层类型是什么?如何使用这个层?
3个回答

36

PruneBharat的答案给出了"Python"层的总体目的:这是一个通用层,与C++不同,它是用Python语言实现的。

本文旨在作为使用"Python"层的教程。

"Python"层教程

"Python"层是什么?

请参阅PruneBharat的精彩回答。

先决条件

为了使用"Python"层,您需要使用标志编译caffe。

WITH_PYTHON_LAYER := 1

'Makefile.config'中进行设置。

如何实现一个"Python"层?

一个"Python"层应该被实现为一个继承自caffe.Layer基类的python类。这个类必须具有以下四种方法:

import caffe
class my_py_layer(caffe.Layer):
  def setup(self, bottom, top):
    pass

  def reshape(self, bottom, top):
    pass

  def forward(self, bottom, top):
    pass

  def backward(self, top, propagate_down, bottom):
    pass

这些方法是什么?

def setup(self, bottom, top):当Caffe构建网络时,调用此方法一次。此函数应该检查输入数量(len(bottom))和输出数量(len(top))是否符合预期。
在此处应该分配网络的内部参数(例如,self.add_blobs()),详见此帖子
此方法可以访问self.param_str,它是从prototxt传递到层级的字符串。详见此帖子

def reshape(self, bottom, top):当Caffe重新调整网络时,调用此方法。此函数应该分配输出(每个top blob)。输出形状通常与bottom形状相关。

def forward(self, bottom, top):实现从bottomtop的前向传递。

def backward(self, top, propagate_down, bottom):此方法实现反向传播,将梯度从top传播到bottompropagate_down是一个长度为len(bottom)的布尔向量,指示梯度应该传播到哪个bottom

有关bottomtop输入的更多信息,请参见此帖子

示例
您可以在这里这里这里查看一些简化的 Python 层示例。
“移动平均”输出层的示例可在此处找到。

可训练参数
“Python”层可以具有可训练的参数(例如“Conv”,“InnerProduct”等)。
要了解如何添加可训练参数的详细信息,请参见此主题此主题。在caffe git中还有一个非常简化的示例。

如何在 prototxt 中添加“Python”层?

有关详情,请参见Bharat的回答。
您需要在 prototxt 中添加以下内容:

layer {
  name: 'rpn-data'
  type: 'Python'  
  bottom: 'rpn_cls_score'
  bottom: 'gt_boxes'
  bottom: 'im_info'
  bottom: 'data'
  top: 'rpn_labels'
  top: 'rpn_bbox_targets'
  top: 'rpn_bbox_inside_weights'
  top: 'rpn_bbox_outside_weights'
  python_param {
    module: 'rpn.anchor_target_layer'  # python module name where your implementation is
    layer: 'AnchorTargetLayer'   # the name of the class implementation
    param_str: "'feat_stride': 16"   # optional parameters to the layer
  }
}

如何使用Pythonic的NetSpec接口添加一个"Python"层?

非常简单:

import caffe
from caffe import layers as L

ns = caffe.NetSpec()
# define layers here...
ns.rpn_labels, ns.rpn_bbox_targets, \
  ns.rpn_bbox_inside_weights, ns.rpn_bbox_outside_weights = \
    L.Python(ns.rpn_cls_score, ns.gt_boxes, ns.im_info, ns.data, 
             name='rpn-data',
             ntop=4, # tell caffe to expect four output blobs
             python_param={'module': 'rpn.anchor_target_layer',
                           'layer': 'AnchorTargetLayer',
                           'param_str': '"\'feat_stride\': 16"'})

如何使用带有"Python"层的神经网络?

从Caffe调用Python代码不需要您担心。Caffe使用boost API从编译好的C++中调用Python代码。
您需要做什么呢?
确保实现您的层的Python模块在$PYTHONPATH中,这样当Caffe import时就能够找到它。
例如,如果您的模块my_python_layer.py/path/to/my_python_layer.py中,则

PYTHONPATH=/path/to:$PYTHONPATH $CAFFE_ROOT/build/tools/caffe train -solver my_solver.prototxt

应该能正常工作。

如何测试我的层?

在使用之前,你应该始终测试你的层。
测试forward函数完全取决于你,因为每个层都有不同的功能。
测试backward方法很容易,因为该方法只实现了一个forward的梯度,可以自动进行数值测试!
查看test_gradient_for_python_layer测试实用程序:

import numpy as np
from test_gradient_for_python_layer import test_gradient_for_python_layer

# set the inputs
input_names_and_values = [('in_cont', np.random.randn(3,4)), 
                          ('in_binary', np.random.binomial(1, 0.4, (3,1))]
output_names = ['out1', 'out2']
py_module = 'folder.my_layer_module_name'
py_layer = 'my_layer_class_name'
param_str = 'some params'
propagate_down = [True, False]

# call the test
test_gradient_for_python_layer(input_names_and_values, output_names, 
                               py_module, py_layer, param_str, 
                               propagate_down)

# you are done!

特别提示

值得注意的是,Python代码只能在CPU上运行。因此,如果您计划在网络的中间使用Python层,且想要使用GPU,则会看到显著的性能下降。这是因为Caffe需要在调用Python层之前从GPU复制数据块到CPU,然后再将其复制回GPU以进行前向/反向传递。
如果Python层是输入层或最上层损失层,则这种下降的程度要小得多。
更新:于2017年9月19日,PR#5904已合并到主分支。该PR通过Python接口公开了数据块的GPU指针。您可以直接从Python访问blob._gpu_data_ptr和blob._gpu_diff_ptr,但需自担风险。


非常感谢您的详细解释!Python层是否也适用于未安装Python的系统?(我能否仅部署Caffe二进制文件?) - mojovski
@mojovski 我认为你需要使用Python库才能使其正常工作。 - Shai
@Shai,我认为pyloss层存在一个bug https://github.com/BVLC/caffe/blob/master/examples/pycaffe/layers/pyloss.py 我认为最后一行应该是bottom[i].diff[...] = sign * top[0].diff[0] * self.diff / bottom[i].num。我的理解正确吗?谢谢。 - kli_nlpr
@kli_nlpr,看起来 top.diff 没有被考虑进去,这确实很奇怪。你可以在 GitHub 上开一个问题来调查这一点。 - Shai
@Shai 我已经在这里创建了一个PR https://github.com/BVLC/caffe/pull/5407 - kli_nlpr

8
非常简单,它是一个层,在这个层中,提供实现代码,而不是使用预定义类型之一 - 所有这些类型都由高效函数支持。
如果你想定义一个自定义损失函数,请自行编写代码,并使用类型Python创建该层。如果你有非标准的输入需求,例如一些数据特定的预处理,也没有问题:请自行编写代码,并使用类型Python创建该层。

我认为我不同意“它是一个提供实现代码的层,而不是使用预定义类型之一的层”的说法。您也可以实现自己的C++和CUDA层。 - Bharat
正确...但是其他用户定义的车辆的存在并不否定该短语。它只是一种层,而不是唯一可能的层类型。 - Prune

7
Python层与需要编译的C++层不同,参数需要添加到proto文件中,并最终需要在layer_factory中注册该层。如果您编写Python层,则无需担心这些问题。层参数可以定义为字符串,在Python中可以作为字符串访问。例如:如果您在层中有一个参数,则可以使用“self.param_str”访问它,如果param_str在您的prototxt文件中定义了。像其他层一样,您需要定义一个类,其中包含以下函数:
  • Setup - 使用从层变量获取的参数初始化您的层
  • Forward - 层的输入和输出是什么
  • Backward - 给定下一层的预测和梯度,计算前一层的梯度
  • Reshape - 如果需要,请重新调整您的blob

Prototxt示例:

layer {
  name: 'rpn-data'
  type: 'Python'
  bottom: 'rpn_cls_score'
  bottom: 'gt_boxes'
  bottom: 'im_info'
  bottom: 'data'
  top: 'rpn_labels'
  top: 'rpn_bbox_targets'
  top: 'rpn_bbox_inside_weights'
  top: 'rpn_bbox_outside_weights'
  python_param {
    module: 'rpn.anchor_target_layer'
    layer: 'AnchorTargetLayer'
    param_str: "'feat_stride': 16"
  }
}

在这里,层的名称为rpn-data,bottom和top分别是该层的输入和输出细节。python_param定义了Python层的参数。'module'指定了您的层的文件名是什么。如果名为'anchor_target_layer.py'的文件位于名为'rpn'的文件夹中,则参数将为'rpn.anchor_target_layer'。'layer'参数是您的类的名称,在本例中为'AnchorTargetLayer'。'param_str'是层的参数,其中包含键'feat_stride'的值16。
与C++/CUDA层不同,目前在caffe中使用Python层时无法在多GPU设置中工作,因此这是使用它们的一个缺点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接