小言_互联网的博客

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

570人阅读  评论(0)

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

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。

代码实现如下:


   
  1. void softmax_bp(Mat outputData, Mat &e, OutLayer &O)
  2. {
  3. for ( int i = 0; i < O.outputNum; i++)
  4. e.ptr< float>( 0)[i] = O.y.ptr< float>( 0)[i] - outputData.ptr< float>( 0)[i]; //计算Y-t
  5.    //将Y-t保存到O5层的局部梯度中                                       
  6. for ( int i = 0; i < O.outputNum; i++)
  7. O.d.ptr< float>( 0)[i] = e.ptr< float>( 0)[i]; // *sigma_derivation(O.y.ptr<float>(0)[i]);
  8. }

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矩阵:

代码实现如下:


   
  1. void full2pool_bp(OutLayer O, PoolLayer &S)
  2. {
  3. int outSize_r = S.inputHeight / S.mapSize;
  4. int outSize_c = S.inputWidth / S.mapSize;
  5. for ( int i = 0; i < S.outChannels; i++) //输出12张4*4图像
  6. {
  7. for ( int r = 0; r < outSize_r; r++)
  8. {
  9. for ( int c = 0; c < outSize_c; c++)
  10. {
  11. int wInt = i*outSize_c*outSize_r + r*outSize_c + c; //i*outSize.c*outSize.r为图像索引,r*outSize.c+c为每张图像中的像素索引
  12. for ( int j = 0; j < O.outputNum; j++) //O5输出层的输出个数
  13. {
  14. //把192个偏导数重组成12个4*4的二维矩阵,作为S4层的局部梯度
  15. 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
  16.         }
  17. }
  18. }
  19. }
  20. }

3. S4-->C3

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

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


   
  1. /*
  2. 矩阵上采样,upc及upr是池化窗口的列、行
  3. 如果是最大值池化模式,则把局域梯度放到池化前最大值的位置,比如池化窗口2*2,池化前最大值的位置分别为左上、右上、左下、右下,则上采样后为:
  4. 5 9 5 0 0 9
  5.      -->   0 0 0 0
  6. 3 6 0 0 0 0
  7. 3 0 0 6
  8. 如果是均值池化模式,则把局域梯度除以池化窗口的尺寸2*2=4:
  9. 5 9 1.25 1.25 2.25 2.25
  10.      -->   1.25 1.25 2.25 2.25
  11. 3 6 0.75 0.75 1.5 1.5
  12.            0.75 0.75 1.5  1.5
  13. */
  14. Mat UpSample(Mat mat, int upc, int upr) //均值池化层的向上采样
  15. {
  16. //int i, j, m, n;
  17. int c = mat.cols;
  18. int r = mat.rows;
  19. Mat res(r*upr, c*upc, CV_32FC1);
  20. float pooling_size = 1.0 / (upc*upr);
  21. for ( int j = 0; j < r*upr; j += upr)
  22. {
  23. for ( int i = 0; i < c*upc; i += upc) // 宽的扩充
  24. {
  25. for ( int m = 0; m < upc; m++)
  26. {
  27. //res[j][i + m] = mat[j / upr][i / upc] * pooling_size;
  28. res.ptr< float>(j)[i + m] = mat.ptr< float>(j/upr)[i/upc] * pooling_size;
  29. }
  30. }
  31. for ( int n = 1; n < upr; n++) // 高的扩充
  32. {
  33. for ( int i = 0; i < c*upc; i++)
  34. {
  35. //res[j + n][i] = res[j][i];
  36. res.ptr< float>(j+n)[i] = res.ptr< float>(j)[i];
  37. }
  38. }
  39. }
  40. return res;
  41. }
  42. //最大值池化层的向上采样
  43. Mat maxUpSample(Mat mat, Mat max_position, int upc, int upr)
  44. {
  45. int c = mat.cols;
  46. int r = mat.rows;
  47. int outsize_r = r*upr;
  48. int outsize_c = c*upc;
  49. Mat res = Mat::zeros(outsize_r, outsize_c, CV_32FC1);
  50. for ( int j = 0; j < r; j++)
  51. {
  52. for ( int i = 0; i < c; i++)
  53. {
  54. int index_r = max_position.ptr< int>(j)[i] / outsize_c; //计算最大值的索引
  55. int index_c = max_position.ptr< int>(j)[i] % outsize_c;
  56. res.ptr< float>(index_r)[index_c] = mat.ptr< float>(j)[i];
  57. }
  58. }
  59. return res;
  60. }
  61. void pool2cov_bp(PoolLayer S, CovLayer &C)
  62. {
  63. for ( int i = 0; i < C.outChannels; i++) //12通道
  64. {
  65. Mat C3e;
  66. if (S.poolType == AvePool) //均值
  67. C3e = UpSample(S.d[i], S.mapSize, S.mapSize); //向上采样,把S4层的局域梯度由4*4扩充为8*8
  68. else if (S.poolType == MaxPool) //最大值
  69. C3e = maxUpSample(S.d[i], S.max_position[i], S.mapSize, S.mapSize);
  70. for ( int r = 0; r < S.inputHeight; r++) //8*8
  71. {
  72. for ( int c = 0; c < S.inputWidth; c++)
  73. {
  74. C.d[i].ptr< float>(r)[c] = C3e.ptr< float>(r)[c] * sigma_derivation(C.y[i].ptr< float>(r)[c]);
  75. }
  76. }
  77. }
  78. }

4. C3-->S2

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

代码实现如下:


   
  1. Mat cov(Mat map, Mat inputData, int type)
  2. {
  3.   Mat flipmap;
  4.   flip(map, flipmap,  -1); //卷积核先顺时针旋转180度
  5.   Mat res = correlation(flipmap, inputData, type);    //然后再进行卷积
  6. return res;
  7. }
  8. void cov2pool_bp(CovLayer C, int cov_type, PoolLayer &S)
  9. {
  10. for ( int i = 0; i < S.outChannels; i++) //S2有6通道
  11. {
  12. for ( int j = 0; j < S.inChannels; j++) //C3有12通道
  13. {
  14.        //得到12*12矩阵:full模式下为(inSize+mapSize-1)*(inSize+mapSize-1)
  15.       Mat corr = cov(C.mapData[i][j], C.d[j], cov_type);
  16. S.d[i] = S.d[i] + corr; //矩阵累加:cnn->S2->d[i] = cnn->S2->d[i] + corr,得到6个12*12局域梯度
  17.     }
  18. }
  19. }

5. S2-->C1

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

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

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


   
  1. //outputData为标签
  2. void cnnbp(CNN &cnn, Mat outputData) 
  3. {
  4. softmax_bp(outputData, cnn.e, cnn.O5);
  5. full2pool_bp(cnn.O5, cnn.S4);
  6. pool2cov_bp(cnn.S4, cnn.C3);
  7. cov2pool_bp(cnn.C3, full, cnn.S2);
  8. pool2cov_bp(cnn.S2, cnn.C1);
  9. }

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


转载:https://blog.csdn.net/shandianfengfan/article/details/115410745
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场