深度神经网络可以按层被拆分为许多基本操作,而这些基本操作通过不同的组合方式也就构建多种多样的DNN。例如最常见的[INPUT - CONV - RELU - POOL - FC]组合。

简单记录一下卷积神经网络中的基本操作:

  • 卷积conv
  • 池化pool
  • Elementwise
  • Deep-Wise卷积

本文大量内容摘自两个学校的教程

http://cs231n.github.io/convolutional-networks/ 

http://www.rle.mit.edu/eems/wp-content/uploads/2017/06/Tutorial-on-DNN-1-of-9-Background-of-DNNs.pdf

0. feature map

  参与计算的数据(feature map) 为三维结构,(W,H,C)分别标识width,height,channel维度尺寸,例如224*224*3的图片。

1.卷积Convolutional

  平面内形象的卷积计算动图可以看https://github.com/vdumoulin/conv_arithmetic,实际计算中增加了channel维度,也需要加起来。

  下图为一个input fmap和多个filter的计算示意图。因为channel维度要相加,input fmap和filter在C维度上相等,而输出fmap的C维度尺寸就是filter的个数。

输入输出对应关系如下:

伪代码如下:

/*
* img: (src_w,src_h,chi)
* weights: cho * (ker_w, ker_h, chi)
* output feature map: 
*     dst_w = (src_w +2*pad - ker_w)/stride + 1
*     dst_h =  (src_h +2*pad - ker_h)/stride + 1   
*     dst_c = cho
*/
for(auto oc = 0; oc < cho;  oc++){//cho kernels
   dw = 0;
for(auto iw = 0; iw+ker_w <= src_w; iw+= stride){
      dh = 0;
for(auto ih = 0; ih + ker_h <= src_h; ih += stride){
        int tmp = 0;
        for(auto wc = 0; wc < chi; wc++){
          for(auto ww = 0; ww < ker_w; ww ++){
            for(auto wh = 0; wh < ker_h; wh++){
              tmp += img[wc][iw][ih] * weight[wc][ww][wh];
            }
          }
        }  
        result[oc][dw][dh] = act(tmp + bias[oc]);
        dh++;
      }
       dw++    } }

6层循环计算出的中间结果需要加上偏移量,经激活函数处理的到新的fmap数据。传统的激活函数有sigmoid和tanh。现在主流的都用运算量小,结果好的Relu系列

 

从卷积伪代码分析,一个卷积操作涉及的乘累加操作很多,单核单线程执行效率低下。而其中的6层循环,没个循环内部都有潜在的并行度值得去开发。

2.池化Pooling

也叫下采样(downsampling),典型的有max和average两种。卷积提取feature map的特征后,池化操作将特征区域内的代表数据保留,这样不仅抗噪声还能减少数据量。如下图:

对应的输入输出关系

以下为max pool的伪代码


/*
* img: (src_w,src_h,chi)
* filter size: (ker_w,ker_h)
* output feature map: 
*     dst_w = (src_w - ker_w)/stride + 1
*     dst_h =  (src_h - ker_h)/stride + 1   
*     dst_c = chi
*/
for(auto oc = 0; oc < cho;  oc++){//cho kernels
   dw = 0; for(auto iw = 0; iw+ker_w <= src_w; iw+= stride){
      dh = 0; for(auto ih = 0; ih + ker_h <= src_h; ih += stride){
        for(auto wc = 0; wc < chi; wc++){
          max = -Inf;
          for(auto ww = 0; ww < ker_w; ww ++){
            for(auto wh = 0; wh < ker_h; wh++){
              if(
img[wc][iw][ih] > max){
                max = img[wc][iw][ih];
              }
            }
          }
          result[oc][dw][dh] = max;
        }
        dh++;
      }
      dw++;
  }
}

 3. 全连接fc

全连接没有conv的权值共享,每一个像素点都需要和输出神经元连接,消耗大量的权值存储。

全连接可以用卷积操作来分解。

例如input fmap为7*7*512,用4096个7*7*512的卷积核计算,stride=1,pad=0,结果是1*1*4096

如果还想缩减尺寸,用1000个1*1*4096的卷积核计算,stride=1,pad=0,结果是1*1*1000

4.逐元素操作 elementwise

ResNet结构中使用了一种连接方式叫做“shortcut connection”如下图

在F(x)+x 和 合并到一起的操作中,需要将两张feature map的对应位置相加得到新的featuremap。伪代码如下


/*
* imgA: (src_w,src_h,chi)
* imgB:(src_w,src_h,chi)
* output feature map: 
*     dst_w = src_w 
*     dst_h = src_h 
*     dst_c = chi
*/

for
(auto iw = 0; iw<= src_w; iw++){ for(auto ih = 0; ih<= src_h; ih ++){     for(auto ic = 0; ic < chi; ic++){          result[iw][ih][ic] = imgA[iw][ih][ic] + imgB[iw][ih][ic];       }   } }

 

5. Deep-wise卷积

MobileNet结构中使用了一种称之为deep-wise的卷积方式来替代原有的传统3D卷积,减少了卷积核的冗余表达。在计算量和参数数量明显下降之后,卷积网络可以应用在更多的移动端平台。

Deep-wise卷积可以简单理解为channel维度不用加起来。伪代码如下

/*
* img: (src_w,src_h,chi)
* weights:  (ker_w, ker_h, chi)
* output feature map: 
*     dst_w = (src_w +2*pad - ker_w)/stride + 1
*     dst_h =  (src_h +2*pad - ker_h)/stride + 1   
*     dst_c = chi
*/
for(auto ic = 0; ic < chi; ic++){
    dw = 0;
    for(auto iw = 0; iw+ker_w <= src_w; iw+= stride){
    dh = 0;
          for(auto ih = 0; ih + ker_h <= src_h; ih += stride){
     int tmp = 0;
     for(auto ww = 0; ww < ker_w; ww ++){
         for(auto wh = 0; wh < ker_h; wh++){
          tmp += img[ic][iw][ih] * weight[ic][ww][wh];
       }
      }
     result[ic][dw][dh] = act(tmp + bias[ic]);
     dh++;
   }  
   dw++
 }
}  

 

posted on 2018-07-27 01:06  chaob  阅读(1217)  评论(0编辑  收藏  举报