CNN反向传播卷积核翻转

前言

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

粗略证明

回顾一下BP的第l层第i个偏置的更新

Ebli=j(δl+1j×Wl+1ji)×σ(zli)

这里面的 δl+1j其实是误差值对第 l+1层的第 j个单元的偏置的导数,也就是下一层偏置的梯度,从而构成递归形式的求解,逼格叫法就是链式求导。

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

X=[x11x21x12x22]K=[k11k21k12k22]

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

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

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

但是为了证明方便,我们就不做这种运算了,直接当做相干来做,但是面试什么的人家问卷积,一定要知道相干不是卷积。

假设X的每个单元对应偏置是bij,这样我们就可以套用BP的那个偏置更新式子去求解Ebij,第一项δl+1j这一项不管了,链式求解它,后面再说它的边界,即全连接部分的计算方法;第二项Wl+1ji代表连接y与第(i,j)位置的输入神经元的权重kij; 最后一项是当前层输入值函数的导数,比如当函数是sigmoid的时候σ(z)=z(1z),所以

Ebij=Ezzxijxijbij=δl+1×kij×σ(xij)

看到这里如果你没有疑问,那就是你卷积的前向操作没学好了。如果学过多通道卷积,肯定会问“同一块特征图的所有单元应该共享偏置啊,为什么这里特征图的每个神经元都有一个偏置?”这个问题一般的解决方法是对同一块特征图所求得的偏置向量求和作为当前特征图需要更新的偏置量,详细后面看代码实现。

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

针对上例,方法是

[δl+1k11δl+1k21δl+1k12δl+1k22]=conv0000δl+10000,[k22k12k21k11]=conv0000δl+10000,rot(K,180°)

这便说明了,卷积里面反向传播为什么翻转卷积核?这个证明就是原因。

代码实现

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

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

前向运算

多通道卷积

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

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×(1B),便可以得到误差对于全连接层偏置的偏导数了。

%   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)么,换成sigmoid就是xij(1xij),他这里提前进行了下一层偏置更新量与当前层值函数导数的乘积:

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);

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

Ebij=δl+1×kij×σ(xij)

这一行代码直接就完成了δl+1×σ(xij)的操作,接下来直接乘以卷积核即可,注意是填充以后与原来卷积核的翻转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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246619.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matlab学习——强连通分量

前言 最近motion graph相关实验,发现实现运动过渡需要构建运动图,而为了避免运动过渡陷入死胡同,需要对图结构进行裁剪,方法就是计算图模型的极大强联通分量,但是自己懒得去实现,所以就去搜了一下matlab中…

【theano-windows】学习笔记十八——混合蒙特卡洛采样

#前言 继续之前的Theano学习,本次主要学习混合蒙特卡洛(Hybrid Monte-Carlo Sampling)采样算法。 国际惯例,参考网址 Hybrid Monte-Carlo Sampling Hybrid Monte Carlo #理论 能量模型所使用的极大似然学习需要鲁棒的算法进行反向阶段的采样。比如…

【音频处理】离散傅里叶变换

前言 最近复现音乐驱动舞蹈的文章《Dancing-to-Music Character Animation》,用到了与傅里叶变换很相似的称为常Q变换的方法去分割音乐,所以对傅里叶变换做了一个小了解,本文不深入各种乱糟糟的理论,比如什么蝶形算法啥的&#x…

【音频处理】短时傅里叶变换

前言 上一篇博客讲了离散傅里叶变换,里面的实例是对整个信号进行计算,虽然理论上有N点傅里叶变换(本博客就不区分FFT和DFT了,因为它俩就是一个东东,只不过复杂度不同),但是我个人理解是这个N点是信号前面连续的N个数值…

【theano-windows】学习笔记十九——循环神经网络

前言 前面已经介绍了RBM和CNN了,就剩最后一个RNN了,抽了一天时间简单看了一下原理,但是没细推RNN的参数更新算法BPTT,全名是Backpropagation Through Time。 【注】严谨来说RNN有两个称呼:①结构上递归的recursive n…

【theano-windows】学习笔记二十——LSTM理论及实现

前言 上一篇学习了RNN,也知道了在沿着时间线对上下文权重求梯度的时候,可能会导致梯度消失或者梯度爆炸,然后我们就得学习一波比较常见的优化方法之LSTM 国际惯例,参考网址: LSTM Networks for Sentiment Analysis …

刚体运动学——欧拉角、四元数、旋转矩阵

前言 刚体运动旋转一般用:欧拉角、四元数、轴角对等表示,在对某个坐标旋转的时候,只需将欧拉角或四元数转换为旋转矩阵,并与原始坐标相乘,便可得到旋转以后的坐标。这里主要看看欧拉角、四元数和旋转矩阵。 国际惯例…

刚体运动学-四元数插值

前言 之前对写了一篇关于刚体运动学相关知识博客:刚体运动学——欧拉角、四元数、旋转矩阵,本篇博客就举例来说明,如何在运动捕捉数据中进行四元数插值。 国际惯例,参考博客: 探讨:向量(方向…

【TensorFlow-windows】学习笔记一——基础理解

前言 因为Theano已经停止更新了,所以在前面学完Theano搭建RBM,CNN,RNN相关结构以后,还是得选择一个主流框架的,由于我自身的学习最终是向强化学习靠近,可能用到的仿真环境是openai gym,所以选择了继续学习TensorFlow&…

【TensorFlow-windows】学习笔记二——低级API

前言 上一篇博客初步了解了tensorflow中建立机器学习模型的方法:可以使用eager execution和graph execution两种模式,可以使用高级API estimator中已经封装好的模型,也可以自己创建estimator,更重要的是我们也可以使用低级API自行…

【TensorFlow-windows】学习笔记三——实战准备

前言 因为学习TensorFlow的内容较多,如果只看API会很无聊,可以结合实例去学习。但是在构建基本的模型之前,需要学一些准备知识:数据读取、预处理、优化器、损失函数 国际惯例,参考网址: TensorFlow中文社…

【TensorFlow-windows】学习笔记四——模型构建、保存与使用

前言 上一章研究了一些基本的构建神经网络所需的结构:层、激活函数、损失函数、优化器之类的,这一篇就解决上一章遗留的问题:使用CNN构建手写数字识别网络、保存模型参数、单张图片的识别 国际惯例,参考博客: tenso…

【TensorFlow-windows】学习笔记五——自编码器

前言 上一篇博客介绍的是构建简单的CNN去识别手写数字,这一篇博客折腾一下自编码,理论很简单,就是实现对输入数据的重构,具体理论可以看我前面的【theano-windows】学习笔记十三——去噪自编码器 国际惯例,参考博客&…

【TensorFlow-windows】学习笔记六——变分自编码器

#前言 对理论没兴趣的直接看代码吧,理论一堆,而且还有点复杂,我自己的描述也不一定准确,但是代码就两三句话搞定了。 国际惯例,参考博文 论文:Tutorial on Variational Autoencoders 【干货】一文读懂…

【TensorFlow-windows】学习笔记七——生成对抗网络

前言 既然学习了变分自编码(VAE),那也必须来一波生成对抗网络(GAN)。 国际惯例,参考网址: 论文: Generative Adversarial Nets PPT:Generative Adversarial Networks (GANs) Generative Adversarial Nets in TensorFlow GAN原理学习笔记…

Openpose——windows编译(炒鸡简单)

前言 最近准备看看rtpose的代码,发现已经由openpose这个项目维护着了,由于经常在windows下调试代码,所以尝试了一下如何在windows下编译openpose源码,整体来说非常简单的。 国际惯例,参考博客: [OpenPos…

【TensorFlow-windows】学习笔记八——简化网络书写

前言 之前写代码的时候都要预先初始化权重,还得担心变量是否会出现被重复定义的错误,但是看网上有直接用tf.layers构建网络,很简洁的方法。 这里主要尝试了不预定义权重,是否能够实现正常训练、模型保存和调用,事实证…

强化学习——Qlearning

前言 在控制决策领域里面强化学习还是占很重比例的,最近出了几篇角色控制的论文需要研究,其中部分涉及到强化学习,都有开源,有兴趣可以点开看看: A Deep Learning Framework For Character Motion Synthesis and Edit…

【TensorFlow-windows】keras接口学习——线性回归与简单的分类

前言 之前有写过几篇TensorFlow相关文章,但是用的比较底层的写法,比如tf.nn和tf.layers,也写了部分基本模型如自编码和对抗网络等,感觉写起来不太舒服,最近看官方文档发现它的教程基本都使用的keras API,这…

【TensorFlow-windows】keras接口——卷积手写数字识别,模型保存和调用

前言 上一节学习了以TensorFlow为底端的keras接口最简单的使用,这里就继续学习怎么写卷积分类模型和各种保存方法(仅保存权重、权重和网络结构同时保存) 国际惯例,参考博客: 官方教程 【注】其实不用看博客,直接翻到文末看我的c…