Caffe源码精读 - 4 - Caffe Layers之pooling_layer(池化层)

Class_4 Caffe Layerspooling_layer(池化层)

 

1. 概述

池化是卷积神经网络中较为常用的一种操作,根本目的是实现降采样,简化计算。

目前池化层从作用面区分,可分为全局池化和局部池化。全局池化是相当于在整张图上做池化,每一张特征图最终得到一个池化值,即H*W*C的特征层,经过全局池化以后得到的是1*1*C的池化输出。局部池化就是指定Feature map上相同的若干区域,进行池化操作。

池化操作又分为最大池化、平均池化。具体操作细节不再赘述。

2. Caffe池化层

2.1 LayerSetUp

    LayerSetUp这个成员每一层都有,主要是读取、解析train.prototxt(网络模型文件,不一定都叫这个名字)文件,并配置当前层相应的参数。

    (1)首先是判断是否是全局池化:

    global_pooling_ = pool_param.global_pooling(); ///< 全局池化

    并根据是否是全局池化,来设置池化核的参数,

if (global_pooling_) {

    kernel_h_ = bottom[0]->height(); ///< 全局池化,池化核H和W等同于输入H和W

    kernel_w_ = bottom[0]->width();

} else {

    if (pool_param.has_kernel_size()) { ///< 池化核,基本就是池化小方块

        kernel_h_ = kernel_w_ = pool_param.kernel_size();

    } else {

        kernel_h_ = pool_param.kernel_h();
    
        kernel_w_ = pool_param.kernel_w();

    }

}

 

      (2)接下来是配置padding和stride信息

if (!pool_param.has_pad_h()) {

    pad_h_ = pad_w_ = pool_param.pad();

} else {

    pad_h_ = pool_param.pad_h();

    pad_w_ = pool_param.pad_w();

}

if (!pool_param.has_stride_h()) {

    stride_h_ = stride_w_ = pool_param.stride();

} else {

    stride_h_ = pool_param.stride_h();

    stride_w_ = pool_param.stride_w();

}

2.2 ReShape

(1) 首先是获得输入数据的shape信息

CHECK_EQ(4, bottom[0]->num_axes()) << "Input must have 4 axes, "

    << "corresponding to (num, channels, height, width)"; ///< 确保是NCHW四个数据轴

channels_ = bottom[0]->channels(); ///< 通道

height_ = bottom[0]->height(); ///< 高

width_ = bottom[0]->width(); ///< 宽

if (global_pooling_) { ///< 计算池化核H和W

    kernel_h_ = bottom[0]->height();

    kernel_w_ = bottom[0]->width();

}

(2) 计算池化后的特征图尺寸

pooled_height_ = static_cast<int>(ceil(static_cast<float>( ///< 池化后的特征图尺寸,和卷积层类似

    height_ + 2 * pad_h_ - kernel_h_) / stride_h_)) + 1;

pooled_width_ = static_cast<int>(ceil(static_cast<float>(

    width_ + 2 * pad_w_ - kernel_w_) / stride_w_)) + 1;

(3) 如果含有填充,还要确保最终得到池化后的特征图是原图像内的元素

if (pad_h_ || pad_w_) {

    // If we have padding, ensure that the last pooling starts strictly

    // inside the image (instead of at the padding); otherwise clip the last.

    if ((pooled_height_ - 1) * stride_h_ >= height_ + pad_h_) {

        --pooled_height_;

    }

    if ((pooled_width_ - 1) * stride_w_ >= width_ + pad_w_) {

        --pooled_width_;

    }

    CHECK_LT((pooled_height_ - 1) * stride_h_, height_ + pad_h_);

    CHECK_LT((pooled_width_ - 1) * stride_w_, width_ + pad_w_);

}

(4) 设置池化输出的shape

top[0]->Reshape(bottom[0]->num(), channels_, pooled_height_,

    pooled_width_);

if (top.size() > 1) {

    top[1]->ReshapeLike(*top[0]);

}

(5) 最后处理最大池化和随机池化的两个参数

// If max pooling, we will initialize the vector index part.

if (this->layer_param_.pooling_param().pool() ==

    PoolingParameter_PoolMethod_MAX && top.size() == 1) {

    max_idx_.Reshape(bottom[0]->num(), channels_, pooled_height_,
        pooled_width_);

}

// If stochastic pooling, we will initialize the random index part.

if (this->layer_param_.pooling_param().pool() ==
    PoolingParameter_PoolMethod_STOCHASTIC) {

    rand_idx_.Reshape(bottom[0]->num(), channels_, pooled_height_,

    pooled_width_);

}

 

2.3 Forward_cpuForward_gpu

const Dtype* bottom_data是拿到输入数据,使用const,不可更改

top_data是输出数据指针;

top_data是pooling输出的总数据量;

mask或top_mask是为了记录pooling输出矩阵的值是取自pooling输入的全局坐标,只针对于最大池化;

caffe使用switch选取池化的方法,第一个是PoolingParameter_PoolMethod_MAX,即最大池化。

for (int n = 0; n < bottom[0]->num(); ++n) {
    for (int c = 0; c < channels_; ++c) {
        for (int ph = 0; ph < pooled_height_; ++ph) { ///< 池化后的H
        for (int pw = 0; pw < pooled_width_; ++pw) { ///< 池化后的W
            int hstart = ph * stride_h_ - pad_h_; ///< 执行池化的方块的起始H
                int wstart = pw * stride_w_ - pad_w_; ///< 执行池化的方块的起始W
                int hend = min(hstart + kernel_h_, height_); ///< 执行池化的方块的终止H
                int wend = min(wstart + kernel_w_, width_); ///< 执行池化的方块的终止W
                hstart = max(hstart, 0);
                wstart = max(wstart, 0);
                const int pool_index = ph * pooled_width_ + pw; ///< pooling输出索引
                for (int h = hstart; h < hend; ++h) {
                    for (int w = wstart; w < wend; ++w) {
                        const int index = h * width_ + w; ///< 输入的全局索引
                        if (bottom_data[index] > top_data[pool_index]) {
                            top_data[pool_index] = bottom_data[index];
                            if (use_top_mask) {
                                top_mask[pool_index] = static_cast<Dtype>(index);
                            } else {
                                mask[pool_index] = index;
                            }
                        }
                    }
                }
            }
        }
        // compute offset
        bottom_data += bottom[0]->offset(0, 1); ///< 多个输入Feature map,计算偏移
        top_data += top[0]->offset(0, 1); ///< 多个输入Feature map对应多个输出,计算偏移
        if (use_top_mask) {
            top_mask += top[0]->offset(0, 1);
        } else {
            mask += top[0]->offset(0, 1);
        }
    }
}

平均池化不做赘述;

随机池化,作者贾扬清并未实现;

 

2.4 Backward_cpuBackward_gpu

池化层的反向传播还是比较简单的,毕竟不像卷积层一样涉及权值的更新。池化层的反向传播只是将输出(top)的diff按照最大池化或平均池化反向传播到输入(bottom)即可。

Backward_cpu的的开始,是先获得数据的指针。

top_diff是输出的偏差矩阵,是由下一层反向传播回来的。

bottom_diff是需要更新的偏差矩阵。

还是以最大池化为例,平均池化相对简单。

if (use_top_mask) {

        top_mask = top[1]->cpu_data();

} else {

    mask = max_idx_.cpu_data();

}

for (int n = 0; n < top[0]->num(); ++n) {

    for (int c = 0; c < channels_; ++c) {

        for (int ph = 0; ph < pooled_height_; ++ph) { ///< pooling输出H

            for (int pw = 0; pw < pooled_width_; ++pw) { ///< pooling输出W

                const int index = ph * pooled_width_ + pw; ///< pooling输出索引

                const int bottom_index = use_top_mask ? top_mask[index] : mask[index]; ///< 从pooling输出的mask中反推输入的索引

                bottom_diff[bottom_index] += top_diff[index]; ///< 将输出diff反向传播到输入diff

            }

        }

        bottom_diff += bottom[0]->offset(0, 1); ///< 输入 diff偏移

        top_diff += top[0]->offset(0, 1); ///< 输出 diff偏移

        if (use_top_mask) { ///< 全局坐标矩阵偏移

            top_mask += top[0]->offset(0, 1); 

        } else {

            mask += top[0]->offset(0, 1);

        }

    }

}

池化曾到此解读完毕。

池化层的GPU(CUDA)实现,不做赘述了,大家自己看吧。知道CPU模式怎么处理,GPU模式应该问题不大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值