Backward Propagation(BP) in Convolutional Neural Network(CNN) 卷积神经网络的反向传播[python代码]
Backward Propagation-BP in Convolutional Neural Network-CNN 卷积神经网络的反向传播[python代码]
- 一、简介(个人、代码、传承)
- 1、个人简介:
- 2、代码下载:https://github.com/RipperLom/CNN_BackwardPropagation_PythonCoding
- 3、传承:没人可以闭门造车,以下是我的参考资料
- 二、大纲
- 三、具体历史
- 四、这个案例的模型(两层----一个卷积池化层、一个全连接层)
- 五、模型前向传播
- 六、反向传播前,预备知识
- 七、反向传播
- 八、权重更新
一、简介(个人、代码、传承)
1、个人简介:
本科----*财经大学-金融学院-金融工程,主修金融衍生品定价;研究生----英国-伯明翰大学-数学学院,主修金融投资策略、AI算法
2、代码下载:https://github.com/RipperLom/CNN_BackwardPropagation_PythonCoding
欢迎大家下载
【1】代码Python版本(带数据)
GitHub下载相应代码(success.py)、数据(data文件夹)和PPT(BP_in_CNN.pdf) ps.真的不好改
网址:https://github.com/RipperLom/CNN_BackwardPropagation_PythonCoding
【2】代码MATLAB代码(带数据)
GitHub下载相应MATLAB代码 ps.数据不好找
网址:https://github.com/RipperLom/CNN_BackwardPropagation_MatlabCode
3、传承:没人可以闭门造车,以下是我的参考资料
如果你不理解Hinton的全连接网络(FC - Fully Connected Layer)的反向传播(BP - Backward Propagation),那么你的功力尚浅还不太适合研究(BP of CNN)
理论取自,
【1】2006年Jake Bouvrie发表的Notes on Convolutional Neural Networks 网址:http://cogprints.org/5869/1/cnn_tutorial.pdf
【2】2015年辛顿等三人(Yann LeCun, Yoshua Bengio, Geoffrey Hinton)发表的深度学习Deep learning 网址:http://pages.cs.wisc.edu/~dyer/cs540/handouts/deep-learning-nature2015.pdf
代码取自:
【1】无数据的MATLAB代码,tornadomeet的博客 Deep learning:五十一(CNN的反向求导及练习) 网址:https://www.cnblogs.com/tornadomeet/p/3468450.html
【2】无数据的MATLAB代码,Stanford Unsupervised Feature Learning and Deep Learning Tutorial 网址:https://github.com/amaas/stanford_dl_ex/tree/master/cnn
二、大纲
这里要把CNN-BP过程讲清楚,需要一步步来。上面简介已经把代码和参考的链接给大家。
接下来要详细的讲述(1)介绍历史,(2)这个代码用到的数据和模型,(3)前项传播过程,(4)为反向传播做的准备(5)反向传播(6)参考(这部分已经给链接了,大家下载代码练习)
三、具体历史
1、Hinton BP算法(三层神经网络----一个隐藏层)
在这不是重点,把结论直接给出来。
如果这里有任何不懂,请查看反向传播相关论文和博客。默认在BP算法熟练掌握的基础上研究CNN-BP,不再赘述。
前向传播:
import tensorflow as tf
# X 是 m * n 维矩阵,m 个样本,n 个特征
a0 = X
z1 = tf.matmul(a0, W1) + b1
a1 = tf.sigmoid(z1)
z2 = tf.matmul(a1, W2) + b2
a2 = tf.sigmoid(z2)
反向传播
import tensorflow as tf
# m 是 样本个数(int)
dz2 = (a2 - Y)
dz1 = tf.matmul(dz2, tf.transpose(W2)) * tf.multiply(a1, (1-a1))
dW2 = (1/m) * tf.matmul(tf.transpose(a1), dz2)
db2 = tf.reduce_mean(dz2)
dW1 = (1/m) * tf.matmul(tf.transpose(a0), dz1)
db1 = tf.reduce_mean(dz1)
2、卷积神经网络(LeNet5为例)
利用TensorFlow框架封装的函数conv2d(input, filter, strides, padding, use_cudnn_on_gpu=True, data_format=“NHWC”, dilations=[1, 1, 1, 1], name=None)进行卷积操作
# 28 28 1 to 12 12 6
with tf.variable_scope('layer1'):
W1 = tf.Variable(tf.random_normal([5, 5, 1, 6], stddev=0.01))
b1 = tf.Variable(tf.random_normal([1, 1, 1, 6], stddev=0.01))
L1 = tf.nn.conv2d(X_img, filter=W1, strides=[1, 1, 1, 1], padding='VALID') + b1
L1 = tf.nn.relu(L1)
L1 = tf.nn.max_pool(L1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')
# 12 12 6 to 4 4 16
with tf.variable_scope('layer2'):
W2 = tf.Variable(tf.random_normal([5, 5, 6, 16], stddev=0.01))
b2 = tf.Variable(tf.random_normal([1, 1, 1, 16], stddev=0.01))
L2 = tf.nn.conv2d(L1, filter=W2, strides=[1, 1, 1, 1], padding='VALID') + b2
L2 = tf.nn.relu(L2)
L2 = tf.nn.max_pool(L2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')
dim = L2.get_shape()[1].value * L2.get_shape()[2].value * L2.get_shape()[3].value
L2_flat = tf.reshape(L2, shape=[-1, dim])
# 4 4 16 to 120
with tf.variable_scope('layer3'):
W3 = tf.Variable(tf.random_normal([dim, 120], stddev=0.01))
b3 = tf.Variable(tf.random_normal([1, 120], stddev=0.01))
L3 = tf.matmul(L2_flat, W3) + b3
L3 = tf.nn.relu(L3)
# 120 to 84
with tf.variable_scope('layer4'):
W4 = tf.Variable(tf.random_normal([120, 84], stddev=0.01))
b4 = tf.Variable(tf.random_normal([1, 84], stddev=0.01))
L4 = tf.matmul(L3, W4) + b4
L4 = tf.nn.relu(L4)
# 84 to 10
with tf.variable_scope('Layer5'):
W5 = tf.Variable(tf.random_normal([84, 10], stddev=0.01))
b5 = tf.Variable(tf.random_normal([1, 10], stddev=0.01))
L5 = tf.matmul(L4, W5) + b5
L5 = tf.nn.softmax(L5, axis=1)
四、这个案例的模型(两层----一个卷积池化层、一个全连接层)
运用手写体识别Mnist经典数据进行分析
batchX, batchY = mnist.train.next_batch(batch_size=batchSize)
参数初始化
模型参数为(1)20个宽高为9 * 9厚度为1的卷积核 w1,(2)20个卷积池化层的偏置 b1,(3)维度为2000 * 10的全连接层权重w2, (4)10个全连接层偏置b2。初始化过程如下:
w1_ = np.random.standard_normal([9, 9, 1, 20]) * 0.1
b1_ = np.random.standard_normal([1, 20]) * 0.1
w2_ = np.random.standard_normal([2000, 10]) * 0.1
b2_ = np.random.standard_normal([1, 10]) * 0.1
五、模型前向传播
1、卷积池化层的前向传播
**函数用sigmoid**函数**,**函数常用relu(选用sigmoid仅仅是方便理论研究)。
池化过程用平均池化,常用最大池化max_pool(这里选用平均池化也仅仅用于理论研究)
需要先定义TensorFlow框架中的占位符(张量)X,w1,b1。
构建张量的过程如下:
with tf.name_scope('layer1'):
# 占位符
X = tf.placeholder(dtype=tf.float32, shape=[None, 784])
w1 = tf.placeholder(dtype=tf.float32, shape=[9, 9, 1, 20])
b1 = tf.placeholder(dtype=tf.float32, shape=[1, 20])
# 卷积池化层的前向传播 ======================================
X_img = tf.reshape(X, shape=[-1, 28, 28, 1])
# 卷积过程
z1 = tf.nn.conv2d(X_img, w1, strides=[1, 1, 1, 1], padding='VALID') + b1
# 用sigmoid**函数**,**函数常用relu(选用sigmoid仅仅是方便理论研究)
s1 = tf.sigmoid(z1)
# 平均池化,常用最大池化max_pool(这里选用平均池化也仅仅用于理论研究)
L1 = tf.nn.avg_pool(s1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="VALID")
运行获取卷积池化数据的过程如下
#forwardPropagation
# layer1 ++++++++++++++++++++++++++++++++++++++
feed_dict = {X: batchX, w1: w1_, b1: b1_}
# 从Tensor到numpy数据
z1_, s1_, L1_ = session.run([z1, s1, L1], feed_dict=feed_dict)
# 数据扁平化处理
L1_flat_ = np.reshape(L1_, newshape=[-1, 2000])
2、全连接层的前向传播
代码如下
def G(z, derive = False):
if derive:
return z * (1 - z)
else:
return 1 / (1 + np.exp(-z))
L1_flat_ = np.reshape(L1_, newshape=[-1, 2000])
z2_ = np.dot(L1_flat_, w2_) + b2_
s2_ = G(z2_)
六、反向传播前,预备知识
1、克罗内克积(kron)
克罗内克积的简介
https://baike.baidu.com/item/克罗内克积/6282573?fr=aladdin
一个维度为 k * d 的矩阵M,与 2 * 2 的元素全是1的矩阵进行克罗内克积的结果,是一个 2k * 2d 的矩阵N。
2、卷积前的梯度(3 * 3 的数据被 2 * 2 卷积得 2 * 2 的数据为例)
这里已经详细证明,文字表述太麻烦了。仔细揣摩,给出结论。
得到的结论,一个图像的某一个通道,与代价函数值对原卷积结果的梯度,进行卷积操作,得到一个代价函数值对原卷积核在某一个厚度上的梯度
太TM难理解了,见反向传播代码品一品。
七、反向传播
1、全连接层反向传播
(1)dz2(z2梯度)
代码(同Hinton BP)
dz2_ = s2_ - batchY
(2)dw2(w2梯度)
代码(同Hinton BP)
dw2_ = (1 / m_) * np.dot(L1_flat_.T, dz2_)
(3)db2(b2梯度)
代码(同Hinton BP)
db2_ = np.mean(dz2_, axis=0)
db2_ = np.reshape(db2_, newshape=[1, 10])
(4)dL1_flat(L1_flat梯度)
代码(同Hinton BP)
dL1_flat_ = np.dot(dz2_, w2_.T)
2、卷积池化层反向传播
(1)dL1(L1梯度)
代码(好理解,并不难)
dL1_ = np.reshape(dL1_flat_, newshape=[-1, 10, 10, 20])
(2)ds1(s1梯度)
代码(注意细节,并不难)
ds1_ = np.zeros([m_, 20, 20, 20])
for k in range(20):
one_dL1_ = np.reshape(dL1_[:, :, :, k], newshape=[-1, 10, 10])
one_ds1_ = np.kron(one_dL1_, np.ones([2, 2])).reshape([-1, 20, 20, 1]) / 4
ds1_[:, :, :, k] = one_ds1_[:, :, :, 0]
(3)dz1(z1梯度)
代码(同Hinton BP)
dz1_ = ds1_ * s1_ * (1 - s1_)
(4)db1(b1梯度)
代码(同Hinton BP)
db1_ = np.mean(np.reshape(dz1_, newshape=[-1, 20]), axis=0)
db1_ = np.reshape(db1_, newshape=[1, 20])
(5)dw1(w1梯度)
代码(难点,在这里好好悟,结合补充的知识)
构建张量的过程如下:
with tf.name_scope('convolve'):
one_X_img = tf.placeholder(dtype=tf.float32, shape=[1, 28, 28, 1])
convolve = tf.placeholder(dtype=tf.float32, shape=[20, 20, 1, 20])
one_kernal = tf.nn.conv2d(one_X_img, convolve, strides=[1, 1, 1, 1], padding='VALID')
one_kernal = tf.reshape(one_kernal, shape=[9, 9, 1, 20])
就样本的一个通道来说,每存在一个样本都会存在一次卷积,并把卷积结果求均值。因此,
通过反复喂数据,避免构建样本个数m个张量;否则m很大的情况下,构建张量的过程再强的电脑也无法解决。
dw1_ = np.zeros([9, 9, 1, 20])
for m in range(m_):
one_X_img_ = np.reshape(X_img_[m, :, :, :], newshape=[1, 28, 28, 1])
convolve_ = np.reshape(dz1_[m, :, :, :], newshape=[20, 20, 1, 20])、
# 通过反复喂数据,避免构建样本个数m_个张量,否则,构建张量的过程再强的电脑也无法解决。
one_kernal_ = session.run(one_kernal, feed_dict={one_X_img: one_X_img_, convolve: convolve_})
dw1_ += one_kernal_
dw1_ = dw1_ / m_
八、权重更新
# update++++++++++++++++++++++++++++++++++++++++
w1_ =w1_ - learningRate * dw1_
b1_ = b1_ - learningRate * db1_
w2_ = w2_ - learningRate * dw2_
b2_ = b2_ - learningRate * db2_
运行结果
'''
000000 epoch 1 Cost 0.7285813373869118
000000 epoch 2 Cost 0.3221266726065763
000000 epoch 3 Cost 0.26143733019178567
000000 epoch 4 Cost 0.2178894449905915
000000 epoch 5 Cost 0.18599715965037983
000000 epoch 6 Cost 0.16401437470181424
000000 epoch 7 Cost 0.1460933880728079
000000 epoch 8 Cost 0.13309557309374226
000000 epoch 9 Cost 0.12221747296100305
000000 epoch 10 Cost 0.11367286562411623
accuracy 0.9695
'''