吴恩达深度学习 | (17) 卷积神经网络专项课程第一周编程作业

吴恩达深度学习专项课程的所有实验均采用iPython Notebooks实现,不熟悉的朋友可以提前使用一下Notebooks。本周的实验包括两个小实验:实验1逐步构建CNN,使用NumPy手写实现卷积神经网络的基本构件,包括卷积层和池化层的前向/反向传播过程,通过纯手写实现,可以更深入的了解CNN的机制和原理;实验2是CNN的应用,使用Tensorflow构建一个简单的卷积神经网络模型,实现图像的分类任务,注意与之前使用的全联接网络进行对比。

目录

一、实验1:逐步构建卷积神经网络

1.实验综述

2.导入必要的包

3.实验大纲

4.卷积层

5.池化层

6.卷积神经网络的反向传播

二、实验2:CNN应用

1.实验综述

2.导入必要的包

3.数据集

4.用TF构建CNN

三、实验完整代码


一、实验1:逐步构建卷积神经网络

1.实验综述

2.导入必要的包

import numpy as np
import h5py #用于与存储为h5格式的数据集进行交互
import matplotlib.pyplot as plt

%matplotlib inline
#设置默认绘图风格
plt.rcParams['figure.figsize'] = (5.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

%load_ext autoreload
%autoreload 2

#设置随机种子 保证生成的随机数一致
np.random.seed(1)

3.实验大纲

4.卷积层

  • zero 填充

 

  def zero_pad(X, pad):
    """
    对数据集X中的所有图像用0进行填充。
    如上图所示,对每张图像每一个通道上的图像的宽度和高度进行填充。
    
    Argument:
    X -- 是一个4维Python数组,维度为 (m, n_H, n_W, n_C) 代表一个batch中的m个图像
    pad -- 整数,表示对于每一张图像在垂直和水平方向上填充的量,即填充几圈。
    
    Returns:
    X_pad -- 返回填充后的图像 维度为 (m, n_H + 2*pad, n_W + 2*pad, n_C)
    """
    
    X_pad = np.pad(X,((0,0),(pad,pad),(pad,pad),(0,0)),'constant',constant_values=0)
    
    return X_pad
  • 一步卷积操作

# GRADED FUNCTION: conv_single_step

def conv_single_step(a_slice_prev, W, b):
    """
    在前一层激活函数输出的一个样本的部分区域(即一张图像的部分区域 (f,f,n_C_prev)与filter一样大)上应用一个由权重参数W定义的一个filter(f,f,n_C_prev),
    进行卷积操作得到一个实数
    
    Arguments:
    a_slice_prev --输入数据中的一个样本的部分区域(即一张图像的部分区域) (f, f, n_C_prev)
    W -- filter窗口中的权重参数 - 维度(f, f, n_C_prev)
    b -- filter窗口中的偏置参数 - 维度 (1, 1, 1)
    
    Returns:
    Z -- 一个实数, 滑动窗口(W,b)在一个在输入数据的一个样本(一张图像)上的一步卷积操作值。
    """
    s = np.multiply(a_slice_prev,W) #对应位置相乘
    Z = np.sum(s) #对s(立体)的所有元素进行求和
    Z = float(b)+Z #加上偏置参数
    return Z
  • CNN 卷积层前向传播


def conv_forward(A_prev, W, b, hparameters):
    """
    实现卷积操作的前向传播
    
    Arguments:
    A_prev -- 上一层激活函数的输出, 四维数组(m, n_H_prev, n_W_prev, n_C_prev)
    W -- filters的权重参数,四维数组 (f, f, n_C_prev, n_C)
    b -- filters的偏置参数,四维数组 (1, 1, 1, n_C)
    hparameters -- Python字典包含步长和填充
        
    Returns:
    Z -- 卷积操作后的输出,四维数组 (m, n_H, n_W, n_C)
    cache -- 缓存重要的参数,与卷积操作的反向传播共享参数
    """
    
    #得到输入的相关信息
    (m,n_H_prev,n_W_prev,n_C_prev) = A_prev.shape
    #得到filters的相关信息
    (f,f,n_C_prev,n_C) = W.shape
    
    #得到步长和pad
    stride = hparameters['stride']
    pad = hparameters['pad']
    
    #计算输出图像的高度和宽度
    n_H = int((n_H_prev-f+2*pad)/stride+1)
    n_W = int((n_W_prev-f+2*pad)/stride+1)
    
    #用0初始化卷积操作的输出
    Z = np.zeros((m,n_H,n_W,n_C))
    
    #对上一层激活函数的输出A_prev 进行填充
    A_prev_pad = zero_pad(A_prev,pad)
    
    for i in range(m):     #对输入的m个填充样本(m张图像)循环处理
        a_prev_pad = A_prev_pad[i] #取出第i个填充图像
        for h in range(n_H):  #遍历输出图像垂直方向上的每个像素点
            for w in range(n_W): #遍历输出图像水平方向上的每个像素点
                for c in range(n_C):#遍历输出图像的每个通道
                    
                    #找到每个输入图像的各个区域
                    vert_start = h*stride #垂直方向起点
                    vert_end = vert_start+f#垂直方向终点
                    horiz_start = w*stride #水平方向起点
                    horiz_end = horiz_start+f#水平方向终点
                    
                    #得到一个区域
                    a_prev_pad_slice = a_prev_pad[vert_start:vert_end,horiz_start:horiz_end,:]
                    #计算该区域上的卷积操作 得到某个输出图像单个通道上的一个值
                    Z[i,h,w,c] = conv_single_step(a_prev_pad_slice,W[:,:,:,c],b[:,:,:,c])
    
    assert(Z.shape == (m,n_H,n_W,n_C))
    
    cache = (A_prev,W,b,hparameters)
    
    return Z, cache

5.池化层

  • 池化层前向传播

# GRADED FUNCTION: pool_forward

def pool_forward(A_prev, hparameters, mode = "max"):
    """
    实现池化层的前向传播.
    
    Arguments:
    A_prev -- 输入数据,包含m张图像 四维数组 (m, n_H_prev, n_W_prev, n_C_prev)
    hparameters -- Python字典,包含超参数f和stride,池化一般没有填充
    mode -- 池化类型 max or avg
    
    Returns:
    A -- 池化层的输出 四维数组(m, n_H, n_W, n_C)
    cache -- 缓存重要的参数 包括输入数据和超参数 与池化层的反向传播共享参数
    """
    
    # 得到池化层输入数据的信息
    (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
    
    # 得到超参数f和stride
    f = hparameters["f"]
    stride = hparameters["stride"]
    
    # 计算输出图像的大小
    n_H = int(1 + (n_H_prev - f) / stride)
    n_W = int(1 + (n_W_prev - f) / stride)
    n_C = n_C_prev
    
    # 初始化池化层的输出数据 四维数组 包含m张图像
    A = np.zeros((m, n_H, n_W, n_C))              
    
    for i in range(m): #遍历输入数据的m张图像
        for h in range(n_H):#遍历每个输出图像垂直方向上的每个像素
            for w in range(n_W):#遍历每个输出图像水平方向上的每个像素
                for c in range(n_C):#遍历每个输出图像的每个通道
                    
                    #找到每个图像各个区域的起始点
                    vert_start = h*stride
                    vert_end = vert_start+f
                    horiz_start = w*stride
                    horiz_end = horiz_start+f
                    
                    #得到一个区域
                    a_prev_slice = A_prev[i,vert_start:vert_end,horiz_start:horiz_end,c]
                    
                    if mode == 'max':
                        A[i,h,w,c] = np.max(a_prev_slice)
                    elif mode == 'average':
                        A[i,h,w,c] = np.mean(a_prev_slice)
         
    
    # 缓存重要参数
    cache = (A_prev, hparameters)
    

    assert(A.shape == (m, n_H, n_W, n_C))
    
    return A, cache

6.卷积神经网络的反向传播

  • 卷积层反向传播

def conv_backward(dZ, cache):
    """
    实现卷积层中卷积操作的反向传播
    
    Arguments:
    dZ -- 代价函数相对于卷积操作输出Z的偏导数, 四维数组(m, n_H, n_W, n_C)
    cache -- 前向传播中缓存的重要参数,用于与反向传播共享参数
    
    Returns:
    dA_prev -- 代价函数相对于卷积层的输入A_prev的偏导数,
               四维数组 (m, n_H_prev, n_W_prev, n_C_prev)
    dW -- 代价函数相对于卷积层权重参数的偏导数
          四维数组(f, f, n_C_prev, n_C)
    db -- 代价函数相对于卷积层偏置参数的偏导数
          四维数组 (1, 1, 1, n_C)
    """
    
    #取出卷积层前向传播缓存的参数
    (A_prev,W,b,hparameters) = cache
    
    #A_prev的相关信息
    (m,n_H_prev,n_W_prev,n_C_prev) = A_prev.shape
    
    #filters W的相关信息
    (f,f,n_C_prev,n_C) = W.shape
    
    #取出超参数
    stride = hparameters['stride']
    pad = hparameters['pad']
    
    #dZ的相关信息
    (m,n_H,n_W,n_C) = dZ.shape
    
    #初始化dA_prev,dW,db
    dA_prev = np.zeros((m,n_H_prev,n_W_prev,n_C_prev))
    dW = np.zeros(W.shape)
    db = np.zeros(b.shape)
    
    #填充A_prev和dA_prev
    A_prev_pad = zero_pad(A_prev,pad)
    dA_prev_pad = zero_pad(dA_prev,pad)
    
    for i in range(m): #遍历m张图像 (m个样本)
        a_prev_pad = A_prev_pad[i]
        da_prev_pad = dA_prev_pad[i]
        for h in range(n_H):#遍历每个输出图像垂直方向上的每个像素
            for w in range(n_W):#遍历每个输出图像水平方向上的每个像素
                for c in range(n_C):#遍历每个输出图像的每个通道
                     #找到每个图像各个区域的起始点
                    vert_start = h*stride
                    vert_end = vert_start+f
                    horiz_start = w*stride
                    horiz_end = horiz_start+f
                    
                    #得到一个区域
                    a_prev_pad_slice = a_prev_pad[vert_start:vert_end,horiz_start:horiz_end,:]
                    da_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :] += W[:,:,:,c] * dZ[i,h,w,c]
                    dW[:,:,:,c] += a_prev_pad_slice * dZ[i,h,w,c]
                    db[:,:,:,c] += dZ[i,h,w,c]
        dA_prev[i, :, :, :] = dA_prev_pad[i,pad:-pad, pad:-pad, :]
                    
    
   
    assert(dA_prev.shape == (m, n_H_prev, n_W_prev, n_C_prev))
    
    return dA_prev, dW, db
  • 池化层的反向传播

Max pooling反向传播:

def create_mask_from_window(x):
    """
    为输入矩阵 x创建一个mask,标识矩阵x中的最大项
    
    Arguments:
    x -- 输入矩阵 2维数组 (f, f)
    
    Returns:
    mask -- 和x同维的数组,最大值位置为1,其余为0
    """
    
    mask = (x==np.max(x))
    
    return mask

avg pooling反向传播:

def distribute_value(dz, shape):
    """
    用shape维的矩阵均匀的分配dz
    
    Arguments:
    dz -- 标量
    shape -- 均匀分配dz的矩阵的维度 (n_H,n_W)
    
    Returns:
    a -- 均匀分配dz的shape维的矩阵
    """
    
    (n_H,n_W) = shape
    a = np.ones(shape)
    a *= (dz/(n_H*n_W))
       
    return a

把上述两部分组合在一起:实现池化层反向传播

def pool_backward(dA, cache, mode = "max"):
    """
    实现池化层的反向传播
    
    Arguments:
    dA -- 代价函数cost相对于池化层输出A的梯度, 和 A同维
    cache -- 池化层前向传播过程中缓存的参数 包括池化层输入和超参数
    mode -- 模式 "max" or "average"
    
    Returns:
    dA_prev -- 代价函数cost相对于池化层输入A_prev的梯度, 和 A_prev同维
    """
    
    #取出cache中的参数
    (A_prev,hparameters) = cache
   
    #取出超参数
    f = hparameters['f']
    stride = hparameters['stride']
    
    #dA的相关信息
    (m,n_H,n_W,n_C) = dA.shape
    #A_prev 的相关信息
    (m,n_H_prev,n_W_prev,n_C_prev) = A_prev.shape
   
    #用0初始化dA_prev
    dA_prev = np.zeros(A_prev.shape)
    
    for i in range(m):                       # 遍历所有训练样本
        
        # 选择A_prev中的一个训练样本
        a_prev = A_prev[i]
        
        for h in range(n_H):#遍历每个输出图像垂直方向上的每个像素
            for w in range(n_W):#遍历每个输出图像水平方向上的每个像素
                for c in range(n_C):#遍历每个输出图像的每个通道
                     #找到每个图像各个区域的起始点
                    vert_start = h*stride
                    vert_end = vert_start+f
                    horiz_start = w*stride
                    horiz_end = horiz_start+f
                    
                    #计算某个模式下的反向传播
                    if mode == "max":
                        
                        # 使用之前的起始点和c定义a_prev的一个slice
                        a_prev_slice = a_prev[vert_start:vert_end,horiz_start:horiz_end,c]

                        # 为 a_prev_slice创建一个mask 
                        mask = create_mask_from_window(a_prev_slice)
                        
                        dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c] += dA[i,vert_start,horiz_start,c]*mask
                       
                        
                    elif mode == "average":
                        
                        # 从dA中得到a
                        a = dA[i,vert_start,horiz_start,c]
                        
                        shape = (f,f)
                      
                        dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c] += distribute_value(a,shape)
                        
  
    assert(dA_prev.shape == A_prev.shape)
    
    return dA_prev

二、实验2:CNN应用

1.实验综述

2.导入必要的包

import math
import numpy as np
import h5py  #用于与存储为h5格式的文件进行交互
import matplotlib.pyplot as plt
#用于在测试时读取图片
import scipy
from PIL import Image
from scipy import ndimage
#Tensorflow深度学习框架
import tensorflow as tf
from tensorflow.python.framework import ops
#cnn_utils.py 中定义了本次实验 需要用到的一些辅助函数
from cnn_utils import *

%matplotlib inline
np.random.seed(1) #设置随机种子

3.数据集

使用之前用过的‘SIGNS’数据集。

  • 导入数据集,可视化某一个样本
#导入数据集
X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = load_dataset()
index = 6
plt.imshow(X_train_orig[index])
print ("y = " + str(np.squeeze(Y_train_orig[:, index])))

  • 数据集预处理
#数据集预处理
X_train = X_train_orig/255. #标准化
X_test = X_test_orig/255.
Y_train = convert_to_one_hot(Y_train_orig, 6).T #标签转为one-hot编码
Y_test = convert_to_one_hot(Y_test_orig, 6).T
print ("number of training examples = " + str(X_train.shape[0]))
print ("number of test examples = " + str(X_test.shape[0]))
print ("X_train shape: " + str(X_train.shape))
print ("Y_train shape: " + str(Y_train.shape))
print ("X_test shape: " + str(X_test.shape))
print ("Y_test shape: " + str(Y_test.shape))
conv_layers = {}

4.用TF构建CNN

  • 创建placeholders

# GRADED FUNCTION: create_placeholders

def create_placeholders(n_H0, n_W0, n_C0, n_y):
    """
    为CNN模型的输入输出创建placeholders.
    
    Arguments:
    n_H0 -- 标量,输入图像的高度
    n_W0 -- 标量,输入图像的宽度
    n_C0 -- 标量,输入图像的通道数
    n_y -- 标量,分类类别数
        
    Returns:
    X -- 输入数据X的placeholder 维度 [None, n_H0, n_W0, n_C0]  dtype "float"
    Y -- 输入数据标签Y的placeholder 维度 [None, n_y]  dtype "float"
    """
    
    X = tf.placeholder(tf.float32,[None,n_H0,n_W0,n_C0])
    Y = tf.placeholder(tf.float32,[None,n_y])

    return X, Y
  • 初始化参数

# GRADED FUNCTION: initialize_parameters

def initialize_parameters():
    """
    用TF初始化两个卷积层的所有filters(权重参数):
                        W1 : [4, 4, 3, 8]
                        W2 : [2, 2, 8, 16]
    Returns:
    parameters -- tensor字典包含 W1, W2
    """
    
    tf.set_random_seed(1)                            
        
    W1 = tf.get_variable('W1',[4,4,3,8],initializer=tf.contrib.layers.xavier_initializer(seed = 0))
    W2 = tf.get_variable('W2',[2,2,8,16],initializer=tf.contrib.layers.xavier_initializer(seed = 0))


    parameters = {"W1": W1,
                  "W2": W2}
    
    return parameters
  • 前向传播

# GRADED FUNCTION: forward_propagation

def forward_propagation(X, parameters):
    """
    实现CNN模型的前向传播:
    CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
    
    Arguments:
    X -- 输入数据的placeholder  维度 (None,n0_H,n0_W,n0_C)
    parameters -- Python字典包含模型的filters "W1", "W2"
            

    Returns:
    Z3 -- 最后前联接层(输出层)的线性单元的输出
    """
    
   
    W1 = parameters['W1']
    W2 = parameters['W2']
    
    #conv2d
    Z1 = tf.nn.conv2d(X,W1,strides=[1,1,1,1],padding='SAME')
    #relu
    A1 = tf.nn.relu(Z1)
    #MAXpool
    P1 = tf.nn.max_pool(A1,ksize=[1,8,8,1],strides=[1,8,8,1],padding='SAME')
    #conv2d
    Z2 = tf.nn.conv2d(P1,W2,strides=[1,1,1,1],padding='SAME')
    #relu
    A2 = tf.nn.relu(Z2)
    #maxpool
    P2 = tf.nn.max_pool(A2,ksize=[1,4,4,1],strides=[1,4,4,1],padding='SAME')
    #flattened
    F = tf.contrib.layers.flatten(P2)
    #FC
    Z3 = tf.contrib.layers.fully_connected(F,num_outputs=6,activation_fn=None)
    

    return Z3
  • 计算cost

# GRADED FUNCTION: compute_cost 

def compute_cost(Z3, Y):
    """
    计算cost
    
    Arguments:
    Z3 -- 前向传播的最后全联接层(输出层)的线性单元输出  维度 (number of examples,6) 每行代表一个样本
    Y --  样本真实标签,维度与Z3一致
    
    Returns:
    cost - 代价函数的tensor
    """
    
    cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3,labels=Y))
    
    
    return cost
  • CNN模型


def model(X_train, Y_train, X_test, Y_test, learning_rate = 0.1,
          num_epochs = 100, minibatch_size = 64, print_cost = True):
    """
    用TF实现3层卷积网络:
    CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
    
    Arguments:
    X_train -- 训练集样本 (None, 64, 64, 3)
    Y_train -- 训练集标签 (None, n_y = 6)
    X_test -- 测试集样本 (None, 64, 64, 3)
    Y_test -- 测试集标签 (None, n_y = 6)
    learning_rate -- 学习率
    num_epochs -- 完整遍历训练集的次数
    minibatch_size -- mini-batch大小
    print_cost -- 为True时  每100个epoch打印一次cost
    
    Returns:
    train_accuracy -- 实数 模型在训练集上的准确率
    test_accuracy -- 实数模型在测试集上的准确率
    parameters -- 模型学好的参数 用于预测
    """
    
    ops.reset_default_graph()                       
    tf.set_random_seed(1)     #TF随机种子                        
    seed = 3       #NumPy随机种子                                  
    (m, n_H0, n_W0, n_C0) = X_train.shape             
    n_y = Y_train.shape[1]                            
    costs = []                                      
    
    # 为X,Y创建placeholder
    
    X,Y = create_placeholders(n_H0,n_W0,n_C0,n_y)

    # 初始化filters
    
    parameters = initialize_parameters()
    
    # ****前向传播,构建TF计算图****
    
    Z3 = forward_propagation(X,parameters)
    
    # 计算cost
    cost = compute_cost(Z3,Y)
    
    # 反向传播 定义优化算法 最小化cost,求解梯度,更新参数
    
    optimizer = tf.train.AdagradOptimizer(learning_rate).minimize(cost)
   
    init = tf.global_variables_initializer()
     
    # 启动会话,对于每一个mini-batch运行一次计算图
    with tf.Session() as sess:
        
        
        sess.run(init)
        
 
        for epoch in range(num_epochs):

            minibatch_cost = 0.
            num_minibatches = int(m / minibatch_size) # mini-batch数量
            seed = seed + 1
            #分割后的所有mini-batch
            minibatches = random_mini_batches(X_train, Y_train, minibatch_size, seed)

            for minibatch in minibatches:

                # 选择一个mini-batch
                (minibatch_X, minibatch_Y) = minibatch
                #在一个mini-batch上运行一次计算图
                _,temp_cost = sess.run([optimizer,cost],{X:minibatch_X,Y:minibatch_Y})
                
                minibatch_cost += temp_cost / num_minibatches
                

            # 每5个epoch打印一次cost
            if print_cost == True and epoch % 5 == 0:
                print ("Cost after epoch %i: %f" % (epoch, minibatch_cost))
            #每1个epoch 记录一次cost
            if print_cost == True and epoch % 1 == 0:
                costs.append(minibatch_cost)
        
        
        # 绘制cost曲线
        plt.plot(np.squeeze(costs))
        plt.ylabel('cost')
        plt.xlabel('iterations (per tens)')
        plt.title("Learning rate =" + str(learning_rate))
        plt.show()

        #计算准确率
        predict_op = tf.argmax(Z3, 1)
        correct_prediction = tf.equal(predict_op, tf.argmax(Y, 1))
        
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
        
        train_accuracy = accuracy.eval({X: X_train, Y: Y_train})
        test_accuracy = accuracy.eval({X: X_test, Y: Y_test})
        print("Train Accuracy:", train_accuracy)
        print("Test Accuracy:", test_accuracy)
                
        return train_accuracy, test_accuracy, parameters
#训练模型
_, _, parameters = model(X_train, Y_train, X_test, Y_test) #100个epoch

  • 为你的工作竖一个大拇指
fname = "images/thumbs_up.jpg"
image = np.array(ndimage.imread(fname, flatten=False))
my_image = scipy.misc.imresize(image, size=(64,64))
plt.imshow(my_image)

三、实验完整代码

完整项目代码

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值