【中文】【吴恩达课后编程作业】Course 4 - 卷积神经网络 - 第一周作业

【中文】【吴恩达课后编程作业】Course 4 - 卷积神经网络 - 第一周作业 - 搭建卷积神经网络模型以及应用(1&2)


上一篇: 【课程4 - 第一周测验】※※※※※ 【回到目录】※※※※※下一篇: 【课程4 - 第二周测验】

声明

  本文参考Kulbear【Convolution model - Step by Step 】【Convolution model - Application】,我基于以上的文章加以自己的理解发表这篇博客,力求让大家以最轻松的姿态理解吴恩达的视频,如有不妥的地方欢迎大家指正。


资料下载

  本文所使用的资料已上传到百度网盘【点击下载(8.55MB)】,提取码:pcoi ,请在开始之前下载好所需资料,或者在本文底部copy资料代码。


【博主使用的python版本:3.6.2】


1. 神经网络的底层搭建

  这里,我们要实现一个拥有卷积层(CONV)和池化层(POOL)的网络,它包含了前向和反向传播。我们来定义一下符号:

  • 上标 [ l ] [l] [l]是指第 l l l

    • 比如: a [ 4 ] a^{[4]} a[4]是指第 4 4 4层的激活值。 W [ 5 ] W^{[5]} W[5] b [ 5 ] b{[5]} b[5]是指第五层的参数。
  • 上标 ( i ) (i) (i)是指来自第 i i i个样本。

    • 比如: x ( i ) x^{(i)} x(i)是指来自输入的第 i i i个样本
  • 下标 i i i是指向量的第 i i i

    • 比如: a i [ l ] a^{[l]}_{i} ai[l]是指来自第 l l l层的第 i i i个激活值,假设这是一个完全连接层(FC)。
  • n H n_H nH n W n_W nW n c n_c nc是指分别表示给定层的图像的高度、宽度和通道数。如果你想特指某一层,那么可以这样写: n H [ l ] n^{[l]}_H nH[l] n W [ l ] n^{[l]}_W nW[l] n c [ l ] n^{[l]}_c nc[l]

  • n H p r e v n_{H_{prev}} nHprev  、  n W p r e v n_{W_{prev}} nWprev 、  n C p r e v n_{C_{prev}} nCprev分别表示前一层的图像的高度、宽度和通道数。如果你想指定特定层 l l l,那么你也可以这样写: n H [ l − 1 ] n^{[l-1]}_H nH[l1] 、 n W [ l − 1 ] n_W^{[l-1]} nW[l1] 、 n C [ l − 1 ] n_C^{[l-1]} nC[l1]

1.1 - 导入库

我们先要引入一些库:

import numpy as np
import h5py
import matplotlib.pyplot as plt

%matplotlib inline
plt.rcParams['figure.figsize'] = (5.0, 4.0) 
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

#ipython很好用,但是如果在ipython里已经import过的模块修改后需要重新reload就需要这样
#在执行用户代码前,重新装入软件的扩展和模块。
%load_ext autoreload   
#autoreload 2:装入所有 %aimport 不包含的模块。
%autoreload 2          

np.random.seed(1)      #指定随机种子

1.2 - 大纲

我们将实现一个卷积神经网络的一些模块,下面我们将列举我们要实现的模块的函数功能:

  • 卷积模块,包含了以下函数:

    • 使用0扩充边界
    • 卷积窗口
    • 前向卷积
    • 反向卷积(可选)
  • 池化模块,包含了以下函数:

    • 前向池化
    • 创建掩码
    • 值分配
    • 反向池化(可选)

我们将在这里从底层搭建一个完整的模块,之后我们会用TensorFlow实现。模型结构如下:
model.png
需要注意的是我们在前向传播的过程中,我们会存储一些值,以便在反向传播的过程中计算梯度值。

1.3- 卷积神经网络

  尽管编程框架使卷积容易使用,但它们仍然是深度学习中最难理解的概念之一。卷积层将输入转换成不同维度的输出,如下所示。
conv_nn

我们将一步步构建卷积层,我们将首先实现两个辅助函数:一个用于零填充,另一个用于计算卷积。

1.3.1 - 边界填充

边界填充将会在图像边界周围添加值为0的像素点,如下图所示:

PAD

**图 1** : ** 边界填充**
使用pading为2的操作对图像(3通道,RGB)进行填充。

使用0填充边界有以下好处:

  • 卷积了上一层之后的CONV层,没有缩小高度和宽度。 这对于建立更深的网络非常重要,否则在更深层时,高度/宽度会缩小。 一个重要的例子是“same”卷积,其中高度/宽度在卷积完一层之后会被完全保留。

  • 它可以帮助我们在图像边界保留更多信息。在没有填充的情况下,卷积过程中图像边缘的极少数值会受到过滤器的影响从而导致信息丢失。

  我们将实现一个边界填充函数,它会把所有的样本图像 X X X都使用0进行填充。我们可以使用np.pad来快速填充。需要注意的是如果你想使用pad = 1填充数组**a**.shape = ( 5 , 5 , 5 , 5 , 5 ) (5,5,5,5,5) (5,5,5,5,5)的第二维,使用pad = 3填充第4维,使用pad = 0来填充剩下的部分,我们可以这么做:


#constant连续一样的值填充,有constant_values=(x, y)时前面用x填充,后面用y填充。缺省参数是为constant_values=(0,0)

a = np.pad(a,( (0,0),(1,1),(0,0),(3,3),(0,0)),'constant',constant_values = (..,..))

#比如:
import numpy as np
arr3D = np.array([[[1, 1, 2, 2, 3, 4],
             [1, 1, 2, 2, 3, 4], 
             [1, 1, 2, 2, 3, 4]], 
             
            [[0, 1, 2, 3, 4, 5], 
             [0, 1, 2, 3, 4, 5], 
             [0, 1, 2, 3, 4, 5]], 
             
            [[1, 1, 2, 2, 3, 4], 
             [1, 1, 2, 2, 3, 4], 
             [1, 1, 2, 2, 3, 4]]])

print 'constant:  \n' + str(np.pad(arr3D, ((0, 0), (1, 1), (2, 2)), 'constant'))

"""
constant:  
[[[0 0 0 0 0 0 0 0 0 0]
  [0 0 1 1 2 2 3 4 0 0]
  [0 0 1 1 2 2 3 4 0 0]
  [0 0 1 1 2 2 3 4 0 0]
  [0 0 0 0 0 0 0 0 0 0]]

 [[0 0 0 0 0 0 0 0 0 0]
  [0 0 0 1 2 3 4 5 0 0]
  [0 0 0 1 2 3 4 5 0 0]
  [0 0 0 1 2 3 4 5 0 0]
  [0 0 0 0 0 0 0 0 0 0]]

 [[0 0 0 0 0 0 0 0 0 0]
  [0 0 1 1 2 2 3 4 0 0]
  [0 0 1 1 2 2 3 4 0 0]
  [0 0 1 1 2 2 3 4 0 0]
  [0 0 0 0 0 0 0 0 0 0]]]
"""

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.shape = (4, 3, 3, 2)
x_paded.shape = (4, 7, 7, 2)
x[1, 1] = [[ 0.90085595 -0.68372786]
 [-0.12289023 -0.93576943]
 [-0.26788808  0.53035547]]
x_paded[1, 1] = [[ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]]

< matplotlib.image.AxesImage at 0x2369ea5d470 >

<matplotlib.image.AxesImage at 0x2369ea5d470>

1.3.2 - 单步卷积

在这里,我们要实现第一步卷积,我们要使用一个过滤器来卷积输入的数据。先来看看下面的这个gif:
Convolution_schematic.gif

**图 2** : **卷积操作**
过滤器大小:f = 2 , 步伐:s = 1)

  在计算机视觉应用中,左侧矩阵中的每个值都对应一个像素值,我们通过将其值与原始矩阵元素相乘,然后对它们进行求和来将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.1602122025

1.3.3 - 卷积神经网络 - 前向传播

  在前向传播的过程中,我们将使用多种过滤器对输入的数据进行卷积操作,每个过滤器会产生一个2D的矩阵,我们可以把它们堆叠起来,于是这些2D的卷积矩阵就变成了高维的矩阵。我们可以看一下下的图:

  我们需要实现一个函数以实现对激活值进行卷积。我们需要在激活值矩阵 A p r e v A_prev Aprev上使用过滤器 W W W进行卷积。该函数的输入是前一层的激活输出 A p r e v A_prev Aprev F F F个过滤器,其权重矩阵为 W W W、偏置矩阵为 b b b,每个过滤器只有一个偏置,最后,我们需要一个包含了步长 s s s和填充 p p p的字典类型的超参数。

小提示:

  • 如果我要在矩阵A_prev(shape = (5,5,3))的左上角选择一个2x2的矩阵进行切片操作,那么可以这样做:
    a_slice_prev = a_prev[0:2,0:2,:]
    
  • 如果我想要自定义切片,我们可以这么做:先定义要切片的位置,vert_startvert_endhoriz_starthoriz_end,它们的位置我们看一下下面的图就明白了。
    vert_horiz_kiank.png
**图 3** : **定义切片的开始、结束位置 (使用 2x2 的过滤器)**
只适用于单通道

我们还是说一下输出的维度的计算公式吧~
n H = ⌊ n H p r e v − f + 2 × p a d s t r i d e ⌋ + 1 n_H = \lfloor \frac{n_{H_{prev}} - f + 2 \times pad}{stride} \rfloor +1 nH=stridenHprevf+2×pad+1
n W = ⌊ n W p r e v − f + 2 × p a d s t r i d e ⌋ + 1 n_W = \lfloor \frac{n_{W_{prev}} - f + 2 \times pad}{stride} \rfloor +1 nW=stridenWprevf+2×pad+1
n C = 过滤器数量 n_C = \text{过滤器数量} nC=过滤器数量

这里我们不会使用矢量化,只是用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"的超参数字典。
    
    返回:
        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.155859324889
cache_conv[0][1][2][3] = [-0.20075807  0.18656139  0.41005165]

最后,卷积层应该包含一个激活函数,我们可以加一行代码来计算:

#获取输出
Z[i, h, w, c] = ...
#计算激活
A[i, h, w, c] = activation(Z[i, h, w, c])

不过,在这里我们不需要这么做。

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>

  池化层没有用于进行反向传播的参数,但是它们有像窗口的大小为 f f f的超参数,它指定fxf窗口的高度和宽度,我们可以计算出最大值或平均值。

1.4.1 - 池化层的前向传播

  现在我们要在同一个函数中实现最大值池化层均值池化层,和之前计算输出维度一样,池化层的计算也是一样的。
n H = ⌊ n H p r e v − f s t r i d e ⌋ + 1 n_H = \lfloor \frac{n_{H_{prev}} - f}{stride} \rfloor +1 nH=stridenHprevf+1
n W = ⌊ n W p r e v − f s t r i d e ⌋ + 1 n_W = \lfloor \frac{n_{W_{prev}} - f}{stride} \rfloor +1 nW=stridenWprevf+1
n C = n C p r e v n_C = n_{C_{prev}} nC=nCprev

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]]]]

1.5 - 卷积神经网络中的反向传播(选学)

  在现在的深度学习框架中,你只需要实现前向传播,框架负责向后传播,所以大多数深度学习工程师不需要费心处理后向传播的细节,卷积网络的后向传递是有点复杂的。但是如果你愿意,你可以选择性来学习本节。

  在前面的课程中,我们已经实现了一个简单的(全连接)神经网络,我们使用反向传播来计算关于更新参数的成本的梯度。类似地,在卷积神经网络中我们可以计算出关于成本的导数来更新参数。反向传播的方程并不简单,吴恩达老师并没有在课堂上推导它们,但我们可以在下面简要介绍。

1.5.1 - 卷积层的反向传播

我们来看一下如何实现卷积层的反向传播

1.5.1.1 - 计算dA

下面的公式是计算 d A dA dA的:
d A + = ∑ h = 0 n H ∑ w = 0 n W W c × d Z h w (1) dA += \sum ^{n_H} _{h=0} \sum ^{n_W} _{w=0} W_{c} \times dZ_{hw} \tag{1} dA+=h=0nHw=0nWWc×dZhw(1)

  其中, W c W_c Wc是过滤器, Z h w Z_{hw} Zhw 是一个标量, Z h w Z_{hw} Zhw是卷积层第 h h h行第 w w w列的使用点乘计算后的输出 Z Z Z的梯度。需要注意的是在每次更新 d A dA dA的时候,都会用相同的过滤器 W c W_c Wc乘以不同的 d Z dZ dZ,因为在前向传播的时候,每个过滤器都与a_slice进行了点乘相加,所以在计算 d A dA dA的时候,我们需要把a_slice的梯度也加进来,我们可以在循环中加一句代码:


da_perv_pad[vert_start:vert_end,horiz_start:horiz_end,:] += W[:,:,:,c] * dZ[i,h,w,c]

1.5.1.2 - 计算dW

这是计算 d W c dW_c dWc的公式( d W c dW_c dWc是一个过滤器的梯度):

d W c + = ∑ h = 0 n H ∑ w = 0 n W a s l i c e × d Z h w (2) dW_c += \sum^{n_H}_{h=0} \sum^{n_W}_{w=0}a_{slice} \times dZ_{hw} \tag{2} dWc+=h=0nHw=0nWaslice×dZhw(2)

  其中, a s l i c e a_{slice} aslice 对应着 Z i j Z_{ij} Zij的激活值。由此,我们就可以推导 W W W的梯度,因为我们使用了过滤器来对数据进行窗口滑动,在这里,我们实际上是切出了和过滤器一样大小的切片,切了多少次就产生了多少个梯度,所以我们需要把它们加起来得到这个数据集的整体 d W dW dW

在代码上我们只需要使用一行代码实现:


dW[:,:,:, c] += a_slice * dZ[i , h , w , c]

1.5.1.3 - 计算db

这个是计算 d b db db的公式:

d b = ∑ h ∑ w d Z h w (3) db = \sum^{}_{h} \sum^{}_{w}dZ_{hw} \tag{3} db=hwdZhw(3)

  和以前的神经网络一样, d b db db是由 d Z dZ dZ的累加计算的,在这里,我们只需要将conv的输出 Z Z Z的所有梯度累加就好了。在代码上我们只需要使用一行代码实现:


db[:,:,:,c] += dZ[ i, h, w, c]

1.5.1.4 - 函数实现

  现在我们将实现反向传播函数conv_backward(),我们需要把所有的训练样本的过滤器、权值、高度、宽度都要加进来,然后使用公式1、2、3计算对应的梯度。

def conv_backward(dZ,cache):
    """
    实现卷积层的反向传播
    
    参数:
        dZ - 卷积层的输出Z的 梯度,维度为(m, n_H, n_W, n_C)
        cache - 反向传播所需要的参数,conv_forward()的输出之一
        
    返回:
        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)
    
    """
    #获取cache的值
    (A_prev, W, b, hparameters) = cache
    
    #获取A_prev的基本信息
    (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
    
    #获取dZ的基本信息
    (m,n_H,n_W,n_C) = dZ.shape
    
    #获取权值的基本信息
    (f, f, n_C_prev, n_C) = W.shape
    
    #获取hparaeters的值
    pad = hparameters["pad"]
    stride = hparameters["stride"]
    
    #初始化各个梯度的结构
    dA_prev = np.zeros((m,n_H_prev,n_W_prev,n_C_prev))
    dW = np.zeros((f,f,n_C_prev,n_C))
    db = np.zeros((1,1,1,n_C))
    
    #前向传播中我们使用了pad,反向传播也需要使用,这是为了保证数据结构一致
    A_prev_pad = zero_pad(A_prev,pad)
    dA_prev_pad = zero_pad(dA_prev,pad)
    
    #现在处理数据
    for i in range(m):
        #选择第i个扩充了的数据的样本,降了一维。
        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
                    vert_end = vert_start + f
                    horiz_start = w
                    horiz_end = horiz_start + f
                    
                    #定位完毕,开始切片
                    a_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_slice * dZ[i,h,w,c]
                    db[:,:,:,c] += dZ[i,h,w,c]
        #设置第i个样本最终的dA_prev,即把非填充的数据取出来。
        dA_prev[i,:,:,:] = da_prev_pad[pad:-pad, pad:-pad, :]
    
    #数据处理完毕,验证数据格式是否正确
    assert(dA_prev.shape == (m, n_H_prev, n_W_prev, n_C_prev))
    
    return (dA_prev,dW,db)
    

测试一下:

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)
#反向传播
dA , dW , db = conv_backward(Z,cache_conv)
print("dA_mean =", np.mean(dA))
print("dW_mean =", np.mean(dW))
print("db_mean =", np.mean(db))

测试结果

dA_mean = 9.60899067587
dW_mean = 10.5817412755
db_mean = 76.3710691956

1.5.2 - 池化层的反向传播

  接下来,我们从最大值池化层开始实现池化层的反向传播。 即使池化层没有反向传播过程中要更新的参数,我们仍然需要通过池化层反向传播梯度,以便为在池化层之前的层(比如卷积层)计算梯度。

1.5.2.1 最大值池化层的反向传播

在开始池化层的反向传播之前,我们需要创建一个create_mask_from_window()的函数,我们来看一下它是干什么的:
X = [ 1 3 4 2 ] → M = [ 0 0 1 0 ] (4) X = \begin{bmatrix} 1 && 3 \\ 4 && 2 \end {bmatrix} \quad \rightarrow \quad M = \begin{bmatrix} 0 && 0 \\ 1 && 0 \end {bmatrix} \tag{4} X=[1432]M=[0100](4)

  正如你所看到的,这个函数创建了一个掩码矩阵,以保存最大值的位置,当为1的时候表示最大值的位置,其他的为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))

测试结果

x = [[ 1.62434536 -0.61175641 -0.52817175]
 [-1.07296862  0.86540763 -2.3015387 ]]
mask = [[ True False False]
 [False False False]]

  为什么我们要创建这一个掩码矩阵呢?想一下我们的正向传播首先是经过卷积层,然后滑动地取卷积层最大值构成了池化层,如果我们不记录最大值的位置,那么我们怎样才能反向传播到卷积层呢?

1.5.2.2 均值池化层的反向传播

  在最大值池化层中,对于每个输入窗口,输出的所有值都来自输入中的最大值,但是在均值池化层中,因为是计算均值,所以输入窗口的每个元素对输出有一样的影响,我们来看看如何反向传播吧~
d Z = 1 → d Z = [ 1 4 1 4 1 4 1 4 ] (5) dZ=1 \quad \rightarrow \quad dZ = \begin{bmatrix} \frac{1}{4} && \frac{1}{4} \\ \frac{1}{4} && \frac{1}{4} \end{bmatrix} \tag{5} dZ=1dZ=[41414141](5)

def distribute_value(dz,shape):
    """
    给定一个值,为按矩阵大小平均分配到每一个矩阵位置中。
    
    参数:
        dz - 输入的实数
        shape - 元组,两个值,分别为n_H , n_W
        
    返回:
        a - 已经分配好了值的矩阵,里面的值全部一样。
    
    """
    #获取矩阵的大小
    (n_H , n_W) = shape
    
    #计算平均值
    average = dz / (n_H * n_W)
    
    #填充入矩阵
    a = np.ones(shape) * average
    
    return a

测试一下:

dz = 2
shape = (2,2)

a = distribute_value(dz,shape)
print("a = " + str(a))

测试结果

a = [[ 0.5  0.5]
 [ 0.5  0.5]]
1.5.2.3 池化层的反向传播
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()
dA_prev = pool_backward(dA, cache, mode = "average")
print("mode = average")
print('mean of dA = ', np.mean(dA))
print('dA_prev[1,1] = ', dA_prev[1,1]) 

测试结果

ode = max
mean of dA =  0.145713902729
dA_prev[1,1] =  [[ 0.          0.        ]
 [ 5.05844394 -1.68282702]
 [ 0.          0.        ]]

mode = average
mean of dA =  0.145713902729
dA_prev[1,1] =  [[ 0.08485462  0.2787552 ]
 [ 1.26461098 -0.25749373]
 [ 1.17975636 -0.53624893]]

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)

我们使用的是【中文】【吴恩达课后编程作业】Course 2 - 改善深层神经网络 - 第三周作业 - TensorFlow入门的数据集
SIGNS.png
我们再来看一下里面有什么:

index = 6
plt.imshow(X_train_orig[index])
print ("y = " + str(np.squeeze(Y_train_orig[:, index])))

执行结果

y = 2

y=2

  在课程2中,我们已经建立过一个神经网络,我想对这个数据集应该不陌生吧~我们再来看一下数据的维度,如果你忘记了独热编码的实现,请看这里

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]**。

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.placeholder(tf.float32,[None, n_H0, n_W0, n_C0])
    Y = tf.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) 来初始化权值/过滤器 W 1 W1 W1 W 2 W2 W2。在这里,我们不需要考虑偏置,因为TensorFlow会考虑到的。需要注意的是我们只需要初始化为2D卷积函数,全连接层TensorFlow会自动初始化的。

def initialize_parameters():
    """
    初始化权值矩阵,这里我们把权值矩阵硬编码:
    W1 : [4, 4, 3, 8]
    W2 : [2, 2, 8, 16]
    
    返回:
        包含了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

测试一下:

tf.reset_default_graph()
with tf.Session() as sess_test:
    parameters = initialize_parameters()
    init = tf.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()

测试结果

W1 = [ 0.00131723  0.14176141 -0.04434952  0.09197326  0.14984085 -0.03514394
 -0.06847463  0.05245192]
W2 = [-0.08566415  0.17750949  0.11974221  0.16773748 -0.0830943  -0.08058
 -0.00577033 -0.14643836  0.24162132 -0.05857408 -0.19055021  0.1345228
 -0.22779644 -0.1601823  -0.16117483 -0.10286498]

2.1.2 - 前向传播

在TensorFlow里面有一些可以直接拿来用的函数:

  • tf.nn.conv2d(X,W1,strides=[1,s,s,1],padding='SAME'):给定输入 X X X和一组过滤器 W 1 W1 W1,这个函数将会自动使用 W 1 W1 W1来对 X X X进行卷积,第三个输入参数是**[1,s,s,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'):给定输入 X X X,该函数将会使用大小为(f,f)以及步伐为(s,s)的窗口对其进行滑动取最大值,你也可以看一下文档

  • tf.nn.relu(Z1):计算Z1的ReLU激活,点这里阅读文档

  • tf.contrib.layers.flatten(P):给定一个输入P,此函数将会把每个样本转化成一维的向量,然后返回一个tensor变量,其维度为(batch_size,k).点这里阅读文档

  • tf.contrib.layers.fully_connected(F, num_outputs):给定一个已经一维化了的输入F,此函数将会返回一个由全连接层计算过后的输出。点这里阅读文档

  使用tf.contrib.layers.fully_connected(F, num_outputs)的时候,全连接层会自动初始化权值且在你训练模型的时候它也会一直参与,所以当我们初始化参数的时候我们不需要专门去初始化它的权值。

我们实现前向传播的时候,我们需要定义一下我们模型的大概样子:

C O N V 2 D → R E L U → M A X P O O L → C O N V 2 D → R E L U → M A X P O O L → F U L L C O N N E C T E D CONV2D \rightarrow RELU \rightarrow MAXPOOL \rightarrow CONV2D \rightarrow RELU \rightarrow MAXPOOL \rightarrow FULLCONNECTED CONV2DRELUMAXPOOLCONV2DRELUMAXPOOLFULLCONNECTED

我们具体实现的时候,我们需要使用以下的步骤和参数:

  • Conv2d : 步伐:1,填充方式:“SAME”
  • ReLU
  • Max pool : 过滤器大小:8x8,步伐:8x8,填充方式:“SAME”
  • Conv2d : 步伐:1,填充方式:“SAME”
  • ReLU
  • Max pool : 过滤器大小:4x4,步伐:4x4,填充方式:“SAME”
  • 一维化上一层的输出
  • 全连接层(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.contrib.layers.flatten(P2)
    
    #全连接层(FC):使用没有非线性激活函数的全连接层
    Z3 = tf.contrib.layers.fully_connected(P,6,activation_fn=None)
    
    return Z3
    

测试一下:

tf.reset_default_graph()
np.random.seed(1)

with tf.Session() as sess_test:
    X,Y = create_placeholders(64,64,3,6)
    parameters = initialize_parameters()
    Z3 = forward_propagation(X,parameters)
    
    init = tf.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()
    
    

测试结果

Z3 = [[-0.44670227 -1.57208765 -1.53049231 -2.31013036 -1.29104376  0.46852064]
 [-0.17601591 -1.57972014 -1.4737016  -2.61672091 -1.00810647  0.5747785 ]]

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.reset_default_graph()

with tf.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.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()

测试结果

cost = 2.91034

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.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.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
    
    #全局初始化所有变量
    init = tf.global_variables_initializer()
    
    #开始运行
    with tf.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.arg_max(Z3,1)
        corrent_prediction = tf.equal(predict_op , tf.arg_max(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.91791956872
当前是第 5 代,成本值为:1.53247521073
当前是第 10 代,成本值为:1.01480386034
当前是第 15 代,成本值为:0.885136671364
当前是第 20 代,成本值为:0.766963448375
当前是第 25 代,成本值为:0.651207884774
当前是第 30 代,成本值为:0.613355720416
当前是第 35 代,成本值为:0.605931192636
当前是第 40 代,成本值为:0.53471290879
当前是第 45 代,成本值为:0.551402201876
当前是第 50 代,成本值为:0.496976461262
当前是第 55 代,成本值为:0.454438356683
当前是第 60 代,成本值为:0.455495661125
当前是第 65 代,成本值为:0.458359180018
当前是第 70 代,成本值为:0.450039617717
当前是第 75 代,成本值为:0.41068668291
当前是第 80 代,成本值为:0.469005130231
当前是第 85 代,成本值为:0.389252956957
当前是第 90 代,成本值为:0.363807530142
当前是第 95 代,成本值为:0.376132288016
当前是第 100 代,成本值为:0.375231474638
当前是第 105 代,成本值为:0.349760836922
当前是第 110 代,成本值为:0.366967062466
当前是第 115 代,成本值为:0.325298860669
当前是第 120 代,成本值为:0.327003779821
当前是第 125 代,成本值为:0.374339781702
当前是第 130 代,成本值为:0.350489996374
当前是第 135 代,成本值为:0.318831278477
当前是第 140 代,成本值为:0.269049352966
当前是第 145 代,成本值为:0.342204230838
corrent_prediction accuracy= Tensor("Mean_1:0", shape=(), dtype=float32)
训练集准确度:0.908333
测试集准确度:0.791667

plot

2.1.5 预测</ del>

#博主遇到点问题 以后更新

3.0 - 相关库代码

3.1 - cnn_utils.py

#cnn_utils.py

import math
import numpy as np
import h5py
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.python.framework import ops
def load_dataset():
    train_dataset = h5py.File('datasets/train_signs.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_signs.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
    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
def random_mini_batches(X, Y, mini_batch_size = 64, seed = 0):
    """
    Creates a list of random minibatches from (X, Y)
    Arguments:
    X -- input data, of shape (input size, number of examples) (m, Hi, Wi, Ci)
    Y -- true "label" vector (containing 0 if cat, 1 if non-cat), of shape (1, number of examples) (m, n_y)
    mini_batch_size - size of the mini-batches, integer
    seed -- this is only for the purpose of grading, so that you're "random minibatches are the same as ours.
    Returns:
    mini_batches -- list of synchronous (mini_batch_X, mini_batch_Y)
    """
    m = X.shape[0]                  # number of training examples
    mini_batches = []
    np.random.seed(seed)
    # Step 1: Shuffle (X, Y)
    permutation = list(np.random.permutation(m))
    shuffled_X = X[permutation,:,:,:]
    shuffled_Y = Y[permutation,:]
    # Step 2: Partition (shuffled_X, shuffled_Y). Minus the end case.
    num_complete_minibatches = math.floor(m/mini_batch_size) # number of mini batches of size mini_batch_size in your partitionning
    for k in range(0, num_complete_minibatches):
        mini_batch_X = shuffled_X[k * mini_batch_size : k * mini_batch_size + mini_batch_size,:,:,:]
        mini_batch_Y = shuffled_Y[k * mini_batch_size : k * mini_batch_size + mini_batch_size,:]
        mini_batch = (mini_batch_X, mini_batch_Y)
        mini_batches.append(mini_batch)
    # Handling the end case (last mini-batch &lt; mini_batch_size)
    if m % mini_batch_size != 0:
        mini_batch_X = shuffled_X[num_complete_minibatches * mini_batch_size : m,:,:,:]
        mini_batch_Y = shuffled_Y[num_complete_minibatches * mini_batch_size : m,:]
        mini_batch = (mini_batch_X, mini_batch_Y)
        mini_batches.append(mini_batch)
    return mini_batches
def convert_to_one_hot(Y, C):
    Y = np.eye(C)[Y.reshape(-1)].T
    return Y
def forward_propagation_for_predict(X, parameters):
    """
    Implements the forward propagation for the model: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SOFTMAX
    Arguments:
    X -- input dataset placeholder, of shape (input size, number of examples)
    parameters -- python dictionary containing your parameters "W1", "b1", "W2", "b2", "W3", "b3"
                  the shapes are given in initialize_parameters
    Returns:
    Z3 -- the output of the last LINEAR unit
    """
    # Retrieve the parameters from the dictionary "parameters" 
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    W3 = parameters['W3']
    b3 = parameters['b3'] 
                                                           # Numpy Equivalents:
    Z1 = tf.add(tf.matmul(W1, X), b1)                      # Z1 = np.dot(W1, X) + b1
    A1 = tf.nn.relu(Z1)                                    # A1 = relu(Z1)
    Z2 = tf.add(tf.matmul(W2, A1), b2)                     # Z2 = np.dot(W2, a1) + b2
    A2 = tf.nn.relu(Z2)                                    # A2 = relu(Z2)
    Z3 = tf.add(tf.matmul(W3, A2), b3)                     # Z3 = np.dot(W3,Z2) + b3
    return Z3
'''
def predict(X, parameters):
    W1 = tf.convert_to_tensor(parameters["W1"])
    b1 = tf.convert_to_tensor(parameters["b1"])
    W2 = tf.convert_to_tensor(parameters["W2"])
    b2 = tf.convert_to_tensor(parameters["b2"])
    W3 = tf.convert_to_tensor(parameters["W3"])
    b3 = tf.convert_to_tensor(parameters["b3"])
    params = {"W1": W1,
              "b1": b1,
              "W2": W2,
              "b2": b2,
              "W3": W3,
              "b3": b3}
    x = tf.placeholder("float", [12288, 1])
    z3 = forward_propagation_for_predict(x, params)
    p = tf.argmax(z3)
    sess = tf.Session()
    prediction = sess.run(p, feed_dict = {x: X})
    return prediction
'''
  • 259
    点赞
  • 265
    收藏
    觉得还不错? 一键收藏
  • 359
    评论
评论 359
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值