SPPNet:Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition
pdf:https://arxiv.org/pdf/1406.4729v2.pdf
论文主要贡献
之前的CNN网络的输入的size都是固定,为什么要这样呢,是因为最后fc层的输入需要是一个固定的尺寸,例如AlexNet的fc6的输入就是需要固定,但是如果各种图片都通过crop或者warp来resize成一样,就会有下面的情况发生:
crop导致看不全,warp导致变形
而该论文通过在conv和fc层之间引入Spatial Pyramid Pooling,可以接受不同的size输入,在SPP层后面产生固定size的输出送入fc层,而且SPP相较于之前的Pooling来说,通过设置不同level的spatial bins来达到Mutil-level,而且由于输入的size是多变的,所以它可以从各个不同的scales来pool feature,有更强的鲁棒性。
随手画了下大致的网络示意图 以AlexNet为例(有点难看 轻喷)
实验效果
- 直接把原来的Pooling换掉,其他不变输入还是fixed的,这样做为了控制变量,直接看SPP的效果,发现也有1个点的提升
- 输入不同size的图片进行训练,比起第一个实验,又有了0.5点的提升吧(注意此处训练是用了不同size,但是测试还是同一size)
- 做了组对比实验,每张图片令min(w,h)= 256,然后保持比例放缩到这个大小,送入网络。另一个是取刚刚放缩好的图片中间部位的224x224送入网络,实验结果发现full image比crop image高0.5个点,这也说明了输入全图更好。
- 测试时取很多view,比如中心、四个角、四个边的中间以及他们的翻转这18个view,然后全图计算一次conv5的feature map就行,通过映射看这18个view在feature map都是哪块,然后把这18块送入SPP后进入fc,发现也是有提升的
caffe实现
#caffe.proto中的关于SPP层参数的定义
message SPPParameter {
enum PoolMethod {
MAX = 0;
AVE = 1;
STOCHASTIC = 2;
}
optional uint32 pyramid_height = 1;
optional PoolMethod pool = 2 [default = MAX]; // The pooling method
enum Engine {
DEFAULT = 0;
CAFFE = 1;
CUDNN = 2;
}
optional Engine engine = 6 [default = DEFAULT];
}
# AlexNet trainval.prototxt
....
layer {
name: "relu5"
type: "ReLU"
bottom: "conv5"
top: "conv5"
}
#之前的Pooling层
#layer {
# name: "pool5"
# type: "Pooling"
# bottom: "conv5"
# top: "pool5"
# pooling_param {
# pool: MAX
# kernel_size: 3
# stride: 2
# }
#}
layer {
name: "spatial_pyramid_pooling"
type: "SPP"
bottom: "conv5"
top: "pool5"
spp_param {
pool: MAX
pyramid_height: 2 # SPP的level的数量
}
}
layer {
name: "fc6"
type: "InnerProduct"
bottom: "pool5"
top: "fc6"
...
}
...
void SPPLayer<Dtype>::LayerSetUp(......){
for (int i = 0; i < pyramid_height_; i++) {
......
// pooling layer setup
LayerParameter pooling_param = GetPoolingParam(
i, bottom_h_, bottom_w_, spp_param);
}
}
// 关于pyramid_height
// pyramid_level是0到pyramid_height-1
// 暂时是这样理解这段代码的,但是没有找到stride在哪???
// pool 1x1 2x2 4x4 8x8 ... pow(2, pyramid_height-1)xpow(2, pyramid_height-1)
LayerParameter SPPLayer<Dtype>::GetPoolingParam(const int pyramid_level,......){
......
LayerParameter pooling_param;
int num_bins = pow(2, pyramid_level);
// find padding and kernel size so that the pooling is
// performed across the entire image
int kernel_h = ceil(bottom_h / static_cast<double>(num_bins));
// remainder_h is the min number of pixels that need to be padded before
// entire image height is pooled over with the chosen kernel dimension
int remainder_h = kernel_h * num_bins - bottom_h;
// pooling layer pads (2 * pad_h) pixels on the top and bottom of the
// image.
int pad_h = (remainder_h + 1) / 2;
// similar logic for width
int kernel_w = ceil(bottom_w / static_cast<double>(num_bins));
int remainder_w = kernel_w * num_bins - bottom_w;
int pad_w = (remainder_w + 1) / 2;
pooling_param.mutable_pooling_param()->set_pad_h(pad_h);
pooling_param.mutable_pooling_param()->set_pad_w(pad_w);
pooling_param.mutable_pooling_param()->set_kernel_h(kernel_h);
pooling_param.mutable_pooling_param()->set_kernel_w(kernel_w);
pooling_param.mutable_pooling_param()->set_stride_h(kernel_h);
pooling_param.mutable_pooling_param()->set_stride_w(kernel_w);
...
}
注意事项
直接复现实验1是很容易,就是输入还是固定的,只是Pooling换掉,只有一点注意,pyramid_height的设置如果过大(图片的size很小)会报的错误:Check failed: pad_h_ < kernel_h_,因为size太小了,到conv5时feature map太小了,而caffe这里限制pad_h_ < kernel_h_
复现实验2就有些许问题,如果你的batch不是1的话,因为load_batch是多线程同步的,caffe会默认用这个batch里的第一个数据的chanel height width作为输出的格式,所以会报类似的错误:Check failed: height <= datum_height (80(第一个数据的height) vs. 64(后续数据的height)) ,暂时如果不修改部分源码的话,只有先把trainval.prototxt和test.prototxt的batchsize都设为1吧,实验2仍需要注意实验1的问题
- 实验3,4自行根据前两条注意事项进行操作