神经网络优化(五) - 卷积网络基础

本节目标:学会使用  CNN  实现对手写数字的识别

上几节重点讲述的 NN :每个神经元与前后相邻层的每一个神经元都有连接关系,输入是特征,输出为预测的结果

参数个数:∑ (前层× 后层+ 后层)

从上面可以看出,一张分辨率仅为28*28像素的黑白图像,在神经网络运算时就有近40万个参数待优化;而现实生活中高分辨率的彩色图像,不但像素点增多,而且有红绿蓝三通道信息,这样直接喂入(全连接)神经网络时,待优化参数将无限增大。

待优化参数的增多,容易导致模型的过拟合,为了避免该现象,在实际应用中一般不会将原始图片直接未入全连接网络。

实际操作方案:先对原始图像进行特征提取,然后将特征喂入全连接网络,之后再然全连接网络计算分类评估值。

1 卷积计算

1.1 无padding的卷积计算

卷积(也称为滤波器)是一种有效提取图像特征的方法。

一般用一个正方形卷积核,遍历图像上的每一个点。图像区域内的每一个像素值乘以卷积核内相对应点的权重,求和,再加上偏置。

输出图像值边长 = (输入图像边长 - 卷积核长 + 1 )/ 步长

 

备注:

5*5*1的灰度图释义为 5 行 5 列的灰度值:1表示单通道,5*5表示分辨率,在矩阵中意味 5 行 5 列。

3*3*1的卷积核对灰度图进行卷积计算,对应值乘积求和,再加上偏置项 b = 1 ;让卷积核中的 [1, 1] 值遍历 5*5 中的每一个灰度值 。

输出图片边长: ( 输入图像边长 5 - 卷积核边长 3 + 偏置项 1 )/  步长 1 = 3

由于仅用了一个3*3*1卷积核,所以输出的深度是1

1.2 含padding的卷积计算

不含padding的卷积计算图像矩阵长度会不一致;有时为了让图像的输出、输出尺寸一致,会再输入图像像素值周边补充0。

在前面 5x5x1 的图片周围进行全零填充,可使输出图片仍保持 5x5x1 的维度。这个全零填充的过程叫做 padding。

输出数据体的长度为:

  ( W − F + 2P ) / S + 1 

  • W:输入数据体尺寸
  • F:卷积层中神经元感知域
  • S:步长
  • P:零填充的数量。

 在 Tensorflow 框架中,用参数 padding = ‘ SAME ’(全部 0 填充) 或 padding = ‘VALID ’(不用 0 填充) 表示 。

 

两者的结果若非整数,都是采用 “ 向上取整 ” 的方式获得最终值。

 

示例:

输入图像为32*32*3,卷积核为5*5*3。

不用 0 填充时,输出(32 - 5 + 1)/ 1 = 28

若让输出结果长度与输入的一致,则需要填充几层 0 ?

32 = ( 32 - 5 + 2P) / 1 + 1,计算得 P = 2,故需要填充 2 层零。 

2 Tensorflow中的卷积计算(单通道)

tensorflow 中计算卷积的函数为 tf.nn.conv2d()

tf.nn.conv2d(
    输入描述 
    卷积核描述
    核滑动步长
    padding
    )

注解:

1)输入描述

 [ batch, 5 , 5, 1 ]

  • batch - 一次喂入图片数量
  • 5, 5 - 两个数组一同描述每张图片的分辨率,5行5列
  • 1 - 图片的通道数,若为灰度图,则为1,若为RGB的彩色,则为3

2)卷积核描述

[ 3, 3, 1, 16 ]

  • 3, 3 - 卷积核的行列分辨率(3行3列)
  • 1 - 通道数,卷积核的通道数是由输入图片的通道数决定的,其必须与输入图片的通道数一致
  • 16 - 该卷积核数量为16个,共计进行16次卷积运算,则输入图片的深度为16,也就是说输出是16通道

3)核滑动步长

[ 1, 1, 1, 1 ] 

  • 第一个 1 - 固定的数值
  • 第二个 1 - 行步长
  • 第三个 1 - 列步长
  • 第四个 1 - 固定的数值

4)padding

该处以字符串形式给出,例如本处 “ VALID “ 表示不用 零 来填充。

 

3 Tensorflow中的卷积计算(多通道)

上面是对单通道的灰度图进行阐述,而实际多是RGB三个颜色组成彩色图片,这也意味这图片对应有三个通道,每一种颜色对应一个通道。

由上面的规则可知,卷积核的深度 = 输入图片的通道数,所以卷积核也必须有 3 个通道。如下图所示

注解:

1)输入图片为RGB彩色图片,将其在R、G、B 的三色建立分量数据,5*5*3 即为每个分量为 5*5,3个通道。

2)卷积核也建立 3*3 个数据,为了匹配和输入数据通道,也建立 3 个通道的卷积核。

3)多通道与单通道的计算方法相似,每一个通道对应乘积和,再将每个通道和相加,最后加上偏置项 b = 1 ,最后得到一个输出值。

4)将滑动卷积核逐一求算出输出值,由于一共用了16个卷积核,所以最后输出图片是 5*5*16。

在Tensorflow中的代码形式与单通道一致,仅将通道数值进行修改即可,具体如下:

 4 池化 Pooling

 池化的目的减少特征数据量,但是不改变图片的深度,因为尽管经过卷积核计算,但是数据量还是非常大的。

Tensorflow 给出了计算池化的函数

  • 最大池化 tf.nn.max_pool 函数,提取图片纹理。
  • 平均池化 tf.nn.avg_pool 函数,保留背景特征。

函数的实际应用:

备注:

  • 1)对输入的描述:给出一次输入 batch 张图片、行列分辨率、输入通道的个数。
  • 2)对池化核的描述:只描述行分辨率和列分辨率,第一个和最后一个参数固定是 1。
  • 3)对池化核滑动步长的描述:只描述横向滑动步长和纵向滑动步长,第一个和最后一个参数固定是 1。
  • 4)是否使用 padding:padding 可以是使用零填充 SAME 或者不使用零填充VALID。

 5 舍弃 dropout

全连接神经网络训练过程中,为了减少过多参数,常使用舍弃dropout的方法。该方法可以有效防止过拟合。

实际上dropout时,是随机将神经元中的某些值置为 0 ,使其不参与参数优化。

将一部分神将元按照一定概率从神经网络中暂时舍弃,在使用神经网络时,会把所有的神经元恢复到神经网络中。

如下图,右图中带有 × 号标记的圆圈表示 dropout 掉的,

在 tensorflow 中的计算方法为

 tf.nn.dropout(上层输出,暂时舍弃的概率)

在实际应用中,常在前向传播过程构建神经网络中使用dropout,减少过拟合、加快模型的训练速度。

if train: 输出 = tf.nn.dropout(上层输出,暂时舍弃的概率)

6 CNN 模块

卷积NN :借助卷积核(Kernel)提取特征后,送入全连接网络。

卷积神经网络可分为两部分:

  • 首先是通过卷积、激活、池化对输入图片特征进行提取主要特征信息
  • 再次将提取的特征信息喂入全连接网络中

卷积神经网络从诞生到现在,已经出现了许多经典网络结构,比如 Lenet-5、Alenet、VGGNet、GoogleNet 和 ResNet 等。每一种网络结构都是以卷积、激活、池化、全连接这四种操作为基础进行扩展。

Lenet-5 是最早出现的卷积神经网络,由 Lecun 团队首先提出,Lenet-5 有效解决了手写数字的识别问题。

7 lenet-5 代码讲解

 该代码来源于文章:

Y. LeCun, L. Bottou, Y. Bengio, and P. Haffner. Grandient-based learning applied to document recognition. Proceedings of the IEEE, 86(11):2278-2324, 1998.

该篇文章的代码如下所示。

 

依据上述代码是依据文章中梳理出来的,而mnist数据集训练是28*28,其计算过程如下:

7.1 前向传播过程 mnist_lenet5_forward

# coding:utf-8
import tensorflow as tf
# 每个图片的像素点为28
IMAGE_SIZE = 28
# 由于是灰度图,每个图片的通道为1
NUM_CHANNELS = 1
# 第一层卷积核的大小为5
CONV1_SIZE = 5
# 第一层使用了32个卷积核
CONV1_KERNEL_NUM = 32
# 第二层卷积核大小为5
CONV2_SIZE = 5
# 第二层使用了64个卷积核
CONV2_KERNEL_NUM = 64
# 第一层神经网络有512个神经元
FC_SIZE = 512
# 第二层神经网络有10个神经元
OUTPUT_NODE = 10

# 权重w的生成函数,
# shape表示生成张量的维度
# regularizer表示正则化权重
def get_weight(shape, regularizer):
    w = tf.Variable(tf.truncated_normal(shape, stddev=0.1))
    if regularizer != None: tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(regularizer)(w)) 
    return w

# 偏置b生成函数
def get_bias(shape):
    b = tf.Variable(tf.zeros(shape))  
    return b

# 卷积生成函数
def conv2d(x, w):
    # x输入图片描述[batch, 行分辨率,列分辨率,通道数]
    # w卷积核描述[行分辨率,列分辨率,通道数,卷积核个数]
    # strides 核滑动步长[1,行步长,列步长,1]
    # padding 填充模式  SAME零填充
    return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')

# 最大池化计算函数
def max_pool_2x2(x):
    # x 输入描述[batch,行分辨率,列分辨率,通道数]
    # ksize 池化核描述[1,行分辨率,列分辨率,1]
    # strides 池化核滑动步长[1,行步长,列步长,1]
    # padding 填充模式
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') 

# 前向传播的网络结构
def forward(x, train, regularizer):
    # 第一层卷积核
    conv1_w = get_weight([CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_KERNEL_NUM], regularizer) 
    # 初始化第一层偏置项b
    conv1_b = get_bias([CONV1_KERNEL_NUM]) 
    # 执行卷积计算
    conv1 = conv2d(x, conv1_w) 
    # 对conv1添加偏置,并通过relu激活函数进行激活输出
    relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_b)) 
    # 最大池化,第一层最终输出pool1
    pool1 = max_pool_2x2(relu1) 

    # 第二层卷积核的深度CONV1_KERNEL_NUM等于上层卷积核的个数
    conv2_w = get_weight([CONV2_SIZE, CONV2_SIZE, CONV1_KERNEL_NUM, CONV2_KERNEL_NUM],regularizer) 
    conv2_b = get_bias([CONV2_KERNEL_NUM])
    # 第二层卷积核的输入是第一层的输出
    conv2 = conv2d(pool1, conv2_w) 
    relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_b))
    # pool2是第二层的最后输出
    pool2 = max_pool_2x2(relu2)

    # 将pool2从三维张量变成二维张量
    # 获得pool2的数据维度,并存入list中
    pool_shape = pool2.get_shape().as_list() 
    # pool_shape[0] 的值是一个batch值
    # pool_shape[1] 特征的长度
    # pool_shape[2] 特征的宽度
    # pool_shape[3] 特征的深度
    # nodes=长度*宽度*深度,所有数据点的个数
    nodes = pool_shape[1] * pool_shape[2] * pool_shape[3] 
    # 将pool2表示成以batch作为行,所有特征点为列的二维形状,
    # 把reshaped 喂入全连接网络中
    reshaped = tf.reshape(pool2, [pool_shape[0], nodes]) 

    fc1_w = get_weight([nodes, FC_SIZE], regularizer) 
    fc1_b = get_bias([FC_SIZE]) 
    # 把上层的输出reshaped 乘以 本层线上的权重fc1_w,再加上偏置fc1_b
    fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_w) + fc1_b) 
    # 如果是训练阶段,则对fc1的50%进行dropout
    if train: fc1 = tf.nn.dropout(fc1, 0.5)

    # 通过第二层全连接网络,初始化第二层W和b
    fc2_w = get_weight([FC_SIZE, OUTPUT_NODE], regularizer)
    fc2_b = get_bias([OUTPUT_NODE])
    # 上层的输出fc1和本层fc2_w相乘,再加上fc2_b
    y = tf.matmul(fc1, fc2_w) + fc2_b
    return y 

7.2 反向传播过程 mnist_lenet5_backward

#coding:utf-8
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_lenet5_forward
import os
import numpy as np

BATCH_SIZE = 100
LEARNING_RATE_BASE =  0.005 
LEARNING_RATE_DECAY = 0.99 
REGULARIZER = 0.0001 
STEPS = 50000 
MOVING_AVERAGE_DECAY = 0.99 
MODEL_SAVE_PATH="./model/" 
MODEL_NAME="mnist_model" 

def backward(mnist):
    x = tf.placeholder(tf.float32,[
    # 每轮喂入的图片数量       
    BATCH_SIZE,
    # 行分辨率
    mnist_lenet5_forward.IMAGE_SIZE,
    # 列分辨率
    mnist_lenet5_forward.IMAGE_SIZE,
    # 输入的通道数
    mnist_lenet5_forward.NUM_CHANNELS]) 
    y_ = tf.placeholder(tf.float32, [None, mnist_lenet5_forward.OUTPUT_NODE])
    # True 意味使用dropout操作
    y = mnist_lenet5_forward.forward(x,True, REGULARIZER) 
    global_step = tf.Variable(0, trainable=False) 

    ce = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
    cem = tf.reduce_mean(ce) 
    loss = cem + tf.add_n(tf.get_collection('losses')) 

    learning_rate = tf.train.exponential_decay( 
        LEARNING_RATE_BASE,
        global_step,
        mnist.train.num_examples / BATCH_SIZE, 
        LEARNING_RATE_DECAY,
        staircase=True) 
    
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

    ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    ema_op = ema.apply(tf.trainable_variables())
    with tf.control_dependencies([train_step, ema_op]): 
        train_op = tf.no_op(name='train')

    saver = tf.train.Saver() 

    with tf.Session() as sess: 
        init_op = tf.global_variables_initializer() 
        sess.run(init_op) 

        ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH) 
        if ckpt and ckpt.model_checkpoint_path:
            saver.restore(sess, ckpt.model_checkpoint_path) 

        for i in range(STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE) 
            reshaped_xs = np.reshape(xs,(  
            BATCH_SIZE,
            mnist_lenet5_forward.IMAGE_SIZE,
            mnist_lenet5_forward.IMAGE_SIZE,
            mnist_lenet5_forward.NUM_CHANNELS))
            _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: reshaped_xs, y_: ys}) 
            if i % 100 == 0: 
                print("After %d training step(s), loss on training batch is %g." % (step, loss_value))
                saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step)

def main():
    mnist = input_data.read_data_sets("./data/", one_hot=True) 
    backward(mnist)

if __name__ == '__main__':
    main()

7.3 测试 mnist_lenet5_test

#coding:utf-8
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_lenet5_forward
import mnist_lenet5_backward
import numpy as np

TEST_INTERVAL_SECS = 5

def test(mnist):
    with tf.Graph().as_default() as g: 
        x = tf.placeholder(tf.float32,[
            mnist.test.num_examples,
            mnist_lenet5_forward.IMAGE_SIZE,
            mnist_lenet5_forward.IMAGE_SIZE,
            mnist_lenet5_forward.NUM_CHANNELS]) 
        y_ = tf.placeholder(tf.float32, [None, mnist_lenet5_forward.OUTPUT_NODE])
        y = mnist_lenet5_forward.forward(x,False,None)

        ema = tf.train.ExponentialMovingAverage(mnist_lenet5_backward.MOVING_AVERAGE_DECAY)
        ema_restore = ema.variables_to_restore()
        saver = tf.train.Saver(ema_restore)
          
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) 
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 

        while True:
            with tf.Session() as sess:
                ckpt = tf.train.get_checkpoint_state(mnist_lenet5_backward.MODEL_SAVE_PATH)
                if ckpt and ckpt.model_checkpoint_path:
                    saver.restore(sess, ckpt.model_checkpoint_path)
                         
                    global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1] 
                    reshaped_x = np.reshape(mnist.test.images,(
                    mnist.test.num_examples,
                     mnist_lenet5_forward.IMAGE_SIZE,
                     mnist_lenet5_forward.IMAGE_SIZE,
                     mnist_lenet5_forward.NUM_CHANNELS))
                    accuracy_score = sess.run(accuracy, feed_dict={x:reshaped_x,y_:mnist.test.labels}) 
                    print("After %s training step(s), test accuracy = %g" % (global_step, accuracy_score))
                else:
                    print('No checkpoint file found')
                    return
            time.sleep(TEST_INTERVAL_SECS) 

def main():
    mnist = input_data.read_data_sets("./data/", one_hot=True)
    test(mnist)

if __name__ == '__main__':
    main()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值