卷积神经网络原理及其C++/Opencv实现(7)—误反向传播代码实现

首先列出本系列博文的链接:

1. 卷积神经网络原理及其C++/Opencv实现(1)

2. 卷积神经网络原理及其C++/Opencv实现(2)

3. 卷积神经网络原理及其C++/Opencv实现(3)

4. 卷积神经网络原理及其C++/Opencv实现(4)—误反向传播法

5. 卷积神经网络原理及其C++/Opencv实现(5)—参数更新

6. 卷积神经网络原理及其C++/Opencv实现(6)—前向传播代码实现

上篇文章中我们讲了5层网络的前向传播的代码实现,有前向就有反向,本文就让我们同样使用C++和Opencv来实现反向传播的代码吧~

如上图所示,误差信息的反向传播过程可以分为以下5步:

1. Softmax-->Affine

2. Affine-->S4

3. S4-->C3

4. C3-->S2

5. S2-->C1

公式推导我们前文已经详细讲过,核心思想是复合函数的链式求导法则,下面我们分别阐述以上5个步骤的代码实现。

1. Softmax-->Affine

根据前文的推导,本步骤的反向传播公式为,其中y为Affine层的输出,Y为Softmax函数的输出,t为标签,0≤i<10。

代码实现如下:

void softmax_bp(Mat outputData, Mat &e, OutLayer &O)
{
  for (int i = 0; i < O.outputNum; i++)
    e.ptr<float>(0)[i] = O.y.ptr<float>(0)[i] - outputData.ptr<float>(0)[i];   //计算Y-t


  //将Y-t保存到O5层的局部梯度中                                       
  for (int i = 0; i < O.outputNum; i++)
    O.d.ptr<float>(0)[i] = e.ptr<float>(0)[i];// *sigma_derivation(O.y.ptr<float>(0)[i]);
}

2. Affine-->S4

本步骤的反向传播公式如下,其中x为Affine的输入,w为Affine层的权重,0≤j<192。

Affine层的输入有192个x,也就是说有192个E关于x的偏导数,把这192个偏导数按顺序重组成12个4*4的二维矩阵,作为S4层的局部梯度,其中有12个d,每个d都是4*4矩阵:

代码实现如下:

void full2pool_bp(OutLayer O, PoolLayer &S)
{
  int outSize_r = S.inputHeight / S.mapSize;
  int outSize_c = S.inputWidth / S.mapSize;
  for (int i = 0; i < S.outChannels; i++)  //输出12张4*4图像
  {
    for (int r = 0; r < outSize_r; r++)
    {
      for (int c = 0; c < outSize_c; c++)
      {
        int wInt = i*outSize_c*outSize_r + r*outSize_c + c;  //i*outSize.c*outSize.r为图像索引,r*outSize.c+c为每张图像中的像素索引
        for (int j = 0; j < O.outputNum; j++)   //O5输出层的输出个数
        {
          //把192个偏导数重组成12个4*4的二维矩阵,作为S4层的局部梯度
          S.d[i].ptr<float>(r)[c] = S.d[i].ptr<float>(r)[c] + O.d.ptr<float>(0)[j] * O.wData.ptr<float>(j)[wInt];  //d_S4 = ∑d_O5*W
        }
      }
    }
  }
}

3. S4-->C3

本步骤的反向传播公式如下,其中upsample为我们前文讲过的池化层向上采样操作,DerivativeRelu为Relu函数的导数,我们前文也讲过。本层的局部梯度是12个8*8的矩阵(0≤i<12):

上述公式的计算代码如下:

/*
矩阵上采样,upc及upr是池化窗口的列、行
如果是最大值池化模式,则把局域梯度放到池化前最大值的位置,比如池化窗口2*2,池化前最大值的位置分别为左上、右上、左下、右下,则上采样后为:
5 9        5 0 0 9
     -->   0 0 0 0
3 6        0 0 0 0
                3 0 0 6
如果是均值池化模式,则把局域梯度除以池化窗口的尺寸2*2=4:
5 9        1.25 1.25 2.25 2.25
     -->   1.25 1.25 2.25 2.25
3 6        0.75 0.75 1.5  1.5
           0.75 0.75 1.5  1.5
*/
Mat UpSample(Mat mat, int upc, int upr)   //均值池化层的向上采样
{
  //int i, j, m, n;
  int c = mat.cols;
  int r = mat.rows;
  
  Mat res(r*upr, c*upc, CV_32FC1);


  float pooling_size = 1.0 / (upc*upr);


  for (int j = 0; j < r*upr; j += upr)
  {
    for (int i = 0; i < c*upc; i += upc)  // 宽的扩充
    {
      for (int m = 0; m < upc; m++)
      {


        //res[j][i + m] = mat[j / upr][i / upc] * pooling_size;
        res.ptr<float>(j)[i + m] = mat.ptr<float>(j/upr)[i/upc] * pooling_size;
      }
    }


    for (int n = 1; n < upr; n++)      //  高的扩充
    {
      for (int i = 0; i < c*upc; i++)
      {
        //res[j + n][i] = res[j][i];
        res.ptr<float>(j+n)[i] = res.ptr<float>(j)[i];


      }
    }
  }
  return res;
}




//最大值池化层的向上采样
Mat maxUpSample(Mat mat, Mat max_position, int upc, int upr)
{
  int c = mat.cols;
  int r = mat.rows;


  int outsize_r = r*upr;
  int outsize_c = c*upc;


  Mat res = Mat::zeros(outsize_r, outsize_c, CV_32FC1);


  for (int j = 0; j < r; j++)
  {
    for (int i = 0; i < c; i++)
    {
      int index_r = max_position.ptr<int>(j)[i] / outsize_c;   //计算最大值的索引
      int index_c = max_position.ptr<int>(j)[i] % outsize_c;
      res.ptr<float>(index_r)[index_c] = mat.ptr<float>(j)[i];
    }
  }
  return res;
}




void pool2cov_bp(PoolLayer S, CovLayer &C)
{
  for (int i = 0; i < C.outChannels; i++)   //12通道
  {
    Mat C3e;
    if (S.poolType == AvePool)  //均值
      C3e = UpSample(S.d[i], S.mapSize, S.mapSize);    //向上采样,把S4层的局域梯度由4*4扩充为8*8
    else if (S.poolType == MaxPool)  //最大值
      C3e = maxUpSample(S.d[i], S.max_position[i], S.mapSize, S.mapSize);




    for (int r = 0; r < S.inputHeight; r++)   //8*8
    {
      for (int c = 0; c < S.inputWidth; c++)
      {
        C.d[i].ptr<float>(r)[c] = C3e.ptr<float>(r)[c] * sigma_derivation(C.y[i].ptr<float>(r)[c]);
      }
    }
  }
}


4. C3-->S2

本步骤的反向传播公式如下,其中rotate180为我们前文讲过的矩阵顺时针旋转180度操作,本层的局部梯度为6个(8+5-1)*(8+5-1)=12*12的矩阵(0≤j<6):

代码实现如下:



Mat cov(Mat map, Mat inputData, int type) 
{
  Mat flipmap;
  flip(map, flipmap, -1);   //卷积核先顺时针旋转180度
  Mat res = correlation(flipmap, inputData, type);   //然后再进行卷积


  return res;
}


void cov2pool_bp(CovLayer C, int cov_type, PoolLayer &S)
{


  for (int i = 0; i < S.outChannels; i++)   //S2有6通道
  {
    for (int j = 0; j < S.inChannels; j++)  //C3有12通道
    {
      //得到12*12矩阵:full模式下为(inSize+mapSize-1)*(inSize+mapSize-1)
      Mat corr = cov(C.mapData[i][j], C.d[j], cov_type);
      S.d[i] = S.d[i] + corr;   //矩阵累加:cnn->S2->d[i] = cnn->S2->d[i] + corr,得到6个12*12局域梯度
    }
  }
}

5. S2-->C1

本步骤的反向传播公式如下,其中upsample为池化层向上采样操作,DerivativeRelu为Relu函数的导数。本层的局部梯度是6个24*24的矩阵(0≤j<6):

由于本步骤的操作与上述第3步一样,只是输入、输出参数不一样,所以也可以调用第3步实现的pool2cov_bp函数来实现本步骤的反向传播。

最后把上述5个步骤合起来,反向传播的代码为:

//outputData为标签
void cnnbp(CNN &cnn, Mat outputData) 
{
  softmax_bp(outputData, cnn.e, cnn.O5);
  full2pool_bp(cnn.O5, cnn.S4);
  pool2cov_bp(cnn.S4, cnn.C3);
  cov2pool_bp(cnn.C3, full, cnn.S2);
  pool2cov_bp(cnn.S2, cnn.C1);
}

欢迎扫码关注以下微信公众号,接下来会不定时更新更加精彩的内容噢~

  • 7
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
深度学习卷积神经网络CNN做手写体识别的VS代码。支持linux版本和VS2012版本。 tiny-cnn: A C++11 implementation of convolutional neural networks ======== tiny-cnn is a C++11 implementation of convolutional neural networks. design principle ----- * fast, without GPU 98.8% accuracy on MNIST in 13 minutes training (@Core i7-3520M) * header only, policy-based design supported networks ----- ### layer-types * fully-connected layer * convolutional layer * average pooling layer ### activation functions * tanh * sigmoid * rectified linear * identity ### loss functions * cross-entropy * mean-squared-error ### optimization algorithm * stochastic gradient descent (with/without L2 normalization) * stochastic gradient levenberg marquardt dependencies ----- * boost C++ library * Intel TBB sample code ------ ```cpp #include "tiny_cnn.h" using namespace tiny_cnn; // specify loss-function and optimization-algorithm typedef network CNN; // tanh, 32x32 input, 5x5 window, 1-6 feature-maps convolution convolutional_layer C1(32, 32, 5, 1, 6); // tanh, 28x28 input, 6 feature-maps, 2x2 subsampling average_pooling_layer S2(28, 28, 6, 2); // fully-connected layers fully_connected_layer F3(14*14*6, 120); fully_connected_layer F4(120, 10); // connect all CNN mynet; mynet.add(&C1); mynet.add(&S2); mynet.add(&F3); mynet.add(&F4); assert(mynet.in_dim() == 32*32); assert(mynet.out_dim() == 10); ``` more sample, read main.cpp build sample program ------ ### gcc(4.6~) without tbb ./waf configure --BOOST_ROOT=your-boost-root ./waf build with tbb ./waf configure --TBB --TBB_ROOT=your-tbb-root --BOOST_ROOT=your-boost-root ./waf build with tbb and SSE/AVX ./waf configure --AVX --TBB --TBB_ROOT=your-tbb-root --BOOST_ROOT=your-boost-root ./waf build ./waf configure --SSE --TBB --TBB_ROOT=your-tbb-root --BOOST_ROOT=your-boost-root ./waf build or edit inlude/config.h to customize default behavior. ### vc(2012~) open vc/tiny_cnn.sln and build in release mode.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌萌哒程序猴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值