TensorFlow学习笔记(8) 经典卷积网络模型

LeNet-5模型

LeNet-5模型一共有7层:

  1. 卷积层。这一层的输入是原始图像像素,模型接受的输入层大小为32*32*1。过滤器尺寸为5*5,深度为6,不使用全0填充,步长为1。输出尺寸为32-5+1=28,深度为6。该卷积层共有5*5*1*6+6=156个参数。
  2. 池化层。这一层的输入是第一层的输出,是一个28*28*6的节点矩阵。本层过滤器尺寸为2*2,长和宽步长为2,输出矩阵为14*14*6。
  3. 卷积层。本层输入矩阵尺寸大小为14*14*6。使用过滤器大小为5*5*16,不使用全0填充,步长为1。输出矩阵大小为10*10*16,共有5*5*6*16+16=2416个参数。
  4. 池化层。输入矩阵5*5*16,过滤器大小2*2,步长2,输出5*5*16。
  5. 全连接层。输入矩阵5*5*16,过滤器大小5*5,输出长度为120的向量,共有5*5*16*120+120=48120个参数
  6. 全连接层。输入节点120,输出节点84,总参数120*84+84=10164个。
  7. 全连接层。输入节点84,输出节点10,总参数120*84+84=10164个。

下面为一个TF程序实现类似该模型的卷积神经网络解决MNIST数字识别问题:

mnist_cnn_inference.py

import tensorflow as tf

#定义神经网络结构相关的参数
INPUT_NODE = 784
OUTPUT_NODE = 10

IMAGE_SIZE = 28
NUM_CHANNELS = 1
NUM_LABELS = 10

#第一层卷积层滤波器的尺寸和深度
CONV1_DEEP = 32
CONV1_SIZE = 5
#第二层卷积层滤波器的尺寸和深度
CONV2_DEEP = 64
CONV2_SIZE = 5
#全连接层的节点个数
FC_SIZE = 512

#定义卷积神经网络,train用于区分训练过程和测试过程
def inference(input_tensor, train, regularizer):
    #声明第一层卷积层,输入28*28*1,输出28*28*32
    with tf.variable_scope('layer1-conv1'):
        conv1_weights = tf.get_variable('weight', [CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_DEEP],
                                        initializer=tf.truncated_normal_initializer(stddev=0.1))
        conv1_biases = tf.get_variable('bias', [CONV1_DEEP], initializer=tf.constant_initializer(0.0))
        conv1 = tf.nn.conv2d(input_tensor, conv1_weights, strides=[1, 1, 1, 1], padding='SAME')
        conv1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_biases))

    #声明第一层池化层,输入28*28*32,输出14*14*32
    with tf.variable_scope('layer2-pool1'):
        pool1 = tf.nn.max_pool(conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

    #声明第三层卷积层,输入14*14*32,输出14*14*64
    with tf.variable_scope('layer3-conv2'):
        conv2_weights = tf.get_variable('weight', [CONV2_SIZE, CONV2_SIZE, CONV1_DEEP, CONV2_DEEP],
                                        initializer=tf.truncated_normal_initializer(stddev=0.1))
        conv2_biases = tf.get_variable('bias', [CONV2_DEEP], initializer=tf.constant_initializer(0.0))
        conv2 = tf.nn.conv2d(pool1, conv2_weights, strides=[1, 1, 1, 1], padding='SAME')
        conv2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_biases))

    #声明第四层池化层,输入14*14*64,输出7*7*64
    with tf.variable_scope('layer4-pool2'):
        pool2 = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

    #将第四层输出格式(7*7*64)转化为第五层的输入格式一个向量
    pool2_shape = pool2.get_shape().as_list()
    nodes = pool2_shape[1] * pool2_shape[2] * pool2_shape[3]   #7*7*64,pool2_shape[0]为一个bantch中数据的个数
    reshaped = tf.reshape(pool2, [pool2_shape[0], nodes])

    #声明第五层全连接层,输入7*7*64=3136长度的向量,输出512
    #引入dropout概念,会在训练时随机将部分节点的输出改为0,避免过拟合问题,一般用在全连接层
    with tf.variable_scope('layer5-fc1'):
        fc1_weights = tf.get_variable('weight', [nodes, FC_SIZE],
                                      initializer=tf.truncated_normal_initializer(stddev=0.1))
        fc1_biases = tf.get_variable('bias', [FC_SIZE], initializer=tf.constant_initializer(0.1))
        if regularizer != None:#只有全连接层的权重需要加入正则化
            tf.add_to_collection('losses', regularizer(fc1_weights))
        fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_weights) + fc1_biases)
        if train:fc1 = tf.nn.dropout(fc1, 0.5)

    #声明第6层全连接层,输入512,输出10,通过softmax之后得到最后的分类结果
    with tf.variable_scope('layer6-fc2'):
        fc2_weights = tf.get_variable('weight', [FC_SIZE, NUM_LABELS],
                                      initializer=tf.truncated_normal_initializer(stddev=0.1))
        fc2_biases = tf.get_variable('bias', [NUM_LABELS], initializer=tf.constant_initializer(0.1))
        if regularizer != None:#只有全连接层的权重需要加入正则化
            tf.add_to_collection('losses', regularizer(fc2_weights))
        fc2 = tf.matmul(fc1, fc2_weights) + fc2_biases

    return fc2

mnist_cnn_train.py
import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_cnn_inference
import numpy as np


# 配置神经网络的参数
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.001
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001 #描述模型复杂度的正则化项在损失函数中的系数
TRAINING_STEPS = 10000
MOVING_AVERAGE_DECAY = 0.99 #滑动平均衰减率

#模型保存的路径和文件名
MODEL_SAVE_PATH = '/mnist_cnn_model/'
MODEL_SAVE_NAME = 'mnist_cnn_model.ckpt'

def train(mnist):
    x = tf.placeholder(tf.float32, [BATCH_SIZE, mnist_cnn_inference.IMAGE_SIZE, mnist_cnn_inference.IMAGE_SIZE,
                                    mnist_cnn_inference.NUM_CHANNELS], name='x-input')
    y_ = tf.placeholder(tf.float32, [None, mnist_cnn_inference.OUTPUT_NODE], name='y-input')
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)

    #直接使用mnist_cnn_inference.py中定义的前向传播结果
    y = mnist_cnn_inference.inference(x, train=False, regularizer=regularizer)
    global_step = tf.Variable(0, trainable=False)

    # 生成一个滑动平均的类,并在所有变量上使用滑动平均
    variables_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    variables_averages_op = variables_averages.apply(tf.trainable_variables())

    # 计算交叉熵及当前batch中的所有样例的交叉熵平均值,并求出损失函数
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    loss = cross_entropy_mean + 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)
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
    with tf.control_dependencies([train_step, variables_averages_op]):
        train_op = tf.no_op(name='train')

    #初始化TF持久化类
    saver = tf.train.Saver()

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

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

def main(argv=None):
    mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
    train(mnist)

if __name__ =='__main__':
    tf.app.run()

如何设计卷积神经网络的架构?下面的正则表达式总结了一些经典的用于图片分类问题的卷积神经网络架构:

输入层>>(卷积层+ >> 池化层?) >> 全连接层+

在上面的公式中+表示一个或多个,?表示一个或没有。一般的卷积神经网络都满足这个结构。在过滤器的深度上,一般卷积网络都采取逐层递增的方式。


Inception-v3模型

Inception结构是一种和上一个模型结构完全不同的卷积神经网络。在LeNet-5中,不同卷积层通过串联的方式连接在一起,而Inception结构是将不同的卷积层通过并联的方式结合在一起。

Inception模块会首先使用不同尺寸的过滤器处理输入矩阵,不同的矩阵代表Inception模块的一条计算路径。虽然过滤器的大小不一,但如果所有的过滤器都是用全0填充且步长为1,那么得到的矩阵的长与宽都与输入矩阵一致。最后将不同过滤器处理的结果矩阵拼接成更深的矩阵。

Inception-v3模型一共有46层,由11个Inception模块组成,由96个卷积层。由TensorFlow-Slim工具可以更加简洁的实现一个卷积层,从而更好的实现复杂的卷积神经网络。

#使用TF原始API实现卷积层
with tf.variable_scope('layer1-conv1'):
    conv1_weights = tf.get_variable('weight', [1,1,1,1], initializer=tf.truncated_normal_initializer(stddev=0.1))
    conv1_biases = tf.get_variable('bias', [2], initializer=tf.constant_initializer(0.0))
    conv1 = tf.nn.conv2d(input, conv1_weights, strides=[1, 1, 1, 1], padding='SAME')
    conv1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_biases))
    
    
#使用TensorFlow-Slim实现卷积层
#其中有三个参数必填:输入节点矩阵,卷积过滤器的深度,卷积过滤器的长和宽
#可选参数:过滤器移动步长、是否使用全0填充,激活函数的选择、变量的命名空间
net = slim.conv2d(input, 32, [3,3])


下面为 Inception-v3模型中的Inception模块的实现

import tensorflow as tf

#slim.arg_scope函数用于设置默认参数取值
#slim.arg_scope第一个参数是一个函数列表,包括下面所要用到的
#在定义函数时则会自动带上后面的参数
with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], stride=1, padding='SAME'):
    net = 上一层的输出
    #整个模块声明的命名空间
    with tf.variable_scope('Mixed_7c'):
        #为该模块每一条路径声明一个命名空间
        with tf.variable_scope('Branch_0'):
            branch_0 = slim.conv2d(net, 320, [1, 1], scope='Conv2d_0a_1x1')
        with tf.variable_scope('Branch_1'):
            branch_1 = slim.conv2d(net, 384, [1, 1], scope='Conv2d_0a_1x1')
            #tf.contact可以将多个矩阵拼接起来,第一个参数3表示矩阵在深度上进行拼接
            branch_1 = tf.concat(3,
                                 [slim.conv2d(branch_1, 384, [1, 3], scope='Conv2d_0b_1x3'),
                                  slim.conv2d(branch_1, 384, [3, 1], scope='Conv2d_0c_3x1')])

        with tf.variable_scope('Branch_2'):
            branch_2 = slim.conv2d(net, 448, [1, 1], scope='Conv2d_0a_1x1')
            branch_2 = slim.conv2d(branch_2, 384, [3, 3], scope='Conv2d_0b_3x3')
            branch_2 = tf.concat(3,
                                 [slim.conv2d(branch_2, 384, [1, 3], scope='Conv2d_0c_1x3'),
                                  slim.conv2d(branch_2, 384, [3, 1], scope='Conv2d_0d_3x1')])
        with tf.variable_scope('Branch_3'):
            branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
            branch_3 = slim.conv2d(branch_3, 192, [1, 1], scope='Conv2d_0b_1x1')

        net = tf.concat(3, [branch_0, branch_1, branch_2, branch_3])




猜你喜欢

转载自blog.csdn.net/qyf394613530/article/details/79310742