TensorFlow第九步CNN BP算法学习

花了两天时间,基本明白了CNN BP的算法。

可把conv看成局部全连接的堆砌,以全连接BP的思路推导。

 

https://www.cnblogs.com/tornadomeet/p/3468450.html

https://blog.csdn.net/happyer88/article/details/46772347

https://blog.csdn.net/zouxy09/article/details/9993371

https://blog.csdn.net/zy3381/article/details/44409535

 

 

 

问题二:当接在卷积层的下一层为pooling层时,求卷积层的误差敏感项。

  假设第l(小写的l,不要看成数字’1’)层为卷积层,第l+1层为pooling层,且pooling层的误差敏感项为: https://images0.cnblogs.com/blog/381513/201312/10233031-f52820fe834e42dc96a4cc7a5b58705f.png  ,卷积层的误差敏感项为:https://images0.cnblogs.com/blog/381513/201312/10233042-6242eed2c71641c19113ca8f1046ddd5.png  , 则两者的关系表达式为:

   https://images0.cnblogs.com/blog/381513/201312/11080853-06c3fdc33e66423490d7560de2cbbeb9.png

这里符号表示的是矩阵的点积操作,即对应元素的乘积。卷积层和unsample()后的pooling层节点是一一对应的,所以下标都是用j表示。后面的符号https://images0.cnblogs.com/blog/381513/201312/11081018-e68028bc98334a07bc495255350769e6.png表示的是第l层第j个节点处激发函数的导数(对节点输入的导数)

  其中的函数unsample()为上采样过程,其具体的操作得看是采用的什么pooling方法了。但unsample的大概思想为:pooling层的每个节点是由卷积层中多个节点(一般为一个矩形区域)共同计算得到,所以pooling层每个节点的误差敏感值也是由卷积层中多个节点的误差敏感值共同产生的,只需满足两层见各自的误差敏感值相等,下面以mean-poolingmax-pooling为例来说明。

  假设卷积层的矩形大小为4×4, pooling区域大小为2×2, 很容易知道pooling后得到的矩形大小也为2*2(本文默认pooling过程是没有重叠的,卷积过程是每次移动一个像素,即是有重叠的,后续不再声明),如果此时pooling后的矩形误差敏感值如下:

   https://images0.cnblogs.com/blog/381513/201312/10233105-9657d1b38edf4044b07f8b3347754e70.png

  则按照mean-pooling,首先得到的卷积层应该是4×4大小,其值分布为(等值复制)

   https://images0.cnblogs.com/blog/381513/201312/10233115-9cbea2eca9974a848d3f94cfc60b6a7f.jpg

  因为得满足反向传播时各层间误差敏感总和不变,所以卷积层对应每个值需要平摊(除以pooling区域大小即可,这里pooling层大小为2×2=4)),最后的卷积层值

分布为:

   https://images0.cnblogs.com/blog/381513/201312/10233136-9eead4aaabe542a387f83213df7bf795.jpg

  mean-pooling时的unsample操作可以使用matalb中的函数kron()来实现,因为是采用的矩阵Kronecker乘积。C=kron(A, B)表示的是矩阵B分别与矩阵A中每个元素相乘,然后将相乘的结果放在C中对应的位置。比如:

    https://images0.cnblogs.com/blog/381513/201312/11085402-6d05724b46a34e8489b07f84bcb5415f.png

delta_meanpooling=np.array([[1,3],[2,4]])
delta_nopooling=np.kron(delta_meanpooling,np.ones([2,2])*1/4)#矩阵Kronecker乘积
print(delta_nopooling)

  如果是max-pooling,则需要记录前向传播过程中pooling区域中最大值的位置,这里假设pooling层值1,3,2,4对应的pooling区域位置分别为右下、右上、左上、左下。则此时对应卷积层误差敏感值分布为:

  https://images0.cnblogs.com/blog/381513/201312/10233156-42bf23327122472794cda8fa31ac938a.jpg 

  当然了,上面2种结果还需要点乘卷积层激发函数对应位置的导数值了,这里省略掉。

 

问题三:当接在pooling层的下一层为卷积层时,求该pooling层的误差敏感项。

  假设第l(pooling)N个通道,即有N张特征图,第l+1(卷积层)M个特征,l层中每个通道图都对应有自己的误差敏感值,其计算依据为第l+1层所有特征核的贡献之和。下面是第l+1层中第j个核对第l层第i个通道的误差敏感值计算方法:

https://images0.cnblogs.com/blog/381513/201312/10233224-de029dc4d24a40e0b6ad19289184ee43.png

  符号表示的是矩阵的卷积操作,这是真正意义上的离散卷积,不同于卷积层前向传播时的相关操作,因为严格意义上来讲,卷积神经网络中的卷积操作本质是一个相关操作,并不是卷积操作,只不过它可以用卷积的方法去实现才这样叫。而求第i个通道的误差敏感项时需要将l+1层的所有核都计算一遍,然后求和。另外因为这里默认pooling层是线性激发函数,所以后面没有乘相应节点的导数。

  举个简单的例子,假设拿出第l层某个通道图,大小为3×3,第l+1层有2个特征核,核大小为2×2,则在前向传播卷积时第l+1层会有2个大小为2×2的卷积图。如果2个特征核分别为:

   https://images0.cnblogs.com/blog/381513/201312/10233241-3ebb0136cb1c48be8aec09f43b84a7af.jpg    https://images0.cnblogs.com/blog/381513/201312/10233254-791913858c8642af9bb0895ce72b6feb.jpg

  反向传播求误差敏感项时,假设已经知道第l+12个卷积图的误差敏感值:

   https://images0.cnblogs.com/blog/381513/201312/10233313-9a3a45cb982f4670bd901569959def95.jpghttps://images0.cnblogs.com/blog/381513/201312/10233340-713919c4ed1347f09a8201e31545c939.jpg 

  离散卷积函数conv2()的实现相关子操作时需先将核旋转180(即左右翻转后上下翻转),但这里实现的是严格意义上的卷积,所以在用conv2()时,对应的参数核不需要翻转(在有些toolbox里面,求这个问题时用了旋转,那是因为它们已经把所有的卷积核都旋转过,这样在前向传播时的相关操作就不用旋转了。并不矛盾)。且这时候该函数需要采用’full’模式,所以最终得到的矩阵大小为3×3,(其中3=2+2-1,刚好符第l层通道图的大小。采用’full’模式需先将第l+12个卷积图扩充,周围填0,padding后如下:

    https://images0.cnblogs.com/blog/381513/201312/10233353-308b9c9421e14dba8ab3b57ddff6c378.jpg     https://images0.cnblogs.com/blog/381513/201312/10233407-84adb9653b7d46f09aa66cafae73407a.jpg 

  扩充后的矩阵和对应的核进行卷积的结果如下情况:

   https://images0.cnblogs.com/blog/381513/201312/10233436-408a944e41604c199f8470414dac4aa3.png

  https://images0.cnblogs.com/blog/381513/201312/10233452-d20e97b0ed1044ae981a798cb36e8a54.png 

  可以通过手动去验证上面的结果,因为是离散卷积操作,而离散卷积等价于将核旋转后再进行相关操作。而第l层那个通道的误差敏感项为上面2者的和,呼应问题三,最终答案为:

   https://images0.cnblogs.com/blog/381513/201312/11000103-da4fac3f557948b6a128b39fe21f695a.png

  

# coding=utf-8
import os  
os.environ["TF_CPP_MIN_LOG_LEVEL"]='2' # 只显示 warning 和 Error 
 
import numpy as np
import scipy.signal as signal

deltaj=np.array([[2,1],[1,3],[1,2],[1,2]]) #[?Red,?Greeen]
kij=np.array([[-0.3,0.1],[0.1,0.2],[0.1,0.2],[0.2,0.4]])#[wi0,wi1]
deltaj=np.reshape(deltaj,[1,2,2,2])#1X2X2 2maps
kij=np.reshape(kij,[2,2,1,2])#2X2 1输入通道 2输出通道

def get_deltai(k,i,j,deltaj,kij):
    deltaj=deltaj[k,:,:,j]
    kij=kij[:,:,i,j]
    return signal.convolve2d(deltaj, kij,'full')
    
print(get_deltai(0,0,1,deltaj,kij))
print(deltaj)
print(kij)

那么这样问题3这样解的依据是什么呢?其实很简单,本质上还是bp算法,即第l层的误差敏感值等于第l+1层的误差敏感值乘以两者之间的权值,只不过这里由于是用了卷积,且是有重叠的,l层中某个点会对l+1层中的多个点有影响。比如说最终的结果矩阵中最中间那个0.3是怎么来的呢?在用2×2的核对3×3的输入矩阵进行卷积时,一共进行了4次移动,而3×3矩阵最中间那个值在4次移动中均对输出结果有影响,且4次的影响分别在右下角、左下角、右上角、左上角。所以它的值为2×0.2+1×0.1+1×0.1-1×0.3=0.3, 建议大家用笔去算一下,那样就可以明白为什么这里可以采用带’full’类型的conv2()实现。

问题四:求与卷积层相连那层的权值、偏置值导数。

  前面3个问题分别求得了输出层的误差敏感值、从pooling层推断出卷积层的误差敏感值、从卷积层推断出pooling层的误差敏感值。下面需要利用这些误差敏感值模型中参数的导数。这里没有考虑pooling层的非线性激发,因此pooling层前面是没有权值的,也就没有所谓的权值的导数了。现在将主要精力放在卷积层前面权值的求导上(也就是问题四)

  假设现在需要求第l层的第i个通道,与第l+1层的第j个通道之间的权值和偏置的导数,则计算公式如下:

   https://images0.cnblogs.com/blog/381513/201312/11091529-b07a8331ddbf49e2b08a3dfd4bc990e6.png

 

 

上面英文描述,matlab的conv2()是数学卷积,内部会把delta矩阵翻转180。他采用matlab conv2()做CNN的卷积计算,求出Kij后转180,前向探索时就不用再转了。如果使用tensorflow.nn.conv2d(),不翻转,实际是求互相关。

  其中符号表示矩阵的相关操作,可以采用conv2()函数实现。在使用该函数时,需将第l+1层第j个误差敏感值翻转。

  例如,如果第l层某个通道矩阵i大小为4×4,如下:

   https://images0.cnblogs.com/blog/381513/201312/10233932-19b796c090f840b28aca289295b78a9a.jpg

  第l+1层第j个特征的误差敏感值矩阵大小为3×3,如下:

   https://images0.cnblogs.com/blog/381513/201312/10233954-b5b06bab27e04d9e8336c0a427f3504f.jpg

  很明显,这时候的特征Kij导数的大小为2×2的,且其结果为:

   https://images0.cnblogs.com/blog/381513/201312/11001753-863c64fb2b7a4d988437c0bcc903085a.png

 

# coding=utf-8
import os  
os.environ["TF_CPP_MIN_LOG_LEVEL"]='2' # 只显示 warning 和 Error 
 
import numpy as np
import tensorflow as tf
 
trainData_in=np.array([[16,2,3,13],\
              [5,11,10,8],\
              [9,7,6,12],\
              [4,14,15,1]])
trainData_in=np.reshape(trainData_in,[-1,2,2,1])#np not tf
 
print(np.shape(trainData_in))
print(trainData_in)
print(np.sum(trainData_in,3))
print(np.reshape(trainData_in,[-1,2,2]))
print(trainData_in[:,:,:,0])
print(np.sum(trainData_in,1))
print(np.sum(trainData_in,2))
print(np.sum(trainData_in,(0,1)))

###CNN:
def nabla_kij(k,i,j,xi,deltaj):
    deltaj=deltaj[k,:,:,j]
    deltaj=np.reshape(deltaj,[list(deltaj.shape)[0],list(deltaj.shape)[1],1,1])
    xi=xi[k:k+1,:,:,i:i+1]
    return tf.nn.conv2d(xi,deltaj,strides=[1,1,1,1],padding='VALID')

x=np.reshape(trainData_in,[-1,4,4,1])
delta=np.array([[0.8,0.1,-0.6],[0.3,0.5,0.7],[-0.4,0,-0.2]])
delta=np.reshape(delta,[1,list(delta.shape)[0],list(delta.shape)[1],1])
x=np.float32(x)
sess=tf.Session()
nabla_kij=sess.run(nabla_kij(0,0,0,x,delta))

print(nabla_kij)
print(nabla_kij[0,:,:,0])

nabla_k=np.zeros([2,2,1,2])
nabla_k[:,:,0,0]+=nabla_kij[0,:,:,0]
print(nabla_k)

 

而此时偏置值bj的导数为1.2 ,将j区域的误差敏感值相加即可(0.8+0.1-0.6+0.3+0.5+0.7-0.4-0.2=1.2),因为bj中的每个节点都有贡献,按照多项式的求导规则(和的导数等于导数的和)很容易得到。

  为什么采用矩阵的相关操作就可以实现这个功能呢?由bp算法可知,lil+1j之间的权值等于l+1j处误差敏感值乘以li处的输入,而j中某个节点因为是由i+1中一个区域与权值卷积后所得,所以j处该节点的误差敏感值对权值中所有元素都有贡献,由此可见,将j中每个元素对权值的贡献(尺寸和核大小相同)相加,就得到了权值的偏导数了(这个例子的结果是由92×2大小的矩阵之和),同样,如果大家动笔去推算一下,就会明白这时候为什么可以用带’valid’conv2()完成此功能。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值