手把手教你搭建卷积神经网络(CNN)

摘要:
下图显示了CNN的结构。因此,本文首先根据上图中卷积神经网络的操作步骤,通过编写函数来逐步实现CNN模型,最后使用TensorFlow框架构建CNN进行图像分类。

本文是基于吴恩达老师的《深度学习》第四课第一周习题所做,如果本文在某些知识点上描述得不够透彻的可以参见相关章节的具体讲解,同时极力推荐各位有志从事计算机视觉的朋友观看一下吴恩达老师的《深度学习》课程。
1.卷积神经网络构成

总的来说,卷积神经网络与神经网络的区别是增加了若干个卷积层,而卷积层又可细分为卷积(CONV)和池化(POOL)两部分操作(这两个重要概念稍后会简单的介绍,如果对CNN的基本概念不是特别了解的同学可以学习达叔的《卷积神经网络》课程);然后是全连接层(FC),可与神经网络的隐藏层相对应;最后是softmax层预测输出值y_hat。下图为CNN的结构图。

虽然深度学习框架会让这种复杂的算法实现起来变得容易,但只有自己实现一遍才能更透彻的理解上图中涉及到的计算操作。因此,本文先按照上图卷积神经网络的运算步骤,通过编写函数一步一步来实现CNN模型,最后会使用TensorFlow框架搭建CNN来进行图片分类。
2.第三方库

以下是在编码实现CNN中用到的第三方库。

    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)

3.前向传播过程

卷积神经网络的前向传播过程包括:填充(padding)、卷积操作(conv)、激活函数(Relu)、池化(pooling)、全连接(FC)、softmax分类,其中激活、全连接、softmax与深层神经网络中的计算方法一致,本文不再赘述,如不了解可参见本人上一篇文章《使用tensorflow搭建深层神经网络》。
3.1 填充(padding)

对输入图像进行卷积操作时,我们会发现一个问题:角落或边缘的像素点被使用的次数相对较少。这样一来在图片识别中会弱化边缘信息。因此我们使用padding操作,在图像原始数据周围填充p层数据,如下图。当填充的数据为0时,我们称之为Zero-padding。除了,能够保留更多有效信息之外,padding操作还可以保证在使用卷积计算前后卷的高和宽不变化。

padding操作需要使用到numpy中的一个函数:np.pad()。假设我们要填充一个数组a,维度为(5,5,5,5,5),如果我们想要第二个维度的pad=1,第4个维度的pad=3,其余pad=0,那么我们如下使用np.pad()

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

    def zero_pad(X, pad):
     
        X_pad = np.pad(X, ((0,0),(pad,pad),(pad,pad),(0,0)), 'constant')
     
        return X_pad

    x = np.random.randn(4,3,3,2)
    x_pad = zero_pad(x, 2)
    print('x.shape=',x.shape)
    print('x_pad.shape=',x_pad.shape)
    print('x[1,1]=',x[1,1])
    print('x_pad[1,1]=',x_pad[1,1])

    x.shape= (4, 3, 3, 2)
    x_pad.shape= (4, 7, 7, 2)
    x[1,1]= [[ 0.90085595 -0.68372786]
     [-0.12289023 -0.93576943]
     [-0.26788808  0.53035547]]
    x_pad[1,1]= [[0. 0.]
     [0. 0.]
     [0. 0.]
     [0. 0.]
     [0. 0.]
     [0. 0.]
     [0. 0.]]

    fig, axarr = plt.subplots(1,2)
    axarr[0].set_title('x')
    axarr[0].imshow(x[0,:,:,0])
    axarr[1].set_title('x_pad')
    axarr[1].imshow(x_pad[0,:,:,0])
    plt.show()

3.2单步卷积(single step of convolution)

在卷积操作中,我们首先需要明确的一个概念是过滤器(核),它是一个通道数与输入图像相同,但高度和宽度为较小奇数(通常为1,3,5,7等,我们可将这个超参数用f表示)的多维数组(f, f, n_c)。在下面动图示例中过滤器是(3,3)的数组np.array([[1,0,1],[0,1,0],[1,0,1]]),其中这9个数字可以事先设定,也可以通过反向传播来学习。设定好过滤器后还需设置步长stride(s),步长即每次移动的像素数,在动图示例中s=1,具体的卷积过程见下图,最后得到一个卷积后的特征矩阵。

 

    def conv_single_step(a_slice_prev, W, b):
     
        s = a_slice_prev * W
     
        Z = np.sum(s)
     
        Z = Z + b
     
        return Z

    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=',Z)

Z= [[[-6.99908945]]]

3.3 卷积层(convolution layer)

在3.2中给出的例子是只有一个过滤器的情况,在CNN卷积层中过滤器会有多个,此时运算会稍微复杂但原理是一致的,只是输出时要将每个过滤器卷积后的图像叠加在一起输出。

在编写代码前有几个关键点需要说明:

1.如果想从矩阵a_prev(形状为(5,5,3))中截取一个(2,2)的片,我们可以

a_slice_prev = a_prev[0:2,0:2,:]

2.a_slice_prev的四角vert_start, vert_end, horiz_start 和 horiz_end的定义如下图所示。

3.卷积操作后输出的矩阵的维度满足以下三个公式:

    def conv_forward(A_prev, W, b, hparameters):
     
        (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
     
        (f, f, n_C_prev, n_C) = W.shape
     
        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)
     
        Z = np.zeros((m, n_H, n_W, n_C))
     
        A_prev_pad = zero_pad(A_prev, pad)
     
        for i in range(m):
            a_prev_pad = A_prev_pad[i, :, :, :]
            for h in range(n_H):
                for w in range(n_W):
                    for c in range(n_C):
     
                        vert_start = stride * h
                        vert_end = vert_start + f
                        horiz_start = stride * w
                        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[:,:,:,c])
     
        assert(Z.shape == (m, n_H, n_W, n_C))
     
        cache = (A_prev, W, b, hparameters)
     
        return Z, cache

 

    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": 2}
     
    Z, cache_conv = conv_forward(A_prev, W, b, hparameters)
     
    print("Z's mean=", np.mean(Z))
    print("Z[3,2,1]=",Z[3,2,1])
    print("cache_conv[0][1][2][3]=",cache_conv[0][1][2][3])

    Z's mean= 0.048995203528855794
    Z[3,2,1]= [-0.61490741 -6.7439236  -2.55153897  1.75698377  3.56208902  0.53036437
      5.18531798  8.75898442]
    cache_conv[0][1][2][3]= [-0.20075807  0.18656139  0.41005165]

3.4池化层(pooling layer)

 池化层的作用是缩减网络的大小,提高计算速度,同时可提高所提取特征的鲁棒性,主要两种类型:最大池化(max-pooling)和平均池化(average-pooling)。

池化层没有需要使用反向传播进行学习的参数,但是有两个超参数需要考虑:窗口大小(f)和步长(s)

池化后输出的矩阵的维度满足以下三个公式:

 

    def pool_forward(A_prev, hparameters, mode = "max"):
     
        (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
     
        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
     
        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_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)
        assert(A.shape == (m, n_H, n_W, n_C))
     
        cache = (A_prev, hparameters)
     
        return A, cache

    A_prev = np.random.randn(2,4,4,3)
    hparameters = {"stride":2, "f":3}
    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 0.86540763 1.13376944]]]
     
     
     [[[1.13162939 1.51981682 2.18557541]]]]
     
    mode = average
    A= [[[[ 0.02105773 -0.20328806 -0.40389855]]]
     
     
     [[[-0.22154621  0.51716526  0.48155844]]]]

 4.反向传播过程

在使用深度学习框架情况下,我们只要保证前向传播的过程即可,框架会自动执行反向传播的过程,因此深度学习工程师不需要关注反向传播的过程。CNN的反向传播过程相对复杂,但是了解其实现的过程可帮助我们更好的了解整个模型,当然如果没时间可以跳过本节直接进入到使用TensorFlow构建CNN。
4.1卷积层的反向传播

4.1.1计算dA

假设过滤器的参数矩阵为Wc,则dA的计算公式如下图:

其中dZhw是卷积层输出Z的第h行w列的梯度下降值,在更新dA时每次用不同的dZ与Wc想乘,这主要是因为在前向传播时,每个过滤器都是和矩阵a_prev的切片点乘后求和,因此在计算dA的反向传播时要加上所有切片对应的梯度值。我们可以将这个公式写成:

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

4.1.2计算dW

dWc的计算公式为

其中a_slice为计算Zij时从a_prev截取的计算矩阵,该式可写为:

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

 4.1.3计算db

db的计算公式为:

可见db为dZ的和,该式可写为:

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

 

    def conv_backward(dZ, cache):
     
        (A_prev, W, b, hparameters) = cache
     
        (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
     
        (f, f, n_C_prev, n_C) = W.shape
     
        stride = hparameters['stride']
        pad = hparameters['pad']
     
        (m, n_H, n_W, n_C) = dZ.shape
     
        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))
     
        A_prev_pad = zero_pad(A_prev, pad)
        dA_prev_pad = zero_pad(dA_prev, pad)
     
        for i in range(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_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]
     
            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

    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": 2}
     
    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 = 1.4524377775388075
    dW_mean = 1.7269914583139097
    db_mean = 7.839232564616838

4.2池化层的反向传播

虽然在pooling层没有需要使用反向传播来更新的参数,但其前有卷积层因此还是需要做反向传播运算。

4.2.1max-pooling的反向传播

首先我们先创建一个辅助函数creat_mask_from_window(),这个函数的主要作用是将Window矩阵中最大的元素标志为1,其余为0.

    def create_mask_from_window(x):
     
        mask = (x == np.max(x))
     
        return mask

 其中mask = (x == np.max(x)),判断的过程如下:

    A[i,j] = True if X[i,j] == x
    A[i,j] = False if X[i,j] != x

测试create_mask_from_window函数

    x = np.random.randn(2,3)
    mask = create_mask_from_window(x)
    print('x = ', x)
    print("mask = ", mask)

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

我们之所以要追踪最大元素的位置,是因为在池化过程中该元素对输出起到了决定性作用,而且会影响到cost的计算。

4.2.2Average pooling的反向传播

与max-pooling输出只受最大值影响不同,average pooling中窗口输入元素之间的同等重要,因此在反向传播中也要给予各元素相同的权重。假设在前向传播中使用的2x2过滤器,因此在反向传播中mask矩阵将如下运算:

    def distribute_value(dz, shape):
     
        (n_H, n_W) = shape
     
        average = dz / (n_H * n_W)
     
        a = average * np.ones(shape)
     
        return a

    a = distribute_value(2, (2,2))
    print('distributed value =', a)

    distributed value = [[0.5 0.5]
     [0.5 0.5]]

4.2.3 整合pooling反向传播

    def pool_backward(dA, cache, mode="max"):
     
        (A_prev, hparameters) = cache
     
        stride = hparameters['stride']
        f = hparameters['f']
     
        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(np.shape(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 = stride * h
                        vert_end = vert_start + f
                        horiz_start = stride * 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[i, vert_start:vert_end,horiz_start:horiz_end,c]
                                       += np.multiply(mask, dA[i,h,w,c])
     
                        elif mode == "average":
     
                            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

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

    mode = max
    mean of dA =  0.14571390272918056
    dA_prev[1,1] =  [[ 0.          0.        ]
     [ 5.05844394 -1.68282702]
     [ 0.          0.        ]]
     
    mode = average
    mean of dA =  0.14571390272918056
    dA_prev[1,1] =  [[ 0.08485462  0.2787552 ]
     [ 1.26461098 -0.25749373]
     [ 1.17975636 -0.53624893]]

至此,我们了解CNN中卷积层和池化层的构造以及前向传播和反向传播的过程,下面我们将使用TensorFlow框架来搭建CNN,并用以识别数字手势图形。
5.卷积神经网络的应用

在前面小节中我们使用python来构建函数逐步了解CNN的机制,而大多数深度学习的实际应用中都是使用某一个深度学习框架来完成的, 下面我们将会看到使用深度学习框架的内置函数带来的便利。

这是我们要用到的第三方库和辅助程序,涉及到的数据和代码可从这里下载。

    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 as tf
    from tensorflow.python.framework import ops
    from cnn_utils import *
     
    np.random.seed(1)

 运行下列代码,导入给定的数据集。

X_train_orig, Y_train_orig, X_test_orit, Y_test_orig, classes = load_dataset()

    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

数据集是0-6六个数据的手势图片,如下:

我们随意选取数据集中的一个样本显示。

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

输出标签为

y= 5

 在之前的文章里已经介绍过数据处理的相关内容,可参见文章《使用TensorFlow搭建深层神经网络》。

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

5.1创建placeholders

我们知道在TensorFlow框架下,在运行Session时想要给model喂数据的话,必须先创建placeholders。此时我们不用给定训练集的样本数,因此使用None作为batch的大小,所以X的维度是[None, n_H0, n_W0, n_C0], Y的维度是[None, n_y],代码如下:

    def create_placeholders(n_H0, n_W0, n_C0, n_y):
     
        X = tf.placeholder(tf.float32, shape = [None, n_H0, n_W0, n_C0])
        Y = tf.placeholder(tf.float32, shape = [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)

5.2初始化参数

假设我们要初始一个参数,其shape为[1,2,3,4],在TensorFlow中初始方式如下:

W = tf.get_variable('W', [1, 2, 3, 4], initializer = ...)

我们只需要初始化权重或过滤器的参数W1,W2即可, 而偏差b、全连接层的参数学习框架会自动帮我们处理,不用在意。

    def initialize_parameters():
     
        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:
        parameters = initialize_parameters()
        init = tf.global_variables_initializer()
        sess.run(init)
        print("W1 = " + str(parameters["W1"].eval()[1,1,1]))
        print("W2 = " + str(parameters["W2"].eval()[1,1,1]))

    W1 = [ 0.00131723  0.1417614  -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]

5.3前向传播

正如多次提及的,使用深度学习框架我们只要处理好前向传播过程,框架会自动帮助我们处理反向传播的过程;而在框架中内置了很多函数可以为我们执行卷积步骤,比如:

(1)tf.nn.conv2d(X, W1, strides = [1, s, s, 1], padding = 'SAME'):这个函数将输入X和W1进行卷积计算,第三个输入strides 规定了在X(shape为(m, n_H_prev, n_W_prev, n_C_prev))各维度上的步长s,第四个输入padding规定padding的方式;

(2)tf.nn.max_pool(A, ksize = [1, f, f, 1], strides = [1, s, s, 1], padding = 'SAME'):这个函数是以ksize和strides规定的方式对输入A进行max-pooling操作;

(3)tf.nn.relu(Z1):Relu作为激活函数;

(4)tf.contrib.layers.flatten(P):将P中每个样本flatten成一维向量,最后返回一个flatten的shape为[batch_size, k]的图;

(5)tf.contrib.layers.fully_connected(F, num_outputs):给定flatten的输入F, 返回一个经全连接层计算的值num_outputs。使用此函数时,可以自动的初始化全连接层的权重系统并且在训练网络时训练权重。

本程序中我们的前向传播过程包括如下步骤: CONV2D - > RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLCONNECTED,各步骤中使用的参数如下:

CONV2D - > stride = 1, padding = "SAME"

RELU ->

MAXPOOL -> f = 8, stride = 8, padding = "SAME"

CONV2D -> stride = 1, padding = "SAME"

RELU ->

MAXPOOL -> f = 4, stride = 4, padding = "SAME"

FLATTEN ->

FULLCONNECTED:不需要调用softmax,因为在这里FC层会输出6个神经元之后会传递给softmax,而在TensorFlow中softmax和cost结成在另外一个单独的函数中。

    def forward_propagation(X, parameters):
     
        W1 = parameters['W1'] / np.sqrt(2)
        W2 = parameters['W2'] / np.sqrt(2)
     
        Z1 = tf.nn.conv2d(X, W1, strides=[1,1,1,1], padding='SAME')
        A1 = tf.nn.relu(Z1)
        P1 = tf.nn.max_pool(A1, ksize=[1,8,8,1], strides=[1,8,8,1], padding='SAME')
     
        Z2 = tf.nn.conv2d(P1, W2, strides=[1,1,1,1], padding='SAME')
        A2 = tf.nn.relu(Z2)
        P2 = tf.nn.max_pool(A2, ksize=[1,4,4,1], strides=[1,4,4,1], padding='SAME')
     
        P2 = tf.contrib.layers.flatten(P2)
        Z3 = tf.contrib.layers.fully_connected(P2, 6, activation_fn=None)
     
        return Z3

 tips:在初始化W1,W2时我们使用的初始化tf.contrib.layers.xavier_initializer,这里xavier只使用np.sqrt(1/n),然而对于relu激活函数使用np.sqrt(1/n)可以取得更好的效果,因此我们需要在初始化后再使用W1,W2时需要再除以np.sqrt(2)。

    tf.reset_default_graph()
     
    with tf.Session() as sess:
        np.random.seed(1)
        X, Y = create_placeholders(64, 64, 3, 6)
        parameters = initialize_parameters()
        Z3 = forward_propagation(X, parameters)
        init = tf.global_variables_initializer()
        sess.run(init)
        a = sess.run(Z3, {X:np.random.randn(2,64,64,3), Y:np.random.randn(2,6)})
        print('Z3 = ' + str(a))

    Z3 = [[ 1.4416984  -0.24909666  5.450499   -0.2618962  -0.20669907  1.3654671 ]
     [ 1.4070846  -0.02573211  5.08928    -0.48669922 -0.40940708  1.2624859 ]]

5.4计算cost

在计算cost时我们需要用到如下两个内置函数:

(1)tf.nn.softmax_cross_entropy_with_logits(logits = Z3, labels = Y):这个函数在计算softmax激活函数同时可计算loss结果;

(2)tf.reduce_mean:这个函数对所有的loss求和得到cost值。

    def compute_cost(Z3, Y):
     
        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:
        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.run(init)
        a = sess.run(cost, {X: np.random.randn(4,64,64,3), Y: np.random.randn(4,6)})
        print("cost = " + str(a))

    tf.reset_default_graph()
     
    with tf.Session() as sess:
        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.run(init)
        a = sess.run(cost, {X: np.random.randn(4,64,64,3), Y: np.random.randn(4,6)})
        print("cost = " + str(a))

    Z3 = [[ 0.63031745 -0.9877705  -0.4421346   0.05680432  0.5849418   0.12013616]
     [ 0.43707377 -1.0388098  -0.5433439   0.0261174   0.57343066  0.02666192]]

 tips:此处跟达叔课后答案有些不同,是因为我们使用的TensorFlow版本不同而已。
5.5整合model

整个model分如下几部:

(1)创建placeholders

(2)初始化参数

(3)前向传播

(4)计算cost

(5)创建optimizer

(6)运行Session

    def model(X_train, Y_train, X_test, Y_test, learning_rate=0.009,
              num_epochs = 100, minibatch_size = 64, print_cost = True):
     
        ops.reset_default_graph()
        tf.set_random_seed(1)
        seed = 3
        (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).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 = 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 == True and epoch % 10 == 0:
                    print("Cost after epoch %i:%f"%(epoch, minibatch_cost))
                if print_cost == True and epoch % 1 == 0:
                    costs.append(minibatch_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"))
            print(accuracy)
            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)

    Cost after epoch 0:1.906084
    Cost after epoch 10:0.971529
    Cost after epoch 20:0.648505
    Cost after epoch 30:0.463869
    Cost after epoch 40:0.385492
    Cost after epoch 50:0.327990
    Cost after epoch 60:0.266418
    Cost after epoch 70:0.224210
    Cost after epoch 80:0.248607
    Cost after epoch 90:0.158102

    Train Accuracy: 0.94166666
    Test Accuracy: 0.825

 在此我们设置num_epochs = 100,可以通过增加迭代代数来提高精度,比如500代。

    fname = "images\myfigure.jpg"
    image = np.array(ndimage.imread(fname, flatten=False))
    my_image = scipy.misc.imresize(image, size=(64,64))
     
    plt.imshow(image)
    plt.show();

免责声明:文章转载自《手把手教你搭建卷积神经网络(CNN)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇win10家庭版的defender注册表关闭和开启小结一下:Delphi 与 UniGui 的安装下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

原生java api操作ES数据库

原生java api操作ES数据库 该部分也只写了一点点,后面有更简单的做法,就没写完 有需要再继续写吧...........参考一下就好了 @SpringBootTest class JavaEsApplicationTests { @Autowired private RestHighLevelClient restHighLevel...

lua 函数调用1 -- 闭包详解和C调用

这里, 简单的记录一下lua中闭包的知识和C闭包调用 前提知识: 在lua api小记2中已经分析了lua中值的结构, 是一个 TValue{value, tt}组合, 如果有疑问, 可以去看一下 一些重要的数据结构     lua中有两种闭包, c闭包和lua闭包     两种闭包的公共部分:        #define ClosureHeader C...

sysbench的安装及使用

sysbench是一个模块化的、跨平台、多线程基准,主要用于评估测试各种不同系统参数下的数据库负载情况。它主要包括以下几种方式的测试:测试工具 文档顺序: 一、安装 二、测试 1、cpu性能2、磁盘io性能3、调度程序性能4、内存分配及传输速度5、POSIX线程性能6、数据库性能(OLTP基准测试)目前sysbench主要支持 MySQL,pgsql,or...

写在一起和分开写的效果

IE7、IE8对css文件中 tr td {padding:0px;}th{padding:0px;} 和 tr td th{padding:0px;} 的解释有所不同。 后者,认不出th的样式。 FF未测试。 --- 哈哈哈。。笔者当年真的不是前端程序员啊。。2016/8/22...

java中的静态导入

     java中的静态导入他是jdk5.0的新特性,所谓静态导入就是不使用类名.属性名,类名.方法名的形式去调用属性或方法,而是通过静态导入,直接使用方法名和属性。 静态导入的语法: import static 包名.类名.静态成员变量; import static 包名.类名.静态成员函数; 下面来看一个例子: 1.自定义一个类,给一个成员变量,给一...

mybatis问题合集:#{}与${}区别、动态sql语句、缓存机制

一、MyBatis 中#{}和${}区别   #{} 是预编译处理,像传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号)   ${} 就是字符串替换。直接替换掉占位符。$方式一般用于传入数据库对象,例如传入表名.   使用 ${} 的话会导致 sql 注入。什么是 SQL 注入呢?比如 select * from u...