0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Max Pooling算子的设计实现

jf_pmFSk4VX 来源:GiantPandaCV 2023-01-10 09:51 次阅读

Max Pooling算子的定义

池化层在深度学习网络中的作用一般是用来缓解卷积层对位置的过度敏感性。池化层每次对输入数据的一个固定形状窗口(池化窗口的大小为pooling height, pooling width)中的元素计算输出,池化层直接计算池化窗口内元素的最大值或者平均值,因此该运算也分别叫做最大池化或平均池化。

在我们本节课要讲的二维最大池化中,池化窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动(滑动的幅度被称为stride)。当池化窗口滑动到某一位置时,窗口中的输入子数组的最大值即输出数组中相应位置的元素。

01ff34bc-9034-11ed-bfe3-dac502259ad0.png

图1展示了池化窗口形状为 2×2 的最大池化,阴影部分为第一个输出元素及其计算所使用的输入元素。

输出数组的高和宽分别为2,其中的4个元素由取最大值运算 max 得出。 如下公式所示,池化操作的步骤依次为从左到右,从上到下,每次向下移动的步长为stride height, 向右移动的步长为stride width. 进行池化操作元素的数量由pooling height和pooling width所组成的2×2的窗口所决定。

Max Pooling Operator的实现

classMaxPoolingOp:publicOperator{
public:
explicitMaxPoolingOp(uint32_tpooling_h,uint32_tpooling_w,uint32_tstride_h,
uint32_tstride_w,uint32_tpadding_h,uint32_tpadding_w);

voidset_pooling_h(uint32_tpooling_height);
voidset_pooling_w(uint32_tpooling_width);

voidset_stride_w(uint32_tstride_width);
voidset_stride_h(uint32_tstride_height);

voidset_padding_h(uint32_tpadding_height);
voidset_padding_w(uint32_tpadding_width);

uint32_tpadding_height()const;
uint32_tpadding_width()const;

uint32_tstride_width()const;
uint32_tstride_height()const;

uint32_tpooling_height()const;
uint32_tpooling_width()const;
private:
uint32_tpooling_h_;//池化核高度大小
uint32_tpooling_w_;//池化核宽度大小
uint32_tstride_h_;//高度上的步长
uint32_tstride_w_;//宽度上的步长
uint32_tpadding_h_;//高度上的填充
uint32_tpadding_w_;//宽度上的填充
};

可以看到如上的Operator中,有6个类内属性,分别对应着我们第一节中讲过的步长(stride), 池化核(pooling)以及在池化前对边缘的扩充,以下我们在分别讲讲:

stride: 池化核每次移动的步长

pooling: 池化核的大小

padding: 对输入特征图的边缘扩充

如下图2是pad(padding值为1)后输入特征图的池化操作(池化核为2):

020e0816-9034-11ed-bfe3-dac502259ad0.png

Max Pooling Layer的实现

MaxPoolingLayer::MaxPoolingLayer(conststd::shared_ptr&op):Layer("maxpooling"){
CHECK(op->op_type_==OpType::kOperatorMaxPooling)<< "Operator has a wrong type: " << int(op->op_type_);
MaxPoolingOp*max_pooling_op=dynamic_cast(op.get());

CHECK(max_pooling_op!=nullptr)<< "MaxPooling operator is empty";
  this->op_=std::make_unique(*max_pooling_op);
}

voidMaxPoolingLayer::Forwards(conststd::vector>>&inputs,
std::vector>>&outputs){
CHECK(this->op_!=nullptr);
CHECK(this->op_->op_type_==OpType::kOperatorMaxPooling);
CHECK(!inputs.empty());
constuint32_tpadding_h=this->op_->padding_height();
constuint32_tpadding_w=this->op_->padding_width();
constuint32_tkernel_h=this->op_->pooling_height();
constuint32_tkernel_w=this->op_->pooling_width();
constuint32_tstride_h=this->op_->stride_height();
constuint32_tstride_w=this->op_->stride_width();

constuint32_tbatch_size=inputs.size();
for(uint32_ti=0;i< batch_size; ++i) {
    const std::shared_ptr>&input_data_=inputs.at(i)->Clone();
input_data_->Padding({padding_h,padding_h,padding_w,padding_w},std::numeric_limits::lowest());
constuint32_tinput_h=input_data_->rows();
constuint32_tinput_w=input_data_->cols();
constuint32_tinput_c=input_data_->channels();
constuint32_toutput_c=input_c;

constuint32_toutput_h=uint32_t(std::floor((input_h-kernel_h)/stride_h+1));
constuint32_toutput_w=uint32_t(std::floor((input_w-kernel_w)/stride_w+1));
CHECK(output_w>0&&output_h>0);

std::shared_ptr>output_data=std::make_shared>(output_c,output_h,output_w);
for(uint32_tic=0;ic< input_c; ++ic) {
      const arma::fmat &input_channel = input_data_->at(ic);
arma::fmat&output_channel=output_data->at(ic);
for(uint32_tr=0;r< input_h - kernel_h + 1; r += stride_h) {
        for (uint32_t c = 0; c < input_w - kernel_w + 1; c += stride_w) {
          const arma::fmat ®ion = input_channel.submat(r, c, r + kernel_h - 1, c + kernel_w - 1);
          output_channel.at(int(r / stride_h), int(c / stride_w)) = region.max();
        }
      }
    }
    outputs.push_back(output_data);
  }
}

std::shared_ptrMaxPoolingLayer::CreateInstance(conststd::shared_ptr&op){
CHECK(op->op_type_==OpType::kOperatorMaxPooling);
std::shared_ptrmax_layer=std::make_sh了ared(op);
returnmax_layer;
}

LayerRegistererWrapperkMaxPoolingLayer(OpType::kOperatorMaxPooling,MaxPoolingLayer::CreateInstance);
voidMaxPoolingLayer::Forwards(conststd::vector>>&inputs,
std::vector>>&outputs){
CHECK(this->op_!=nullptr);
CHECK(this->op_->op_type_==OpType::kOperatorMaxPooling);
CHECK(!inputs.empty());
}

我们重点来看Forwards函数, 首先判断输入是否为空并获得池化操作相关的属性值(原本存放在op中).

计算池化后的输出特征图大小, 公式为:

for(uint32_ti=0;i< batch_size; ++i) {
    const std::shared_ptr>&input_data_=inputs.at(i)->Clone();
input_data_->Padding({padding_h,padding_h,padding_w,padding_w},std::numeric_limits::lowest());

如上的过程表示对输入的特征图四周进行填充,填充的大小由于padding_w和padding_h决定。这两个Layer计算时候的属性由op中得到,也就是说padding_w和padding_h存放在this->op中, this->op_ = std::make_unique(*max_pooling_op);

for(uint32_ti=0;i< batch_size; ++i) {
    const std::shared_ptr>&input_data_=inputs.at(i)->Clone();
input_data_->Padding({padding_h,padding_h,padding_w,padding_w},std::numeric_limits::lowest());
constuint32_tinput_h=input_data_->rows();
constuint32_tinput_w=input_data_->cols();
constuint32_tinput_c=input_data_->channels();
constuint32_toutput_c=input_c;

constuint32_toutput_h=uint32_t(std::floor((input_h-kernel_h)/stride_h+1));
constuint32_toutput_w=uint32_t(std::floor((input_w-kernel_w)/stride_w+1));
CHECK(output_w>0&&output_h>0);

如上的过程表示根据输入的特征图大小input_h和input_w来计算对应的输出特征值大小output_h和output_w. 计算的公式如上文所示。如果输入的特征数据input_data_有填充,则根据填充数据的输入大小来计算对应的输出大小。

for(uint32_ti=0;i< batch_size; ++i) {
  ...
    for (uint32_t ic = 0; ic < input_c; ++ic) {
      const arma::fmat &input_channel = input_data_->at(ic);
arma::fmat&output_channel=output_data->at(ic);
for(uint32_tr=0;r< input_h - kernel_h + 1; r += stride_h) {
        for (uint32_t c = 0; c < input_w - kernel_w + 1; c += stride_w) {
          const arma::fmat ®ion = input_channel.submat(r, c, r + kernel_h - 1, c + kernel_w - 1);
          output_channel.at(int(r / stride_h), int(c / stride_w)) = region.max();
        }
      }
    }
}

for(uint32_t ic =0; ic < input_c;++ic) 表示对输入的特征图进行逐通道的池化操作, 设当前进行操作的输入特征图通道为input_channel, 池化后的输出特征图放置于output_channel中。池化的过程如下公式所描述:

在上述的代码中region表示当前输入特征数据需要进行池化的部分,对应于公式中[r:r+kernel height -1,c:c+kernel width -1]

中的数据。输入特征的数据是逐个通道进行处理(池化操作)的,从ic = 0到ic = input_channel - 1, 当前池化的数据保存在region中。

input_channel.submat(r, c, r + kernel_h -1, c + kernel_w -1)取得一个池化区域内的所有元素,随后使用region.max()取得区域内(kernel_h和kernel_w组成的范围)的最大值, 并且每次区域移动的位置是stride_h和stride_w, 取得最大值后存放在输出特征图中对应的位置中,输出存放的位置为输出特征图outut_channel的(int(r/stride_h),int(c/stride_w))的位置中。这部分可能描述地比较晦涩,请结合视频一起食用。

Max Pooling Layer的其他部分

MaxPoolingLayer::MaxPoolingLayer(conststd::shared_ptr&op):Layer("maxpooling"){
CHECK(op->op_type_==OpType::kOperatorMaxPooling)<< "Operator has a wrong type: " << int(op->op_type_);
MaxPoolingOp*max_pooling_op=dynamic_cast(op.get());

CHECK(max_pooling_op!=nullptr)<< "MaxPooling operator is empty";
  this->op_=std::make_unique(*max_pooling_op);
}

LayerRegistererWrapperkMaxPoolingLayer(OpType::kOperatorMaxPooling,MaxPoolingLayer::CreateInstance);

以上的步骤完成了Max Pooling层的注册, 具体流程已经在第五节中讲过。MaxPoolingLayer::MaxPoolingLayer初始化部分根据传入的op对this->op_进行赋值,this->op_中保存了stride,padding,pooling等计算时需要的属性信息

单元测试

TEST(test_layer,forward_maxpooling1){
usingnamespacekuiper_infer;
uint32_tstride_h=1;
uint32_tstride_w=1;
uint32_tpadding_h=0;
uint32_tpadding_w=0;
uint32_tpooling_h=2;
uint32_tpooling_w=2;

std::shared_ptr
max_op=std::make_shared(pooling_h,pooling_w,stride_h,stride_w,padding_h,padding_w);
std::shared_ptrmax_layer=LayerRegisterer::CreateLayer(max_op);
CHECK(max_layer!=nullptr);

arma::fmatinput_data="012;"
"345;"
"678;";
std::shared_ptr>input=std::make_shared>(2,input_data.n_rows,input_data.n_cols);
input->at(0)=input_data;
input->at(1)=input_data;

std::vector>>inputs;
std::vector>>outputs;
inputs.push_back(input);

max_layer->Forwards(inputs,outputs);
ASSERT_EQ(outputs.size(),1);
constauto&output=outputs.at(0);
LOG(INFO)<< "
" << output->data();
ASSERT_EQ(output->rows(),2);
ASSERT_EQ(output->cols(),2);

ASSERT_EQ(output->at(0,0,0),4);
ASSERT_EQ(output->at(0,0,1),5);
ASSERT_EQ(output->at(0,1,0),7);
ASSERT_EQ(output->at(0,1,1),8);

ASSERT_EQ(output->at(1,0,0),4);
ASSERT_EQ(output->at(1,0,1),5);
ASSERT_EQ(output->at(1,1,0),7);
ASSERT_EQ(output->at(1,1,1),8);
}

可以看到, 我们的输入为 arma::fmat input_data ="0 1 2 ; 3 4 5 ;6 7 8; " , 池化核的大小为2, 每次移动的步长stride =1,所以根据我们在第一节中的计算, 最后的输出特征图大小应该是2乘2大小, 池化得到的值分别为4 5 7 8.

021f7632-9034-11ed-bfe3-dac502259ad0.png






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • ARM处理器
    +关注

    关注

    6

    文章

    361

    浏览量

    41839

原文标题:自制深度学习推理框架-第六课-Max Pooling算子的实现

文章出处:【微信号:GiantPandaCV,微信公众号:GiantPandaCV】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    MAX44290ANT+T和MAX44290ANT+TG7有什么区别呢?

    MAX44290ANT+T和MAX44290ANT+TG7请问有什么区别呢? 是否有文件呢 谢谢
    发表于 12-19 07:17

    MAX232实现单电源供电下放大3.3Vpp方波?

    请问大佬,可以通过MAX232实现单电源5V供电下,把40KHZ,3.3Vpp的方波放大到多少?怎么实现呢?(老师提示是用H桥和MAX转换电路)
    发表于 11-04 20:08

    基于 DSP5509 进行数字图像处理中 Sobel 算子边缘检测的硬件连接电路图

    以下是基于 DSP5509 进行数字图像处理中 Sobel 算子边缘检测的硬件设计方案: 一、总体架构 图像采集:使用合适的图像传感器,如 CMOS 传感器,通过相应的接口(如 SPI、I2C 等
    发表于 09-25 15:25

    摩尔线程携手智源研究院完成基于Triton的大模型算子库适配

    里,即成功完成了近60个算子的功能验证,精度符合交付标准,并实现对Bert-large模型的全面支持。FlagGems算子库在摩尔线程MUSA架构上展现出了接近手写算子的计算性能,且性
    的头像 发表于 08-02 11:06 925次阅读

    ZN-Z84,能ping通,但是NI MAX不识别,找不到设备

    前提是,安装原厂的user程序,利用usb驱动和接口,是可以实现程控通信的。 ZN-Z84,开关单元,利用网线连接,结果IP 都用cmd的ping 命令ping通了,死活都无法在NI MAX 和IO
    发表于 08-02 09:08

    ZN-Z84,能ping通,但是NI MAX不识别,找不到设备

    前提是,安装原厂的user程序,利用usb驱动和接口,是可以实现程控通信的。 ZN-Z84,开关单元,利用网线连接,结果IP 都用cmd的ping 命令ping通了,死活都无法在NI MAX 和IO
    发表于 07-27 10:18

    ClickHouse内幕(3)基于索引的查询优化

    ClickHouse索引采用唯一聚簇索引的方式,即Part内数据按照order by keys有序,在整个查询计划中,如果算子能够有效利用输入数据的有序性,对算子的执行性能将有巨大的提升。本文讨论
    的头像 发表于 06-11 10:46 1052次阅读
    ClickHouse内幕(3)基于索引的查询优化

    Arm发布全新终端计算子系统,加速AI体验与产品上市

    全球领先的半导体知识产权(IP)提供商Arm控股有限公司(纳斯达克股票代码:ARM)今日正式推出全新的Arm终端计算子系统(CSS),以推动人工智能(AI)体验的前沿发展,并助力芯片合作伙伴在构建基于Arm架构的解决方案时实现更高效、更快速的流程,从而加速产品上市。
    的头像 发表于 05-30 14:23 603次阅读

    Arm宣布推出终端计算子系统(CSS),提供领先的人工智能体验

    Arm 控股有限公司(纳斯达克股票代码:ARM,以下简称“Arm”)今日宣布推出 Arm® 终端计算子系统 (CSS),以提供领先的人工智能 (AI) 体验,助力芯片合作伙伴更轻松、快速地构建基于 Arm 架构的解决方案,并加速其产品上市进程。
    的头像 发表于 05-30 14:11 1285次阅读
    Arm宣布推出终端计<b class='flag-5'>算子</b>系统(CSS),提供领先的人工智能体验

    请问是否可以使用MAX9634对12V 300A的电流进行电流侦测?

    我需要对当前板子上的输入电源进行电流进行采集实时电流,输入电压为12V,电流约300A 左右。 想要选用MAX9634 物料来实现当前设计(已有其它项目使用了MAX9634TEUK+物料做12V
    发表于 05-24 08:14

    MAX13085EESA+与MAX13485EESA+的差异在什么地方?

    MAX13085EESA+与MAX13485EESA+,这两款同系列的8SO芯片的差异在什么地方,是否可以互相替代
    发表于 05-23 08:10

    MAX17205电量计如何正确配置所有寄存器?

    第一个问题:MAX17205 用EZ配置电池相关参数,但我发现Empty Voltage最低只能设置为3V,我想设置成2.5V或2.4V有办法实现吗? 第二个问题:我看MAX17205芯片手册,有
    发表于 05-22 08:17

    Arm新Arm Neoverse计算子系统(CSS):Arm Neoverse CSS V3和Arm Neoverse CSS N3

    Arm宣布了两款新的Arm Neoverse计算子系统(CSS),它们基于“迄今为止最好的一代Neoverse技术”。是什么让这些新产品在拥挤的计算技术领域脱颖而出? Arm的两个新Arm
    的头像 发表于 04-24 17:53 1149次阅读
    Arm新Arm Neoverse计<b class='flag-5'>算子</b>系统(CSS):Arm Neoverse CSS V3和Arm Neoverse CSS N3

    max6675测温不准的原因?如何解决?

    define MAX6675_CS_Pin GPIO_PIN_12 define MAX6675_CS_GPIO_Port GPIOB SPI_HandleTypeDef hspi2
    发表于 03-18 07:51

    PSoC4s MAX如何让距离传感器进入坚固的地面?

    我们正在考虑在连接到 FG 的金属框架上实现 PSoC4s MAX 接近传感器。 图像示意图如下: 当我们验证操作时,PSoC4s MAX的距离传感器的灵敏度非常差,因为金属框架变成了坚固
    发表于 01-29 07:01