前言

前面煞费苦心地严格按照上下标证明BP,主要就是为了锻炼自己的证明时候的严谨性,那么这里也严格按照上下标的计算方法推导为何卷积的反向传播需要将卷积核旋转180°

粗略证明

回顾一下BP的第ll层第ii个偏置的更新

∂E∂bli=∑j(δl+1j×Wl+1ji)×σ′(zli)

\frac{\partial E}{\partial b^l_i}=\sum_j\left(\delta^{l+1}_j\times W_{ji}^{l+1}\right)\times\sigma'\left(z^l_i\right)
这里面的 δl+1j\delta^{l+1}_j其实是误差值对第 l+1l+1层的第 jj个单元的偏置的导数,也就是下一层偏置的梯度,从而构成递归形式的求解,逼格叫法就是链式求导。

再来看CNN,粗略地说,每个卷积核KK对应的局部输入 XX与输出值yy可以构成一个简单的BP网络,即

X=[x11x21x12x22]K=[k11k21k12k22]

X=\begin{bmatrix} x_{11}&x_{12}\\ x_{21}&x_{22}\\ \end{bmatrix} \quad K=\begin{bmatrix} k_{11}&k_{12}\\ k_{21}&k_{22}\\ \end{bmatrix}

zy=conv(K,X)+θ=k11x11+k12x12+k21x21+k22x22+θ=σ(z)

\begin{aligned} z&=conv \left(K,X \right) +\theta=k_{11}x_{11}+k_{12}x_{12}+k_{21}x_{21}+k_{22}x_{22}+\theta \\ y&=\sigma\left(z\right) \end{aligned}

这里强调一下,其实正常的卷积是这样的

conv(K,X)=k22x11+k21x12+k12x21+k11x22

conv \left(K,X \right) =k_{22}x_{11}+k_{21}x_{12}+k_{12}x_{21}+k_{11}x_{22}
但是为了证明方便,我们就不做这种运算了,直接当做相干来做,但是面试什么的人家问卷积,一定要知道相干不是卷积。

假设XX的每个单元对应偏置是bijb_{ij},这样我们就可以套用BP的那个偏置更新式子去求解∂E∂bij\frac{\partial E}{\partial b_{ij}},第一项δl+1j\delta^{l+1}_j这一项不管了,链式求解它,后面再说它的边界,即全连接部分的计算方法;第二项Wl+1jiW_{ji}^{l+1}代表连接yy与第(i,j)(i,j)位置的输入神经元的权重kijk_{ij}; 最后一项是当前层输入值函数的导数,比如当函数是sigmoidsigmoid的时候σ′(z)=z(1−z)\sigma'(z)=z(1-z),所以

∂E∂bij=∂E∂z⋅∂z∂xij⋅∂xij∂bij=δl+1×kij×σ′(xij)

\begin{aligned} \frac{\partial E}{\partial b_{ij}}&=\frac{\partial E}{\partial z}\cdot\frac{\partial z}{\partial x_{ij}}\cdot\frac{\partial x_{ij}}{\partial b_{ij}}\\ &=\delta^{l+1}\times k_{ij}\times\sigma'\left(x_{ij}\right) \end{aligned}
看到这里如果你没有疑问,那就是你卷积的前向操作没学好了。如果学过多通道卷积,肯定会问“同一块特征图的所有单元应该共享偏置啊,为什么这里特征图的每个神经元都有一个偏置?”这个问题一般的解决方法是对同一块特征图所求得的偏置向量求和作为当前特征图需要更新的偏置量,详细后面看代码实现。

关键的一步来了,如何使用卷积来实现这个式子的三个乘法,其实主要体现在如何使用卷积来实现δl+1×kij\delta^{l+1}\times k_{ij},使得计算的结果大小刚好是XX这么大的维度。如何将图形越卷积维度越大?摈弃CNN中的卷积,单考验你对卷积在图像处理中的操作及其作用,如果想不出来个一二三,建议学习一下那本绿皮的《数字图像处理》,作者好像叫冈萨雷斯,如果喜欢编程,可以看很多视觉库中的卷积操作,比如matlab中关于卷积的三种处理,详见此博客,我们使用full convolution的卷积操作,通过对特征图边缘填充零使其维度变大,然后再执行卷积即可。

针对上例,方法是

[δl+1k11δl+1k21δl+1k12δl+1k22]=conv⎛⎝⎜⎡⎣⎢0000δl+10000⎤⎦⎥,[k22k12k21k11]⎞⎠⎟=conv⎛⎝⎜⎡⎣⎢0000δl+10000⎤⎦⎥,rot(K,180°)⎞⎠⎟

\begin{aligned} \begin{bmatrix} \delta^{l+1}k_{11}&\delta^{l+1}k_{12}\\ \delta^{l+1}k_{21}&\delta^{l+1}k_{22} \end{bmatrix}&= conv\left( \begin{bmatrix} 0 & 0 & 0 \\ 0 & \delta^{l+1} & 0 \\ 0 & 0 & 0 \end{bmatrix}, \begin{bmatrix} k_{22}&k_{21}\\ k_{12}&k_{11} \end{bmatrix} \right)\\ &=conv\left( \begin{bmatrix} 0 & 0 & 0 \\ 0 & \delta^{l+1} & 0 \\ 0 & 0 & 0 \end{bmatrix}, rot(K,180°) \right) \end{aligned}
这便说明了,卷积里面反向传播为什么翻转卷积核?这个证明就是原因。

代码实现

matlabdeeplearning toolbox中,将sigmoid作为激活函数,因而实际的当前层的偏置计算方法为:下一层的偏置矩阵先做补零扩充,然后与卷积核的180°翻转做卷积,得到的矩阵与当前层的神经元对应元素做乘法即可,还有一些其它技巧依据代码做补充。

网络预设

先看看如何设计网络,其实主要就是看每层权重和偏置的维度罢了:

池化:

if strcmp(net.layers{l}.type, 's')mapsize = mapsize / net.layers{l}.scale;assert(all(floor(mapsize)==mapsize), ['Layer ' num2str(l) ' size must be integer. Actual: ' num2str(mapsize)]);for j = 1 : inputmapsnet.layers{l}.b{j} = 0;end
end

当前层是池化层的时候,没权重,且偏置为0,主要是因为池化操作只是简单的下采样操作,用于降低卷积后的特征图的维度,不需要使用权重和偏置做运算。

卷积:

if strcmp(net.layers{l}.type, 'c')mapsize = mapsize - net.layers{l}.kernelsize + 1;fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;for j = 1 : net.layers{l}.outputmaps  %  output mapfan_in = inputmaps * net.layers{l}.kernelsize ^ 2;for i = 1 : inputmaps  %  input mapnet.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));endnet.layers{l}.b{j} = 0;endinputmaps = net.layers{l}.outputmaps;
end

这里注意看一下,针对第ll个卷积操作连接第ii个输入特征图和第jj个输出特征图的卷积核,使用fan_in,fan_out准则初始化,其实这就间接告诉我们,两层特征图之间的卷积参数量为上一层特征图个数×\times下一层特征图个数(卷积核个数)×\times卷积核高×\times卷积核宽;针对第ll层的第ll次卷积操作的第jj个输出特征图的共享偏置值设置为0。

前向运算

多通道卷积

我发现很多新手童鞋不知道多通道卷积到底是何种蛇皮操作,只要你记住,经过卷积后得到的特征图个数等于卷积核个数就行了,切记不是卷积核个数乘以输入特征图个数。具体操作是先使用每个卷积核对所有特征图卷积一遍,然后再加和,比如第二个特征图的值的计算方法就是上一层的第1个特征图与第2个卷积核卷积+上一层的第2个特征图与第2个卷积核卷积+⋯\cdots,一直加到最后一个即可。

for j = 1 : net.layers{l}.outputmaps   %  for each output map%  create temp output mapz = zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);for i = 1 : inputmaps   %  for each input map%  convolve with corresponding kernel and add to temp output mapz = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j}, 'valid');end%  add bias, pass through nonlinearitynet.layers{l}.a{j} = sigm(z + net.layers{l}.b{j});
end

池化

采用均值池化,对上一层的输出特征图对应区域单元求加和平均,可以采用值为1的卷积核,大小由设定的池化区域决定

for j = 1 : inputmapsz = convn(net.layers{l - 1}.a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2), 'valid');   %  !! replace with variablenet.layers{l}.a{j} = z(1 : net.layers{l}.scale : end, 1 : net.layers{l}.scale : end, :);
end

全连接

经过多次卷积、池化操作后,为了做分类,我们把最后一层所有特征图拉长并拼接成一个向量,连接标签向量,这就叫全连接。

for j = 1 : numel(net.layers{n}.a)sa = size(net.layers{n}.a{j});net.fv = [net.fv; reshape(net.layers{n}.a{j}, sa(1) * sa(2), sa(3))];
end

最后计算输出

net.o = sigm(net.ffW * net.fv + repmat(net.ffb, 1, size(net.fv, 2)));

反向传播

全连接部分

反向传播必然是先计算全连接部分的误差值,然后向前推到由特征图拉长的向量的每个神经元的偏置,这也就是上面提到的为什么没有共享偏置的根源,因为在反向传播的第一层便对每个神经元单独求解偏置更新量了。想想之前关于BP总结的那个式子Δc×W×B×(1−B)\Delta c\times W\times B\times(1-B),便可以得到误差对于全连接层偏置的偏导数了。

%   error
net.e = net.o - y;
%  loss function
net.L = 1/2* sum(net.e(:) .^ 2) / size(net.e, 2);%%  backprop deltas
net.od = net.e .* (net.o .* (1 - net.o));   %  output delta  △c
net.fvd = (net.ffW' * net.od);              %  feature vector delta  △c X B
if strcmp(net.layers{n}.type, 'c')         %  only conv layers has sigm functionnet.fvd = net.fvd .* (net.fv .* (1 - net.fv));
end

然后再把全连接层的列向量分离成最后一层特征图大小的块,因为它本来就是由最后一层拉长的,很容易进行反操作还原回去。

sa = size(net.layers{n}.a{1});
fvnum = sa(1) * sa(2);
for j = 1 : numel(net.layers{n}.a)net.layers{n}.d{j} = reshape(net.fvd(((j - 1) * fvnum + 1) : j * fvnum, :), sa(1), sa(2), sa(3));
end

现在技巧来了,我们知道池化层其实就是对卷积层的元素进行简单的处理,比如每块加和求均值,那么我们就可以粗略得将其还原回去,下述代码就是当当前层由池化操作得来的时候,将第此层的偏置更新量扩大成上一层的输入特征图大小:

expand(net.layers{l + 1}.d{j}, [net.layers{l + 1}.scale net.layers{l + 1}.scale 1]) / net.layers{l + 1}.scale ^ 2

然后有一个蛇皮操作就是,理论部分不是说过要计算σ(xij)\sigma(x_{ij})么,换成sigmoid就是xij(1−xij)x_{ij}(1-x_{ij}),他这里提前进行了下一层偏置更新量与当前层值函数导数的乘积:

net.layers{l}.d{j} = net.layers{l}.a{j} .* (1 - net.layers{l}.a{j}) .* (expand(net.layers{l + 1}.d{j}, [net.layers{l + 1}.scale net.layers{l + 1}.scale 1]) / net.layers{l + 1}.scale ^ 2);

回顾一下我们的偏置导数计算公式:

∂E∂bij=δl+1×kij×σ′(xij)

\frac{\partial E}{\partial b_{ij}}=\delta^{l+1}\times k_{ij}\times\sigma'\left(x_{ij}\right)

这一行代码直接就完成了δl+1×σ′(xij)\delta^{l+1}\times\sigma'(x_{ij})的操作,接下来直接乘以卷积核即可,注意是填充以后与原来卷积核的翻转180°做卷积操作

 z = z + convn(net.layers{l + 1}.d{j}, rot180(net.layers{l + 1}.k{i}{j}), 'full');

因为是批量更新,所以需要对所有的偏置向量除以样本总数

for l = 2 : nif strcmp(net.layers{l}.type, 'c')for j = 1 : numel(net.layers{l}.a)for i = 1 : numel(net.layers{l - 1}.a)net.layers{l}.dk{i}{j} = convn(flipall(net.layers{l - 1}.a{i}), net.layers{l}.d{j}, 'valid') / size(net.layers{l}.d{j}, 3);endnet.layers{l}.db{j} = sum(net.layers{l}.d{j}(:)) / size(net.layers{l}.d{j}, 3);endendend

这里蕴含了利用偏置更新量计算权值更新量的操作,这个与BP一毛一样,就是偏置更新量乘以前一层的单元值即可。

还有就是最后说的由于共享卷积核,所以同一卷积核的偏置更新量也应该一样,直接求均值就行

net.dffW = net.od * (net.fv)' / size(net.od, 2);
net.dffb = mean(net.od, 2);

CNN反向传播卷积核翻转相关推荐

  1. CNN 反向传播推导

    CNN卷积神经网络推导和实现 本文的论文来自: Notes on Convolutional Neural Networks, Jake Bouvrie. 这个主要是CNN的推导和实现的一些笔记,再看 ...

  2. 卷积神经网络(CNN)反向传播算法推导

    作者丨南柯一梦宁沉沦@知乎(已授权) 来源丨https://zhuanlan.zhihu.com/p/61898234 编辑丨极市平台 导读 在本篇文章中我们将从直观感受和数学公式两方面来介绍CNN反 ...

  3. cnn 反向传播推导_深度学习中的参数梯度推导(三)下篇

    前言 在深度学习中的参数梯度推导(三)中篇里,我们总结了CNN的BP推导第一步:BP通过池化层时梯度的计算公式.本篇(下篇)则继续推导CNN相关的其他梯度计算公式. 注意:本文默认读者已具备深度学习上 ...

  4. CNN反向传播源码实现——CNN数学推导及源码实现系列(4)

    前言 本系列文章链接: CNN前置知识:模型的数学符号定义--卷积网络从零实现系列(1)_日拱一两卒的博客-CSDN博客https://blog.csdn.net/yangwohenmai1/arti ...

  5. 四张图彻底搞懂CNN反向传播算法(通俗易懂)

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:机器学习算法那些事 阅读本文之前,可以先阅读之前讲述的全 ...

  6. 卷积神经网络(CNN)反向传播算法

    在卷积神经网络(CNN)前向传播算法中,我们对CNN的前向传播算法做了总结,基于CNN前向传播算法的基础,我们下面就对CNN的反向传播算法做一个总结.在阅读本文前,建议先研究DNN的反向传播算法:深度 ...

  7. cnn 反向传播推导_反向传播算法推导过程(非常详细)

    1. 前向传播 假设 为 的矩阵(其中, 为样本个数(batch size), 为特征维数): 与 的维数为 为 的矩阵, 与 的维数为 为 的矩阵, 与 的维数为 为 的矩阵, 前向算法: 假设输出 ...

  8. CNN反向传播算法过程

    主模块 规格数据输入(加载,调格式,归一化) 定义网络结构 设置训练参数 调用初始化模块 调用训练模块 调用测试模块 画图 初始化模块 设置初始化参数(输入通道,输入尺寸) 遍历层(计算尺寸,输入输出 ...

  9. 深度学习(四):卷积神经网络(CNN)模型结构,前向传播算法和反向传播算法介绍。

    在前面我们讲述了DNN的模型与前向反向传播算法.而在DNN大类中,卷积神经网络(Convolutional Neural Networks,以下简称CNN)是最为成功的DNN特例之一.CNN广泛的应用 ...

最新文章

  1. 如何利用WebScarab绕过JS验证
  2. 16、Windows API 服务
  3. 记一个简单的保护if 的sh脚本
  4. 电脑休眠和睡眠的区别_关机、睡眠、休眠有啥区别?微软说非特殊情况不要关机...
  5. python——作用域 == is
  6. 台式计算机内存是什么意思,电脑提示计算机内存不足是什么意思
  7. Codeforces Round #523 (Div. 2)
  8. python复杂网络库networkx:算法
  9. js基础练习:实现资料查找
  10. dg修改归档目录 oracle_Oracle RAC归档管理: 修改归档位置(FRA和其他位置)
  11. C#汉字转拼音帮助类
  12. 160个破解练习之CrackMe 006
  13. 浏览器下载大文件时下载完成但大小对不上
  14. 表单提交时报错:No result defined for action com.ylj.action.BbsAction and result input
  15. SQL SERVER 2008 R2 故障转移群集实验总结
  16. 利用backtrace和ucontex定位segment错误【转】
  17. AGV移动机器人PID运动控制
  18. AutoGluon-教程1-简单的入门模型
  19. ANTMINER KA3 166T能否颠覆Polkadot未来
  20. Ubuntu 搜狗拼音安装详细步骤

热门文章

  1. The World is a Theatre(组合数学)
  2. win10电脑黑屏只有鼠标箭头_win7开机黑屏只有鼠标怎么办,我来教你解决
  3. 深度学习之卷积神经网络(7)池化层
  4. python初学篇笔记_Python学习笔记(基础篇)
  5. 接口测试用例模板_ITest:京东数科接口自动化测试实践
  6. 怎么让电脑屏幕一直亮着_笔记本开机白屏怎么回事 笔记本开机白屏解决方法【详解】...
  7. jsp tag 自定义标签实现按钮的显示
  8. C/C++内存分配、内存区划分、常量存储区、堆、栈、自由存储区、全局区(静态区)、代码区
  9. 精心整理吐血推荐的AUTOSAR科普介绍材料
  10. node.js之打包工具webpack