这是吴恩达-神经网络和深度学习第四周的课后作业,在这里整理一下思路。
最后的模型是用来判断“是否是猫脸”的任意指定层数的神经网络,其中最后一层的激活函数是sigmoid,中间的隐藏层都用relu。
为了获得每一层的w,b参数,我们使用梯度下降优化,步骤如下:
- 初始化
输入层数,输出初始化的parameters字典,包含每一层的w和b - 前向传播
2.1 线性函数,输入A_prev,W,b,输出Z并保存linear cache(包含A_prev,W,b)
2.2 综合了一层中的线性函数与激活函数,输入A_prev,W,b和激活函数名,输出A和cache(包含linear cache和activation cache)
2.3 综合了所有层的线性函数与激活函数,输入X和parameters字典,输出AL和caches列表,这个列表有L个元素,由每一层的cache组成。 - 损失函数
输入AL和Y,计算cost - 后向传播
4.1 线性函数,输入dZ,cache(这一层的linear cache),输出dA_prev,dW,db。
4.2 综合了一层中的线性函数与激活函数,输入dA,cache(包含linear cache和activation cache)和激活函数名,输出dA_prev,dW,db。其实4.2与4.1的输出是一样的,但是4.2多了一个由dA计算dZ的环节,相应地参数中也要多一个激活函数名。
4.3 综合了所有层的线性函数与激活函数。输入AL,Y,caches,输出grads字典,包含所有的参数的梯度 - 更新参数
输入当前参数,grads字典和学习率,输出更新后的参数。
以上五步是完成了一次迭代,迭代足够多的次数后,我们就获得了所需的每一层的参数w和b,于是我们的模型就唯一确定了。我们还可以写一个predict函数来评估准确率,输入测试集的XY和parameters,输出对测试集的X进行辨认的标签集,并打印准确率。
下面是代码:
0.准备工作
import numpy as np
import h5py
import matplotlib.pyplot as plt
from testCases import *#参见资料包,或者在文章底部copy
from dnn_utils import sigmoid, sigmoid_backward, relu, relu_backward #参见资料包
import lr_utils #参见资料包,或者在文章底部copy
np.random.seed(1)
1. 初始化
这里要注意的是,第l层的w[l]在初始化时要除以np.sqrt(第l-1层的节点数),这是为了防止梯度消失或梯度爆炸。我一开始遗漏了这个细节,导致后面的cost下降十分缓慢几乎不能动。
L是layers_dims的元素个数,是包含了输入层的层数,也就是我们通常意义上的“层数”+1。所谓第一层,应该是第一个隐藏层,而不是输入层。
def initialize_parameters_deep(layers_dims):
"""
此函数是为了初始化多层网络参数而使用的函数。
参数:
layers_dims - 包含我们网络中每个图层的节点数量的列表
返回:
parameters - 包含参数“W1”,“b1”,...,“WL”,“bL”的字典:
W1 - 权重矩阵,维度为(layers_dims [1],layers_dims [1-1])
bl - 偏向量,维度为(layers_dims [1],1)
"""
np.random.seed(3)
L=len(layers_dims)
parameters={}
for l in range(1,L):
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))
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
2. 前向传播
2.1 线性函数
def linear_forward(A,W,b):
"""
实现前向传播的线性部分。
参数:
A - 来自上一层(或输入数据)的激活,维度为(上一层的节点数量,示例的数量)
W - 权重矩阵,numpy数组,维度为(当前图层的节点数量,前一图层的节点数量)
b - 偏向量,numpy向量,维度为(当前图层节点数量,1)
返回:
Z - 激活功能的输入,也称为预激活参数
cache - 一个包含“A”,“W”和“b”的字典,存储这些变量以有效地计算后向传递
"""
Z=np.dot(W,A)+b
assert(Z.shape==(W.shape[0],A.shape[1]))
cache=(A,W,b)
return Z,cache
2.2 综合线性函数与激活函数
def linear_activation_forward(A_prev,W,b,activation):
"""
实现LINEAR-> ACTIVATION 这一层的前向传播
参数:
A_prev - 来自上一层(或输入层)的激活,维度为(上一层的节点数量,示例数)
W - 权重矩阵,numpy数组,维度为(当前层的节点数量,前一层的大小)
b - 偏向量,numpy阵列,维度为(当前层的节点数量,1)
activation - 选择在此层中使用的激活函数名,字符串类型,【"sigmoid" | "relu"】
返回:
A - 激活函数的输出,也称为激活后的值
cache - 一个包含“linear_cache”和“activation_cache”的字典,我们需要存储它以有效地计算后向传递
"""
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)
assert(A.shape==(W.shape[0],A_prev.shape[1]))
cache=(linear_cache,activation_cache)
return A,cache
2.3 综合所有层的线性函数和激活函数
def L_model_forward(X,parameters):
"""
实现[LINEAR-> RELU] *(L-1) - > LINEAR-> SIGMOID计算前向传播,也就是多层网络的前向传播,为后面每一层都执行LINEAR和ACTIVATION
参数:
X - 数据,numpy数组,维度为(输入节点数量,示例数)
parameters - initialize_parameters_deep()的输出
返回:
AL - 最后的激活值
caches - 包含以下内容的缓存列表:
linear_relu_forward()的每个cache(有L-1个,索引为从0到L-2)
linear_sigmoid_forward()的cache(只有一个,索引为L-1)
"""
A=X
L=len(parameters)//2
caches=[]
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)
assert(AL.shape==(1,X.shape[1]))
return AL,caches
3.损失函数
def compute_cost(AL,Y):
"""
实施等式(4)定义的成本函数。
参数:
AL - 与标签预测相对应的概率向量,维度为(1,示例数量)
Y - 标签向量(例如:如果不是猫,则为0,如果是猫则为1),维度为(1,数量)
返回:
cost - 交叉熵成本
"""
m=Y.shape[1]
cost=(-1/m)*np.sum(np.multiply(Y,np.log(AL))+np.multiply(1-Y,np.log(1-AL)))
cost=np.squeeze(cost)
assert(cost.shape==())
return cost
4.后向传播
4.1 线性函数
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)
assert(W.shape==dW.shape)
assert(b.shape==db.shape)
assert(A_prev.shape==dA_prev.shape)
return dA_prev,dW,db
4.2 综合线性函数与激活函数
def linear_activation_backward(dA,cache,activation="relu"):
"""
实现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
4.3 综合所有层的线性函数与激活函数
实际上参数AL和Y的唯一作用就是计算dAL,所以也可以认为参数就是dAL和caches。
def L_model_backward(AL,Y,caches):
"""
对[LINEAR-> RELU] *(L-1) - > LINEAR - > SIGMOID组执行反向传播,就是多层网络的向后传播
参数:
AL - 概率向量,正向传播的输出(L_model_forward())
Y - 标签向量(例如:如果不是猫,则为0,如果是猫则为1),维度为(1,数量)
caches - 包含以下内容的cache列表:
linear_activation_forward("relu")的cache,不包含输出层
linear_activation_forward("sigmoid")的cache
返回:
grads - 具有梯度值的字典
grads [“dA”+ str(l)] = ...
grads [“dW”+ str(l)] = ...
grads [“db”+ str(l)] = ...
"""
grads={}
L=len(caches) #共有L层(不包括输入层)
m=AL.shape[1]
Y = Y.reshape(AL.shape)
dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
current_cache=L #第L层:输入dAL和caches[L],输出dA(L-1),dWL,dbL
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 (1,L)):#从第1层至第L-1层为relu函数
grads["dA"+str(l-1)],grads["dW"+str(l)],grads["db"+str(l)]=linear_activation_backward(grads["dA"+str(l)],caches[l-1],activation="relu")
return grads
5.更新参数
def update_parameters(parameters, grads, learning_rate):
"""
使用梯度下降更新参数
参数:
parameters - 包含你的参数的字典
grads - 包含梯度值的字典,是L_model_backward的输出
返回:
parameters - 包含更新参数的字典
参数[“W”+ str(l)] = ...
参数[“b”+ str(l)] = ...
"""
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
6.综合以上五步完成模型
这个模型会输出学习得到的参数,也可以打印学习过程中不断下降的成本值。
def L_layer_model(X, Y, layers_dims, learning_rate=0.0075, num_iterations=3000, print_cost=False,isPlot=True):
"""
实现一个L层神经网络:[LINEAR-> RELU] *(L-1) - > LINEAR-> SIGMOID。
参数:
X - 输入的数据,维度为(n_x,例子数)
Y - 标签,向量,0为非猫,1为猫,维度为(1,数量)
layers_dims - 层数的向量,维度为(n_y,n_h,···,n_h,n_y)
learning_rate - 学习率
num_iterations - 迭代的次数
print_cost - 是否打印成本值,每100次打印一次
isPlot - 是否绘制出误差值的图谱
返回:
parameters - 模型学习的参数。 然后他们可以用来预测。
"""
np.random.seed(1)
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 tens)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
return parameters
然后代入数据
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] # 5-layer model
parameters = L_layer_model(train_x, train_y, layers_dims, num_iterations = 2500, print_cost = True,isPlot=True)
运行结果如下:
目前迭代了0次,成本值为0.715732.
目前迭代了100次,成本值为0.674738.
目前迭代了200次,成本值为0.660337.
目前迭代了300次,成本值为0.646289.
目前迭代了400次,成本值为0.629813.
目前迭代了500次,成本值为0.606006.
目前迭代了600次,成本值为0.569004.
目前迭代了700次,成本值为0.519797.
目前迭代了800次,成本值为0.464157.
目前迭代了900次,成本值为0.408420.
目前迭代了1000次,成本值为0.373155.
目前迭代了1100次,成本值为0.305724.
目前迭代了1200次,成本值为0.268102.
目前迭代了1300次,成本值为0.238725.
目前迭代了1400次,成本值为0.206323.
目前迭代了1500次,成本值为0.179439.
目前迭代了1600次,成本值为0.157987.
目前迭代了1700次,成本值为0.142404.
目前迭代了1800次,成本值为0.128652.
目前迭代了1900次,成本值为0.112443.
目前迭代了2000次,成本值为0.085056.
目前迭代了2100次,成本值为0.057584.
目前迭代了2200次,成本值为0.044568.
目前迭代了2300次,成本值为0.038083.
目前迭代了2400次,成本值为0.034411.
7.评估准确性
def predict(X, y, parameters):
"""
该函数用于预测L层神经网络的结果,当然也包含两层
参数:
X - 测试集
y - 标签
parameters - 训练模型的参数
返回:
p - 给定数据集X的预测
"""
m=X.shape[1]
probas,caches=L_model_forward(X,parameters)
p=np.zeros((1,m))
assert(probas.shape==(1,m))
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
pred_train = predict(train_x, train_y, parameters) #训练集
pred_test = predict(test_x, test_y, parameters) #测试集
结果如下:
准确率=0.9952153110047847
准确率=0.78
8.用自己的图片验证
这我放在当前文件夹下的my_image.jpg
import scipy
from scipy import ndimage
my_image = "my_image.jpg" # change this to the name of your image file
my_label_y = [1] # the true class of your image (1 -> cat, 0 -> non-cat)
fname = my_image
image = np.array(ndimage.imread(fname, flatten=False))
my_image = scipy.misc.imresize(image, size=(64,64)).reshape((64*64*3,1))
my_predicted_image = predict(my_image, my_label_y, parameters)
plt.imshow(image)
print ("y = " + str(np.squeeze(my_predicted_image)) + ", your L-layer model predicts a \"" + classes[int(np.squeeze(my_predicted_image)),].decode("utf-8") + "\" picture.")
结果如下
准确率=1.0
y = 1.0, your L-layer model predicts a "cat" picture.