吴恩达深度学习专项课程的所有实验均采用iPython Notebooks实现,不熟悉的朋友可以提前使用一下Notebooks。本周的实验包括两个小实验:实验1逐步构建CNN,使用NumPy手写实现卷积神经网络的基本构件,包括卷积层和池化层的前向/反向传播过程,通过纯手写实现,可以更深入的了解CNN的机制和原理;实验2是CNN的应用,使用Tensorflow构建一个简单的卷积神经网络模型,实现图像的分类任务,注意与之前使用的全联接网络进行对比。
目录
一、实验1:逐步构建卷积神经网络
1.实验综述
2.导入必要的包
import numpy as np
import h5py #用于与存储为h5格式的数据集进行交互
import matplotlib.pyplot as plt
%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.实验大纲
4.卷积层
- zero 填充
def zero_pad(X, pad):
"""
对数据集X中的所有图像用0进行填充。
如上图所示,对每张图像每一个通道上的图像的宽度和高度进行填充。
Argument:
X -- 是一个4维Python数组,维度为 (m, n_H, n_W, n_C) 代表一个batch中的m个图像
pad -- 整数,表示对于每一张图像在垂直和水平方向上填充的量,即填充几圈。
Returns:
X_pad -- 返回填充后的图像 维度为 (m, n_H + 2*pad, n_W + 2*pad, n_C)
"""
X_pad = np.pad(X,((0,0),(pad,pad),(pad,pad),(0,0)),'constant',constant_values=0)
return X_pad
- 一步卷积操作
# GRADED FUNCTION: conv_single_step
def conv_single_step(a_slice_prev, W, b):
"""
在前一层激活函数输出的一个样本的部分区域(即一张图像的部分区域 (f,f,n_C_prev)与filter一样大)上应用一个由权重参数W定义的一个filter(f,f,n_C_prev),
进行卷积操作得到一个实数
Arguments:
a_slice_prev --输入数据中的一个样本的部分区域(即一张图像的部分区域) (f, f, n_C_prev)
W -- filter窗口中的权重参数 - 维度(f, f, n_C_prev)
b -- filter窗口中的偏置参数 - 维度 (1, 1, 1)
Returns:
Z -- 一个实数, 滑动窗口(W,b)在一个在输入数据的一个样本(一张图像)上的一步卷积操作值。
"""
s = np.multiply(a_slice_prev,W) #对应位置相乘
Z = np.sum(s) #对s(立体)的所有元素进行求和
Z = float(b)+Z #加上偏置参数
return Z
- CNN 卷积层前向传播
def conv_forward(A_prev, W, b, hparameters):
"""
实现卷积操作的前向传播
Arguments:
A_prev -- 上一层激活函数的输出, 四维数组(m, n_H_prev, n_W_prev, n_C_prev)
W -- filters的权重参数,四维数组 (f, f, n_C_prev, n_C)
b -- filters的偏置参数,四维数组 (1, 1, 1, n_C)
hparameters -- Python字典包含步长和填充
Returns:
Z -- 卷积操作后的输出,四维数组 (m, n_H, n_W, n_C)
cache -- 缓存重要的参数,与卷积操作的反向传播共享参数
"""
#得到输入的相关信息
(m,n_H_prev,n_W_prev,n_C_prev) = A_prev.shape
#得到filters的相关信息
(f,f,n_C_prev,n_C) = W.shape
#得到步长和pad
stride = hparameters['stride']
pad = hparameters['pad']
#计算输出图像的高度和宽度
n_H = int((n_H_prev-f+2*pad)/stride+1)
n_W = int((n_W_prev-f+2*pad)/stride+1)
#用0初始化卷积操作的输出
Z = np.zeros((m,n_H,n_W,n_C))
#对上一层激活函数的输出A_prev 进行填充
A_prev_pad = zero_pad(A_prev,pad)
for i in range(m): #对输入的m个填充样本(m张图像)循环处理
a_prev_pad = A_prev_pad[i] #取出第i个填充图像
for h in range(n_H): #遍历输出图像垂直方向上的每个像素点
for w in range(n_W): #遍历输出图像水平方向上的每个像素点
for c in range(n_C):#遍历输出图像的每个通道
#找到每个输入图像的各个区域
vert_start = h*stride #垂直方向起点
vert_end = vert_start+f#垂直方向终点
horiz_start = w*stride #水平方向起点
horiz_end = horiz_start+f#水平方向终点
#得到一个区域
a_prev_pad_slice = a_prev_pad[vert_start:vert_end,horiz_start:horiz_end,:]
#计算该区域上的卷积操作 得到某个输出图像单个通道上的一个值
Z[i,h,w,c] = conv_single_step(a_prev_pad_slice,W[:,:,:,c],b[:,:,:,c])
assert(Z.shape == (m,n_H,n_W,n_C))
cache = (A_prev,W,b,hparameters)
return Z, cache
5.池化层
- 池化层前向传播
# GRADED FUNCTION: pool_forward
def pool_forward(A_prev, hparameters, mode = "max"):
"""
实现池化层的前向传播.
Arguments:
A_prev -- 输入数据,包含m张图像 四维数组 (m, n_H_prev, n_W_prev, n_C_prev)
hparameters -- Python字典,包含超参数f和stride,池化一般没有填充
mode -- 池化类型 max or avg
Returns:
A -- 池化层的输出 四维数组(m, n_H, n_W, n_C)
cache -- 缓存重要的参数 包括输入数据和超参数 与池化层的反向传播共享参数
"""
# 得到池化层输入数据的信息
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
# 得到超参数f和stride
f = hparameters["f"]
stride = hparameters["stride"]
# 计算输出图像的大小
n_H = int(1 + (n_H_prev - f) / stride)
n_W = int(1 + (n_W_prev - f) / stride)
n_C = n_C_prev
# 初始化池化层的输出数据 四维数组 包含m张图像
A = np.zeros((m, n_H, n_W, n_C))
for i in range(m): #遍历输入数据的m张图像
for h in range(n_H):#遍历每个输出图像垂直方向上的每个像素
for w in range(n_W):#遍历每个输出图像水平方向上的每个像素
for c in range(n_C):#遍历每个输出图像的每个通道
#找到每个图像各个区域的起始点
vert_start = h*stride
vert_end = vert_start+f
horiz_start = w*stride
horiz_end = horiz_start+f
#得到一个区域
a_prev_slice = A_prev[i,vert_start:vert_end,horiz_start:horiz_end,c]
if mode == 'max':
A[i,h,w,c] = np.max(a_prev_slice)
elif mode == 'average':
A[i,h,w,c] = np.mean(a_prev_slice)
# 缓存重要参数
cache = (A_prev, hparameters)
assert(A.shape == (m, n_H, n_W, n_C))
return A, cache
6.卷积神经网络的反向传播
- 卷积层反向传播
def conv_backward(dZ, cache):
"""
实现卷积层中卷积操作的反向传播
Arguments:
dZ -- 代价函数相对于卷积操作输出Z的偏导数, 四维数组(m, n_H, n_W, n_C)
cache -- 前向传播中缓存的重要参数,用于与反向传播共享参数
Returns:
dA_prev -- 代价函数相对于卷积层的输入A_prev的偏导数,
四维数组 (m, n_H_prev, n_W_prev, n_C_prev)
dW -- 代价函数相对于卷积层权重参数的偏导数
四维数组(f, f, n_C_prev, n_C)
db -- 代价函数相对于卷积层偏置参数的偏导数
四维数组 (1, 1, 1, n_C)
"""
#取出卷积层前向传播缓存的参数
(A_prev,W,b,hparameters) = cache
#A_prev的相关信息
(m,n_H_prev,n_W_prev,n_C_prev) = A_prev.shape
#filters W的相关信息
(f,f,n_C_prev,n_C) = W.shape
#取出超参数
stride = hparameters['stride']
pad = hparameters['pad']
#dZ的相关信息
(m,n_H,n_W,n_C) = dZ.shape
#初始化dA_prev,dW,db
dA_prev = np.zeros((m,n_H_prev,n_W_prev,n_C_prev))
dW = np.zeros(W.shape)
db = np.zeros(b.shape)
#填充A_prev和dA_prev
A_prev_pad = zero_pad(A_prev,pad)
dA_prev_pad = zero_pad(dA_prev,pad)
for i in range(m): #遍历m张图像 (m个样本)
a_prev_pad = A_prev_pad[i]
da_prev_pad = dA_prev_pad[i]
for h in range(n_H):#遍历每个输出图像垂直方向上的每个像素
for w in range(n_W):#遍历每个输出图像水平方向上的每个像素
for c in range(n_C):#遍历每个输出图像的每个通道
#找到每个图像各个区域的起始点
vert_start = h*stride
vert_end = vert_start+f
horiz_start = w*stride
horiz_end = horiz_start+f
#得到一个区域
a_prev_pad_slice = a_prev_pad[vert_start:vert_end,horiz_start:horiz_end,:]
da_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :] += W[:,:,:,c] * dZ[i,h,w,c]
dW[:,:,:,c] += a_prev_pad_slice * dZ[i,h,w,c]
db[:,:,:,c] += dZ[i,h,w,c]
dA_prev[i, :, :, :] = dA_prev_pad[i,pad:-pad, pad:-pad, :]
assert(dA_prev.shape == (m, n_H_prev, n_W_prev, n_C_prev))
return dA_prev, dW, db
- 池化层的反向传播
Max pooling反向传播:
def create_mask_from_window(x):
"""
为输入矩阵 x创建一个mask,标识矩阵x中的最大项
Arguments:
x -- 输入矩阵 2维数组 (f, f)
Returns:
mask -- 和x同维的数组,最大值位置为1,其余为0
"""
mask = (x==np.max(x))
return mask
avg pooling反向传播:
def distribute_value(dz, shape):
"""
用shape维的矩阵均匀的分配dz
Arguments:
dz -- 标量
shape -- 均匀分配dz的矩阵的维度 (n_H,n_W)
Returns:
a -- 均匀分配dz的shape维的矩阵
"""
(n_H,n_W) = shape
a = np.ones(shape)
a *= (dz/(n_H*n_W))
return a
把上述两部分组合在一起:实现池化层反向传播
def pool_backward(dA, cache, mode = "max"):
"""
实现池化层的反向传播
Arguments:
dA -- 代价函数cost相对于池化层输出A的梯度, 和 A同维
cache -- 池化层前向传播过程中缓存的参数 包括池化层输入和超参数
mode -- 模式 "max" or "average"
Returns:
dA_prev -- 代价函数cost相对于池化层输入A_prev的梯度, 和 A_prev同维
"""
#取出cache中的参数
(A_prev,hparameters) = cache
#取出超参数
f = hparameters['f']
stride = hparameters['stride']
#dA的相关信息
(m,n_H,n_W,n_C) = dA.shape
#A_prev 的相关信息
(m,n_H_prev,n_W_prev,n_C_prev) = A_prev.shape
#用0初始化dA_prev
dA_prev = np.zeros(A_prev.shape)
for i in range(m): # 遍历所有训练样本
# 选择A_prev中的一个训练样本
a_prev = A_prev[i]
for h in range(n_H):#遍历每个输出图像垂直方向上的每个像素
for w in range(n_W):#遍历每个输出图像水平方向上的每个像素
for c in range(n_C):#遍历每个输出图像的每个通道
#找到每个图像各个区域的起始点
vert_start = h*stride
vert_end = vert_start+f
horiz_start = w*stride
horiz_end = horiz_start+f
#计算某个模式下的反向传播
if mode == "max":
# 使用之前的起始点和c定义a_prev的一个slice
a_prev_slice = a_prev[vert_start:vert_end,horiz_start:horiz_end,c]
# 为 a_prev_slice创建一个mask
mask = create_mask_from_window(a_prev_slice)
dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c] += dA[i,vert_start,horiz_start,c]*mask
elif mode == "average":
# 从dA中得到a
a = dA[i,vert_start,horiz_start,c]
shape = (f,f)
dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c] += distribute_value(a,shape)
assert(dA_prev.shape == A_prev.shape)
return dA_prev
二、实验2:CNN应用
1.实验综述
2.导入必要的包
import math
import numpy as np
import h5py #用于与存储为h5格式的文件进行交互
import matplotlib.pyplot as plt
#用于在测试时读取图片
import scipy
from PIL import Image
from scipy import ndimage
#Tensorflow深度学习框架
import tensorflow as tf
from tensorflow.python.framework import ops
#cnn_utils.py 中定义了本次实验 需要用到的一些辅助函数
from cnn_utils import *
%matplotlib inline
np.random.seed(1) #设置随机种子
3.数据集
使用之前用过的‘SIGNS’数据集。
- 导入数据集,可视化某一个样本
#导入数据集
X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = load_dataset()
index = 6
plt.imshow(X_train_orig[index])
print ("y = " + str(np.squeeze(Y_train_orig[:, index])))
- 数据集预处理
#数据集预处理
X_train = X_train_orig/255. #标准化
X_test = X_test_orig/255.
Y_train = convert_to_one_hot(Y_train_orig, 6).T #标签转为one-hot编码
Y_test = convert_to_one_hot(Y_test_orig, 6).T
print ("number of training examples = " + str(X_train.shape[0]))
print ("number of test examples = " + str(X_test.shape[0]))
print ("X_train shape: " + str(X_train.shape))
print ("Y_train shape: " + str(Y_train.shape))
print ("X_test shape: " + str(X_test.shape))
print ("Y_test shape: " + str(Y_test.shape))
conv_layers = {}
4.用TF构建CNN
- 创建placeholders
# GRADED FUNCTION: create_placeholders
def create_placeholders(n_H0, n_W0, n_C0, n_y):
"""
为CNN模型的输入输出创建placeholders.
Arguments:
n_H0 -- 标量,输入图像的高度
n_W0 -- 标量,输入图像的宽度
n_C0 -- 标量,输入图像的通道数
n_y -- 标量,分类类别数
Returns:
X -- 输入数据X的placeholder 维度 [None, n_H0, n_W0, n_C0] dtype "float"
Y -- 输入数据标签Y的placeholder 维度 [None, n_y] dtype "float"
"""
X = tf.placeholder(tf.float32,[None,n_H0,n_W0,n_C0])
Y = tf.placeholder(tf.float32,[None,n_y])
return X, Y
- 初始化参数
# GRADED FUNCTION: initialize_parameters
def initialize_parameters():
"""
用TF初始化两个卷积层的所有filters(权重参数):
W1 : [4, 4, 3, 8]
W2 : [2, 2, 8, 16]
Returns:
parameters -- tensor字典包含 W1, W2
"""
tf.set_random_seed(1)
W1 = tf.get_variable('W1',[4,4,3,8],initializer=tf.contrib.layers.xavier_initializer(seed = 0))
W2 = tf.get_variable('W2',[2,2,8,16],initializer=tf.contrib.layers.xavier_initializer(seed = 0))
parameters = {"W1": W1,
"W2": W2}
return parameters
- 前向传播
# GRADED FUNCTION: forward_propagation
def forward_propagation(X, parameters):
"""
实现CNN模型的前向传播:
CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
Arguments:
X -- 输入数据的placeholder 维度 (None,n0_H,n0_W,n0_C)
parameters -- Python字典包含模型的filters "W1", "W2"
Returns:
Z3 -- 最后前联接层(输出层)的线性单元的输出
"""
W1 = parameters['W1']
W2 = parameters['W2']
#conv2d
Z1 = tf.nn.conv2d(X,W1,strides=[1,1,1,1],padding='SAME')
#relu
A1 = tf.nn.relu(Z1)
#MAXpool
P1 = tf.nn.max_pool(A1,ksize=[1,8,8,1],strides=[1,8,8,1],padding='SAME')
#conv2d
Z2 = tf.nn.conv2d(P1,W2,strides=[1,1,1,1],padding='SAME')
#relu
A2 = tf.nn.relu(Z2)
#maxpool
P2 = tf.nn.max_pool(A2,ksize=[1,4,4,1],strides=[1,4,4,1],padding='SAME')
#flattened
F = tf.contrib.layers.flatten(P2)
#FC
Z3 = tf.contrib.layers.fully_connected(F,num_outputs=6,activation_fn=None)
return Z3
- 计算cost
# GRADED FUNCTION: compute_cost
def compute_cost(Z3, Y):
"""
计算cost
Arguments:
Z3 -- 前向传播的最后全联接层(输出层)的线性单元输出 维度 (number of examples,6) 每行代表一个样本
Y -- 样本真实标签,维度与Z3一致
Returns:
cost - 代价函数的tensor
"""
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3,labels=Y))
return cost
- CNN模型
def model(X_train, Y_train, X_test, Y_test, learning_rate = 0.1,
num_epochs = 100, minibatch_size = 64, print_cost = True):
"""
用TF实现3层卷积网络:
CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
Arguments:
X_train -- 训练集样本 (None, 64, 64, 3)
Y_train -- 训练集标签 (None, n_y = 6)
X_test -- 测试集样本 (None, 64, 64, 3)
Y_test -- 测试集标签 (None, n_y = 6)
learning_rate -- 学习率
num_epochs -- 完整遍历训练集的次数
minibatch_size -- mini-batch大小
print_cost -- 为True时 每100个epoch打印一次cost
Returns:
train_accuracy -- 实数 模型在训练集上的准确率
test_accuracy -- 实数模型在测试集上的准确率
parameters -- 模型学好的参数 用于预测
"""
ops.reset_default_graph()
tf.set_random_seed(1) #TF随机种子
seed = 3 #NumPy随机种子
(m, n_H0, n_W0, n_C0) = X_train.shape
n_y = Y_train.shape[1]
costs = []
# 为X,Y创建placeholder
X,Y = create_placeholders(n_H0,n_W0,n_C0,n_y)
# 初始化filters
parameters = initialize_parameters()
# ****前向传播,构建TF计算图****
Z3 = forward_propagation(X,parameters)
# 计算cost
cost = compute_cost(Z3,Y)
# 反向传播 定义优化算法 最小化cost,求解梯度,更新参数
optimizer = tf.train.AdagradOptimizer(learning_rate).minimize(cost)
init = tf.global_variables_initializer()
# 启动会话,对于每一个mini-batch运行一次计算图
with tf.Session() as sess:
sess.run(init)
for epoch in range(num_epochs):
minibatch_cost = 0.
num_minibatches = int(m / minibatch_size) # mini-batch数量
seed = seed + 1
#分割后的所有mini-batch
minibatches = random_mini_batches(X_train, Y_train, minibatch_size, seed)
for minibatch in minibatches:
# 选择一个mini-batch
(minibatch_X, minibatch_Y) = minibatch
#在一个mini-batch上运行一次计算图
_,temp_cost = sess.run([optimizer,cost],{X:minibatch_X,Y:minibatch_Y})
minibatch_cost += temp_cost / num_minibatches
# 每5个epoch打印一次cost
if print_cost == True and epoch % 5 == 0:
print ("Cost after epoch %i: %f" % (epoch, minibatch_cost))
#每1个epoch 记录一次cost
if print_cost == True and epoch % 1 == 0:
costs.append(minibatch_cost)
# 绘制cost曲线
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per tens)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
#计算准确率
predict_op = tf.argmax(Z3, 1)
correct_prediction = tf.equal(predict_op, tf.argmax(Y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
train_accuracy = accuracy.eval({X: X_train, Y: Y_train})
test_accuracy = accuracy.eval({X: X_test, Y: Y_test})
print("Train Accuracy:", train_accuracy)
print("Test Accuracy:", test_accuracy)
return train_accuracy, test_accuracy, parameters
#训练模型
_, _, parameters = model(X_train, Y_train, X_test, Y_test) #100个epoch
- 为你的工作竖一个大拇指
fname = "images/thumbs_up.jpg"
image = np.array(ndimage.imread(fname, flatten=False))
my_image = scipy.misc.imresize(image, size=(64,64))
plt.imshow(my_image)
三、实验完整代码