如何在caffe中卷积两个blob
How to convolve two blobs in caffe
在 caffe 中,convolution
层采用一个底部 blob,并将其与学习过滤器(使用权重类型初始化 - "Xavier"、"MSRA" 等)进行卷积。然而,我的问题是我们是否可以简单地将两个底部 blob 卷积并产生一个顶部 blob。这样做最优雅的方法是什么?这样做的目的是:底部的一个 blob 将是 data
,另一个将是前几层生成的动态过滤器(根据 data
变化)(我正在尝试实现 dynamic convolution).
我的尝试:
我想到的一种方法是修改 filler.hpp
并将底部 blob 分配为 filler
矩阵本身(而不是 "Xavier"、"MSRA" 等.).然后我认为卷积层会从那里开始。我们可以设置 lr = 0
来表示我们的自定义填充器初始化的权重不应该改变。但是,看了源码后,还是不知道该怎么做。另一方面,我不想破坏caffe的工作流程。我仍然希望转换层正常运行,如果我希望的话。
显然比较繁琐的方法是使用Slice
、tile
and/orScale
层的组合来字面上实现卷积。我认为它会起作用,但结果会很乱。还有其他想法吗?
编辑 1:
我通过修改caffe的卷积层写了一个新层。特别是,在第 27 行的 src/caffe/layers/conv_layer.cpp
中,它采用 filler
定义的权重并将其与底部的 blob 进行卷积。因此,我没有从 filler
中填充该 blob,而是修改了图层,使其现在有两个底部。底部之一直接分配给填充物。现在我不得不做一些其他的改变,例如:
weight
blob 对所有样本具有相同的值。这里对于不同的样本会有不同的值。所以我将第 32 行从:
this->forward_cpu_gemm(
bottom_data + n * this->bottom_dim_,
weight,
top_data + n * this->top_dim_);
至:
this->forward_cpu_gemm(
bottom_data + n * bottom[1]->count(1),
bottom[0]->cpu_data() + n * bottom[0]->count(1),
top_data + n * this->top_dim_);
为了让事情更简单,我假设不涉及偏差项,步幅始终为 1,填充始终为 0,组始终为 1 等。但是,当我测试前向传递时,它给了我一些奇怪的答案(使用简单的卷积核 = np.ones((1,1,3,3))
。该内核的学习率设置为零,因此它不会改变。但是,我无法得到正确的答案。任何建议将不胜感激.
请不要提出使用现有层的解决方案,例如 Slice, Eltwise, Crop
。我已经实现了 - 它有效 - 但它非常复杂且内存效率低下。
总的来说,我认为你的做法是正确的。
对于"weird"的卷积结果,我猜bug最有可能是:
考虑二维卷积
假设bottom[1]
的形状是(num, channels, height, width)
,
因为caffe
中的卷积是作为2矩阵的乘法执行的,weight
(代表卷积核)和col_buffer
(从要卷积的数据重组),weight
是 num_out
行和 channels / this->group_ * kernel_h * kernel_w
列,col_buffer
是 channels / this->group_ * kernel_h * kernel_w
行和 height_out * width_out
列,所以作为 weight
的 blob动态卷积层,bottom[0]
的形状最好是(num, num_out, channels/group, kernel_h, kernel_w)
以满足
bottom[0]->count(1) == num_out * channels / this->group_ * kernel_h * kernel_w
,其中num_out
是动态卷积层输出特征图的个数。
也就是说,要使卷积函数
this->forward_cpu_gemm(bottom_data + n * bottom[1]->count(1)
, bottom[0]->cpu_data() + n * bottom[0]->count(1)
, top_data + n * this->top_dim_);
正常工作,必须确保
bottom[0]->shape(0) == bottom[1]->shape(0) == num
bottom[0]->count(1) == num_out * channels / this->group_ * kernel_h * kernel_w
所以很可能你使用的简单的4维卷积核np.ones((1,1,3,3))
可能不满足上述条件导致错误的卷积结果.
希望它很清楚并对您有所帮助。
##########北京时间2016年10月10日更新1##########
我添加了一个动态卷积层 here 但还没有进行单元测试。该层不破坏caffe的工作流程,只是将BaseConvolution的一些私有成员class更改为受保护。
涉及的文件有:
include/caffe/layers/dyn_conv_layer.hpp,base_conv_layer.hpp
src/caffe/layers/dyn_conv_layer.cpp(cu)
和caffe
中的卷积层增长差不多,区别主要有:
- 覆盖函数
LayerSetUp()
以正确初始化 this->kernel_dim_
、this->weight_offset_
等以进行卷积,并忽略卷积层通常用于包含权重和偏差的初始化 this->blobs_
;
- 覆盖函数
Reshape()
以检查作为内核容器的 bottom[1]
是否具有正确的卷积形状。
因为没时间测试,可能会有bug,很高兴看到你的反馈。
##########更新2,2016年10月12日,北京时间##########
我刚刚更新了 dynamic convolution 的测试用例。涉及的文件是src/caffe/test/test_dyn_convolution_layer.cpp
。它似乎工作正常,但可能需要更彻底的测试。
您可以通过 cd $CAFFE_ROOT/build && ccmake ..
、cmake -DBUILD_only_tests="dyn_convolution_layer" ..
和 make runtest
构建此 caffe 以检查它。
在 caffe 中,convolution
层采用一个底部 blob,并将其与学习过滤器(使用权重类型初始化 - "Xavier"、"MSRA" 等)进行卷积。然而,我的问题是我们是否可以简单地将两个底部 blob 卷积并产生一个顶部 blob。这样做最优雅的方法是什么?这样做的目的是:底部的一个 blob 将是 data
,另一个将是前几层生成的动态过滤器(根据 data
变化)(我正在尝试实现 dynamic convolution).
我的尝试:
我想到的一种方法是修改 filler.hpp
并将底部 blob 分配为 filler
矩阵本身(而不是 "Xavier"、"MSRA" 等.).然后我认为卷积层会从那里开始。我们可以设置 lr = 0
来表示我们的自定义填充器初始化的权重不应该改变。但是,看了源码后,还是不知道该怎么做。另一方面,我不想破坏caffe的工作流程。我仍然希望转换层正常运行,如果我希望的话。
显然比较繁琐的方法是使用Slice
、tile
and/orScale
层的组合来字面上实现卷积。我认为它会起作用,但结果会很乱。还有其他想法吗?
编辑 1:
我通过修改caffe的卷积层写了一个新层。特别是,在第 27 行的 src/caffe/layers/conv_layer.cpp
中,它采用 filler
定义的权重并将其与底部的 blob 进行卷积。因此,我没有从 filler
中填充该 blob,而是修改了图层,使其现在有两个底部。底部之一直接分配给填充物。现在我不得不做一些其他的改变,例如:
weight
blob 对所有样本具有相同的值。这里对于不同的样本会有不同的值。所以我将第 32 行从:
this->forward_cpu_gemm(
bottom_data + n * this->bottom_dim_,
weight,
top_data + n * this->top_dim_);
至:
this->forward_cpu_gemm(
bottom_data + n * bottom[1]->count(1),
bottom[0]->cpu_data() + n * bottom[0]->count(1),
top_data + n * this->top_dim_);
为了让事情更简单,我假设不涉及偏差项,步幅始终为 1,填充始终为 0,组始终为 1 等。但是,当我测试前向传递时,它给了我一些奇怪的答案(使用简单的卷积核 = np.ones((1,1,3,3))
。该内核的学习率设置为零,因此它不会改变。但是,我无法得到正确的答案。任何建议将不胜感激.
请不要提出使用现有层的解决方案,例如 Slice, Eltwise, Crop
。我已经实现了 - 它有效 - 但它非常复杂且内存效率低下。
总的来说,我认为你的做法是正确的。
对于"weird"的卷积结果,我猜bug最有可能是:
考虑二维卷积
假设bottom[1]
的形状是(num, channels, height, width)
,
因为caffe
中的卷积是作为2矩阵的乘法执行的,weight
(代表卷积核)和col_buffer
(从要卷积的数据重组),weight
是 num_out
行和 channels / this->group_ * kernel_h * kernel_w
列,col_buffer
是 channels / this->group_ * kernel_h * kernel_w
行和 height_out * width_out
列,所以作为 weight
的 blob动态卷积层,bottom[0]
的形状最好是(num, num_out, channels/group, kernel_h, kernel_w)
以满足
bottom[0]->count(1) == num_out * channels / this->group_ * kernel_h * kernel_w
,其中num_out
是动态卷积层输出特征图的个数。
也就是说,要使卷积函数
this->forward_cpu_gemm(bottom_data + n * bottom[1]->count(1)
, bottom[0]->cpu_data() + n * bottom[0]->count(1)
, top_data + n * this->top_dim_);
正常工作,必须确保
bottom[0]->shape(0) == bottom[1]->shape(0) == num
bottom[0]->count(1) == num_out * channels / this->group_ * kernel_h * kernel_w
所以很可能你使用的简单的4维卷积核np.ones((1,1,3,3))
可能不满足上述条件导致错误的卷积结果.
希望它很清楚并对您有所帮助。
##########北京时间2016年10月10日更新1##########
我添加了一个动态卷积层 here 但还没有进行单元测试。该层不破坏caffe的工作流程,只是将BaseConvolution的一些私有成员class更改为受保护。
涉及的文件有:
include/caffe/layers/dyn_conv_layer.hpp,base_conv_layer.hpp
src/caffe/layers/dyn_conv_layer.cpp(cu)
和caffe
中的卷积层增长差不多,区别主要有:
- 覆盖函数
LayerSetUp()
以正确初始化this->kernel_dim_
、this->weight_offset_
等以进行卷积,并忽略卷积层通常用于包含权重和偏差的初始化this->blobs_
; - 覆盖函数
Reshape()
以检查作为内核容器的bottom[1]
是否具有正确的卷积形状。
因为没时间测试,可能会有bug,很高兴看到你的反馈。
##########更新2,2016年10月12日,北京时间##########
我刚刚更新了 dynamic convolution 的测试用例。涉及的文件是src/caffe/test/test_dyn_convolution_layer.cpp
。它似乎工作正常,但可能需要更彻底的测试。
您可以通过 cd $CAFFE_ROOT/build && ccmake ..
、cmake -DBUILD_only_tests="dyn_convolution_layer" ..
和 make runtest
构建此 caffe 以检查它。