深度学习案例:一步步搭建多层神经网络以及应用
该案例来自吴恩达深度学习系列课程一《神经网络和深度学习》第四周编程作业,一步步搭建多层神经网络以及应用,作业内容是搭建一个能够识别猫的多层的神经网络。作业提供的资料包括:训练集(train_catvnoncat.h5)、测试集(test_catvnoncat.h5)、测试实例(testCases.py)、神经网络工具包(dnn_utils.py)、加载数据的工具包(lr_utils.py),下载请移步参考链接一。
文章目录
- 1 介绍
- 1.1 案例应用核心公式
- 1.2 涉及库和主要接口
- 2 编码
- 2.1 数据初始化
- 2.2 正向传播
- 2.2.1 linear forward
- 2.2.2 linear activation forward
- 2.2.3 L model forward
- 2.3 计算成本
- 2.4 反向传播
- 2.4.1 linear backward
- 2.4.2 linear activation backward
- 2.4.3 L model backward
- 2.5 更新参数
- 2.6 搭建多层神经网络
- 2.7 结果预测
- 3 测试
- 3.1 数据集训练
- 3.2 结果分析
- 4 参考
1 介绍
1.1 案例应用核心公式
正向传播
Z
[
l
]
(
n
[
l
]
,
m
)
=
W
[
l
]
(
n
[
l
]
,
n
[
l
−
1
]
)
⋅
A
[
l
−
1
]
(
n
[
l
−
1
]
,
m
)
+
b
[
l
]
(
n
[
l
]
,
1
)
(1)
\underset{(n^{[l]},m)}{{Z}^{[l]}}=\underset{(n^{[l]},n^{[l-1]})}{{W}^{[l]}}\cdot\underset{(n^{[l-1]},m)}{A^{[l-1]}}+\underset{(n^{[l]},1)}{{b}^{[l]}}\tag{1}
(n[l],m)Z[l]=(n[l],n[l−1])W[l]⋅(n[l−1],m)A[l−1]+(n[l],1)b[l](1)
A [ l ] ( n [ l ] , m ) = g [ l ] ( Z [ l ] ) ( n [ l ] , m ) (2) \underset{(n^{[l]},m)}{{A}^{[l]}}=\underset{(n^{[l]},m)}{g^{[l]}({Z}^{[l]})}\tag{2} (n[l],m)A[l]=(n[l],m)g[l](Z[l])(2)
计算成本
J
=
−
1
m
∑
i
=
1
m
(
Y
∗
log
(
A
[
L
]
)
+
(
1
−
Y
)
∗
log
(
1
−
A
[
L
]
)
)
(3)
J=-\frac{1}{m}\sum\limits_{i=1}^m{(Y*\log{(A^{[L]})}+(1-Y)*\log{(1-A^{[L]})})}\tag{3}
J=−m1i=1∑m(Y∗log(A[L])+(1−Y)∗log(1−A[L]))(3)
反向传播
d
Z
[
l
]
(
n
[
l
]
,
m
)
=
d
A
[
l
]
(
n
[
l
]
,
m
)
∗
g
[
l
]
′
(
Z
[
l
]
)
(
n
[
l
]
,
m
)
(4)
\underset{(n^{[l]},m)}{\mathrm{d}{{Z}^{[l]}}}=\underset{(n^{[l]},m)}{\mathrm{d}{{A}^{[l]}}}*\underset{(n^{[l]},m)}{{{g}^{[ l]}}'({{Z}^{[l]}})}\tag{4}
(n[l],m)dZ[l]=(n[l],m)dA[l]∗(n[l],m)g[l]′(Z[l])(4)
d W [ l ] ( n [ l ] , n [ l − 1 ] ) = 1 m ⋅ d Z [ l ] ( n [ l ] , m ) ⋅ A [ l − 1 ] T ( m , n [ l − 1 ] ) (5) \underset{(n^{[l]},n^{[l-1]})}{\mathrm{d}{{W}^{[l]}}}=\frac{1}{m}\cdot \underset{(n^{[l]},m)}{\mathrm{d}{{Z}^{[l]}}}\cdot \underset{(m,n^{[l-1]})}{{A}^{[l-1]T}}\tag{5} (n[l],n[l−1])dW[l]=m1⋅(n[l],m)dZ[l]⋅(m,n[l−1])A[l−1]T(5)
d b [ l ] ( n [ l ] , 1 ) = 1 m ⋅ n p . s u m ( d Z [ l ] ( n [ l ] , m ) , a x i s = 1 , k e e p d i m s = T r u e ) (6) \underset{(n^{[l]},1)}{\mathrm{d}{{b}^{[l]}}}=\frac{1}{m}\cdot\text{ }np.sum(\underset{(n^{[l]},m)}{\mathrm{d}{{Z}^{[l]}}},axis=1,keepdims=True)\tag{6} (n[l],1)db[l]=m1⋅ np.sum((n[l],m)dZ[l],axis=1,keepdims=True)(6)
d A [ l − 1 ] ( n [ l − 1 ] , m ) = W [ l ] T ( n [ l − 1 ] , n [ l ] ) ⋅ d Z [ l ] ( n [ l ] , m ) (7) \underset{(n^{[l-1]},m)}{\mathrm{d}{{A}^{[l-1]}}}=\underset{(n^{[l-1]},n^{[l]})}{{W}^{[l]T}}\cdot \underset{(n^{[l]},m)}{\mathrm{d}{{Z}^{[l]}}}\tag{7} (n[l−1],m)dA[l−1]=(n[l−1],n[l])W[l]T⋅(n[l],m)dZ[l](7)
更新参数
W
[
l
]
=
W
[
l
]
−
α
⋅
d
W
(8)
W^{[l]}=W^{[l]}-\alpha \cdot \mathrm{d}W\tag{8}
W[l]=W[l]−α⋅dW(8)
b [ l ] = b [ l ] − α ⋅ d b [ l ] (9) b^{[l]}=b^{[l]}-\alpha \cdot \mathrm{d}b^{[l]}\tag{9} b[l]=b[l]−α⋅db[l](9)
1.2 涉及库和主要接口
numpy库:用于进行科学计算。
numpy.asarray:将输入转化为数组
numpy.where:返回根据条件选择的值
matplotlib库:用于绘制图表。
matplotlib.pyplot.subplot:将轴添加到当前图形或检索现有轴,创建子图
h5py库:与h5文件中存储的数据集进行交互。
dnn_utils:神经网络工具包。
lr_utils:加载数据工具包。
2 编码
import matplotlib.pyplot as plt
import numpy as np
import lr_utils
from dnn_utils import sigmoid, sigmoid_backward, relu, relu_backward
2.1 数据初始化
初始化多层网络参数。
参数
layers_dims
:包含我们网络中每个图层的节点数量的列表
返回
parameters
:包含权重矩阵W1
, W2
, … 和偏置量b1
, b2
, … 的字典。
def initialize_parameters_deep(layers_dims):
parameters = {}
L = len(layers_dims) - 1
for l in range(1, L + 1):
parameters["W" + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) / np.sqrt(layers_dims[l - 1])
parameters["b" + str(l)] = np.zeros((layers_dims[l], 1))
return parameters
代码中的
/ np.sqrt(layers_dims[l - 1])
作用是对权重进行缩放,这是 He 初始化的一种变体,用于保持网络的输入和输出的方差一致,避免梯度爆炸或消失问题。
2.2 正向传播
实现多层网络的正向传播,具体通过三个函数实现:
-
LINEAR:实现线性部分,即公式(1);
-
LINEAR ->ACTIVATION:联立第一个函数,实现涉及激活函数的部分,即公式(2);
-
[LINEAR -> RELU] ×(L-1) -> LINEAR -> SIGMOID:综合运用第二个函数实现多层网络正向传播。
2.2.1 linear forward
实现前向传播的线性部分,主要是公式(1),并返回相关值用于后续函数。
参数
A_prev
:来自上一层或输入数据的激活,numpy数组,维度为
(
n
[
l
−
1
]
,
m
)
(n^{[l-1]},m)
(n[l−1],m)
W
:权重矩阵,numpy数组,维度为
(
n
[
l
]
,
n
[
l
−
1
]
)
(n^{[l]},n^{[l-1]})
(n[l],n[l−1])
b
:偏向量,numpy向量,维度为
(
n
[
l
]
,
1
)
(n^{[l]},1)
(n[l],1)
返回
Z
:公式(1)的结果,激活功能的输入,也称为预激活参数,维度为
(
n
[
l
]
,
m
)
(n^{[l]},m)
(n[l],m)
cache
:一个包含A_prev
、W
、b
的元组
代码
def linear_forward(A_prev, W, b):
Z = np.dot(W, A_prev) + b
cache = (A_prev, W, b)
return Z, cache
2.2.2 linear activation forward
实现涉及激活函数部分的前向传播,主要是公式(2),根据激活函数不同分情况调用dnn_utils中的对应函数,同时调用前一个函数,返回相关值用于后续函数。
参数
A_prev
:来自上一层或输入数据的激活,numpy数组,维度为
(
n
[
l
−
1
]
,
m
)
(n^{[l-1]},m)
(n[l−1],m)
W
:权重矩阵,numpy数组,维度为
(
n
[
l
]
,
n
[
l
−
1
]
)
(n^{[l]},n^{[l-1]})
(n[l],n[l−1])
b
:偏向量,numpy向量,维度为
(
n
[
l
]
,
1
)
(n^{[l]},1)
(n[l],1)
activation
:在此层中使用的激活函数名,字符串
返回
A
:公式(2)的结果,激活函数的输出,也称为激活后的值,numpy数组,维度为
(
n
[
l
]
,
m
)
(n^{[l]},m)
(n[l],m)
cache
:一个包含linear_cache
(来自函数linear_forward返回的cache)和activation_cache
(包含本层的Z
)的元组
代码
def linear_activation_forward(A_prev, W, b, activation):
Z, linear_cache = linear_forward(A_prev, W, b)
if activation == "sigmoid":
A, activation_cache = sigmoid(Z)
elif activation == "relu":
A, activation_cache = relu(Z)
cache = (linear_cache, activation_cache)
return A, cache
2.2.3 L model forward
综合调用第二个函数,根据需要实现多层网络正向传播,并返回相关值用于反向传播。此处前 L − 1 L-1 L−1 层采用RELU激活函数,第 L L L 层采用SIGMOID激活函数。
参数
X
:训练的数据,numpy数组,维度为
(
n
x
,
m
)
(n_x,m)
(nx,m)
parameters
:初始化的参数,字典,即函数initialize_parameters_deep()的输出
返回
AL
:最后的激活值,numpy数组,维度为
(
n
[
L
]
,
m
)
(n^{[L]},m)
(n[L],m) ,本例中即
(
1
,
m
)
(1,m)
(1,m)
caches
:列表,包含根据需要采取不同激活函数后,每一层linear_activation_forward()函数返回的cache值
代码
def L_model_forward(X, parameters):
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)
AL, cache = linear_activation_forward(A, parameters["W" + str(L)], parameters["b" + str(L)], "sigmoid")
caches.append(cache)
return AL, caches
2.3 计算成本
计算交叉熵成本,即公式(3)。
参数
AL
:与标签预测相对应的概率向量,最后的激活值,numpy数组,维度为
(
1
,
m
)
(1,m)
(1,m)
Y
:标签向量,维度为
(
1
,
m
)
(1,m)
(1,m) ,本例中如果是猫则为1,如果不是猫则为0
返回
cost
:交叉熵成本,即公式(3)的运算结果
代码
def compute_cost(AL, Y):
m = Y.shape[1]
cost = (-1 / m) * np.sum(Y * np.log(AL) + (1 - Y) * np.log(1 - AL))
cost = np.squeeze(cost)
return cost
2.4 反向传播
实现多层网络的反向传播,具体通过三个函数实现:
-
LINEAR:实现线性部分,即公式(5)(6)(7);
-
LINEAR <- ACTIVATION:联立第一个函数,实现涉及激活函数的部分,即公式(4);
-
[LINEAR <- RELU] ×(L-1) <- LINEAR <- SIGMOID:综合运用第二个函数实现多层网络反向传播。
2.4.1 linear backward
实现反向传播的线性部分,主要是公式(5)(6)(7),并返回相关值用于后续函数。
参数
dZ
:当前第
l
l
l 层的线性输出的成本梯度,numpy数组,与 Z 维度相同
cache
:来自当前层正向传播线性部分的cache值,包含A_prev
、W
、b
返回
dA_prev
:第
l
−
1
l-1
l−1 层激活
A
A
A 的成本梯度,与
A
p
r
e
v
A_{prev}
Aprev 维度相同
dW
:当前第
l
l
l 层的
W
W
W 的成本梯度,与
W
W
W 的维度相同
db
:当前第
l
l
l 层的
b
b
b 的成本梯度,与
b
b
b 维度相同
代码
def linear_backward(dZ, cache):
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
2.4.2 linear activation backward
实现涉及激活函数部分的后向传播,主要是公式(4),根据激活函数不同分情况调用dnn_utils中的对应函数,先计算出 d Z dZ dZ 后将参数导入前一个函数计算,返回相关值用于后续函数。
参数
dA
:当前层
l
l
l 的激活后的梯度值,与
A
A
A 维度相同
cache
:来自当前层正向传播线性激活部分的cache值,包含linear_cache
(来自函数linear_forward返回的cache)和activation_cache
(包含本层的Z
)
activation
- 要在此层中使用的激活函数名,字符串类型,【“sigmoid” | “relu”】
返回
dA_prev
:第
l
−
1
l-1
l−1 层激活
A
A
A 的成本梯度,与
A
p
r
e
v
A_{prev}
Aprev 维度相同
dW
:当前第
l
l
l 层的
W
W
W 的成本梯度,与
W
W
W 的维度相同
db
:当前第
l
l
l 层的
b
b
b 的成本梯度,与
b
b
b 维度相同
代码
def linear_activation_backward(dA, cache, activation="relu"):
linear_cache, activation_cache = cache
if activation == "relu":
dZ = relu_backward(dA, activation_cache)
elif activation == "sigmoid":
dZ = sigmoid_backward(dA, activation_cache)
dA_prev, dW, db = linear_backward(dZ, linear_cache)
return dA_prev, dW, db
2.4.3 L model backward
综合调用第二个函数,根据需要实现多层网络反向传播,并返回梯度相关值用于参数更新。此处前 L − 1 L-1 L−1 层采用RELU激活函数,第 L L L 层采用SIGMOID激活函数。
参数
AL
:多层正向传播函数最后的激活值,当前权重和偏向量下预测的概率向量,numpy数组,维度为
(
1
,
m
)
(1,m)
(1,m)
Y
:标签向量,维度为
(
1
,
m
)
(1,m)
(1,m) ,本例中如果是猫则为1,如果不是猫则为0
caches
:对应多层正向传播函数最后返回的caches值,需要注意的是caches[i-1]实际为为第i层的cache值
返回
grads
:具有梯度值的字典,包含dA1
, dW1
, db1
, dA2
, dW2
, db2
…
代码
def L_model_backward(AL, Y, caches):
grads = {}
L = len(caches)
m = AL.shape[1]
Y = Y.reshape(AL.shape)
dAL = -(np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(dAL, caches[L - 1], "sigmoid")
for l in reversed(range(1, L)):
dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads["dA" + str(l + 1)], caches[l - 1], "relu")
grads["dA" + str(l)] = dA_prev_temp
grads["dW" + str(l)] = dW_temp
grads["db" + str(l)] = db_temp
return grads
2.5 更新参数
利用公式(8)(9),使用梯度下降更新参数。
参数
parameters
:包含参数W1
, W2
, … b1
, b2
, … 的字典,初值来自数据初始化的返回值,后续不断自我迭代
grads
:包含梯度值的字典,是多层反向传播的输出
返回
parameters
:梯度下降迭代后更新的参数字典
代码
def update_parameters(parameters, grads, learning_rate):
L = len(parameters) // 2
for l in range(L):
parameters["W" + str(l + 1)] = parameters["W" + str(l + 1)] - learning_rate * grads["dW" + str(l + 1)]
parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * grads["db" + str(l + 1)]
return parameters
2.6 搭建多层神经网络
实现一个L层神经网络参数。
参数
X
:输入的数据,numpy数组,维度为
(
n
x
,
m
)
(n_x,m)
(nx,m)
Y
:标签向量,维度为
(
1
,
m
)
(1,m)
(1,m) ,本例中如果是猫则为1,如果不是猫则为0
layers_dims
:包含我们网络中每个图层的节点数量的列表
learning_rate
:学习率
num_iterations
- 迭代的次数
print_cost
:是否打印成本值,布尔值
isPlot
:是否绘制出误差值的图谱,布尔值
返回
parameters
:模型学习结束后的参数,可以用他们来预测。
代码
def L_layer_model(X, Y, layers_dims, learning_rate=0.0075, num_iterations=3000, print_cost=False, isPlot=True):
costs = []
parameters = initialize_parameters_deep(layers_dims)
for i in range(num_iterations):
AL, caches = L_model_forward(X, parameters)
cost = compute_cost(AL, Y)
grads = L_model_backward(AL, Y, caches)
parameters = update_parameters(parameters, grads, learning_rate)
if i % 100 == 0:
costs.append(cost)
if print_cost:
print("第 %i 次迭代,成本值为: %f" % (i, cost))
if isPlot:
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per hundreds)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
return parameters
2.7 结果预测
用于预测多层神经网络的结果,具体是将训练模型的参数和输入的数据进行一次正向传播,得到的激活值也就包含每个样例的预测,再将这些预测结果处理并与原标签比较后得到预测结果。
参数
X
:输入的数据,numpy数组,维度为
(
n
x
,
m
)
(n_x,m)
(nx,m)
Y
:标签向量,维度为
(
1
,
m
)
(1,m)
(1,m) ,本例中如果是猫则为1,如果不是猫则为0
parameters
:训练模型的参数
返回
P
:对给定数据集
X
X
X 的预测结果
代码
def predict(X, Y, parameters):
m = X.shape[1]
P = np.zeros((1, m))
probas, caches = L_model_forward(X, parameters)
for i in range(m):
if probas[0, i] > 0.5:
P[0, i] = 1
else:
P[0, i] = 0
print("准确率为:" + str(float(np.sum(P == Y) / m)))
return P
3 测试
3.1 数据集训练
数据加载和预处理
train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = lr_utils.load_dataset()
train_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0], -1).T
test_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T
train_x = train_x_flatten / 255
train_y = train_set_y
test_x = test_x_flatten / 255
test_y = test_set_y
神经网络训练和预测
layers_dims = [12288, 20, 7, 5, 1]
parameters = L_layer_model(train_x, train_y, layers_dims, num_iterations=2500, print_cost=True, isPlot=True)
pred_train = predict(train_x, train_y, parameters)
pred_test = predict(test_x, test_y, parameters)
3.2 结果分析
运行结果
打印预测错误图片
def print_mislabeled_images(classes, X, Y, P):
mislabeled_indices = np.asarray(np.where(P + Y == 1)) # 二维数组,给出满足条件的元素所在的行和列
plt.rcParams['figure.figsize'] = (40.0, 40.0)
num_images = len(mislabeled_indices[0])
for i in range(num_images):
index = mislabeled_indices[1][i]
plt.subplot(2, num_images, i + 1)
plt.imshow(X[:, index].reshape(64, 64, 3), interpolation='nearest')
plt.axis('off')
plt.title(
"Predicted: " + classes[int(P[0, index])].decode("utf-8") + "\n Class: " + classes[int(Y[0, index])].decode(
"utf-8"))
plt.show()
print_mislabeled_images(classes, test_x, test_y, pred_test)
4 参考
【中文】【吴恩达课后编程作业】Course 1 - 神经网络和深度学习 - 第四周作业(1&2)
NumPy reference — NumPy v2.1 Manual
API Reference — Matplotlib 3.9.2 documentation