卷积神经网络实践
安装相关库和包
使用pycharm,新建juan_ji.py文件。
- numpy 是Python科学计算的基本包。
- matplotlib 是在Python中常用的绘制图形的库。
import numpy as np
import h5py
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (5.0, 4.0)
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
卷积函数
- 零填充
- 卷积窗口
- 正向卷积
- 反向卷积(可选)
零填充
零填充是在图像的边界周围添加零。下图所示,一张猫咪的图片,是RGB格式,有三个图片通道,填充的padding=2.
填充的主要好处有:
- 允许使用CONV层而不必缩小其高度和宽度。这对于构建更深的网络很重要,因为高度/宽度会随着更深的层而缩小。一个重要、特殊的例子是"same"卷积,其中高度/宽度在一层之后被精确保留。
- 有助于我们将更多信息保留在图像边缘。如果不进行填充,卷积过程中图像边缘的极少数值会受到过滤器的影响从而导致信息丢失。
实现零填充函数,该功能将使用零填充处理一个批次X的所有图像数据。注意,如果要填充维度为(5,5,5,5,5)的数组“a”,第二维的填充为pad = 1,第四维的填充为pad = 3,其余为pad = 0,你可以这样做:a = np.pad(a, ((0,0), (1,1), (0,0), (3,3), (0,0)), 'constant', constant_values = (..,..))
# 定义零填充函数
def zero_pad(X, pad):
"""
把数据集X的图像边界全部使用0来扩充pad个宽度和高度。
参数:
X - 图像数据集,维度为(样本数,图像高度,图像宽度,图像通道数)
pad - 整数,每个图像在垂直和水平维度上的填充量
返回:
X_paded - 扩充后的图像数据集,维度为(样本数,图像高度 + 2*pad,图像宽度 + 2*pad,图像通道数)
"""
X_paded = np.pad(X, (
(0, 0), # 样本数,不填充
(pad, pad), # 图像高度,你可以视为上面填充x个,下面填充y个(x,y)
(pad, pad), # 图像宽度,你可以视为左边填充x个,右边填充y个(x,y)
(0, 0)), # 通道数,不填充
'constant', constant_values=0) # 连续一样的值填充
return X_paded
测试一下
# # 测试一下
# np.random.seed(1)
# x = np.random.randn(4,3,3,2)
# x_paded = zero_pad(x,2)
# #查看信息
# print ("x.shape =", x.shape)
# print ("x_paded.shape =", x_paded.shape)
# print ("x[1, 1] =", x[1, 1])
# print ("x_paded[1, 1] =", x_paded[1, 1])
# #绘制图
# fig, axarr = plt.subplots(1,2) #一行两列
# axarr[0].set_title('x')
# axarr[0].imshow(x[0,:,:,0])
# axarr[1].set_title('x_paded')
# axarr[1].imshow(x_paded[0,:,:,0])
卷积的单个步骤
在这里,我们要实现第一步卷积,我们要使用一个过滤器来卷积输入的数据。
在计算机视觉应用中,左侧矩阵中的每个值都对应一个像素值,我们通过将其值与3✖3过滤器相乘,然后对它们进行求和来将3x3滤波器与图像进行卷积。我们需要实现一个函数,可以将一个3x3滤波器与单独的切片块进行卷积并输出一个实数。现在我们开始实现。
# 定义卷积操作
def conv_single_step(a_slice_prev, W, b):
"""
在前一层的激活输出的一个片段上应用一个由参数W定义的过滤器。
这里切片大小和过滤器大小相同
参数:
a_slice_prev - 输入数据的一个片段,维度为(过滤器大小,过滤器大小,上一通道数)
W - 权重参数,包含在了一个矩阵中,维度为(过滤器大小,过滤器大小,上一通道数)
b - 偏置参数,包含在了一个矩阵中,维度为(1,1,1)
返回:
Z - 在输入数据的片X上卷积滑动窗口(w,b)的结果。
"""
s = np.multiply(a_slice_prev, W) + b
Z = np.sum(s)
return Z
# 测试一下
np.random.seed(1)
#这里切片大小和过滤器大小相同
a_slice_prev = np.random.randn(4,4,3)
W = np.random.randn(4,4,3)
b = np.random.randn(1,1,1)
Z = conv_single_step(a_slice_prev,W,b)
print("Z = " + str(Z))
最后测试输出的是
卷积神经网络的正向传递
在正向传递中,你将使用多个滤波器对输入进行卷积。每个“卷积”都会输出一个2D矩阵。然后,你将堆叠这些输出以获得更高维的矩阵。
想实现自定义切片,我们可以这么做:先定义要切片的位置,vert_start、vert_end、 horiz_start、 horiz_end,它们的位置我们看一下下面的图就明白了。
定义前向传播函数,使用for循环实现。
# 定义前向传播函数
def conv_forward(A_prev, W, b, hparameters):
"""
实现卷积函数的前向传播
参数:
A_prev - 上一层的激活输出矩阵,维度为(m, n_H_prev, n_W_prev, n_C_prev),(样本数量,上一层图像的高度,上一层图像的宽度,上一层过滤器数量)
W - 权重矩阵,维度为(f, f, n_C_prev, n_C),(过滤器大小,过滤器大小,上一层的过滤器数量,这一层的过滤器数量)
b - 偏置矩阵,维度为(1, 1, 1, n_C),(1,1,1,这一层的过滤器数量)
hparameters - 包含了"stride"与 "pad"的超参数字典。
stride是过滤器的步长
pad是图像的填充数padding
返回:
Z - 卷积输出,维度为(m, n_H, n_W, n_C),(样本数,图像的高度,图像的宽度,过滤器数量)
cache - 缓存了一些反向传播函数conv_backward()需要的一些数据
"""
# 获取来自上一层数据的基本信息
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
# 获取权重矩阵的基本信息
(f, f, n_C_prev, n_C) = W.shape
# 获取超参数hparameters的值
stride = hparameters["stride"]
pad = hparameters["pad"]
# 计算卷积后的图像的宽度高度,参考上面的公式,使用int()来进行板除
n_H = int((n_H_prev - f + 2 * pad) / stride) + 1
n_W = int((n_W_prev - f + 2 * pad) / stride) + 1
# 使用0来初始化卷积输出Z
Z = np.zeros((m, n_H, n_W, n_C))
# 通过A_prev创建填充过了的A_prev_pad
A_prev_pad = zero_pad(A_prev, pad)
for i in range(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_slice_prev = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :]
# 执行单步卷积
Z[i, h, w, c] = conv_single_step(a_slice_prev, W[:, :, :, c], b[0, 0, 0, c])
# 数据处理完毕,验证数据格式是否正确
assert (Z.shape == (m, n_H, n_W, n_C))
# 存储一些缓存值,以便于反向传播使用
cache = (A_prev, W, b, hparameters)
return (Z, cache)
# 测试一下
np.random.seed(1)
A_prev = np.random.randn(10,4,4,3)
W = np.random.randn(2,2,3,8)
b = np.random.randn(1,1,1,8)
hparameters = {"pad" : 2, "stride": 1}
Z , cache_conv = conv_forward(A_prev,W,b,hparameters)
print("np.mean(Z) = ", np.mean(Z))
print("cache_conv[0][1][2][3] =", cache_conv[0][1][2][3])
测试输出如下
池化层
池化(POOL)层减少了输入的高度和宽度。它有助于减少计算量,而且可以使特征检测器在输入中的位置保持不变。池化层有两种:
-
最大池化:在输入上滑动 (f,f)窗口,并将窗口的最大值存储在输出中。
-
平均池化:在输入上滑动 (f,f)窗口,并将该窗口的平均值存储在输出中。
池化层有窗口大小f,是一个超参数。指定了要计算最大值或平均值的窗口的高度和宽度。定义池化层的前向传播并进行测试输出。
# 定义池化层前向传播
def pool_forward(A_prev, hparameters, mode="max"):
"""
实现池化层的前向传播
参数:
A_prev - 输入数据,维度为(m, n_H_prev, n_W_prev, n_C_prev)
hparameters - 包含了 "f" 和 "stride"的超参数字典
mode - 模式选择【"max" | "average"】
返回:
A - 池化层的输出,维度为 (m, n_H, n_W, n_C)
cache - 存储了一些反向传播需要用到的值,包含了输入和超参数的字典。
"""
# 获取输入数据的基本信息
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
# 获取超参数的信息
f = hparameters["f"]
stride = hparameters["stride"]
# 计算输出维度
n_H = int((n_H_prev - f) / stride) + 1
n_W = int((n_W_prev - f) / stride) + 1
n_C = n_C_prev
# 初始化输出矩阵
A = np.zeros((m, n_H, n_W, n_C))
for i in range(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_slice_prev = A_prev[i, vert_start:vert_end, horiz_start:horiz_end, c]
# 对切片进行池化操作
if mode == "max":
A[i, h, w, c] = np.max(a_slice_prev)
elif mode == "average":
A[i, h, w, c] = np.mean(a_slice_prev)
# 池化完毕,校验数据格式
assert (A.shape == (m, n_H, n_W, n_C))
# 校验完毕,开始存储用于反向传播的值
cache = (A_prev, hparameters)
return A, cache
# 测试一下
np.random.seed(1)
A_prev = np.random.randn(2,4,4,3)
hparameters = {"f":4 , "stride":1}
A , cache = pool_forward(A_prev,hparameters,mode="max")
A, cache = pool_forward(A_prev, hparameters)
print("mode = max")
print("A =", A)
print("----------------------------")
A, cache = pool_forward(A_prev, hparameters, mode = "average")
print("mode = average")
print("A =", A)
测试输出如下:
卷积神经网络的反向传播
在深度学习框架中,你只需要实现正向传播,该框架就可以处理反向传播,因此大多数深度学习工程师不需要理会反向传播的细节。卷积网络的反向传播很复杂。
池化层的反向传播
即使池化层没有用于反向传播更新的参数,你仍需要通过池化层对梯度进行反向传播,以便计算池化层之前的层的梯度。在进入池化层的反向传播之前,首先构建一个名为create_mask_from_window()的辅助函数,该函数将执行以下操作:
此函数创建一个“掩码”矩阵,该矩阵追踪矩阵的最大值。True(1)表示最大值在X中的位置,其他条目为False(0)。
定义该函数如下
# 定义辅助反向池化函数
def create_mask_from_window(x):
"""
从输入矩阵中创建掩码,以保存最大值的矩阵的位置。
参数:
x - 一个维度为(f,f)的矩阵
返回:
mask - 包含x的最大值的位置的矩阵
"""
mask = x == np.max(x)
return mask
# 测试一下
np.random.seed(1)
x = np.random.randn(2,3)
mask = create_mask_from_window(x)
print("x = " + str(x))
print("mask = " + str(mask))
测试输出如下
定义池化层反向传播函数
def pool_backward(dA,cache,mode = "max"):
"""
实现池化层的反向传播
参数:
dA - 池化层的输出的梯度,和池化层的输出的维度一样
cache - 池化层前向传播时所存储的参数。
mode - 模式选择,【"max" | "average"】
返回:
dA_prev - 池化层的输入的梯度,和A_prev的维度相同
"""
#获取cache中的值
(A_prev , hparaeters) = cache
#获取hparaeters的值
f = hparaeters["f"]
stride = hparaeters["stride"]
#获取A_prev和dA的基本信息
(m , n_H_prev , n_W_prev , n_C_prev) = A_prev.shape
(m , n_H , n_W , n_C) = dA.shape
#初始化输出的结构
dA_prev = np.zeros_like(A_prev)
#开始处理数据
for i in range(m):
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
vert_end = vert_start + f
horiz_start = w
horiz_end = horiz_start + f
#选择反向传播的计算方式
if mode == "max":
#开始切片
a_prev_slice = a_prev[vert_start:vert_end,horiz_start:horiz_end,c]
#创建掩码
mask = create_mask_from_window(a_prev_slice)
#计算dA_prev
dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c] += np.multiply(mask,dA[i,h,w,c])
elif mode == "average":
#获取dA的值
da = dA[i,h,w,c]
#定义过滤器大小
shape = (f,f)
#平均分配
dA_prev[i,vert_start:vert_end, horiz_start:horiz_end ,c] += distribute_value(da,shape)
#数据处理完毕,开始验证格式
assert(dA_prev.shape == A_prev.shape)
return dA_prev
测试一下
# 测试一下
np.random.seed(1)
A_prev = np.random.randn(5, 5, 3, 2)
hparameters = {"stride" : 1, "f": 2}
A, cache = pool_forward(A_prev, hparameters)
dA = np.random.randn(5, 4, 2, 2)
dA_prev = pool_backward(dA, cache, mode = "max")
print("mode = max")
print('mean of dA = ', np.mean(dA))
print('dA_prev[1,1] = ', dA_prev[1,1])
print()
测试输出结果如下
Tensorflow实现卷积神经网络
新建tf_juan_ji.py文件。
导入相关库和下载相关数据
下载地址点击后,进入下载地址,然后下载数据集和必须的文件。
导入库和数据集代码如下
import math
import numpy as np
import h5py
import matplotlib.pyplot as plt
import scipy
from PIL import Image
from scipy import ndimage
import tensorflow.compat.v1 as tf
tf.disable_eager_execution()
from tensorflow.python.framework import ops
from cnn_utils import *
import cnn_utils
# 加载数据集
X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = cnn_utils.load_dataset()
# 查看数据集的一张图片
index = 6
plt.imshow(X_train_orig[index])
plt.show()
print("y = " + str(np.squeeze(Y_train_orig[:, index])))
查看结果如下
SIGNS数据集内是6个手势符号的图片集,表示从0到5的数字。
检查数据集维度
# 检查数据集的维度
X_train = X_train_orig/255.
X_test = X_test_orig/255.
Y_train = convert_to_one_hot(Y_train_orig, 6).T
Y_test = convert_to_one_hot(Y_test_orig, 6).T
创建占位符
TensorFlow要求您为运行会话时将输入到模型中的输入数据创建占位符。现在我们要实现创建占位符的函数,因为我们使用的是小批量数据块,输入的样本数量可能不固定,所以我们在数量那里我们要使用None作为可变数量。因此,X的维度应为 [None, n_H0, n_W0, n_C0],Y的尺寸应为 [None, n_y]。
定义创建占位符函数
# 定义创建占位符函数
def create_placeholders(n_H0, n_W0, n_C0, n_y):
"""
为session创建占位符
参数:
n_H0 - 实数,输入图像的高度
n_W0 - 实数,输入图像的宽度
n_C0 - 实数,输入的通道数
n_y - 实数,分类数
输出:
X - 输入数据的占位符,维度为[None, n_H0, n_W0, n_C0],类型为"float"
Y - 输入数据的标签的占位符,维度为[None, n_y],维度为"float"
"""
X = tf.compat.v1.placeholder(tf.float32, [None, n_H0, n_W0, n_C0])
Y = tf.compat.v1.placeholder(tf.float32, [None, n_y])
return X, Y
# 测试一下
X , Y = create_placeholders(64,64,3,6)
print ("X = " + str(X))
print ("Y = " + str(Y))
测试输出如下
初始化参数
你将使用tf.contrib.layers.xavier_initializer(seed = 0)
初始化权重/滤波器和。你无需担心偏差变量,因为TensorFlow函数可以处理偏差。还要注意你只会为conv2d函数初始化权重/滤波器,TensorFlow将自动初始化全连接部分的层。
定义初始化参数
# 初始化参数
def initialize_parameters():
"""
初始化权值矩阵,这里我们把权值矩阵硬编码:
W1 : [4, 4, 3, 8]
W2 : [2, 2, 8, 16]
返回:
包含了tensor类型的W1、W2的字典
"""
tf.compat.v1.set_random_seed(1)
W1 = tf.compat.v1.get_variable("W1", [4, 4, 3, 8], initializer=tf.keras.initializers.glorot_normal(seed=0))
W2 = tf.compat.v1.get_variable("W2", [2, 2, 8, 16], initializer=tf.keras.initializers.glorot_normal(seed=0))
parameters = {"W1": W1,
"W2": W2}
return parameters
#测试一下
tf.compat.v1.reset_default_graph()
with tf.compat.v1.Session() as sess_test:
parameters = initialize_parameters()
init = tf.compat.v1.global_variables_initializer()
sess_test.run(init)
print("W1 = " + str(parameters["W1"].eval()[1, 1, 1]))
print("W2 = " + str(parameters["W2"].eval()[1, 1, 1]))
sess_test.close()
测试输出如下
前向传播
在TensorFlow中,有内置函数可以直接调用执行卷积步骤。
- tf.nn.conv2d(X,W1, strides = [1,s,s,1], padding = ‘SAME’): 给定输入X和一组滤波器W1,函数将使用W1的滤波器卷积X。第三个输入([1,f,f,1])表示输入的每个维度(m, n_H_prev, n_W_prev, n_C_prev)的步幅。
- tf.nn.max_pool(A, ksize = [1,f,f,1], strides = [1,s,s,1], padding = ‘SAME’): 给定输入A,此函数使用大小为(f,f)的窗口和大小为(s,s)的步幅在每个窗口上进行最大池化。
- tf.nn.relu(Z1): 计算Z1的ReLU激活输出(可以是任何形状)。
- tf.contrib.layers.flatten§: 给定输入P,此函数将每个示例展平为一维向量,同时保持批量大小。它返回维度为[batch_size,k]的展平张量。
- tf.contrib.layers.fully_connected(F, num_outputs): 给定展平的输入F,它将返回用全连接层计算出的输出。
我们实现前向传播的时候,我们需要定义一下我们模型的大概样子:
具体地,我们将在所有步骤中使用以下参数:- Conv2D:步幅为1,填充为“SAME”
- ReLU
- Max pool:使用8x8的滤波器和8x8的步幅,填充为“SAME”
- Conv2D:步幅为1,填充为“SAME”
- ReLU
- Max pool:使用4x4的滤波器和4x4的步幅,填充为“SAME”
- 展平之前的输出。
- FULLYCONNECTED(FC)层:应用不含非线性激活函数的全连接层。请勿在此处调用softmax。这将在输出层中产生6个神经元,然后将其传递给softmax。在TensorFlow中,softmax和cost函数被合并为一个函数,在计算损失时将调用另一个函数。
定义前向传播函数
# 定义前向传播函数
def forward_propagation(X, parameters):
"""
实现前向传播
CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
参数:
X - 输入数据的placeholder,维度为(输入节点数量,样本数量)
parameters - 包含了“W1”和“W2”的python字典。
返回:
Z3 - 最后一个LINEAR节点的输出
"""
W1 = parameters['W1']
W2 = parameters['W2']
# Conv2d : 步伐:1,填充方式:“SAME”
Z1 = tf.nn.conv2d(X, W1, strides=[1, 1, 1, 1], padding="SAME")
# ReLU :
A1 = tf.nn.relu(Z1)
# Max pool : 窗口大小:8x8,步伐:8x8,填充方式:“SAME”
P1 = tf.nn.max_pool(A1, ksize=[1, 8, 8, 1], strides=[1, 8, 8, 1], padding="SAME")
# Conv2d : 步伐:1,填充方式:“SAME”
Z2 = tf.nn.conv2d(P1, W2, strides=[1, 1, 1, 1], padding="SAME")
# ReLU :
A2 = tf.nn.relu(Z2)
# Max pool : 过滤器大小:4x4,步伐:4x4,填充方式:“SAME”
P2 = tf.nn.max_pool(A2, ksize=[1, 4, 4, 1], strides=[1, 4, 4, 1], padding="SAME")
# 一维化上一层的输出
P = tf.compat.v1.layers.flatten(P2)
# 全连接层(FC):使用没有非线性激活函数的全连接层
Z3 = tf.compat.v1.layers.dense(P, 6)
return Z3
# 测试一下
tf.compat.v1.reset_default_graph()
np.random.seed(1)
with tf.compat.v1.Session() as sess_test:
X, Y = create_placeholders(64, 64, 3, 6)
parameters = initialize_parameters()
Z3 = forward_propagation(X, parameters)
init = tf.compat.v1.global_variables_initializer()
sess_test.run(init)
a = sess_test.run(Z3, {X: np.random.randn(2, 64, 64, 3), Y: np.random.randn(2, 6)})
print("Z3 = " + str(a))
sess_test.close()
测试输出
计算损失
- tf.nn.softmax_cross_entropy_with_logits(logits = Z3, labels = Y): 计算softmax熵损失,该函数会计算softmax激活函数以及由此产生的损失。
- tf.reduce_mean: 计算张量各维度上元素的均值,用它来对所有训练示例的损失求和,以获得总损失。
# 计算损失
def compute_cost(Z3, Y):
"""
计算成本
参数:
Z3 - 正向传播最后一个LINEAR节点的输出,维度为(6,样本数)。
Y - 标签向量的placeholder,和Z3的维度相同
返回:
cost - 计算后的成本
"""
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3, labels=Y))
return cost
# 测试一下
tf.compat.v1.reset_default_graph()
with tf.compat.v1.Session() as sess_test:
np.random.seed(1)
X, Y = create_placeholders(64, 64, 3, 6)
parameters = initialize_parameters()
Z3 = forward_propagation(X, parameters)
cost = compute_cost(Z3, Y)
init = tf.compat.v1.global_variables_initializer()
sess_test.run(init)
a = sess_test.run(cost, {X: np.random.randn(4, 64, 64, 3), Y: np.random.randn(4, 6)})
print("cost = " + str(a))
sess_test.close()
测试输出
构建模型
你将合并以上实现的辅助函数以构建模型并在SIGNS数据集上对其进行训练。模型应该有以下操作
- 创建占位符
- 初始化参数
- 正向传播
- 计算损失
- 创建优化函数
最后,你将创建一个会话并为num_epochs运行一个for循环,获取小批次处理,然后针对每个小批次运行优化函数。
定义模型如下
# 构建模型
def model(X_train, Y_train, X_test, Y_test, learning_rate=0.009,num_epochs=100, minibatch_size=64, print_cost=True, isPlot=True):
"""
使用TensorFlow实现三层的卷积神经网络
CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
参数:
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 - 每个小批量数据块的大小
print_cost - 是否打印成本值,每遍历100次整个数据集打印一次
isPlot - 是否绘制图谱
返回:
train_accuracy - 实数,训练集的准确度
test_accuracy - 实数,测试集的准确度
parameters - 学习后的参数
"""
ops.reset_default_graph() # 能够重新运行模型而不覆盖tf变量
tf.compat.v1.set_random_seed(1) # 确保你的数据和我一样
seed = 3 # 指定numpy的随机种子
(m, n_H0, n_W0, n_C0) = X_train.shape
n_y = Y_train.shape[1]
costs = []
# 为当前维度创建占位符
X, Y = create_placeholders(n_H0, n_W0, n_C0, n_y)
# 初始化参数
parameters = initialize_parameters()
# 前向传播
Z3 = forward_propagation(X, parameters)
# 计算成本
cost = compute_cost(Z3, Y)
# 反向传播,由于框架已经实现了反向传播,我们只需要选择一个优化器就行了
optimizer = tf.compat.v1.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
# 全局初始化所有变量
init = tf.compat.v1.global_variables_initializer()
# 开始运行
with tf.compat.v1.Session() as sess:
# 初始化参数
sess.run(init)
# 开始遍历数据集
for epoch in range(num_epochs):
minibatch_cost = 0
num_minibatches = int(m / minibatch_size) # 获取数据块的数量
seed = seed + 1
minibatches = cnn_utils.random_mini_batches(X_train, Y_train, minibatch_size, seed)
# 对每个数据块进行处理
for minibatch in minibatches:
# 选择一个数据块
(minibatch_X, minibatch_Y) = minibatch
# 最小化这个数据块的成本
_, temp_cost = sess.run([optimizer, cost], feed_dict={X: minibatch_X, Y: minibatch_Y})
# 累加数据块的成本值
minibatch_cost += temp_cost / num_minibatches
# 是否打印成本
if print_cost:
# 每5代打印一次
if epoch % 5 == 0:
print("当前是第 " + str(epoch) + " 代,成本值为:" + str(minibatch_cost))
# 记录成本
if epoch % 1 == 0:
costs.append(minibatch_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()
# 开始预测数据
## 计算当前的预测情况
predict_op = tf.argmax(Z3, 1)
corrent_prediction = tf.equal(predict_op, tf.argmax(Y, 1))
##计算准确度
accuracy = tf.reduce_mean(tf.cast(corrent_prediction, "float"))
print("corrent_prediction accuracy= " + str(accuracy))
train_accuracy = accuracy.eval({X: X_train, Y: Y_train})
test_accuary = accuracy.eval({X: X_test, Y: Y_test})
print("训练集准确度:" + str(train_accuracy))
print("测试集准确度:" + str(test_accuary))
return (train_accuracy, test_accuary, parameters)
启动模型后输出如下
# 启动模型
_, _, parameters = model(X_train,Y_train, X_test,Y_test,num_epochs=150)
完整代码
juan_ji.py
import numpy as np
import h5py
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (5.0, 4.0)
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
# 定义零填充函数
def zero_pad(X, pad):
"""
把数据集X的图像边界全部使用0来扩充pad个宽度和高度。
参数:
X - 图像数据集,维度为(样本数,图像高度,图像宽度,图像通道数)
pad - 整数,每个图像在垂直和水平维度上的填充量
返回:
X_paded - 扩充后的图像数据集,维度为(样本数,图像高度 + 2*pad,图像宽度 + 2*pad,图像通道数)
"""
X_paded = np.pad(X, (
(0, 0), # 样本数,不填充
(pad, pad), # 图像高度,你可以视为上面填充x个,下面填充y个(x,y)
(pad, pad), # 图像宽度,你可以视为左边填充x个,右边填充y个(x,y)
(0, 0)), # 通道数,不填充
'constant', constant_values=0) # 连续一样的值填充
return X_paded
# # 测试一下
# np.random.seed(1)
# x = np.random.randn(4,3,3,2)
# x_paded = zero_pad(x,2)
# #查看信息
# print ("x.shape =", x.shape)
# print ("x_paded.shape =", x_paded.shape)
# print ("x[1, 1] =", x[1, 1])
# print ("x_paded[1, 1] =", x_paded[1, 1])
# #绘制图
# fig, axarr = plt.subplots(1,2) #一行两列
# axarr[0].set_title('x')
# axarr[0].imshow(x[0,:,:,0])
# axarr[1].set_title('x_paded')
# axarr[1].imshow(x_paded[0,:,:,0])
# 定义卷积操作
def conv_single_step(a_slice_prev, W, b):
"""
在前一层的激活输出的一个片段上应用一个由参数W定义的过滤器。
这里切片大小和过滤器大小相同
参数:
a_slice_prev - 输入数据的一个片段,维度为(过滤器大小,过滤器大小,上一通道数)
W - 权重参数,包含在了一个矩阵中,维度为(过滤器大小,过滤器大小,上一通道数)
b - 偏置参数,包含在了一个矩阵中,维度为(1,1,1)
返回:
Z - 在输入数据的片X上卷积滑动窗口(w,b)的结果。
"""
s = np.multiply(a_slice_prev, W) + b
Z = np.sum(s)
return Z
# # 测试一下
# np.random.seed(1)
# #这里切片大小和过滤器大小相同
# a_slice_prev = np.random.randn(4,4,3)
# W = np.random.randn(4,4,3)
# b = np.random.randn(1,1,1)
#
# Z = conv_single_step(a_slice_prev,W,b)
#
# print("Z = " + str(Z))
# 定义前向传播函数
def conv_forward(A_prev, W, b, hparameters):
"""
实现卷积函数的前向传播
参数:
A_prev - 上一层的激活输出矩阵,维度为(m, n_H_prev, n_W_prev, n_C_prev),(样本数量,上一层图像的高度,上一层图像的宽度,上一层过滤器数量)
W - 权重矩阵,维度为(f, f, n_C_prev, n_C),(过滤器大小,过滤器大小,上一层的过滤器数量,这一层的过滤器数量)
b - 偏置矩阵,维度为(1, 1, 1, n_C),(1,1,1,这一层的过滤器数量)
hparameters - 包含了"stride"与 "pad"的超参数字典。
stride是过滤器的步长
pad是图像的填充数padding
返回:
Z - 卷积输出,维度为(m, n_H, n_W, n_C),(样本数,图像的高度,图像的宽度,过滤器数量)
cache - 缓存了一些反向传播函数conv_backward()需要的一些数据
"""
# 获取来自上一层数据的基本信息
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
# 获取权重矩阵的基本信息
(f, f, n_C_prev, n_C) = W.shape
# 获取超参数hparameters的值
stride = hparameters["stride"]
pad = hparameters["pad"]
# 计算卷积后的图像的宽度高度,参考上面的公式,使用int()来进行板除
n_H = int((n_H_prev - f + 2 * pad) / stride) + 1
n_W = int((n_W_prev - f + 2 * pad) / stride) + 1
# 使用0来初始化卷积输出Z
Z = np.zeros((m, n_H, n_W, n_C))
# 通过A_prev创建填充过了的A_prev_pad
A_prev_pad = zero_pad(A_prev, pad)
for i in range(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_slice_prev = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :]
# 执行单步卷积
Z[i, h, w, c] = conv_single_step(a_slice_prev, W[:, :, :, c], b[0, 0, 0, c])
# 数据处理完毕,验证数据格式是否正确
assert (Z.shape == (m, n_H, n_W, n_C))
# 存储一些缓存值,以便于反向传播使用
cache = (A_prev, W, b, hparameters)
return (Z, cache)
# # 测试一下
# np.random.seed(1)
#
# A_prev = np.random.randn(10,4,4,3)
# W = np.random.randn(2,2,3,8)
# b = np.random.randn(1,1,1,8)
#
# hparameters = {"pad" : 2, "stride": 1}
#
# Z , cache_conv = conv_forward(A_prev,W,b,hparameters)
#
# print("np.mean(Z) = ", np.mean(Z))
# print("cache_conv[0][1][2][3] =", cache_conv[0][1][2][3])
# 定义池化层前向传播
def pool_forward(A_prev, hparameters, mode="max"):
"""
实现池化层的前向传播
参数:
A_prev - 输入数据,维度为(m, n_H_prev, n_W_prev, n_C_prev)
hparameters - 包含了 "f" 和 "stride"的超参数字典
mode - 模式选择【"max" | "average"】
返回:
A - 池化层的输出,维度为 (m, n_H, n_W, n_C)
cache - 存储了一些反向传播需要用到的值,包含了输入和超参数的字典。
"""
# 获取输入数据的基本信息
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
# 获取超参数的信息
f = hparameters["f"]
stride = hparameters["stride"]
# 计算输出维度
n_H = int((n_H_prev - f) / stride) + 1
n_W = int((n_W_prev - f) / stride) + 1
n_C = n_C_prev
# 初始化输出矩阵
A = np.zeros((m, n_H, n_W, n_C))
for i in range(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_slice_prev = A_prev[i, vert_start:vert_end, horiz_start:horiz_end, c]
# 对切片进行池化操作
if mode == "max":
A[i, h, w, c] = np.max(a_slice_prev)
elif mode == "average":
A[i, h, w, c] = np.mean(a_slice_prev)
# 池化完毕,校验数据格式
assert (A.shape == (m, n_H, n_W, n_C))
# 校验完毕,开始存储用于反向传播的值
cache = (A_prev, hparameters)
return A, cache
# # 测试一下
# np.random.seed(1)
# A_prev = np.random.randn(2,4,4,3)
# hparameters = {"f":4 , "stride":1}
#
# A , cache = pool_forward(A_prev,hparameters,mode="max")
# A, cache = pool_forward(A_prev, hparameters)
# print("mode = max")
# print("A =", A)
# print("----------------------------")
# A, cache = pool_forward(A_prev, hparameters, mode = "average")
# print("mode = average")
# print("A =", A)
# 定义辅助反向池化函数
def create_mask_from_window(x):
"""
从输入矩阵中创建掩码,以保存最大值的矩阵的位置。
参数:
x - 一个维度为(f,f)的矩阵
返回:
mask - 包含x的最大值的位置的矩阵
"""
mask = x == np.max(x)
return mask
# # 测试一下
# np.random.seed(1)
#
# x = np.random.randn(2,3)
#
# mask = create_mask_from_window(x)
#
# print("x = " + str(x))
# print("mask = " + str(mask))
# 定义池化层反向传播
def pool_backward(dA, cache, mode="max"):
"""
实现池化层的反向传播
参数:
dA - 池化层的输出的梯度,和池化层的输出的维度一样
cache - 池化层前向传播时所存储的参数。
mode - 模式选择,【"max" | "average"】
返回:
dA_prev - 池化层的输入的梯度,和A_prev的维度相同
"""
# 获取cache中的值
(A_prev, hparaeters) = cache
# 获取hparaeters的值
f = hparaeters["f"]
stride = hparaeters["stride"]
# 获取A_prev和dA的基本信息
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
(m, n_H, n_W, n_C) = dA.shape
# 初始化输出的结构
dA_prev = np.zeros_like(A_prev)
# 开始处理数据
for i in range(m):
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
vert_end = vert_start + f
horiz_start = w
horiz_end = horiz_start + f
# 选择反向传播的计算方式
if mode == "max":
# 开始切片
a_prev_slice = a_prev[vert_start:vert_end, horiz_start:horiz_end, c]
# 创建掩码
mask = create_mask_from_window(a_prev_slice)
# 计算dA_prev
dA_prev[i, vert_start:vert_end, horiz_start:horiz_end, c] += np.multiply(mask, dA[i, h, w, c])
elif mode == "average":
# 获取dA的值
da = dA[i, h, w, c]
# 定义过滤器大小
shape = (f, f)
# 平均分配
dA_prev[i, vert_start:vert_end, horiz_start:horiz_end, c] += distribute_value(da, shape)
# 数据处理完毕,开始验证格式
assert (dA_prev.shape == A_prev.shape)
return dA_prev
# 测试一下
np.random.seed(1)
A_prev = np.random.randn(5, 5, 3, 2)
hparameters = {"stride" : 1, "f": 2}
A, cache = pool_forward(A_prev, hparameters)
dA = np.random.randn(5, 4, 2, 2)
dA_prev = pool_backward(dA, cache, mode = "max")
print("mode = max")
print('mean of dA = ', np.mean(dA))
print('dA_prev[1,1] = ', dA_prev[1,1])
print()
tf_juan_ji.py
import math
import numpy as np
import h5py
import matplotlib.pyplot as plt
import scipy
from PIL import Image
from scipy import ndimage
import tensorflow.compat.v1 as tf
tf.disable_eager_execution()
from tensorflow.python.framework import ops
from cnn_utils import *
import cnn_utils
# 加载数据集
X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = cnn_utils.load_dataset()
# # 查看数据集的一张图片
# index = 6
# plt.imshow(X_train_orig[index])
# plt.show()
# 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
Y_test = convert_to_one_hot(Y_test_orig, 6).T
# 定义创建占位符函数
def create_placeholders(n_H0, n_W0, n_C0, n_y):
"""
为session创建占位符
参数:
n_H0 - 实数,输入图像的高度
n_W0 - 实数,输入图像的宽度
n_C0 - 实数,输入的通道数
n_y - 实数,分类数
输出:
X - 输入数据的占位符,维度为[None, n_H0, n_W0, n_C0],类型为"float"
Y - 输入数据的标签的占位符,维度为[None, n_y],维度为"float"
"""
X = tf.compat.v1.placeholder(tf.float32, [None, n_H0, n_W0, n_C0])
Y = tf.compat.v1.placeholder(tf.float32, [None, n_y])
return X, Y
# # 测试一下
# X , Y = create_placeholders(64,64,3,6)
# print ("X = " + str(X))
# print ("Y = " + str(Y))
# 初始化参数
def initialize_parameters():
"""
初始化权值矩阵,这里我们把权值矩阵硬编码:
W1 : [4, 4, 3, 8]
W2 : [2, 2, 8, 16]
返回:
包含了tensor类型的W1、W2的字典
"""
tf.compat.v1.set_random_seed(1)
W1 = tf.compat.v1.get_variable("W1", [4, 4, 3, 8], initializer=tf.keras.initializers.glorot_normal(seed=0))
W2 = tf.compat.v1.get_variable("W2", [2, 2, 8, 16], initializer=tf.keras.initializers.glorot_normal(seed=0))
parameters = {"W1": W1,
"W2": W2}
return parameters
# #测试一下
# tf.compat.v1.reset_default_graph()
# with tf.compat.v1.Session() as sess_test:
# parameters = initialize_parameters()
# init = tf.compat.v1.global_variables_initializer()
# sess_test.run(init)
# print("W1 = " + str(parameters["W1"].eval()[1, 1, 1]))
# print("W2 = " + str(parameters["W2"].eval()[1, 1, 1]))
#
# sess_test.close()
# 定义前向传播函数
def forward_propagation(X, parameters):
"""
实现前向传播
CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
参数:
X - 输入数据的placeholder,维度为(输入节点数量,样本数量)
parameters - 包含了“W1”和“W2”的python字典。
返回:
Z3 - 最后一个LINEAR节点的输出
"""
W1 = parameters['W1']
W2 = parameters['W2']
# Conv2d : 步伐:1,填充方式:“SAME”
Z1 = tf.nn.conv2d(X, W1, strides=[1, 1, 1, 1], padding="SAME")
# ReLU :
A1 = tf.nn.relu(Z1)
# Max pool : 窗口大小:8x8,步伐:8x8,填充方式:“SAME”
P1 = tf.nn.max_pool(A1, ksize=[1, 8, 8, 1], strides=[1, 8, 8, 1], padding="SAME")
# Conv2d : 步伐:1,填充方式:“SAME”
Z2 = tf.nn.conv2d(P1, W2, strides=[1, 1, 1, 1], padding="SAME")
# ReLU :
A2 = tf.nn.relu(Z2)
# Max pool : 过滤器大小:4x4,步伐:4x4,填充方式:“SAME”
P2 = tf.nn.max_pool(A2, ksize=[1, 4, 4, 1], strides=[1, 4, 4, 1], padding="SAME")
# 一维化上一层的输出
P = tf.compat.v1.layers.flatten(P2)
# 全连接层(FC):使用没有非线性激活函数的全连接层
Z3 = tf.compat.v1.layers.dense(P, 6)
return Z3
# # 测试一下
# tf.compat.v1.reset_default_graph()
# np.random.seed(1)
#
# with tf.compat.v1.Session() as sess_test:
# X, Y = create_placeholders(64, 64, 3, 6)
# parameters = initialize_parameters()
# Z3 = forward_propagation(X, parameters)
#
# init = tf.compat.v1.global_variables_initializer()
# sess_test.run(init)
#
# a = sess_test.run(Z3, {X: np.random.randn(2, 64, 64, 3), Y: np.random.randn(2, 6)})
# print("Z3 = " + str(a))
#
# sess_test.close()
# 计算损失
def compute_cost(Z3, Y):
"""
计算成本
参数:
Z3 - 正向传播最后一个LINEAR节点的输出,维度为(6,样本数)。
Y - 标签向量的placeholder,和Z3的维度相同
返回:
cost - 计算后的成本
"""
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3, labels=Y))
return cost
# # 测试一下
# tf.compat.v1.reset_default_graph()
#
# with tf.compat.v1.Session() as sess_test:
# np.random.seed(1)
# X, Y = create_placeholders(64, 64, 3, 6)
# parameters = initialize_parameters()
# Z3 = forward_propagation(X, parameters)
# cost = compute_cost(Z3, Y)
#
# init = tf.compat.v1.global_variables_initializer()
# sess_test.run(init)
# a = sess_test.run(cost, {X: np.random.randn(4, 64, 64, 3), Y: np.random.randn(4, 6)})
# print("cost = " + str(a))
#
# sess_test.close()
# 构建模型
def model(X_train, Y_train, X_test, Y_test, learning_rate=0.009,num_epochs=100, minibatch_size=64, print_cost=True, isPlot=True):
"""
使用TensorFlow实现三层的卷积神经网络
CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
参数:
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 - 每个小批量数据块的大小
print_cost - 是否打印成本值,每遍历100次整个数据集打印一次
isPlot - 是否绘制图谱
返回:
train_accuracy - 实数,训练集的准确度
test_accuracy - 实数,测试集的准确度
parameters - 学习后的参数
"""
ops.reset_default_graph() # 能够重新运行模型而不覆盖tf变量
tf.compat.v1.set_random_seed(1) # 确保你的数据和我一样
seed = 3 # 指定numpy的随机种子
(m, n_H0, n_W0, n_C0) = X_train.shape
n_y = Y_train.shape[1]
costs = []
# 为当前维度创建占位符
X, Y = create_placeholders(n_H0, n_W0, n_C0, n_y)
# 初始化参数
parameters = initialize_parameters()
# 前向传播
Z3 = forward_propagation(X, parameters)
# 计算成本
cost = compute_cost(Z3, Y)
# 反向传播,由于框架已经实现了反向传播,我们只需要选择一个优化器就行了
optimizer = tf.compat.v1.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
# 全局初始化所有变量
init = tf.compat.v1.global_variables_initializer()
# 开始运行
with tf.compat.v1.Session() as sess:
# 初始化参数
sess.run(init)
# 开始遍历数据集
for epoch in range(num_epochs):
minibatch_cost = 0
num_minibatches = int(m / minibatch_size) # 获取数据块的数量
seed = seed + 1
minibatches = cnn_utils.random_mini_batches(X_train, Y_train, minibatch_size, seed)
# 对每个数据块进行处理
for minibatch in minibatches:
# 选择一个数据块
(minibatch_X, minibatch_Y) = minibatch
# 最小化这个数据块的成本
_, temp_cost = sess.run([optimizer, cost], feed_dict={X: minibatch_X, Y: minibatch_Y})
# 累加数据块的成本值
minibatch_cost += temp_cost / num_minibatches
# 是否打印成本
if print_cost:
# 每5代打印一次
if epoch % 5 == 0:
print("当前是第 " + str(epoch) + " 代,成本值为:" + str(minibatch_cost))
# 记录成本
if epoch % 1 == 0:
costs.append(minibatch_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()
# 开始预测数据
## 计算当前的预测情况
predict_op = tf.argmax(Z3, 1)
corrent_prediction = tf.equal(predict_op, tf.argmax(Y, 1))
##计算准确度
accuracy = tf.reduce_mean(tf.cast(corrent_prediction, "float"))
print("corrent_prediction accuracy= " + str(accuracy))
train_accuracy = accuracy.eval({X: X_train, Y: Y_train})
test_accuary = accuracy.eval({X: X_test, Y: Y_test})
print("训练集准确度:" + str(train_accuracy))
print("测试集准确度:" + str(test_accuary))
return (train_accuracy, test_accuary, parameters)
# 启动模型
_, _, parameters = model(X_train,Y_train, X_test,Y_test,num_epochs=150)