卷积网络基础

  人工智能实践:Tensorflow笔记(9)

Posted by     Keyon                      on April 23, 2018

卷积神经网络

全连接 NN:

每个神经元与前后相邻层的每一个神经元都有连接关系,输入是特征,输出为预测的结果。

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

CQb8JK.png

待优化的参数过多,容易导致模型过拟合。

在实际应用中,会先对原始图像进行特征提取,把提取到的特征喂给全连接网络,再让全连接网络计算出分类评估值。

卷积( Convolutional ):

卷积是一种有效提取图片特征的方法。一般用一个正方形卷积核,遍历图片上的每一个像素点。图片与卷积核重合区域内相对应的每一个像素值乘卷积核内相对应点的权重,然后求和,再加上偏置后,最后得到输出图片中的一个像素值。

CQb3i6.png

全零填充( Padding ):

有时会在输入图片周围进行全零填充,这样可以保证输出图片的尺寸和输入图片一致。

CQHvRS.png

使用 padding 和不使用 padding 的输出维度:

CQHoKH.png

注:上一行公式是使用 padding 的输出图片边长,下一行公式是不使用 padding 的输出图片边长。公式如果不能整除,需要向上取整数。如果用全零填充,也就是 padding=SAME。如果不用全零填充,也就是 padding=VALID。

Tensorflow 给出的计算卷积的函数:

CQH7qA.md.png

注:函数中要给出四个信息:对输入图片的描述、对卷积核的描述、对卷积核、滑动步长的描述以及是否使用 padding。

对多通道的图片求卷积:

CQHxxg.png

对于彩色图,按层分解开,可以直观表示为上面这张图,三个颜色分量:红色分量、绿色分量和蓝色分量。

CQbNsH.md.png

池化 (Pooling):

CQbcQg.png

Tensorflow 给出了计算池化的函数。最大池化用 tf.nn.max_pool 函数,平均 池化用 tf.nn.avg_pool 函数。

函数中要给出四个信息,对输入的描述、对池化核的描述、对池化核滑动步长的描述和是否使用 padding。

舍弃(Dropout):

CQbgyQ.png

Dropout 可以有效减少过拟合。

Tensorflow 提供的 Dropout 的函数:用 tf.nn.dropout 函数。第一个参数链接上一层的输出,第二个参数给出神经元舍弃的概率。

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

Dropout 一般会放到全连接网络中。

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

Lenet-5代码讲解

Lenet 神经网络结构为:

  1. 输入为 32✖️32✖️1 的图片大小,为单通道的输入;
  2. 进行卷积,卷积核大小为 5✖️5✖️1,个数为 6,步长为 1,非全零填充模式;
  3. 将卷积结果通过非线性激活函数;
  4. 进行池化,池化大小为 2✖️2,步长为 1,全零填充模式;
  5. 进行卷积,卷积核大小为 5✖️5✖️6,个数为 16,步长为 1,非全零填充模式;
  6. 将卷积结果通过非线性激活函数;
  7. 进行池化,池化大小为 2✖️2,步长为 1,全零填充模式;
  8. 全连接层进行 10 分类。

Lenet 神经网络的结构图及特征提取过程如下所示:

CQqYt0.png

根据 Lenet 神经网络的结构可得,Lenet 神经网络具有如下特点:

  1. 卷积(Conv)、池化(ave-pooling)、非线性激活函数(sigmoid)相互交替;
  2. 层与层之间稀疏连接,减少计算复杂度。

对 Lenet 神经网络进行微调,使其适应 Mnist 数据集:

注:由于 Mnist 数据集中图片大小为 28✖️28✖️1 的灰度图片,而 Lenet 神经网络的输入 为 32✖️32✖️1,故需要对 Lenet 神经网络进行微调。

  1. 输入为 28✖️28✖️1 的图片大小,为单通道的输入;
  2. 进行卷积,卷积核大小为 5✖️5✖️1,个数为 32,步长为 1,全零填充模式;
  3. 将卷积结果通过非线性激活函数;
  4. 进行池化,池化大小为 2✖️2,步长为 2,全零填充模式;
  5. 进行卷积,卷积核大小为 5✖️5✖️32,个数为 64,步长为 1,全零填充模式;
  6. 将卷积结果通过非线性激活函数;
  7. 进行池化,池化大小为 2✖️2,步长为 2,全零填充模式;
  8. 全连接层,进行 10 分类。

Lenet 进行微调后的结构如下所示:

CQqUpT.png

Lenet 神经网络在 Mnist 数据集上的实现,主要分为三个部分:前向传播过程 (mnist_lenet5_forward.py)、反向传播过程(mnist_lenet5_backword.py)、 测试过程(mnist_lenet5_test.py)。

前向传播过程(mnist_lenet5_forward.py)

#coding:utf-8
import tensorflow as tf
IMAGE_SIZE = 28
NUM_CHANNELS = 1
CONV1_SIZE = 5
CONV1_KERNEL_NUM = 32
CONV2_SIZE = 5
CONV2_KERNEL_NUM = 64
FC_SIZE = 512
OUTPUT_NODE = 10

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

def get_bias(shape): 
	b = tf.Variable(tf.zeros(shape))  
	return b

def conv2d(x,w):  
	return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):  
	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) 
    conv1_b = get_bias([CONV1_KERNEL_NUM]) 
    conv1 = conv2d(x, conv1_w) 
    relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_b)) 
    pool1 = max_pool_2x2(relu1) 

    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 = max_pool_2x2(relu2)

    pool_shape = pool2.get_shape().as_list() 
    nodes = pool_shape[1] * pool_shape[2] * pool_shape[3] 
    reshaped = tf.reshape(pool2, [pool_shape[0], nodes]) 

    fc1_w = get_weight([nodes, FC_SIZE], regularizer) 
    fc1_b = get_bias([FC_SIZE]) 
    fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_w) + fc1_b) 
    if train: fc1 = tf.nn.dropout(fc1, 0.5)

    fc2_w = get_weight([FC_SIZE, OUTPUT_NODE], regularizer)
    fc2_b = get_bias([OUTPUT_NODE])
    y = tf.matmul(fc1, fc2_w) + fc2_b
    return y 

  • 定义前向传播过程中常用到的参数。
  • 把前向传播过程中,常用到的方法定义为函数,方便调用。
    • 卷积层计算函数描述如下:tf.nn.conv2d(输入描述[batch,行分辨率,列分辨率,通道数],卷积核描述[行分辨率,列分辨率,通道数,卷积核个数],核滑动步长[1,行步长,列步长,1],填充模式 padding)
    • 最大池化层计算函数描述如下:tf.nn.max_pool(输入描述[batch,行分辨率,列分辨率,通道数],池化核描述[1,行分辨率,列分辨率,1],池化核滑动步长[1,行步长,列步长,1], 填充模式 padding)
  • 定义前向传播过程:
    • 实现第一层卷积
      • tf.nn.relu()用来实现非线性激活,相比 sigmoid 和 tanh 函数,relu 函数可以实现快速的收敛。
    • 实现第二层卷积
    • 将第二层池化层的输出 pool2 矩阵转化为全连接层的输入格式即向量形式
      • get_shape 函数用于获取一个张量的维度,并且输出张量每个维度上面的值。
    • 实现第三层全连接层
    • 实现第四层全连接层的前向传播过程

反向传播过程(mnist_lenet5_backward.py)

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



  • 定义训练过程中的超参数
  • 完成反向传播过程
    • 给 x, y_是占位
      • x = tf.placeholder(dtype,shape,name=None) : tf.placeholder()函数有三个参数,dtype 表示数据类型,常用的类型为 tf,float32,tf.float64 等数值类型,shape 表示数据形状,namen 表示名称。
    • 调用前向传播过程
    • 求含有正则化的损失值
      • sparse_softmax_cross_entropy_with_logits(_sentinel=None, labels=None, logits=None,name=None):此函数的参数 logits 为神经网络最后一层的输出,它的大小为[batch_size, num_classes],参数 labels 表示实际标签值,大小为[batch_size,num_classes]。 第一步是先对网络最后一层的输出做一个 softmax,输出为属于某一属性的概率向量;再将概率向量与实际标签向量做交叉熵,返回向量。
      • tf.reduce_mean( input_tensor, reduction_indices=None, keep_dims=False, name=None):此函数表示对得到的向量求取均值。参数 input_tensor 表示要减少的张量;参数 reduction_indices 表示求取均值的维度;参数 keep_dims 含义为:如果 为 true,则保留长度为 1 的缩小尺寸。name 表示操作的名称。
    • 实现指数衰减学习率
    • learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE, global_step, mnist.train.num_examples / BATCH_SIZE, LEARNING_RATE_DECAY, staircase=True)
    • 实现滑动平均模型
      • ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
      • ema_op = ema.apply(tf.trainable_variables())
    • 将 train_step 和 ema_op 两个训练操作绑定到 train_op 上
    • 实例化一个保存和恢复变量的 saver,并创建一个会话

测试过程(mnist_lenet5_test.py)

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

  • tf.equaf(x,y) 此函数用于判断函数的两个参数 x 与 y 是否相等,一般 x 表示预测值,y 表示实际值。