吴恩达深度学习专项课程的所有实验均采用Jupyter Notebooks实现,不熟悉的朋友可以提前使用一下Notebooks。本周的编程作业主要包含两部分:第一部分Python基础,旨在通过该实验熟悉Python Numpy的基本操作,函数的用法、向量矩阵的操作以及理解NumPy中的广播,最后能够编写出向量化的代码,为之后的实验打好基础;第2部分,用神经网络的思维来实现逻辑回归算法,包括构建学习算法的一般框架,为之后实现神经网络算法做准备。
目录
一、实验1:Python基础
1.用numpy构建基础函数
- sigmoid函数
首先使用math.exp()实现sigmoid函数,然后再用np.exp()实现,感受后者的优越性。
math.exp():
import math
def basic_sigmoid(x): #使用math.exp() 参数必须是标量 函数的输入输出都是标量
s=1/(1+math.exp(-x))
return s
basic_sigmoid(3)
x=[1,2,3]
basic_sigmoid(x) #当输入不是标量时 会报错
np.exp():
import numpy as np
x=np.array([1,2,3]) #声明一个一维数组
print(np.exp(x)) #相当于对数组中的每个元素都取以e为底的指数
#数组加一个标量 相当于数组中每个元素都加上该标量
x=np.array([1,2,3])
print(x+3)
def sigmoid(x): #使用np.exp()实现sigmoid函数 输入可以是标量、向量、矩阵、高维张量
s=1/(1+np.exp(-x))
return s #s 与 x 同维
x=np.array([1,2,3])
sigmoid(x)
- 计算sigmoid 梯度
计算sigmoid函数的导数:
def sigmoid_derivative(x):
s=1/(1+np.exp(-x))
ds=s*(1-s)
return ds
x=np.array([1,2,3])
print(sigmoid_derivative(x))
- 数组变形
两个常用的函数:
X.shape 返回矩阵/向量X的大小; X.reshape() 把矩阵/向量X转变为其他形状
比如,在计算机中图片用3维数组来存储,接下来你需要做的是把它转换为一个列向量:
def image2vector(image):
#image 三维数组 (length,height,depth)
#返回一个列向量v (length*height*depth,1)
v = image.reshape(image.shape[0]*image.shape[1]*image.shape[2],1)
#v = image.reshape(-1,1)
return v #列向量 2维数组 (m,1)
#注意NumPy中最好用2维数组来声明向量 向量是特殊的矩阵(m,1)或(1,n) 不要用一维数组来声明向量
image = np.array([[[ 0.67826139, 0.29380381],
[ 0.90714982, 0.52835647],
[ 0.4215251 , 0.45017551]],
[[ 0.92814219, 0.96677647],
[ 0.85304703, 0.52351845],
[ 0.19981397, 0.27417313]],
[[ 0.60659855, 0.00533165],
[ 0.10820313, 0.49978937],
[ 0.34144279, 0.94630077]]])
print ("image2vector(image) = " + str(image2vector(image)))
- 对矩阵中的行进行规范化
矩阵中的每个行向量除以它的模长,返回规范化后的矩阵,如:
def normalizeRows(x):
x_norm=np.linalg.norm(x,axis=1,keepdims=True) #axis=1 对每一个行向量求模长
#keepdims保持形状 用2维数组表示向量 避免不必要的麻烦
x=x/x_norm #每一个行向量除以它的模长 进行规范化 广播
return x
x = np.array([
[0, 3, 4],
[1, 6, 4]])
print("normalizeRows(x) = " + str(normalizeRows(x)))
- 广播和softmax函数
接下来用NumPy实现softmax函数,你可以把softmax函数看作是一个规范化的函数:
def softmax(x):
x_exp=np.exp(x)
x_sum=np.sum(x_exp,axis=1) #对每一行求和 返回一个一维数组
x_sum=x_sum.reshape(x_exp.shape[0],1) #将一维数组转换为列向量(2维数组)
#x_sum=x_sum.reshape(-1,1)
s=x_exp/x_sum #广播
return s
x = np.array([
[9, 2, 5, 0, 0],
[7, 5, 0, 0 ,0]])
print("softmax(x) = " + str(softmax(x)))
2.向量化
在深度学习中,应尽可能避免显式的for循环,使用向量化编程,提高运行效率:
非向量化和向量化对比:
#非向量化和向量化对比
#非向量化实现
import time
x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]
### 向量内积 ###
tic = time.process_time()
dot = 0
for i in range(len(x1)):
dot+= x1[i]*x2[i]
toc = time.process_time()
print ("dot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")
### 向量外积 ###
tic = time.process_time()
outer = np.zeros((len(x1),len(x2))) # we create a len(x1)*len(x2) matrix with only zeros
for i in range(len(x1)):
for j in range(len(x2)):
outer[i,j] = x1[i]*x2[j]
toc = time.process_time()
print ("outer = " + str(outer) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")
### 向量乘法 对应位置元素相乘 ###
tic = time.process_time()
mul = np.zeros(len(x1))
for i in range(len(x1)):
mul[i] = x1[i]*x2[i]
toc = time.process_time()
print ("elementwise multiplication = " + str(mul) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")
### 矩阵与向量相乘 ###
W = np.random.rand(3,len(x1)) # Random 3*len(x1) numpy array
tic = time.process_time()
gdot = np.zeros(W.shape[0])
for i in range(W.shape[0]):
for j in range(len(x1)):
gdot[i] += W[i,j]*x1[j]
toc = time.process_time()
print ("gdot = " + str(gdot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")
#向量化实现
x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]
### 向量内积 ###
tic = time.process_time()
dot = np.dot(x1,x2)
toc = time.process_time()
print ("dot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")
### 向量外积 ###
tic = time.process_time()
outer = np.outer(x1,x2)
toc = time.process_time()
print ("outer = " + str(outer) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")
### 向量乘法 对应位置元素相乘 ###
tic = time.process_time()
mul = np.multiply(x1,x2)
toc = time.process_time()
print ("elementwise multiplication = " + str(mul) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")
### 矩阵与向量乘法 ###
tic = time.process_time()
dot = np.dot(W,x1)
toc = time.process_time()
print ("gdot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")
- 实现L1和L2损失函数
def L1(yhat,y):
loss=np.sum(np.abs(yhat-y))
return loss
yhat = np.array([.9, 0.2, 0.1, .4, .9])
y = np.array([1, 0, 0, 1, 1])
print("L1 = " + str(L1(yhat,y)))
'''
def L2(yhat,y):
loss=(yhat-y).dot(yhat-y) #向量内积 用一维数组表示向量有时也是可以的,但是最好用2维数组表示
return loss
'''
def L2(yhat,y):
yhat = yhat.reshape(-1,1)
y = y.reshape(-1,1)
loss = (yhat-y).T.dot(yhat-y)
return loss[0][0]
yhat = np.array([.9, 0.2, 0.1, .4, .9])
y = np.array([1, 0, 0, 1, 1])
print("L2 = " + str(L2(yhat,y)))
二、实验2:用神经网络的思维实现逻辑回归
本次实验利用逻辑回归算法构建一个分类器(2分类),判断一张图片是否是关于猫的图片。
- 尽可能不要在代码中使用显式循环,使用向量化编程。
通过本次实验你将学到如下技能
- 构建学习算法的一般结构,包括:
- 参数初始化
- 计算算法的代价函数和梯度
- 使用优化算法求解参数(梯度下降法)
- 按照正确的顺序把以上三个函数放在一个主模型函数中
1.导入必要的包
import numpy as np
import matplotlib.pyplot as plt
import h5py #用于与存储在H5文件中的数据集交互
#scipy和PIL 用于最后用你自己的图片测试你的模型
import scipy
from PIL import Image
from scipy import ndimage
from lr_utils import load_dataset #该函数在lr_utils.py文件中
%matplotlib inline
2.数据集预处理
给定的数据集(data.h5)包含:
一个训练集,包含m_train张标记图片,y=1猫图,y=0非猫图
一个测试集,包含m_test张标记图片
每张图片都是彩色图,形状(num_px(height),num_px(width),3),height=width,3代表RGB颜色通道。
- 加载原始数据集
# 加载数据
#_orig代表原始数据 接下来会对数据进行预处理 得到train_set_x
#train_set_x_orig/test_set_x_orig是一个四维数组 (mtrain/mtest,height,width,3)
train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset()
load_dataset()函数:
def load_dataset():
train_dataset = h5py.File('./datasets/train_catvnoncat.h5', "r")
train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # your train set features
train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # your train set labels
test_dataset = h5py.File('datasets/test_catvnoncat.h5', "r")
test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # your test set features
test_set_y_orig = np.array(test_dataset["test_set_y"][:]) # your test set labels
classes = np.array(test_dataset["list_classes"][:]) # the list of classes
#转变为行向量 用2维数组表示
train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0]))
test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0]))
return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes
- 可视化数据集中的一张图片
#可视化其中的一张图片
index=25
plt.imshow(train_set_x_orig[index]) #每行代表一个样本/图片
print ("y = " + str(train_set_y[:, index]) + ", it's a '" + classes[np.squeeze(train_set_y[:, index])].decode("utf-8") + "' picture.")
- 查看原始数据集的相关信息
m_train = train_set_x_orig.shape[0] #训练集图片数
m_test = test_set_x_orig.shape[0] #测试集图片数
num_px = train_set_x_orig[0].shape[0] #每张图片height/width
print ("Number of training examples: m_train = " + str(m_train))
print ("Number of testing examples: m_test = " + str(m_test))
print ("Height/Width of each image: num_px = " + str(num_px))
print ("Each image is of size: (" + str(num_px) + ", " + str(num_px) + ", 3)")
print ("train_set_x shape: " + str(train_set_x_orig.shape)) #四维数组
print ("train_set_y shape: " + str(train_set_y.shape)) #2维数组 表示的行向量
print ("test_set_x shape: " + str(test_set_x_orig.shape))
print ("test_set_y shape: " + str(test_set_y.shape))
- 对原始数据集进行预处理
原始数据集转型:
#对训练集和测试集进行转型
train_set_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0], -1).T
test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T
print ("train_set_x_flatten shape: " + str(train_set_x_flatten.shape))
print ("train_set_y shape: " + str(train_set_y.shape))
print ("test_set_x_flatten shape: " + str(test_set_x_flatten.shape))
print ("test_set_y shape: " + str(test_set_y.shape))
print ("sanity check after reshaping: " + str(train_set_x_flatten[0:5,0]))
对转型后的数据集进行标准化:
#图片特征向量中的每个值0-255 接下来标准化数据集 转换为0-1之间
train_set_x = train_set_x_flatten/255 #训练集
test_set_x = test_set_x_flatten/255 #测试集
3.学习算法的一般结构
用神经网络的思维构建逻辑回归算法,下面这幅图解释为什么逻辑回归可以看作一个简单的神经网络:
- 逻辑回归算法的数学表达式
- 关键步骤
1)初始化模型参数
2)最小化代价学习模型参数
3)用学好的参数在测试集上进行预测
4)分析结果并得出结论
4.构建学习算法的各个部分
构建一个神经网络的主要步骤:
- 定义网络结构(输入特征的维数等)
- 初始化模型参数
- 迭代:
- 前向传播计算当前的损失
- 反向传播计算当前的梯度(最终输出对模型参数求偏导)
- 用梯度下降法更新参数
分别构建上述3部分,最后把他们汇总到一个函数中,我们称为模型model().
- sigmoid函数
def sigmoid(z):
'''
计算sigmoid(z)
参数:
z -- 可以是标量、向量、矩阵等任意形式的数组
返回:
s -- sigmoid(z)
'''
s=1/(1+np.exp(-z))
return s
print ("sigmoid([0, 2]) = " + str(sigmoid(np.array([0,2]))))
- 初始化参数
def initialize_with_zeros(dim):
'''
初始化参数w为(dim,1)的零列向量 初始化b=0
参数:
dim -- 输入特征数量
返回:
w -- 初始化的权重参数(dim,1)
b -- 初始化的偏置参数 b=0
'''
w=np.zeros((dim,1))
#w = np.zeros(dim).reshape(dim,1)
#w = np.zeros(dim).reshape(-1,1)
#w = np.zeros(dim).reshape((dim,1))
b=0
#断言
assert(w.shape==(dim,1))
assert(isinstance(b,float) or isinstance(b,int))
return w,b
dim = 2
w, b = initialize_with_zeros(dim)
print ("w = " + str(w))
print ("b = " + str(b))
- 前向/反向传播
前向传播计算代价,反向传播计算梯度。
def propagate(w,b,X,Y):
'''
计算代价和梯度
参数:
w -- 权重参数 (num_px*num_px*3,1)
b -- 偏置参数 标量
X -- 训练集特征矩阵 (num_px*num_px*3,mtrain) 每一列为一张图片的特征向量
Y -- 训练集标签 行向量(1,mtrain) 0非猫 1猫
返回:
cost -- 逻辑回归算法的代价
dw -- 代价对w的梯度 与w同维
db -- 代价对b的梯度 与b同维
'''
m=X.shape[1] #训练样本数
A=sigmoid(np.dot(w.T,X)+b) #(1,m)
cost=-np.mean(Y*np.log(A)+(1-Y)*np.log(1-A))
dw=np.dot(X,(A-Y).T)/m #对m个样本产生的梯度求均值
db=np.mean(A-Y)
assert(dw.shape==w.shape)
assert(db.dtype==float)
cost=np.squeeze(cost)
assert(cost.shape==())
grads={'dw':dw,'db':db}
return grads,cost
w, b, X, Y = np.array([[1],[2]]), 2, np.array([[1,2],[3,4]]), np.array([[1,0]])
grads, cost = propagate(w, b, X, Y)
print ("dw = " + str(grads["dw"]))
print ("db = " + str(grads["db"]))
print ("cost = " + str(cost))
- 优化
使用梯度下降法最小化代价求解最优的参
数。
def optimize(w,b,X,Y,num_iterations,learning_rate,print_cost=False):
'''
使用梯度下降法优化参数w,b
参数:
w -- 权重参数 (num_px*num_px*3,1)
b -- 偏置参数
X -- 训练集特征矩阵 (num_px*num_px*3,mtrain) 每一列为一张图片的特征向量
Y -- 训练集标签 行向量(1,mtrain) 0非猫 1猫
num_iterations -- 梯度下降法迭代次数
learing_rate -- 学习率取值
print_cost:为True时,每100次迭代打印一次cost
返回:
params -- 字典形式返回最优的参数w,b
grads -- 字典形式返回最后的梯度
costs -- 用列表存储每100次迭代后的cost 用于绘制cost随迭代次数的变化曲线
'''
costs=[]
for i in range(num_iterations):
grads,cost=propagate(w,b,X,Y)
dw=grads['dw']
db=grads['db']
w=w-learning_rate*dw
b=b-learning_rate*db
if i%100==0:
costs.append(cost)
if print_cost and i%100==0:
print ("Cost after iteration %i: %f" %(i, cost))
params={'w':w,'b':b}
grads={'dw':dw,'db':db}
return params,grads,costs
params, grads, costs = optimize(w, b, X, Y, num_iterations= 100, learning_rate = 0.009, print_cost = False)
print ("w = " + str(params["w"]))
print ("b = " + str(params["b"]))
print ("dw = " + str(grads["dw"]))
print ("db = " + str(grads["db"]))
- 预测
def predict(w,b,X):
'''
使用学好的参数w,b预测新样本的类别0/1
参数:
w -- 学习的权重参数 (num_px*num_px*3,1)
b -- 学习的偏置参数 标量
X -- 测试集 (num_px*num_px*3,mtest) 每一列是新样本的特征向量
返回:
Y_prediction -- 预测的新样本类别 0/1
'''
m=X.shape[1]
Y_prediction=np.zeros((1,m)) #2维数组表示行向量
w=w.reshape(X.shape[0],1) #(dim,1)
A=sigmoid(np.dot(w.T,X)+b) #(1,m)
'''
for i in range(A.shape[1]):
Y_prediction[0][i]=np.rint(A[0][i])
'''
Y_prediction = np.rint(A)
assert(Y_prediction.shape==(1,m))
return Y_prediction
print ("predictions = " + str(predict(params["w"], params["b"], X)))
5.把上述实现的函数汇总到一个模型中
def model(X_train, Y_train, X_test, Y_test, num_iterations = 2000, learning_rate = 0.5, print_cost = False):
"""
构建完整的逻辑回归模型
参数:
X_train -- 训练集特征矩阵 (num_px * num_px * 3, m_train)每一列是样本的特征向量
Y_train -- 训练集标签 (1,m_train) 行向量
X_test -- 测试集特征矩阵 (num_px * num_px * 3, m_test)每一列是样本的特征向量
Y_test -- 测试集标签 (1,m_test) 行向量
num_iterations -- 梯度下降法迭代次数
learing_rate -- 学习率取值
print_cost:为True时,每100次迭代打印一次cost
返回:
d -- 模型相关信息 字典形式
"""
#初始化模型参数为0
w, b = initialize_with_zeros(X_train.shape[0])
# 使用梯度下降法优化求解参数
parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost)
# 得到最优的参数
w = parameters["w"]
b = parameters["b"]
# 使用学好的参数 在测试集和训练集上进行预测
Y_prediction_test = predict(w, b, X_test)
Y_prediction_train = predict(w, b, X_train)
# 打印训练集和测试集上的准确率
print("train accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100))
print("test accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100))
#存储模型的相关信息
d = {"costs": costs,
"Y_prediction_test": Y_prediction_test,
"Y_prediction_train" : Y_prediction_train,
"w" : w,
"b" : b,
"learning_rate" : learning_rate,
"num_iterations": num_iterations}
return d
d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 2000, learning_rate = 0.005, print_cost = True)
模型在训练集上的准确率非常高,在测试集上的准确率不高,二者差距很大,说明模型出现了过拟合。在之后的学习中我们会学习一些解决过拟合的方法,和更高级的分类器。
- 对测试集的某张图片进行预测
# 对测试集的某张图片进行预测
index = 1
plt.imshow(test_set_x[:,index].reshape((num_px, num_px, 3)))
#print ("y = " + str(test_set_y[:,index]) + ", it's a '" + classes[np.squeeze(d['Y_prediction_test'][:,index])].decode("utf-8") + "' picture.")
print ("y = " + str(test_set_y[0,index]) + ", you predicted that it is a \"" + classes[int(np.squeeze(d["Y_prediction_test"][0,index]))].decode("utf-8") + "\" picture.")
- 绘制代价随梯度下降法迭代次数的变化曲线
#绘制代价随梯度下降迭代次数的变化曲线
costs = np.squeeze(d['costs'])
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('iterations (per hundreds)')
plt.title("Learning rate =" + str(d["learning_rate"]))
plt.show()
可以看到代价随迭代次数在下降,说明参数在不断优化。
增加迭代次数,会发现训练集准确率会继续上升,而测试集准确率会下降,出现了过拟合。
6.进一步分析
- 选择一个合适的学习率
学习率决定了更新参数的幅度。过大模型可能不会收敛,甚至会发散;过小模型收敛的非常缓慢,需要很多次迭代,才能收敛到最优值。可以广泛的尝试不同的学习率取值,找一个最好的。
对不同的学习率选择,绘制不同的代价随迭代次数的变化曲线,观察此时训练集和测试集的准确率。
learning_rates = [0.01, 0.001, 0.0001] #不同的学习率选择
models = {} #不同的学习率产生不同的模型
for i in learning_rates:
print ("learning rate is: " + str(i))
models[str(i)] = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 1500, learning_rate = i, print_cost = False)
print ('\n' + "-------------------------------------------------------" + '\n')
for i in learning_rates:
plt.plot(np.squeeze(models[str(i)]["costs"]), label= str(models[str(i)]["learning_rate"]))
plt.ylabel('cost')
plt.xlabel('iterations')
legend = plt.legend(loc='upper center', shadow=True)
frame = legend.get_frame()
frame.set_facecolor('0.90')
plt.show()
- 不同的学习率产生不同的代价和不同的预测结果
- 学习率太大如上图中的0.01,曲线会出现波动,甚至会发散,但在上例中,0.01最后也能收敛,训练集上的代价非常低
- 在训练集上代价低不见得是一个好的模型,此时需要检查是否出现过拟合,如果训练集准确率明显大于测试集,则出现了过拟合。
- 选择一个最优的学习率来最小化代价求解参数;如果模型出现了过拟合,使用一些技术缓解过拟合(正则化等之后会讲)
7.用你自己的图片进行测试
#图片名称
my_image = "gargouille.jpg"
# 图片相对路径
fname = "images/" + my_image
#读取图片 存储到数组中
image = np.array(ndimage.imread(fname, flatten=False))
#对图片进行转型 预处理
my_image = scipy.misc.imresize(image, size=(num_px,num_px)).reshape((1, num_px*num_px*3)).T
#使用学好的参数 预测图片类别
my_predicted_image = predict(d["w"], d["b"], my_image)
plt.imshow(image)
print("y = " + str(np.squeeze(my_predicted_image)) + ", your algorithm predicts a \"" + classes[int(np.squeeze(my_predicted_image)),].decode("utf-8") + "\" picture.")