本文参考何宽大神的文章,https://blog.csdn.net/u013733326/article/details/80086090
基于以上的文章加以自己的理解发表这篇博客,希望对大家的学习有所帮助
何宽大神的代码使用的是tf1.x,我所用的是tf2.x,一些代码有所改动,希望大家注意
1. 神经网络的底层搭建
1.1 - 导入库
我们先要引入一些库:
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'
np.random.seed(1)#指定随机种子
1.2 - 大纲
我们将实现一个卷积神经网络的一些模块,下面我们将列举我们要实现的模块的函数功能:
-
卷积模块,包含了以下函数:
- 使用0扩充边界
- 卷积窗口
- 前向卷积
- 反向卷积(可选)
-
池化模块,包含了以下函数:
- 前向池化
- 创建掩码
- 值分配
- 反向池化(可选)
我们将在这里从底层搭建一个完整的模块,之后我们会用TensorFlow实现。模型结构如下:
1.3- 卷积神经网络
尽管编程框架使卷积容易使用,但它们仍然是深度学习中最难理解的概念之一。卷积层将输入转换成不同维度的输出,如下所示。
我们将一步步构建卷积层,我们将首先实现两个辅助函数:一个用于零填充,另一个用于计算卷积。
1.3.1 - 边界填充
边界填充将会在图像边界周围添加值为0的像素点,如下图所示:
使用0填充边界有以下好处:
-
卷积了上一层之后的CONV层,没有缩小高度和宽度。 这对于建立更深的网络非常重要,否则在更深层时,高度/宽度会缩小。 一个重要的例子是“same”卷积,其中高度/宽度在卷积完一层之后会被完全保留。
-
它可以帮助我们在图像边界保留更多信息。在没有填充的情况下,卷积过程中图像边缘的极少数值会受到过滤器的影响从而导致信息丢失。
-
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])
代码运行结果如下:
这里博主有点迷多维数组,特意输出了一下x,可以看出:
x [[[[ 1.62434536 -0.61175641]
[-0.52817175 -1.07296862]
[ 0.86540763 -2.3015387 ]]
[[ 1.74481176 -0.7612069 ]
[ 0.3190391 -0.24937038]
[ 1.46210794 -2.06014071]]
[[-0.3224172 -0.38405435]
[ 1.13376944 -1.09989127]
[-0.17242821 -0.87785842]]]
[[[ 0.04221375 0.58281521]
[-1.10061918 1.14472371]
[ 0.90159072 0.50249434]]
[[ 0.90085595 -0.68372786]
[-0.12289023 -0.93576943]
[-0.26788808 0.53035547]]
[[-0.69166075 -0.39675353]
[-0.6871727 -0.84520564]
[-0.67124613 -0.0126646 ]]]
[[[-1.11731035 0.2344157 ]
[ 1.65980218 0.74204416]
[-0.19183555 -0.88762896]]
[[-0.74715829 1.6924546 ]
[ 0.05080775 -0.63699565]
[ 0.19091548 2.10025514]]
[[ 0.12015895 0.61720311]
[ 0.30017032 -0.35224985]
[-1.1425182 -0.34934272]]]
[[[-0.20889423 0.58662319]
[ 0.83898341 0.93110208]
[ 0.28558733 0.88514116]]
[[-0.75439794 1.25286816]
[ 0.51292982 -0.29809284]
[ 0.48851815 -0.07557171]]
[[ 1.13162939 1.51981682]
[ 2.18557541 -1.39649634]
[-1.44411381 -0.50446586]]]]
1.3.2 - 单步卷积
在这里,我们要实现第一步卷积,我们要使用一个过滤器来卷积输入的数据。先来看看下面的这个gif:
在计算机视觉应用中,左侧矩阵中的每个值都对应一个像素值,我们通过将其值与原始矩阵元素相乘,然后对它们进行求和来将3x3滤波器与图像进行卷积。我们需要实现一个函数,可以将一个3x3滤波器与单独的切片块进行卷积并输出一个实数。现在我们开始实现conv_single_step()
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))
Z = -23.16021220252078
1.3.3 - 卷积神经网络 - 前向传播
在前向传播的过程中,我们将使用多种过滤器对输入的数据进行卷积操作,每个过滤器会产生一个2D的矩阵,我们可以把它们堆叠起来,于是这些2D的卷积矩阵就变成了高维的矩阵。我们可以看一下下的图:
我们需要实现一个函数以实现对激活值进行卷积。我们需要在激活值矩阵[Math Processing Error]A_prevAprev上使用过滤器[Math Processing Error]WW进行卷积。该函数的输入是前一层的激活输出[Math Processing Error]A_prevAprev,[Math Processing Error]FF个过滤器,其权重矩阵为[Math Processing Error]WW、偏置矩阵为[Math Processing Error]bb,每个过滤器只有一个偏置,最后,我们需要一个包含了步长[Math Processing Error]ss和填充[Math Processing Error]pp的字典类型的超参数。
小提示:
- 如果我要在矩阵A_prev(shape = (5,5,3))的左上角选择一个2x2的矩阵进行切片操作,那么可以这样做:
<span style="color:#000000"><code class="language-python">a_slice_prev <span style="color:#669900">=</span> a_prev<span style="color:#999999">[</span><span style="color:#98c379">0</span><span style="color:#999999">:</span><span style="color:#98c379">2</span><span style="color:#999999">,</span><span style="color:#98c379">0</span><span style="color:#999999">:</span><span style="color:#98c379">2</span><span style="color:#999999">,</span><span style="color:#999999">:</span><span style="color:#999999">]</span> </code></span>
- 1
- 如果我想要自定义切片,我们可以这么做:先定义要切片的位置,
vert_start
、vert_end
、horiz_start
、horiz_end
,它们的位置我们看一下下面的图就明白了。
**图 3** : **定义切片的开始、结束位置 (使用 2x2 的过滤器)**
只适用于单通道
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"的超参数字典。
返回:
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])
输出:
np.mean(Z) = 0.15585932488906465 cache_conv[0][1][2][3] = [-0.20075807 0.18656139 0.41005165]
1.4 - 池化层
池化层会减少输入的宽度和高度,这样它会较少计算量的同时也使特征检测器对其在输入中的位置更加稳定。下面介绍两种类型的池化层:
-
最大值池化层:在输入矩阵中滑动一个大小为fxf的窗口,选取窗口里的值中的最大值,然后作为输出的一部分。
-
均值池化层:在输入矩阵中滑动一个大小为fxf的窗口,计算窗口里的值中的平均值,然后这个均值作为输出的一部分。
-
-
<td> <img src="https://img-blog.csdn.net/20180425215257202?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM3MzMzMjY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" style="width:500px;height:300px;"> <td>
- 1
- 2
- 3
池化层没有用于进行反向传播的参数,但是它们有像窗口的大小为[Math Processing Error]ff的超参数,它指定fxf窗口的高度和宽度,我们可以计算出最大值或平均值。
1.4.1 - 池化层的前向传播
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)
输出
mode = max
A = [[[[1.74481176 1.6924546 2.10025514]]]
[[[1.19891788 1.51981682 2.18557541]]]]
----------------------------
mode = average
A = [[[[-0.09498456 0.11180064 -0.14263511]]]
[[[-0.09525108 0.28325018 0.33035185]]]]
反向传播我偷懒了就没做
2. 神经网络的应用
我们已经使用了原生代码实现了卷积神经网络,现在我们要使用TensorFlow来实现,然后应用到手势识别中,在这里我们要实现4个函数,一起来看看吧~
2.1.0 TensorFlow模型
我们先来导入库:
import math
import numpy as np
import h5py
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import tensorflow as tf
from tensorflow.python.framework import ops
import cnn_utils
%matplotlib inline
np.random.seed(1)
构建网络
#构建网络
X_train_orig , Y_train_orig , X_test_orig , Y_test_orig , classes = cnn_utils.load_dataset()#加载数据集
再看一下里面的图片:
index = 19
plt.imshow(X_train_orig[index])
print ("y = " + str(np.squeeze(Y_train_orig[:, index])))
输出:
在课程2中,我们已经建立过一个神经网络,我想对这个数据集应该不陌生吧~我们再来看一下数据的维度,如果你忘记了独热编码的实现,请看https://blog.csdn.net/weixin_47440593/article/details/107721334
X_train = X_train_orig/255.
X_test = X_test_orig/255.
Y_train = cnn_utils.convert_to_one_hot(Y_train_orig, 6).T
Y_test = cnn_utils.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 = {}
输出结果:
number of training examples = 1080
number of test examples = 120
X_train shape: (1080, 64, 64, 3)
Y_train shape: (1080, 6)
X_test shape: (120, 64, 64, 3)
Y_test shape: (120, 6)
2.1.1 创建placeholders
TensorFlow要求您为运行会话时将输入到模型中的输入数据创建占位符。现在我们要实现创建占位符的函数,因为我们使用的是小批量数据块,输 入的样本数量可能不固定,所以我们在数量那里我们要使用None作为可变数量。输入X的维度为**[None,n_H0,n_W0,n_C0],对应的Y是[None,n_y]**。
tf.compat.v1.disable_eager_execution()#防止出现Error: tf.placeholder() is not compatible with eager execution.
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])#这里注意下tf版本,和原博客不一致
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))
输出结果:
X = Tensor("Placeholder:0", shape=(?, 64, 64, 3), dtype=float32)
Y = Tensor("Placeholder_1:0", shape=(?, 6), dtype=float32)
2.1.2 初始化参数
现在我们将使用tf.contrib.layers.xavier_initializer(seed = 0)
来初始化权值/过滤器W1W1W1、W2W2W2。在这里,我们不需要考虑偏置,因为TensorFlow会考虑到的。需要注意的是我们只需要初始化为2D卷积函数,全连接层TensorFlow会自动初始化的。
注意:由于TF2.x删除了contrib,我使用了initializer = tf.initializers.GlorotUniform(seed=1))来代替initializer=tf.contrib.layers.xavier_initializer(seed=1))
#初始化参数
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.initializers.GlorotUniform(seed=0))
W2 = tf.compat.v1.get_variable("W2",[2,2,8,16],initializer = tf.initializers.GlorotUniform(seed=0))
parameters = {"W1": W1,
"W2": W2}
return parameters
测试一下:
ops.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()
输出:
2.1.2 - 前向传播
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
测试一下:
ops.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.get_variable()
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()
输出:
2.1.3 计算成本
我们要在这里实现计算成本的函数,下面的两个函数是我们要用到的:
-
tf.nn.softmax_cross_entropy_with_logits(logits = Z3 , lables = 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()
输出结果:
2.1.4 构建模型
最后,我们已经实现了我们所有的函数,我们现在就可以实现我们的模型了。
我们之前在课程2就实现过random_mini_batches()
这个函数,它返回的是一个mini-batches的列表。
在实现这个模型的时候我们要经历以下步骤:
- 创建占位符
- 初始化参数
- 前向传播
- 计算成本
- 反向传播
- 创建优化器
最后,我们将创建一个session来运行模型。
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.random.set_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)
运行结果如下:
当前是第 0 代,成本值为:1.912230797111988
当前是第 5 代,成本值为:1.569796048104763
当前是第 10 代,成本值为:1.0709842927753925
当前是第 15 代,成本值为:0.8364478275179863
当前是第 20 代,成本值为:0.6450640186667442
当前是第 25 代,成本值为:0.5164481848478317
当前是第 30 代,成本值为:0.4250429607927799
当前是第 35 代,成本值为:0.3875112319365144
当前是第 40 代,成本值为:0.39796690735965967
当前是第 45 代,成本值为:0.35784107632935047
当前是第 50 代,成本值为:0.33078993018716574
当前是第 55 代,成本值为:0.2784481458365917
当前是第 60 代,成本值为:0.23763000266626477
当前是第 65 代,成本值为:0.23787363898009062
当前是第 70 代,成本值为:0.2309840233065188
当前是第 75 代,成本值为:0.20303455460816622
当前是第 80 代,成本值为:0.19067543931305408
当前是第 85 代,成本值为:0.16841116221621633
当前是第 90 代,成本值为:0.13843759335577488
当前是第 95 代,成本值为:0.16666325414553285
当前是第 100 代,成本值为:0.16687324037775397
当前是第 105 代,成本值为:0.13381452416069806
当前是第 110 代,成本值为:0.1545729306526482
当前是第 115 代,成本值为:0.10977528267540038
当前是第 120 代,成本值为:0.08463473827578127
当前是第 125 代,成本值为:0.11592008848674595
当前是第 130 代,成本值为:0.13320672535337508
当前是第 135 代,成本值为:0.09206865262240171
当前是第 140 代,成本值为:0.09328121761791408
当前是第 145 代,成本值为:0.08370728208683431
2.7 - 测试你自己的图片(选做)