搭建多层神经网络
一、目的
我们要使用非线性激活函数搭建一个两层神经网络和具有多个隐藏层的神经网络
二、训练集与测试集
百度网盘,提取码:1234
三、编程
这是本次编程需要用的的所有模块和工具类
import numpy as np
import matplotlib.pyplot as plt
import h5py
from dnn_utils import *
import lr_utils
import testCases
并且我们为了得到相同的结果,我们指定一个随机种子np.random.seed(1)
3.1 网络结构和方法
在本次编程中,我们要构建两个神经网络,一个是构建两层的神经网络,一个是构建多层的神经网络,本文会提到 [LINEAR-> ACTIVATION]转发函数,比如我有一个多层的神经网络,结构是输入层->隐藏层->隐藏层->···->隐藏层->输出层,在每一层中,我会首先计算Z = np.dot(W,A) + b
,这叫做【linear_forward】,然后再计算A = relu(Z) 或者 A = sigmoid(Z),这叫做【linear_activation_forward】,合并起来就是这一层的计算方法,所以每一层的计算都有两个步骤,先是计算Z,再计算A,可以参照下图:
构建一个多层的神经网络的步骤如下:
- 初始化网络参数
- 前向传播
2.1 计算一层中的线性求和的部分
2.2 计算激活函数的部分(ReLU使用L-1次,Sigmod使用1次)
2.3 整合线性求和与激活函数 - 计算成本
- 反向传播
4.1 线性部分的反向传播公式
4.2 激活函数部分的反向传播公式
4.3 整合线性部分与激活函数的反向传播公式 - 更新参数
注意:对于每个前向函数,都有一个相应的后向函数, 这就是为什么在我们的转发模块的每一步都会在cache中存储一些值,cache的值对计算梯度很有用, 在反向传播模块中,我们将使用cache来计算梯度, 现在我们正式开始分别构建两层神经网络和多层神经网络。
3.2 初始化网络参数
3.2.1 两层神经网络参数初始化
对于一个两层的神经网络结构而言,模型结构是线性->ReLU->线性->sigmod函数。
# 两层神经网络参数初始化
def initialize_parameters(n_x, n_h, n_y):
"""
两层神经网络参数初始化
:param n_x: 输入层节点数量
:param n_h: 隐藏层节点数量
:param n_y: 输出层节点数量
:return: parameters:一个包含W和b初始值的字典值
"""
W1 = np.random.randn(n_h, n_x) * 0.01
b1 = np.zeros((n_h, 1))
W2 = np.random.randn(n_y, n_h) * 0.01
b2 = np.zeros((n_y, 1))
assert (W1.shape == (n_h, n_x))
assert (b1.shape == (n_h, 1))
assert (W2.shape == (n_y, n_h))
assert (b2.shape == (n_y, 1))
parameters = {
"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2
}
return parameters
测试一下
# 测试initialize_parameters
print("==============测试initialize_parameters==============")
parameters = initialize_parameters(3, 2, 1)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
3.2.2 L层神经网络参数初始化
对于一个L层的神经网络而言,初始化会是什么样的?假设X(输入数据)的维度为(n_x, m),则:
第 L-1 层各参数维度:
W
[
L
−
1
]
=
(
n
[
L
−
1
]
,
n
[
L
−
2
]
)
W^{[L-1]=}(n^{[L-1]}, n^{[L-2]})
W[L−1]=(n[L−1],n[L−2])
b
[
L
−
1
]
=
(
n
[
L
−
1
]
,
1
)
b^{[L-1]=}(n^{[L-1]}, 1)
b[L−1]=(n[L−1],1)
Z
[
L
−
1
]
=
W
[
L
−
1
]
A
[
L
−
2
]
+
b
[
L
−
1
]
=
(
n
[
L
−
1
]
,
m
)
=
A
[
L
−
1
]
Z^{[L-1]} = W^{[L-1]} A^{[L-2]} + b^{[L-1]}=(n^{[L-1]}, m)=A^{[L-1]}
Z[L−1]=W[L−1]A[L−2]+b[L−1]=(n[L−1],m)=A[L−1]
第 L 层各参数维度:
W
[
L
]
=
(
n
[
L
]
,
n
[
L
−
1
]
)
W^{[L]}=(n^{[L]}, n^{[L-1]})
W[L]=(n[L],n[L−1])
b
[
L
]
=
(
n
[
L
]
,
1
)
b^{[L]}=(n^{[L]}, 1)
b[L]=(n[L],1)
Z
[
L
]
=
W
[
L
]
A
[
L
−
1
]
+
b
[
L
]
=
(
n
[
L
]
,
m
)
=
A
[
L
]
Z^{[L]} = W^{[L]} A^{[L-1]} + b^{[L]}=(n^{[L]}, m)=A^{[L]}
Z[L]=W[L]A[L−1]+b[L]=(n[L],m)=A[L]
# L层神经网络参数初始化
def L_initialize_parameters(layers_dims):
"""
L层神经网络参数初始化
:param layers_dims: 包含神经网络各层节点的列表
:return: parameters:一个包含W和b初始值的字典值
"""
parameters = {}
L = len(layers_dims)
for l in range(1, L):
parameters["W" + str(l)], parameters["b" + str(l)] = \
[np.random.randn(layers_dims[l], layers_dims[l - 1]) / np.sqrt(layers_dims[l - 1]),
np.zeros((layers_dims[l], 1))]
assert (parameters["W" + str(l)].shape == (layers_dims[l], layers_dims[l - 1]))
assert (parameters["b" + str(l)].shape == (layers_dims[l], 1))
return parameters
测试一下
# 测试L_initialize_parameters
print("==============测试L_initialize_parameters==============")
layers_dims = [5, 4, 3]
parameters = L_initialize_parameters(layers_dims)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
参数随机初始化完成,我们开始构建正向传播
3.3 正向传播
正向传播有以下三个步骤:
- LINEAR,线性求和部分
- LINEAR - >ACTIVATION,线性激活部分,其中激活函数将会使用ReLU或Sigmoid
- [LINEAR - > RELU] ×(L-1) - > LINEAR - > SIGMOID(整个模型),整合部分
线性正向传播模块(向量化所有示例)使用如下公式进行计算:
Z
[
l
]
=
W
[
l
]
A
[
l
−
1
]
+
b
[
l
]
Z^{[l]} = W^{[l]}A^{[l-1]} +b^{[l]}
Z[l]=W[l]A[l−1]+b[l]
3.3.1 LINEAR,线性求和部分
# 正向传播
# 正向传播中的linear部分
def linear_forward(A_prev, W, b):
"""
正向传播中的linear部分
:param A_prev: 前一层的激活函数的输出,维度(上一层节点数量, 样本数量)
:param W: 本层权重,维度(本层节点数量,上一层节点数量)
:param b: 本层偏向量,维度(本层节点数量,1)
:return: Z:本层预激活参数
linear_cache: 一个存储本层Z, W, b值的元组
"""
Z = np.dot(W, A_prev) + b
assert (Z.shape == (W.shape[0], A_prev.shape[1]))
linear_cache = (A_prev, W, b)
return Z, linear_cache
测试一下
# 测试linear_forward
print("==============测试linear_forward==============")
A_prev, W, b = testCases.linear_forward_test_case()
Z, linear_cache = linear_forward(A_prev, W, b)
print("Z = " + str(Z))
3.3.2 LINEAR->ACTIVATION,线性激活部分
为了更方便,我们将把两个功能(线性和激活)分组为一个功能(LINEAR-> ACTIVATION)。 因此,我们将实现一个执行LINEAR前进步骤,然后执行ACTIVATION前进步骤的功能。我们来看看这激活函数的数学实现
Sigmoid: σ ( Z ) = σ ( W A + b ) = 1 1 + e − ( W A + b ) \sigma(Z) = \sigma(W A + b) = \frac{1}{ 1 + e^{-(W A + b)}} σ(Z)=σ(WA+b)=1+e−(WA+b)1
ReLU: A = R E L U ( Z ) = m a x ( 0 , Z ) A=RELU(Z)=max(0,Z) A=RELU(Z)=max(0,Z)
为了实现LINEAR->ACTIVATION这个步骤, 使用的公式是:
A [ l ] = g ( Z [ l ] ) = g ( W [ l ] A [ l − 1 ] + b [ l ] ) A^{[l]} = g(Z^{[l]}) = g(W^{[l]}A^{[l-1]} +b^{[l]}) A[l]=g(Z[l])=g(W[l]A[l−1]+b[l])
其中,函数g会是sigmoid() 或者是 relu(),当然,sigmoid()只在输出层使用,现在我们开始构建正向传播线性激活部分
# 正向传播中的线性激活部分
def linear_activation_forward(A_prev, W, b, activation):
"""
正向传播中的线性激活部分
:param A_prev: 前一层的激活函数的输出,维度(上一层节点数量, 样本数量)
:param W: 本层权重,维度(本层节点数量,上一层节点数量)
:param b: 本层偏向量,维度(本层节点数量,1)
:param activation: 选择此层使用的激活函数,sigmoid/relu
:return: A:本层激活函数的输出
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)
assert (A.shape == (W.shape[0], A_prev.shape[1]))
cache = (linear_cache, activation_cache)
return A, cache
测试一下
# 测试linear_activation_forward
print("==============测试linear_activation_forward==============")
A_prev, W,b = testCases.linear_activation_forward_test_case()
A, linear_activation_cache = linear_activation_forward(A_prev, W, b, activation = "sigmoid")
print("sigmoid,A = " + str(A))
A, linear_activation_cache = linear_activation_forward(A_prev, W, b, activation = "relu")
print("ReLU,A = " + str(A))
我们把两层模型需要的前向传播函数做完了,那么多层网络模型的前向传播是怎样的呢?
3.3.3 L层神经网络正向传播
我们调用上面的那两个函数来实现它,为了在实现L层神经网络时更加方便,我们需要一个函数来复制前一个函数(带有RELU的linear_activation_forward)L-1次,然后用一个带有SIGMOID的linear_activation_forward跟踪它,我们来看一下它的结构是怎样的:
在下面的代码中,AL表示
A
[
L
]
=
σ
(
Z
[
L
]
)
=
σ
(
W
[
L
]
A
[
L
−
1
]
+
b
[
L
]
)
A^{[L]} = \sigma(Z^{[L]}) = \sigma(W^{[L]} A^{[L-1]} + b^{[L]})
A[L]=σ(Z[L])=σ(W[L]A[L−1]+b[L]) ,即
Y
^
\hat{Y}
Y^,预测向量
# L层神经网络正向传播
def L_model_forward(X, parameters):
"""
L层神经网络正向传播
:param X: 输入数据,维度(n_x, m)
:param parameters: W和b的初始值
:return: AL:最后的预测结果,维度(1, m)
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)], activation="relu")
caches.append((cache))
AL, cache = linear_activation_forward(A, parameters["W" + str(L)],
parameters["b" + str(L)], activation="sigmoid")
caches.append(cache)
assert (AL.shape == (1, X.shape[1]))
return AL, caches
测试一下
# 测试L_model_forward
print("==============测试L_model_forward==============")
X, parameters = testCases.L_model_forward_test_case()
AL, caches = L_model_forward(X, parameters)
print("AL = " + str(AL))
print("caches 的长度为 = " + str(len(caches)))
3.4 计算成本
我们已经把这两个模型的前向传播部分完成了,我们需要计算成本(误差),以确定它到底有没有在学习,成本的计算公式如下:
L = − 1 m ∑ i = 1 m ( y ( i ) log ( a [ L ] ( i ) ) + ( 1 − y ( i ) ) log ( 1 − a [ L ] ( i ) ) ) L=-\frac{1}{m} \sum\limits_{i = 1}^{m} (y^{(i)}\log\left(a^{[L] (i)}\right) + (1-y^{(i)})\log\left(1- a^{[L](i)}\right)) L=−m1i=1∑m(y(i)log(a[L](i))+(1−y(i))log(1−a[L](i)))
# 计算误差
def compute_cost(AL, Y):
"""
计算误差
:param AL: 与标签向量相对的预测向量,维度(1, m)
:param Y: 标签向量,维度(1, m)
:return: 交叉熵成本
"""
m = Y.shape[1]
cost = - np.sum(np.multiply(Y, np.log(AL)) + np.multiply((1 - Y), (np.log(1 - AL)))) / m
cost = np.squeeze(cost)
assert (cost.shape == ())
return cost
测试一下
# 测试compute_cost
print("==============测试compute_cost==============")
Y, AL = testCases.compute_cost_test_case()
print("cost = " + str(compute_cost(AL, Y)))
3.5 反向传播
反向传播用于计算相对于参数的损失函数的梯度,我们来看看向前和向后传播的流程图:
再来看一看对于线性计算的部分的公式:
使用
d
Z
[
l
]
dZ^{[l]}
dZ[l]来计算三个输出
(
d
W
[
l
]
,
d
b
[
l
]
,
d
A
[
l
]
)
(dW^{[l]}, db^{[l]}, dA^{[l]})
(dW[l],db[l],dA[l]),公式如下:
d W [ l ] = ∂ L ∂ W [ l ] = 1 m d Z [ l ] A [ L − 1 ] T dW^{[l]} = \frac{\partial \mathcal{L} }{\partial W^{[l]}} = \frac{1}{m}dZ^{[l]}A^{[L-1]T} dW[l]=∂W[l]∂L=m1dZ[l]A[L−1]T
d b [ l ] = ∂ L ∂ b [ l ] = 1 m ∑ i = 1 m d Z [ l ] ( i ) db^{[l]} = \frac{\partial \mathcal{L} }{\partial b^{[l]}} = \frac{1}{m} \sum_{i = 1}^{m} dZ^{[l](i)} db[l]=∂b[l]∂L=m1i=1∑mdZ[l](i)
d A [ l − 1 ] = ∂ L ∂ A [ l − 1 ] = W [ l ] T d Z [ l ] dA^{[l-1]} = \frac{\partial \mathcal{L} }{\partial A^{[l-1]}} = W^{[l] T} dZ^{[l]} dA[l−1]=∂A[l−1]∂L=W[l]TdZ[l]
与前向传播类似,我们有需要使用三个步骤来构建反向传播:
- LINEAR,反向线性求和部分
- LINEAR -> ACTIVATION,反向线性激活部分,其中ACTIVATION 计算Relu或者Sigmoid 的结果
- [LINEAR -> RELU] × (L-1) -> LINEAR -> SIGMOID 后向计算 (整个模型),整合部分
3.5.1 LINEAR,反向线性求和部分
# 反向传播
# 反向传播中linear部分
def linear_backward(dZ, cache):
"""
反向传播中linear部分
:param dZ: 目前层的成本梯度
:param cache: 来自前向传播的元组cache,包含A_prev, W, b
:return: 返回目前层的dW,db和前一层的dA
"""
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)
assert (dW.shape == W.shape)
assert (db.shape == b.shape)
assert (dA_prev.shape == dA_prev.shape)
return dA_prev, dW, db
测试一下
# 测试linear_backward
print("==============测试linear_backward==============")
dZ, linear_cache = testCases.linear_backward_test_case()
dA_prev, dW, db = linear_backward(dZ, linear_cache)
print("dA_prev = " + str(dA_prev))
print("dW = " + str(dW))
print("db = " + str(db))
3.5.2 LINEAR->ACTIVATION,反向线性激活部分
在dnn_utils.py文件中提供了sigmoid和relu函数的反向传播函数,大家可以自行查看
如果
g
(
.
)
g(.)
g(.)是激活函数, 那么sigmoid_backward 和 relu_backward 计算公式如下:
d Z [ l ] = d A [ l ] ∗ g ′ ( Z [ l ] ) dZ^{[l]} = dA^{[l]} * g'(Z^{[l]}) dZ[l]=dA[l]∗g′(Z[l])
# 实现反向传播中LINEAR->ACTIVATION部分
def linear_activation_backward(dA, cache, activation):
"""
实现反向传播中LINEAR->ACTIVATION部分
:param dA: 目前层激活后的梯度值
:param cache: 我们在前向传播存储linear_cache和activation_cache的元组
:param activation: 在此层使用的激活函数名,sigmoid/relu
:return: 返回目前层的dW,db和前一层的dA
"""
linear_cache, activation_cache = cache
if activation == "sigmoid":
dZ = sigmoid_backward(dA, activation_cache)
dA_prev, dW, db = linear_backward(dZ, linear_cache)
elif activation == "relu":
dZ = relu_backward(dA, activation_cache)
dA_prev, dW, db = linear_backward(dZ, linear_cache)
return dA_prev, dW, db
测试一下
# 测试linear_activation_backward
print("==============测试linear_activation_backward==============")
AL, linear_activation_cache = testCases.linear_activation_backward_test_case()
dA_prev, dW, db = linear_activation_backward(AL, linear_activation_cache, activation="sigmoid")
print("sigmoid:")
print("dA_prev = " + str(dA_prev))
print("dW = " + str(dW))
print("db = " + str(db) + "\n")
dA_prev, dW, db = linear_activation_backward(AL, linear_activation_cache, activation="relu")
print("relu:")
print("dA_prev = " + str(dA_prev))
print("dW = " + str(dW))
print("db = " + str(db))
我们已经把两层模型的后向计算完成了,对于多层模型我们也使用这两个函数来完成
3.5.3 L层神经网络反向传播
我们看一下流程:
在之前的正常传播中,我们存储了一些包含(X_prev,W,b和Z)的cache,在反向传播中,我们将会使用它们来计算梯度值,所以,在L层模型中,我们需要从L层遍历所有的隐藏层,在每一步中,我们需要使用那一层的cache值来进行反向传播
上面我们提到了
A
[
L
]
A^{[L]}
A[L]它属于输出层,
A
[
L
]
=
σ
(
Z
[
L
]
)
A^{[L]} = \sigma(Z^{[L]})
A[L]=σ(Z[L]),所以我们需要计算
d
A
L
dAL
dAL,计算公式如下:
d A L = y a [ L ] + 1 − y 1 − a [ L ] dAL=\frac{y}{a^{[L]}}+\frac{1-y}{1-a^{[L]}} dAL=a[L]y+1−a[L]1−y
# L层神经网络反向传播
def L_model_backward(AL, Y, caches):
"""
L层神经网络反向传播
:param AL: 预测向量,维度与Y一致,(1, m)
:param Y: 标签向量
:param caches: 正向传播中包含每层linear_cache和activation_cache列表
:return: grads:包含每层的dA, dW, db的字典值,用于后面梯度下降的参数更新
"""
grads = {}
L = len(caches)
dAL = - np.divide(Y, AL) + np.divide((1 - Y), (1 - AL))
grads["dA" + str(L)] = dAL
grads["dA" + str(L - 1)], grads["dW" + str(L)], grads["db" + str(L)] = \
linear_activation_backward(dAL, caches[L-1], activation="sigmoid")
for l in reversed(range(L-1)):
grads["dA" + str(l)], grads["dW" + str(l + 1)], grads["db" + str(l + 1)] = \
linear_activation_backward(grads["dA" + str(l + 1)], caches[l], activation="relu")
return grads
测试一下
# 测试L_model_backward
print("==============测试L_model_backward==============")
AL, Y_assess, caches = testCases.L_model_backward_test_case()
grads = L_model_backward(AL, Y_assess, caches)
print("dW1 = " + str(grads["dW1"]))
print("db1 = " + str(grads["db1"]))
print("dA1 = " + str(grads["dA1"]))
3.6 更新参数
现在我们把正反向传播都完成了,然后开始更新参数,我们来看看更新参数的公式
W [ l ] = W [ l ] − α d W [ l ] W^{[l]} = W^{[l]} - \alpha \text{ } dW^{[l]} W[l]=W[l]−α dW[l]
b [ l ] = b [ l ] − α d b [ l ] b^{[l]} = b^{[l]} - \alpha \text{ } db^{[l]} b[l]=b[l]−α db[l]
其中 α \alpha α是学习速率
# 更新参数
def update_parameters(parameters, grads, alpha):
"""
更新参数
:param parameters: 各层初始化后的W和b
:param grads: 各层dA, dW, db
:param alpha: 学习速率
:return: 更新后的各层W和b
"""
L = len(parameters) // 2
for l in range(L):
parameters["W" + str(l + 1)] = parameters["W" + str(l + 1)] -\
alpha * grads["dW" + str(l + 1)]
parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - \
alpha * grads["db" + str(l + 1)]
return parameters
测试一下
# 测试update_parameters
print("==============测试update_parameters==============")
parameters, grads = testCases.update_parameters_test_case()
parameters = update_parameters(parameters, grads, 0.1)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
至此为止,我们已经实现该神经网络中所有需要的函数,现在开始搭建神经网络
3.7 搭建两层神经网络
一个两层的神经网络模型图如下:
# 搭建两层神经网络
def two_layer_model(X, Y, layers_dims, alpha, n_iters, print_cost, isPlot):
"""
搭建两层神经网络
:param X: 训练集,维度(n_x, m)
:param Y: 标签向量, 维度(1, m)
:param layers_dims: 各层节点个数列表
:param alpha: 学习速率
:param n_iters: 迭代次数
:param print_cost: 成本打印控制
:param isPlot: 成本绘图控制
:return: A2:预测向量
parameters:在此情况下各层最佳参数W和b
"""
costs = []
n_x, n_h, n_y = layers_dims
# 初始化参数
parameters = initialize_parameters(n_x, n_h, n_y)
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
for i in range(n_iters):
# 正向传播
A1, cache1 = linear_activation_forward(X, W1, b1, activation="relu")
A2, cache2 = linear_activation_forward(A1, W2, b2, activation="sigmoid")
# 计算误差
cost = compute_cost(A2, Y)
#反向传播初始化
dA2 = - np.divide(Y, A2) + np.divide((1 - Y), (1 - A2))
# 反向传播
dA1, dW2, db2 = linear_activation_backward(dA2, cache2, activation="sigmoid")
dA0, dW1, db1 = linear_activation_backward(dA1, cache1, activation="relu")
grads = {
"dA2": dA2,
"dW2": dW2,
"db2": db2,
"dA1": dA1,
"dW1": dW1,
"db1": db1
}
# 更新参数
parameters = update_parameters(parameters, grads, alpha)
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
if i % 100 == 0:
costs.append((cost))
if print_cost:
print("第", i, "次迭代,成本为", cost)
if isPlot:
plt.plot(costs)
plt.xlabel("per hundred iters")
plt.ylabel("cost")
return A2, parameters
现在我们需要加载数据集,不懂去看我的另一篇文章:003_wz_wed_DL_课程一第二周编程题
# 加载数据集
train_dataset_orig = h5py.File("./datasets/train_catvnoncat.h5", "r")
test_dataset_orig = h5py.File("./datasets/test_catvnoncat.h5", "r")
train_dataset_x = np.array(train_dataset_orig["train_set_x"][:])
train_dataset_y = np.array(train_dataset_orig["train_set_y"][:])
test_dataset_x = np.array(test_dataset_orig["test_set_x"][:])
test_dataset_y = np.array(test_dataset_orig["test_set_y"][:])
train_dataset_x_tran = train_dataset_x.reshape(train_dataset_x.shape[0], -1).T
train_dataset_y_tran = train_dataset_y.reshape(train_dataset_y.shape[0], -1).T
test_dataset_x_tran = test_dataset_x.reshape(test_dataset_x.shape[0], -1).T
test_dataset_y_tran = test_dataset_y.reshape(test_dataset_y.shape[0], -1).T
train_dataset_x_stan = train_dataset_x_tran / 255
train_dataset_y_stan = train_dataset_y_tran
test_dataset_x_stan = test_dataset_x_tran / 255
test_dataset_y_stan = test_dataset_y_tran
测试一下
n_x = 12288
n_h = 7
n_y = 1
layers_dims = [n_x, n_h, n_y]
pred, parameters = two_layer_model(train_dataset_x_stan, train_dataset_y_stan, layers_dims=layers_dims, alpha=0.0075,
n_iters=2500, print_cost=True, isPlot=True)
m = pred.shape[1]
for i in range(m):
if pred[0, i] > 0.5:
pred[0, i] = 1
else:
pred[0, i] = 0
print("准确率为:", np.mean(pred == train_dataset_y_stan) * 100, "%")
plt.show()
3.8 两层神经网络预测
完成训练之后我们可以进行预测了
# 预测
def predict(X, Y, parameters):
"""
预测
:param X: 测试集,维度(n_x, 样本数量)
:param Y: 标签向量,维度(1, 样本数量)
:param parameters: 训练出来的最优参数W和b
:return: pred:预测向量
"""
# 正向传播
pred, caches = L_model_forward(X, parameters)
m = Y.shape[1]
for i in range(m):
if pred[0, i] > 0.5:
pred[0, i] = 1
else:
pred[0, i] = 0
print("准确率为:", np.mean(pred == Y) * 100, "%")
plt.show()
return pred
测试一下
# 两层神经网络预测
pred = predict(test_dataset_x_stan, test_dataset_y_stan, parameters)
可以看到在训练集上的准确率达到了100%,而在测试集上准确率只有72%,这说明数据太少,过拟合了
我们也可以自己选择一张图片进行预测,详细步骤看我这篇文章:003_wz_wed_DL_课程一第二周编程题
3.9 多层神经网络搭建
先来看看多层的网络的结构
# 搭建深层神经网络
def L_layer_model(X, Y, layers_dims, alpha, n_iters, print_cost, isPlot):
"""
深层神经网络搭建
:param X: 训练集,维度(n_x, m)
:param Y: 标签向量, 维度(1, m)
:param layers_dims: 各层节点个数列表
:param alpha: 学习速率
:param n_iters: 迭代次数
:param print_cost: 成本打印控制
:param isPlot: 成本绘图控制
:return: AL:预测向量
parameters:在此情况下各层最佳参数W和b
"""
costs = []
# 初始化参数
parameters = L_initialize_parameters(layers_dims)
for i in range(n_iters):
# 正向传播
AL, caches = L_model_forward(X, parameters)
# 计算误差
cost = compute_cost(AL, Y)
# 反向传播
grads = L_model_backward(AL, Y, caches)
# 更新参数
parameters = update_parameters(parameters, grads, alpha)
# 成本打印控制
if i % 100 == 0:
costs.append(cost)
if print_cost:
print("第", i, "次迭代,成本为", cost)
# 成本绘图控制
if isPlot:
plt.plot(costs)
plt.xlabel("per hundred iters")
plt.ylabel("cost")
return AL, parameters
测试一下
# 4层神经网络
layers_dims = [12288, 20, 7, 5, 1]
pred, parameters = L_layer_model(train_dataset_x_stan, train_dataset_y_stan, layers_dims=layers_dims, alpha=0.0075,
n_iters=2500, print_cost=True, isPlot=True)
m = pred.shape[1]
for i in range(m):
if pred[0, i] > 0.5:
pred[0, i] = 1
else:
pred[0, i] = 0
print("准确率为:", np.mean(pred == train_dataset_y_stan) * 100, "%")
plt.show()
3.10 多层神经网络预测
使用同一个预测函数即可
# 深层神经网络预测
pred = predict(test_dataset_x_stan, test_dataset_y_stan, parameters)
对于准确率而言,可以看到,由逻辑回归70%->两层神经网络72%->多层神经网络80%,准确率在逐步递增,或许继续调整超参数,准确率还可以继续上升
3.11 结果分析
我们看一看是哪些图片在L层模型中被错误地标记了,导致准确率没有提高
# 打印标记错误图片
def print_mislabeled_images(classes, X, Y, pred):
"""
打印标记错误图片
:param classes: 数据集标签:cat/no-cat
:param X: 测试集,维度(n_x, 样本数量)
:param Y: 标签向量,维度(1, 样本数量)
:param pred: 预测向量
:return:
"""
a = pred + Y
mislabeled_indices = np.asarray(np.where(a == 1))
plt.rcParams['figure.figsize'] = (40.0, 40.0) # set default size of plots
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(
"Prediction: " + classes[int(pred[0, index])].decode("utf-8") + " \n Class: " + classes[Y[0, index]].decode(
"utf-8")
)
测试一下
classes = np.array(test_dataset_orig["list_classes"][:])
# 打印被错误标记的图片
print_mislabeled_images(classes, test_dataset_x_stan, test_dataset_y_stan, pred)
plt.show()
对于将猫标记未非猫的图片,我认为是:
- 相机的拍摄角度不同,如图4,5
- 是猫的部位特写,如图2
对于将非猫标记为猫的图片,我认为是:
- 图片对比度和亮度高,如图1,3,6,10
- 图片背景复杂,色彩均匀,如图7,8,9
3.12 源码
四、总结
为什么我们在构建多层神经网络之前,要搭建两层神经网络呢?
因为多层神经网络和两层神经网络的唯一不同点是隐藏层的数量,两层神经网络的隐藏层一旦实现,我们就可以利用其函数实现多层神经网络的隐藏层,在此间我们唯一需要注意的就是数据的维度,数据维度很重要,希望大家都能手写计算一下,所以在代码中频繁使用assert
来确定数据的维度正确,这也方便我们排除错误;我们对于隐藏层的实现,分为两个步骤:1.线性求和部分 2.线性激活部分,最后将其进行整合构建多层神经网络,我们再来看一下其结构:
最后,我们再来顺一遍搭建多层神经网络的流程
流程 | 方法 |
---|---|
参数初始化 | 随机初始化参数,返回parameters |
循环开始 | start |
正向传播 | 传入 A [ 1 ] A^{[1]} A[1]和parameters,线性求和、激活,保存参数,返回AL和caches |
计算成本 | 传入AL,利用公式计算成本,返回cost |
反向传播 | 传入dAL,计算dW,db,dA_prev,返回grads |
更新参数 | 传入grads,利用公式更新参数,返回更新后的parameters |
循环结束 | end |
训练后预测 | 得到相对最佳参数,传入测试集,进行预测 |