文章目录
- 前言
- 一.全连接神经网络
- 1.1从单个感知机到神经网络
- 1.2DNN的基本结构
- 1.3前向传播算法
- 1.4梯度下降算法
- 1.5反向传播算法
- 1.6激活函数
- 1.relu函数
- 2.sigmoid函数
- 3.tanh函数
- 4.softmax函数
- 二.卷积神经网络
- 2.1卷积神经网络的基本结构
- 1.卷积
- 2.池化
- 2.2卷积神经网络的前向传播
- 1.卷积层
- 2.池化层
- 3.全连接层
- 2.3卷积神经网络的反向传播
- 1.已知池化层的 δ l δ^l δl,推到上一层的 δ l − 1 δ^{l-1} δl−1
- 2已知卷积层的 δ l δ^{l} δl,求上一层的 δ l − 1 δ^{l-1} δl−1
- 三.用numpy搭建神经网络
- 3.1DNN的numpy实现:
- 1.定义激活函数以及激活函数的导数:
- 2参数w,b初始化:
- 3.定义前向传播函数:
- 4.计算损失:
- 5.定义反向传播函数:
- 6.参数更新:
- 7.模型定义:
- 3.2 CNN的numpy实现:
- 1.卷积层的前向传播:
- 2.池化层的前向传播:
- 3.卷积层的反向传播:
- 4.池化层的反向传播:
- 四.参考资料
前言
近期学习了神经网络前向传播和反向传播的数学原理,希望通过写下这篇文章进一步巩固自己对近期学习知识的理解,以及帮助同样有相同需求的人。若有错误,望指出。
一.全连接神经网络
1.1从单个感知机到神经网络
感知机是神经网络的基本组成单元,其基本结构如图1所示:
图1.单个感知机的结构
感知机是由多个输入和一个输出的模型。输入与权重相乘再求和,得到一个线性关系的结果:
得到的线性结果再通过激活函数:
最后得到的输出只能应用与二元分类,并且无法学习更复杂非线性模型,这是单个感知机的缺陷。
因此在单个感知机的基础上,通过增加单层感知机的个数以及感知机的层数,构成了人工神经网络(ANN),因此,人工神经网络也被称作多层感知机(MLP),如图2。
图2 多层感知机结构
1.2DNN的基本结构
神经网络之所以被称作网络,是因为它们通常是用许多不同函数复合在一起来表示。该模型与一个有向无环图相关联,而图描述了函数是如何复合在一起的。例如,我们有三个函数,,,,,连接在一个链上以形成。 在这种情况下,被称为网络的第一层,也就是输入层,到被称为隐藏层,被称为输出层。也就是说,DNN按照层的位置来划分的话,可以分为3类:输入层,隐藏层,输出层。一般来说,输入层为网络的第一层,输出层为最后一层。如图3为DNN结构样例:
图3:DNN结构样例
1.3前向传播算法
前向传播其实就是向神经网络中输入X得到Y的过程。也就是可以把神经网络写成函数,而函数的系数就是神经网络训练的参数W。接下来我们就从数学的角度来推导前向传播的过程
图4
记为第-1层的第个神经元到第层第个神经元的权重,为第层第个神经元的偏置,为第层第个神经元的输出值,为激活函数,所以
写成矩阵形式:
记,则。
利用式子一层层计算,就能根据得到相应的输出。
1.4梯度下降算法
梯度下降算法是神经网络进行反向传播的基础,因此,在介绍反向传播算法之前,我们先介绍一下什么是梯度下降算法以及梯度下降算法的数学原理。
1.梯度下降算法的解释:
假设甲现在处在一个山沟当中,如图5所示。在山沟的最低点有一个宝藏,甲希望能够找到这个宝藏。那么甲如何才能够沿着正确的方向找到宝藏呢?由于宝藏在山沟的最低点,所以甲会按着一定的步伐沿着下坡的方向前进,于是就有了①→②→③→④→⑤,由于甲的步伐过大,所以直接跨过了最低点,甲于是沿着另一个坡下坡的方向前进,于是有了⑤→⑥,最终到达了最低点。
图5
2.梯度下降的数学解释:
首先我们引入梯度下降算法的公式:
假设我们有一个目标函数,我们希望我们有一种算法,能够找到目标函数的最低值,有什么办法能够帮助我们找到呢?参考甲寻宝的过程,我们知道沿着下坡的方向,就能找到最低值。而在函数中,上坡与下坡代表着函数在该点梯度的方向与梯度的反方向。在多变量函数中,梯度是一个向量,用符号▽表示,向量有方向,梯度的方向就指出了函数在给定点的上升最快的方向,反方向就是下降最快的方向。 针对目标函数,我们来求解他的梯度,其实就是求个关于的偏微分。
在知道我们应该沿着哪个方向下坡时,我们又会面临另一个问题,我们每次迈出的步伐是多少,于是我们引入来表示步伐,也称为学习率。如果设置过大,会导致我们直接迈过最低点,就如第⑤步一样;如果我们设置过小,会导致我们走了很久也没有到达最低点,因此设置合理的值很重要。
有了梯度和学习率我们就能够通过多次迭代,找到
1.5反向传播算法
有了对梯度下降算法的了解,我们现在开始去看看神经网络是如何通过反向传播来更新参数的。
在介绍反向传播之前,我们引入损失函数,用来描述预测值与真实值之间的差异。
在反向传播的过程中,我们需要计算来对参数进行更新。在这里我们假设我们的误差函数为均方误差:
其中为网络第层的输出,为真实值,表示范数
我们又知道和满足:
所以我们可以把损失函数写成:
于是损失函数关于的梯度为:
注:表示两向量的内积,例:
在求解时,我们根据微分的链式法则,可以分解为
我们可以先求解出公共部分
记为:
对于第层的和,又为多少呢,根据链式求导法则,我们可以知道:
我们再根据链式求导法则:
因此我们根据上一层的梯度来求解的梯度,这也是反向传播的特点,从层一步一步求解到第层。
又因为 ,所以我们可以计算出第层的梯度值:
到此,我们就求解出了和的梯度,然后我们用梯度下降算法,来对参数进行更新:
如此一来,我们就实现了神经网络的反向传播。
1.6激活函数
我们知道,如果没有激活函数,那么整个神经网络都是线性模型,就算网络有多深,其拟合能力和logistics回归差不多,而在添加了激活函数后,模型才有线性变成了非线性,因此,激活函数在神经网络中起了十分重要的作用。在这一部分,我讲介绍一些激活函数,以及这些激活函数的作用。
1.relu函数
relu全称为整流线性单元(rectified linear unit)其函数表达式为:,其函数如图6所示:
图6
ReLU函数其实是分段线性函数,把所有的负值都变为0,而正值不变,这种操作被成为单侧抑制。(也就是说:在输入是负值的情况下,它会输出0,那么神经元就不会被激活。这意味着同一时间只有部分神经元会被激活,从而使得网络很稀疏,进而对计算来说是非常有效率的。)正因为有了这单侧抑制,才使得神经网络中的神经元也具有了稀疏激活性。尤其体现在深度神经网络模型(如CNN)中,当模型增加N层之后,理论上ReLU神经元的激活率将降低2的N次方倍。
其优点有:
1.没有饱和区,不存在梯度消失问题。
2.没有复杂的指数运算,计算简单、效率提高。
3.实际收敛速度较快,比 Sigmoid/tanh 快很多。
4.比 Sigmoid 更符合生物学神经激活机制。
relu函数的一些变种:
(1).Leaky ReLU:带泄露线性整流函数(Leaky ReLU)的梯度为一个常数,通常取0.01。在输入值为正的时候,带泄露线性整流函数和普通斜坡函数保持一致。函数为:
(2).绝对值整流:固定来得到,它一般用于图像中的对象识别,其中寻找输入照明极性反转下不变的特性是有意义的。
2.sigmoid函数
sigmoid函数也叫Logistic函数,用于隐层神经元输出,取值范围为(0,1),它可以将一个实数映射到(0,1)的区间,可以用来做二分类。在特征相差比较复杂或是相差不是特别大时效果比较好。Sigmoid作为激活函数有以下优缺点:
优点:平滑、易于求导。
缺点:激活函数计算量大,反向传播求误差梯度时,求导涉及除法;反向传播时,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练。
其函数式为:
函数图像如下图7
图7
3.tanh函数
tanh是双曲函数中的一个,tanh()为双曲正切。在数学中,双曲正切“tanh”是由基本双曲函数双曲正弦和双曲余弦推导而来,其函数表达式为:
tanh其实是sigmoid经过放大后得到的:
因此tanh的取值范围为,如下图
4.softmax函数
在数学,尤其是概率论和相关领域中,归一化指数函数,或称Softmax函数,是逻辑函数的一种推广。它能将一个含任意实数的K维向量z“压缩”到另一个K维实向量σ(z)中,使得每一个元素的范围都在(0,1)之间,并且所有元素的和为1。该函数多于多分类问题中。
函数表达式为:
二.卷积神经网络
2.1卷积神经网络的基本结构
卷积神经网络通常是又卷积层,池化层,全连接层组合而成,图8展示的是卷积神经网络中最经典的模型之一:VGG模型的网络结构。
图8 VGG模型
图9展示的是VGG模型各层的组成:
图9 VGG模型具体细节
其中conv代表卷积层,maxpool表示池化层,FC表示全连接层。
接下来我们来简述一下卷积神经网络中的卷积和池化是什么。
1.卷积
卷积概念的提出,是在信号与系统这么学科上,它的定义是一个信号(可以理解成一个函数)经过一个线性系统以后发生的变化。
对于连续信号(函数),卷积的定义为:
对于离散信号(函数),卷积的定义为:
对于二维离散信号(函数)的卷积,定义为:
在卷积神经网络中,卷积的定义和上述定义略有不同,例如对于二维卷积,有:
虽然两个式子上形式上很相识,可是按照按个的数学定义来说是不同,产生两个式子不同的原因是因为对于普通二维信号的卷积,卷积核需要进行翻转操作,而在卷积神经网络的卷积中,卷积核不需要进行翻转操作。
卷积的作用:特征提取。
2.池化
池化是使用某一个位置的总体统计特征来代替网络在该位置的输出。例如最大池化(maxpooling)会给出相邻矩阵区域内的最大值。其他常用的池化函数包括平均值。范数等。
池化的作用:
(1)帮助输入的表示近似不变。
(2)保留主要特征的同时减少参数个数(降维)。
2.2卷积神经网络的前向传播
1.卷积层
其中表示激活函数,一般为函数。表示卷积操作。
2.池化层
如果输入矩阵的大小为,池化层的filter大小为,则输出矩阵的大小为。
如果是最大值池化,则选取对应区域的最大值。如果是均值池化,则求对应区域的平均值。
3.全连接层
全连接层的前向传播和我们在第一部分讲述的一样,这里就不再重复,只给出对应的数学公式。
2.3卷积神经网络的反向传播
在进行CNN反向传播推到之前,回顾我们需要在第一部分反向传播中定义的,我们将根据来进行推到。
1.已知池化层的,推到上一层的
假设池化层的filter大小为2×2。
池化后的矩阵为:
如果我们用的最大值池化,我们对矩阵进行还原,即:
如果我们用的均值池化,我们对矩阵进行还原,即:
这样我们求出上一层的
其中,upsample函数完成了池化误差矩阵放大与误差重新分配的逻辑。
2已知卷积层的,求上一层的
我们呢以及卷积层的前向传播公式为:
在DNN中,我们知道和存在递推关系,并且根据和的关系:,
因此我们有:
含有卷积的式子求导时,卷积核被旋转了180度。即式子中的rot180(),翻转180度的意思是上下翻转一次,接着左右翻转一次。
假设矩阵,卷积核为2×2,pad=1.忽略,根据:
具体为:
利用卷积的定义,我们知道:
根据上式,我们对求导:
因此:
以上就是卷积层的反向传播,接下来我们来求解的梯度
对于我们有:
对于,我们有:
上述内容就是CNN反向传播的全部过程。
三.用numpy搭建神经网络
本部分在仅导入numpy包的条件下实现对神经网络的搭建。
3.1DNN的numpy实现:
1.定义激活函数以及激活函数的导数:
def sigmod(x):
return 1/(1+np.exp(-x))
def relu(x):
if x >= 0:
return x
else:
return 0
def relu_backward(dA, cache):
"""
cache - 缓存了一些反向传播函数conv_backward()需要的一些数据
"""
Z = cache
dz = np.array(dA,copy = True)
dz[Z<=0] = 0
return dz
def sigmoid_backward(dA, cache):
"""
cache - 缓存了一些反向传播函数conv_backward()需要的一些数据
"""
z = cache
s = 1/(1+np.exp(-z))
dz = dA*s*(1-s)
return dz
2参数w,b初始化:
def init_parameter(layers_dim):
"""
layers_dim:DNN中各层的节点数
"""
np.random.seed(3)
parameters = {}
for i in range(1,len(layers_dim)):
#parameters['w'+str(i)] = np.random.randn(layers_dim[i],layers_dim[i-1])*0.01
parameters['w' + str(i)] = np.random.randn(layers_dim[i], layers_dim[i - 1]) *np.sqrt(2/layers_dim[i-1])
parameters['b'+str(i)] = np.zeros((layers_dim[i],1))
#print(str(i)+"+"+str(parameters["w"+str(i)].shape)+"+"+str(parameters['b'+str(i)].shape))
return parameters
3.定义前向传播函数:
def linear_forward(A,w,b):
"""
实现前向传播的线性部分。
参数:
A - 来自上一层(或输入数据)的激活,维度为(上一层的节点数量,示例的数量)
W - 权重矩阵,numpy数组,维度为(当前图层的节点数量,前一图层的节点数量)
b - 偏向量,numpy向量,维度为(当前图层节点数量,1)
返回:
Z - 激活功能的输入,也称为预激活参数
cache - 一个包含“A”,“W”和“b”的字典,存储这些变量以有效地计算后向传递
"""
z = np.dot(w,A)+b
cache = (A,w,b)
return z,cache
def linear_activation_forward(A_prev,w,b,activation):
"""
A_prev - 来自上一层(或输入层)的激活,维度为(上一层的节点数量,示例数)
activation - 选择在此层中使用的激活函数名,字符串类型,【"sigmoid" | "relu"】
cache - 一个包含“linear_cache”和“activation_cache”的字典,我们需要存储它以有效地计算后向传递
"""
if activation == 'sigmoid':
Z, linear_cache = linear_forward(A_prev, w, b)
A,activation_cache = sigmoid(Z)
elif activation =='relu':
Z, linear_cache = linear_forward(A_prev, w, b)
A ,activation_cache= relu(Z)
cache = (linear_cache,activation_cache)
return A,cache
def L_model_forward(X,parameters):
"""
AL - 最后的激活值
caches - 包含以下内容的缓存列表:
linear_relu_forward()的每个cache(有L-1个,索引为从0到L-2)
linear_sigmoid_forward()的cache(只有一个,索引为L-1)
"""
caches = []
A = X
L = len(parameters) // 2
for l in range(1,L):
A_prev = A
A,cache = linear_activation_forward(A_prev,parameters['w'+str(l)],parameters['b'+str(l)],'relu')
caches.append(cache)
A_prev = A
AL,cache = linear_activation_forward(A_prev,parameters['w'+str(L)],parameters['b'+str(L)],'sigmoid')
caches.append(cache)
return AL,caches
4.计算损失:
def compute_cost(Y,AL):
"""
AL - 与标签预测相对应的概率向量,维度为(1,示例数量)
Y - 标签向量(例如:如果不是猫,则为0,如果是猫则为1),维度为(1,数量)
"""
m = Y.shape[1]
cost = -1/m*np.sum(np.multiply(np.log(AL),Y)+np.multiply(np.log(1-AL),(1-Y)))
cost = np.squeeze(cost)
return cost
5.定义反向传播函数:
def linear_backward(dz,cache):
"""
为单层实现反向传播的线性部分(第L层)
参数:
dZ - 相对于(当前第l层的)线性输出的成本梯度
cache - 来自当前层前向传播的值的元组(A_prev,W,b)
返回:
dA_prev - 相对于激活(前一层l-1)的成本梯度,与A_prev维度相同
dW - 相对于W(当前层l)的成本梯度,与W的维度相同
db - 相对于b(当前层l)的成本梯度,与b维度相同
"""
A_prev,w,b = cache
m = A_prev.shape[1]
dw = np.dot(dz,A_prev.T)/m
db = np.sum(dz,axis=1,keepdims=True)/m
dA_prev = np.dot(w.T,dz)
return dA_prev,dw,db
def linear_activation_backward(dA,cache,activation):
"""
实现LINEAR-> ACTIVATION层的后向传播。
参数:
dA - 当前层l的激活后的梯度值
cache - 我们存储的用于有效计算反向传播的值的元组(值为linear_cache,activation_cache)
activation - 要在此层中使用的激活函数名,字符串类型,【"sigmoid" | "relu"】
返回:
dA_prev - 相对于激活(前一层l-1)的成本梯度值,与A_prev维度相同
dW - 相对于W(当前层l)的成本梯度值,与W的维度相同
db - 相对于b(当前层l)的成本梯度值,与b的维度相同
"""
linear_cache,activation_cache = cache
if activation == "relu":
dz = relu_backward(dA,activation_cache)
dA_prev,dw,db = linear_backward(dz,linear_cache)
elif activation == "sigmoid":
dz = sigmoid_backward(dA,activation_cache)
dA_prev,dw,db = linear_backward(dz,linear_cache)
return dA_prev,dw,db
def L_model_backward(AL,Y,caches):
"""
grads - 具有梯度值的字典
grads [“dA”+ str(l)] = ...
grads [“dW”+ str(l)] = ...
grads [“db”+ str(l)] = ...
"""
grads = {}
L = len(caches)
m = AL.shape[1]
Y = Y.reshape(AL.shape)
dAL = -(np.divide(Y,AL)-np.divide(1-Y,1-AL))
current_cache = caches[L-1]
grads["dA"+str(L)],grads["dw"+str(L)],grads["db"+str(L)] = linear_activation_backward(dAL,current_cache,"sigmoid")
for l in reversed(range(L-1)):
current_cache = caches[l]
dA_prev_temp,dw_temp,db_temp = linear_activation_backward(grads["dA"+str(l+2)],current_cache,"relu")
grads["dA"+str(l+1)] = dA_prev_temp
grads["dw"+str(l+1)] = dw_temp
grads["db"+str(l+1)] = db_temp
return grads
6.参数更新:
使用梯度下降法,对参数进行更新
def update_parameters(parameters,grads,learning_rate = 0.1):
L = len(parameters)//2
for l in range(L):
parameters["w"+str(l+1)] -= learning_rate*grads["dw"+str(l+1)]
parameters["b"+str(l+1)] -= learning_rate*grads["db"+str(l+1)]
return parameters
7.模型定义:
def dnn_model(X_total,Y_total,layers_dims,num_iterations = 10000,print_cost = True):
np.random.seed(1)
costs = []
batchsize = 16
parameters = init_parameter(layers_dims)
v,s = init_adam(parameters)
t = 0
for i in range(0,num_iterations):
X,Y = batch_data(X_total,Y_total,batchsize)
AL,caches = L_model_forward(X,parameters)
cost = compute_cost(Y,AL)
# acc = comput_arccuracy(Y,AL)
grads = L_model_backward(AL,Y,caches)
t = t+1
parameters,v,s = update_parameters_with_Adam(parameters,grads,v,s,t)
if print_cost and i%100 == 0:
print("cost after iteration %i:%f"%(i,cost))
costs.append(cost)
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per tens')
plt.show()
test_file_path = "C:\\Users\\10372\\Desktop\\Learning\\机器学习\\数据集\\考生本地用资源\\test_data.txt"
test_x,test_y = data(test_file_path)
AL, caches = L_model_forward(test_x, parameters)
cost = compute_cost(test_y, AL)
print("test cost is %f"%(cost))
return parameters
完整代码请参看:
链接: CNN代码.
3.2 CNN的numpy实现:
1.卷积层的前向传播:
def zero_pad(X,pad):
x_paded = np.pad(X,(
(0,0),
(pad,pad),
(pad,pad),
(0,0)),
'constant',constant_values=0)
return x_paded
def conv_single_step(a_slice_prev,w,b):
s = np.multiply(a_slice_prev,w)+b
z = np.sum(s)
return z
def conv_forward(A_prev, W, b, hparameters):
# 获取来自上一层数据的基本信息
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
# 获取权重矩阵的基本信息
(f, f, n_C_prev, n_C) = W.shape
# 获取超参数hparameters的值
stride = hparameters["stride"]
pad = hparameters["pad"]
n_H = int((n_H_prev - f + 2 * pad) / stride) + 1
n_W = int((n_W_prev - f + 2 * pad) / stride) + 1
Z = np.zeros((m, n_H, n_W, n_C))
A_prev_pad = zero_pad(A_prev, pad)
for i in range(m):
a_prev_pad = A_prev_pad[i]
for h in range(n_H):
for w in range(n_W):
for c in range(n_C):
vert_start = h * stride
vert_end = vert_start + f
horiz_start = w * stride
horiz_end = horiz_start + f
a_slice_prev = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :]
Z[i, h, w, c] = conv_single_step(a_slice_prev, W[:, :, :, c], b[0, 0, 0, c])
assert (Z.shape == (m, n_H, n_W, n_C))
cache = (A_prev, W, b, hparameters)
return (Z, cache)
2.池化层的前向传播:
def pool_forward(A_prev,hparameters,mode = "max"):
(m, n_H_prev, n_W_prev, n_c_prev) = A_prev.shape
f = hparameters["f"]
stride = hparameters["stride"]
n_H = int((n_H_prev - f) / stride) + 1
n_W = int((n_W_prev - f) / stride) + 1
n_C = n_c_prev
A = np.zeros((m,n_H,n_W,n_C))
for i in range(m): # 遍历样本
for h in range(n_H): # 在输出的垂直轴上循环
for w in range(n_W): # 在输出的水平轴上循环
for c in range(n_C): # 循环遍历输出的通道
# 定位当前的切片位置
vert_start = h * stride # 竖向,开始的位置
vert_end = vert_start + f # 竖向,结束的位置
horiz_start = w * stride # 横向,开始的位置
horiz_end = horiz_start + f # 横向,结束的位置
# 定位完毕,开始切割
a_slice_prev = A_prev[i, vert_start:vert_end, horiz_start:horiz_end, c]
# 对切片进行池化操作
if mode == "max":
A[i, h, w, c] = np.max(a_slice_prev)
elif mode == "average":
A[i, h, w, c] = np.mean(a_slice_prev)
cache = (A_prev,hparameters)
return (A,cache)
3.卷积层的反向传播:
def conv_backward(dZ, cache):
# 获取cache的值
(A_prev, W, b, hparameters) = cache
# 获取A_prev的基本信息
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
# 获取dZ的基本信息
(m, n_H, n_W, n_C) = dZ.shape
# 获取权值的基本信息
(f, f, n_C_prev, n_C) = W.shape
# 获取hparaeters的值
pad = hparameters["pad"]
stride = hparameters["stride"]
# 初始化各个梯度的结构
dA_prev = np.zeros((m, n_H_prev, n_W_prev, n_C_prev))
dW = np.zeros((f, f, n_C_prev, n_C))
db = np.zeros((1, 1, 1, n_C))
# 前向传播中我们使用了pad,反向传播也需要使用,这是为了保证数据结构一致
A_prev_pad = zero_pad(A_prev, pad)
dA_prev_pad = zero_pad(dA_prev, pad)
# 现在处理数据
for i in range(m):
# 选择第i个扩充了的数据的样本,降了一维。
a_prev_pad = A_prev_pad[i]
da_prev_pad = dA_prev_pad[i]
for h in range(n_H):
for w in range(n_W):
for c in range(n_C):
# 定位切片位置
vert_start = h
vert_end = vert_start + f
horiz_start = w
horiz_end = horiz_start + f
# 定位完毕,开始切片
a_slice = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :]
# 切片完毕,使用上面的公式计算梯度
da_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :] += W[:, :, :, c] * dZ[i, h, w, c]
dW[:, :, :, c] += a_slice * dZ[i, h, w, c]
db[:, :, :, c] += dZ[i, h, w, c]
# 设置第i个样本最终的dA_prev,即把非填充的数据取出来。
dA_prev[i, :, :, :] = da_prev_pad[pad:-pad, pad:-pad, :]
# 数据处理完毕,验证数据格式是否正确
assert (dA_prev.shape == (m, n_H_prev, n_W_prev, n_C_prev))
return (dA_prev, dW, db)
4.池化层的反向传播:
def create_mask_from_window(x):
mask = x == np.max(x)
return mask
def distribute_value(dz, shape):
# 获取矩阵的大小
(n_H, n_W) = shape
# 计算平均值
average = dz / (n_H * n_W)
# 填充入矩阵
a = np.ones(shape) * average
return a
def pool_backward(dA, cache, mode="max"):
# 获取cache中的值
(A_prev, hparaeters) = cache
# 获取hparaeters的值
f = hparaeters["f"]
stride = hparaeters["stride"]
# 获取A_prev和dA的基本信息
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
(m, n_H, n_W, n_C) = dA.shape
# 初始化输出的结构
dA_prev = np.zeros_like(A_prev)
# 开始处理数据
for i in range(m):
a_prev = A_prev[i]
for h in range(n_H):
for w in range(n_W):
for c in range(n_C):
# 定位切片位置
vert_start = h
vert_end = vert_start + f
horiz_start = w
horiz_end = horiz_start + f
# 选择反向传播的计算方式
if mode == "max":
# 开始切片
a_prev_slice = a_prev[vert_start:vert_end, horiz_start:horiz_end, c]
# 创建掩码
mask = create_mask_from_window(a_prev_slice)
# 计算dA_prev
dA_prev[i, vert_start:vert_end, horiz_start:horiz_end, c] += np.multiply(mask, dA[i, h, w, c])
elif mode == "average":
# 获取dA的值
da = dA[i, h, w, c]
# 定义过滤器大小
shape = (f, f)
# 平均分配
dA_prev[i, vert_start:vert_end, horiz_start:horiz_end, c] += distribute_value(da, shape)
# 数据处理完毕,开始验证格式
assert (dA_prev.shape == A_prev.shape)
return dA_prev