此专栏文章随时更新编辑,如果文章还没写完,请耐心等待,正常的频率是日更。
此文章主要是吴恩达在Cursera上的系列课程“深度学习(DeepLearning)”的学习笔记,这一篇是关于课程1第四周编程的笔记。
该专栏专注于梳理深度学习相关学科的基础知识。
以下是正文:
通过前面几篇文章的介绍(深度学习(Deep Learning)基础概念3-6),我们已经由浅入深的梳理了搭建神经网络模型的python实现。
从第四篇文章解决逻辑回归问题的神经网络开始,本质上是搭建一个具有1层的神经网络,然后第五篇文章我们讨论了如何开始搭建2层神经网络,本篇文章作为如何搭建基础神经网络的最后一篇,我们来讨论一下搭建L层神经网络的思路和python实现。
下面先放出文章结构:我们的目标是什么
程序设计
动手搭建神经网络模型初始化
前向传播
计算代价
后向传播
优化参数
整合模型
数据测试
以下是正文:
1. 我们的目标是什么
从我们要解决的问题出发,假设我有一张图片,想通过程序判断,该图片是不是一只猫?
这个能判断图片是否是一只猫的程序就是我们要实现的目标:L层神经网络
如何得到这个神经网络呢?通过大量有标记图片的训练(监督学习)。即输入所有图片像素(特征值)和对应图片是否是一只猫(标签),优化模型参数。
当然,这里我们只是以识别猫的图片任务为例,这里搭建的神经网络可以用于训练解决其他任务,只要将任务的特征值和标签输入该神经网络进行训练。
2 程序设计
在我们动手搭建神经网络模型直线,先考虑一下如何设计该程序。
根据前面几篇文章的经验,我们知道,一个神经网络模型的工作流程如下:确定模型参数
前向传播
计算代价
后向传播
优化参数
上述2-5循环反复,直到得到我们满意的参数
下面我们先写出该流程的伪代码:
parameters = initialize_parameters()
for i in iterations:
forward_propagation()
compute_cost()
backward_propagation()
update_parameters()
接下来就可以开始动手写代码了。
3. 动手搭建神经网络模型
首先介绍一下我们需要用到的packagesnumpy用于科学计算。
matplotlib.pyplot用于画图。
testCases提供了一些测试集,用于评估我们的函数性能。这里的"import *"是引入所有函数的意思。
dnn_utils_v2库中引入了一些计算函数的方法,如sigmoid和relu。
%matplotlib inline是jupyter notebook里的命令, 意思是将那些用matplotlib绘制的图显示在页面里而不是弹出一个窗口
plt.rcParams是在设置绘图的一些参数。
‘autoreload 2’的意思是,如果我们对引入的库中的模型进行修改,ipython会自动重新加载这些模型。具体例子见这里。
np.random.seed(1)为保证每个人运行代码都得到相同的结果(实验结果的可复现),因此我们随机生成种子,使代码中生成一致的随机数。
import numpy as np
import h5py
import matplotlib.pyplot as plt
from testCases_v2 import *
from dnn_utils_v2 import sigmoid, sigmoid_backward, relu, relu_backward
%matplotlib inline
plt.rcParams['figure.figsize'] = (5.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
%load_ext autoreload
%autoreload 2
np.random.seed(1)
参考资料:
3.1 初始化
首先我们做的是,根据初始化神经网络的参数。
这里的参数指的是所有的W和b,这里,每一层的参数是以矩阵的形式存在的。
因此,本质上,我们需要确定矩阵的维度和元素。
先说元素,为保证每一个神经元能起到不同的效果,我们给每层的W矩阵一个随机数。对b取值初始化全0.
再说维度,对于
层的参数
来说,行数是该层神经元的数量,列数是上一层神经元的数量。
对于b来说,由于numpy中传播规则的存在,b是行数与W相同,1列的向量。
代码:
def initialize_parameters_deep(layer_dims):
"""Arguments:layer_dims -- python array (list) containing the dimensions of each layer in our networkReturns:parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":Wl -- weight matrix of shape (layer_dims[l], layer_dims[l-1])bl -- bias vector of shape (layer_dims[l], 1)"""
np.random.seed(3)
parameters = {}
L = len(layer_dims) # number of layers in the network
for l in range(1, L):
parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) * 0.01
parameters['b' + str(l)] = np.zeros((layer_dims[l],1))
assert(parameters['W' + str(l)].shape == (layer_dims[l], layer_dims[l-1]))
assert(parameters['b' + str(l)].shape == (layer_dims[l], 1))
return parameters
3.2 前向传播
先看一下这个图,前向传播的过程可以描述为,先重复线性加权和relu的过程l-1次,最后再进行一次线性加权和sigmoid。
我们首先定义函数:计算线性加权的linear_forward()
先考虑一下这个函数的输入和输出:
线性加权函数
如果我们把每一层的输入看做上一层激活函数的输出,用
表示,这里把第一层的输入X看做
。
那么,线性加权函数linear_forward()的需要的输入就是上一层的输出A和加权参数W、b。
输出是
和cache,这里的cache是用来保存这一层的参数,用于后向传播。
def linear_forward(A, W, b):
"""Implement the linear part of a layer's forward propagation.Arguments:A -- activations from previous layer (or input data): (size of previous layer, number of examples)W -- weights matrix: numpy array of shape (size of current layer, size of previous layer)b -- bias vector, numpy array of shape (size of the current layer, 1)Returns:Z -- the input of the activation function, also called pre-activation parametercache -- a python dictionary containing "A", "W" and "b" ; stored for computing the backward pass efficiently"""
Z = np.dot(W,A)+b
assert(Z.shape == (W.shape[0], A.shape[1]))
cache = (A, W, b)
return Z, cache
线性加权—激活函数
有了线性加权函数,我们就可以进一步定义线性加权—激活函数。
还记得我们开开始搭建神经网络之前先引入了一些列计算激活函数的model:
from dnn_utils_v2 import sigmoid, sigmoid_backward, relu, relu_backward
这里的sigmoid,relu可以用于计算激活函数的输出。
现在,我们可以先定义线性加权—激活函数的伪代码了。
def linear_activation_forward():
if "sigmoid":
linear_forward()
sigmoid()
elif "relu":
linear_forward()
relu()
这就是数据在一层神将网络前向传播的实现,首先对上一层的输出进行线性加权,然后根据激活函数是sigmoid还是relu进行计算。
下面给出完整函数实现:
def linear_activation_forward(A_prev, W, b, activation):
"""Implement the forward propagation for the LINEAR->ACTIVATION layerArguments:A_prev -- activations from previous layer (or input data): (size of previous layer, number of examples)W -- weights matrix: numpy array of shape (size of current layer, size of previous layer)b -- bias vector, numpy array of shape (size of the current layer, 1)activation -- the activation to be used in this layer, stored as a text string: "sigmoid" or "relu"Returns:A -- the output of the activation function, also called the post-activation valuecache -- a python dictionary containing "linear_cache" and "activation_cache";stored for computing the backward pass efficiently"""
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
上面是一层神将网络前向传播的实现,接下来就是如何实现L层神经网络的前向传播了:
这里我们仅需要把线性加权—激活函数(relu)循环执行
次,最后执行一次加权—激活函数(sigmoid)。代码如下:
def L_model_forward(X, parameters):
"""Implement forward propagation for the [LINEAR->RELU]*(L-1)->LINEAR->SIGMOID computationArguments:X -- data, numpy array of shape (input size, number of examples)parameters -- output of initialize_parameters_deep()Returns:AL -- last post-activation valuecaches -- list of caches containing:every cache of linear_relu_forward() (there are L-1 of them, indexed from 0 to L-2)the cache of linear_sigmoid_forward() (there is one, indexed L-1)"""
caches = []
A = X
L = len(parameters) // 2 # number of layers in the neural network
# Implement [LINEAR -> RELU]*(L-1). Add "cache" to the "caches" list.
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)
# Implement LINEAR -> SIGMOID. Add "cache" to the "caches" list.
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
这里可以通过parameters大小的一半来确认神经网络的层数,利用caches.append(cache)每次运行把新的cache保存到caches里。
这里是一个编程技巧,首先定义caches = [],是一个列表,然后利用列表的性质.append()来添加新的内容,之前我们说过cache的类型是元组,而列表里的元素可以是任何类型。
所以当我们想创建一个变量用于存储不同类型的数据时,一半选择创建列表。
这里我们的目标函数搭建完成了L_model_forward(X, parameters) -> (AL, caches)
3.3 计算代价
计算代价的目的是确认我们的神经网络通过一次次的迭代正在不断优化!
先给出公式:
代价函数的实现就是计算上述公式:
def compute_cost(AL, Y):
"""Implement the cost function defined by equation (7).Arguments:AL -- probability vector corresponding to your label predictions, shape (1, number of examples)Y -- true "label" vector (for example: containing 0 if non-cat, 1 if cat), shape (1, number of examples)Returns:cost -- cross-entropy cost"""
m = Y.shape[1]
# Compute loss from aL and y.
cost = -(1/m)*(np.dot(Y,np.log(AL).T) + np.dot((1-Y), np.log(1-AL).T))
cost = np.squeeze(cost) # To make sure your cost's shape is what we expect (e.g. this turns [[17]] into 17).
assert(cost.shape == ())
return cost
这里,代价函数的另一个作用是给我们一个优化的“方向”,这个方向就是——代价最小!
实现方式就是通过求代价函数对于神经网络的线性加权部分的参数的偏导数。
当然,这也就是下面我们要开始搭建的函数:后向传播。
3.4 后向传播
搭建网络的最后一部分:后向传播,先看流程图:
这里,我们的反向传播目标是求
如果我们有了
就非常容易的得到上面的值,为什么呢,因为Z是关于W和b的线性函数
所以:
把我们的目标函数写成如上形式的好处是:我们的目标
都是关于
的函数!
也就是说,我们的输入只需要
下面是代码:
def linear_backward(dZ, cache):
"""Implement the linear portion of backward propagation for a single layer (layer l)Arguments:dZ -- Gradient of the cost with respect to the linear output (of current layer l)cache -- tuple of values (A_prev, W, b) coming from the forward propagation in the current layerReturns:dA_prev -- Gradient of the cost with respect to the activation (of the previous layer l-1), same shape as A_prevdW -- Gradient of the cost with respect to W (current layer l), same shape as Wdb -- Gradient of the cost with respect to b (current layer l), same shape as b"""
A_prev, W, b = cache
m = A_prev.shape[1]
dW = (1/m)*np.dot(dZ,A_prev.T)
db = (1/m)*np.sum(dZ,1,keepdims=True)
dA_prev = np.dot(W.T,dZ)
assert (dA_prev.shape == A_prev.shape)
assert (dW.shape == W.shape)
assert (db.shape == b.shape)
return dA_prev, dW, db
有了这个函数,只要得到
就可以实现真正的后向传播了。
这时候我们又需要用到一开始提到的库里面的函数:
from dnn_utils_v2 import sigmoid, sigmoid_backward, relu, relu_backward
这里的 sigmoid_backward和relu_backward可以用来非常方便的得到
。
下面搭建后向传播函数,和前向传播一样,这里需要区分激活函数是那种:“relu”还是“sigmoid”。
def linear_activation_backward(dA, cache, activation):
"""Implement the backward propagation for the LINEAR->ACTIVATION layer.Arguments:dA -- post-activation gradient for current layer lcache -- tuple of values (linear_cache, activation_cache) we store for computing backward propagation efficientlyactivation -- the activation to be used in this layer, stored as a text string: "sigmoid" or "relu"Returns:dA_prev -- Gradient of the cost with respect to the activation (of the previous layer l-1), same shape as A_prevdW -- Gradient of the cost with respect to W (current layer l), same shape as Wdb -- Gradient of the cost with respect to b (current layer l), same shape as 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
在这个函数的基础上,我们继续完成L层神经网络的后向传播函数:第一步,求
,也就是
对
求偏导数。
利用linear_activation_backward()迭代向上一层求导。
def L_model_backward(AL, Y, caches):
"""Implement the backward propagation for the [LINEAR->RELU] * (L-1) -> LINEAR -> SIGMOID groupArguments:AL -- probability vector, output of the forward propagation (L_model_forward())Y -- true "label" vector (containing 0 if non-cat, 1 if cat)caches -- list of caches containing:every cache of linear_activation_forward() with "relu" (it's caches[l], for l in range(L-1) i.e l = 0...L-2)the cache of linear_activation_forward() with "sigmoid" (it's caches[L-1])Returns:grads -- A dictionary with the gradientsgrads["dA" + str(l)] = ...grads["dW" + str(l)] = ...grads["db" + str(l)] = ..."""
grads = {}
L = len(caches) # the number of layers
m = AL.shape[1]
Y = Y.reshape(AL.shape) # after this line, Y is the same shape as AL
# Initializing the backpropagation
dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
# Lth layer (SIGMOID -> LINEAR) gradients. Inputs: "AL, Y, caches". Outputs: "grads["dAL"], grads["dWL"], grads["dbL"]
current_cache = caches[L-1]
grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(dAL, current_cache, activation = "sigmoid")
for l in reversed(range(L-1)):
# lth layer: (RELU -> LINEAR) gradients.
# Inputs: "grads["dA" + str(l + 2)], caches". Outputs: "grads["dA" + str(l + 1)] , grads["dW" + str(l + 1)] , grads["db" + str(l + 1)]
current_cache = caches[l]
dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads["dA" + str(l+2)], current_cache, activation = "relu")
grads["dA" + str(l + 1)] = dA_prev_temp
grads["dW" + str(l + 1)] = dW_temp
grads["db" + str(l + 1)] = db_temp
### END CODE HERE ###
return grads
3.5 优化参数
又到了优化参数的时间,得到了
,乘以学习率
就可以方便的优化参数了!
def update_parameters(parameters, grads, learning_rate):
"""
Update parameters using gradient descent
Arguments:
parameters -- python dictionary containing your parameters
grads -- python dictionary containing your gradients, output of L_model_backward
Returns:
parameters -- python dictionary containing your updated parameters
parameters["W" + str(l)] = ...
parameters["b" + str(l)] = ...
"""
L = len(parameters) // 2 # number of layers in the neural network
# Update rule for each parameter. Use a for loop.
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
4. 整合模型
最后一步,整合模型!
首先看一下我们已经写完的函数:
def initialize_parameters_deep(layer_dims):
...
return parameters
def L_model_forward(X, parameters):
...
return AL, caches
def compute_cost(AL, Y):
...
return cost
def L_model_backward(AL, Y, caches):
...
return grads
def update_parameters(parameters, grads, learning_rate):
...
return parameters
上面的函数也对应着我们搭建网络的顺序:初始化参数
前向传播
计算代价
后向传播
优化参数
代码:
def L_layer_model(X, Y, layers_dims, learning_rate = 0.0075, num_iterations = 3000, print_cost=False):#lr was 0.009
"""Implements a L-layer neural network: [LINEAR->RELU]*(L-1)->LINEAR->SIGMOID.Arguments:X -- data, numpy array of shape (number of examples, num_px * num_px * 3)Y -- true "label" vector (containing 0 if cat, 1 if non-cat), of shape (1, number of examples)layers_dims -- list containing the input size and each layer size, of length (number of layers + 1).learning_rate -- learning rate of the gradient descent update rulenum_iterations -- number of iterations of the optimization loopprint_cost -- if True, it prints the cost every 100 stepsReturns:parameters -- parameters learnt by the model. They can then be used to predict."""
np.random.seed(1)
costs = [] # keep track of cost
# Parameters initialization.
parameters = initialize_parameters_deep(layers_dims)
# Loop (gradient descent)
for i in range(0, num_iterations):
# Forward propagation: [LINEAR -> RELU]*(L-1) -> LINEAR -> SIGMOID.
AL, caches = L_model_forward(X, parameters)
# Compute cost.
cost = compute_cost(AL, Y)
# Backward propagation.
grads = L_model_backward(AL, Y, caches)
# Update parameters.
parameters = update_parameters(parameters, grads, learning_rate)
# Print the cost every 100 training example
if print_cost and i % 100 == 0:
print ("Cost after iteration%i:%f" %(i, cost))
if print_cost and i % 100 == 0:
costs.append(cost)
# plot the cost
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per tens)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
return parameters
通过不停的迭代,训练我们的模型参数,最终得到性能优异的模型。
5. 数据测试
train_x_orig, train_y, test_x_orig, test_y, classes = load_data() #读入数据
parameters = L_layer_model(train_x, train_y, layers_dims, num_iterations = 2500, print_cost = True) #训练模型参数
我们可以看到,通过不断的训练,模型的代价越来越小。
pred_train = predict(train_x, train_y, parameters)
> Accuracy: 0.985645933014
在训练集上达到了0.9856的精度,非常高!
pred_test = predict(test_x, test_y, parameters)
> Accuracy: 0.8
在测试集上也有0.8的精度!
ok,至此我们的目标达成!搭建一个L层的神经网络,并利用猫的图片进行训练,训练后的模型在测试集上达到80%的精度!