先来看一个网络:
这是一个简单的CNN的前半部分,不包含全连接层,而且已有一个卷积层和一个池化层,卷积核大小是2X2,步长1,Padding为0,Pooling操作为Max Pooling,大小同样是2x2
先来看正向的计算,卷积操作就没什么好说的了,不了解的可以随便百度一下,下面直接写公式:
是节点的加权输入,是激活函数ReLU
self.activative = lambda x: np.maximum(0, x) #激活函
self.dirivative = lambda x: 1 if x > 0 else 0 #导数
self.u_a = conv(self.input, self.filter, stride = 1, padding = 0) #卷积函数,实现略
self.b = self.activative(self.u_a)
算出所有的后,就是Max Pooling了:
self.c, position = maxpooling(self.b, size = 2) #实现略,position为矩阵,记录位置,后面要用到
卷积层和池化层的前向计算都说完了,虽然实际中一般不止一层,不过都是可以套用的,接下来就是全连接层了:
如图所示,max pooling的结果‘拉平’后就是全连接层的输入向量了:
input_vector = self.c.flatten()
这是之前的一篇关于DNN的推导,就不赘述了:
https://www.jianshu.com/p/bed8d5dac958
关于全连接层的误差传播已经知道怎么算了,接下来的问题就是将误差传回池化层及卷积层了:
上图中是FC(全连接)层中输入层的误差,也是池化层的下一层的误差,公式在上面一篇文章中已经讨论了:
而输入层是没有激活函数的,所以,即:
在得到误差项之后,进一步求Pooling操作之前的误差项,如果Max Pooling如下:
则upsample操作则同样:
self.delta_c = dnn.getInputDelta(input_vector, target).reshape(2, 2) #获得FC输入层的误差
self.delta_b = upsample(self.delta_c, position)
推导过程如下:
若x1为最大值,则不难求得下列偏导数:
因为只有最大的那一项会队x5产生影响,所以其余项的偏导数都为0,又因为:
,所以:
如下图所示:
池化层没有参数需要更新,所以只要把误差传给上一层就可以了,接下的问题就是已知卷积层的上一层(也就是正向计算的下一层)误差,求卷积层的误差以及更新卷积核了。
首先已知了上一层所有节点的误差项,来看看如何更新卷积核的梯度。由于任一都对所有有影响,根据全导数公式:
上面已经讨论过是节点的加权输入,所以:
for i in range(2):
for j in range(2):
self.filter[i,j] += self.learningrate * (self.delta_b * self.input[i:i+m, j:j+n]).sum()
最后,就是把误差继续往上一层传递了,如图:
先看几个例子:
归纳一下,可以发现如下图的规律:
公式如下:
写成卷积形式:
filterrot = np.rot90(np.rot90(self.filter))
delta_bpadding = np.zeros((6, 6))
deldelta_bpadding[1:-1, 1:-1] = self.delta_b
self.delta_a = conv(deldelta_bpadding, filterrot) * getDirivative(self.input)
总算写完了,只是后面的有些粗糙,以后有时间再完善吧
网友评论