本篇介绍卷积神经网络之前向传播的基本实现。
本篇中卷积神经网络的结构为:
卷积层->池化层->卷积层->池化层->全连接层->全连接层
其中的全连接层还引入了dropout的概念。dropout在训练时会随机将部分节点的输出改为0(使神经元以一定概率失活)。dropout可以避免过拟合(overfitting)问题。
代码和注释中有详细的介绍:
代码语言:javascript复制import tensorflow as tf
#基于MNIST 数据集,稍作更改便可应用于其他数据集。#MNIST 数据集信息,IMAGE_HEIGHT = 28IMAGE_WIDTH = 28NUM_CHANNELS = 1#颜色通道数NUM_LABELS =10#神经网络参数#INPUT_NODE = IMAGE_HEIGHT*IMAGE_WIDTHOUTPUT_NODE = NUM_LABELS#第1层卷积层的尺寸(5x5)和深度CONV1_SIZE = 5CONV1_DEEP = 32#第2层卷积层的尺寸(5x5)和深度CONV2_SIZE = 5CONV2_DEEP = 64#第一个全连接层的节点个数FC1_SIZE = 520
def inference(input_tensor, train, regularizer, avg_class, reuse = True): '''卷积神经网络前向传播,参数train用于区分训练过程和测试过程''' #第一层,卷积层 with tf.variable_scope('layer1-conv1', reuse =reuse): #权重矩阵 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)) #过滤器x,y向步长均为1 (strides的第2,3个元素)。strides的第1,第4个元素只能为1 #全0填充 padding='SAME';不填充则用padding='VALID' conv1 = tf.nn.conv2d(input_tensor, conv1_weights, strides =[1,1,1,1], padding='SAME') #添加偏置 conv1 = tf.nn.bias_add(conv1, conv1_biases) #ReLU引入非线性 relu1 = tf.nn.relu(conv1) #输出矩阵shape 为 IMAGE_HEIGHTXIMAGE_WIDTHXCONV1_DEEP, 即28x28x32 #第二层,池化层(下采样) with tf.variable_scope('layer2-pool1', reuse =reuse): #使用最大池化层,过滤器的尺寸为2X2, 过滤器x,y向步长均为2,全零填充 pool1 = tf.nn.max_pool(relu1, ksize=(1,2,2,1), strides =[1,2,2,1], padding='SAME')#输出矩阵shape 为 14x14x32$$$$ #第三层,卷积层 with tf.variable_scope('layer3-conv2', reuse =reuse): #权重矩阵 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)) #过滤器x,y向步长均为1 (strides的第2,3个元素)。strides的第1,第4个元素只能为1 #全0填充 padding='SAME';不填充则用padding='VALID' conv2 = tf.nn.conv2d(pool1, conv2_weights, strides =[1,1,1,1], padding='SAME') #添加偏置 conv2 = tf.nn.bias_add(conv2, conv2_biases) #ReLU引入非线性 relu2 = tf.nn.relu(conv2) #输出矩阵shape ,我们仅改变了深度,即14X14X64 #第四层,池化层(下采样) with tf.variable_scope('layer4-pool2', reuse =reuse): #使用最大池化层,过滤器的尺寸为2X2, 过滤器x,y向步长均为2,全零填充 pool2 = tf.nn.max_pool(relu2, ksize=(1,2,2,1), strides =[1,2,2,1], padding='SAME')# #输出矩阵shape 为 7X7X64
代码语言:javascript复制 #第五层,(第1个)全连接层 #全连接层的输入格式为向量,张量需要展平。 #get_shape().as_list()可以计算(最后一个池化层)各维度大小,返回到一个列表 # 因每层神经网络的输入输出都为一个batch(一同训练的一批样本)的张量,pool_shape[0]为一个batch中样本的个数 #而inference函数前面都不用考虑batch pool_shape = pool2.get_shape().as_list() nodes = pool_shape[1] * pool_shape[2] * pool_shape[3] #得到第一个全连接层的输入节点数 #注意 #pool_shape[0]这里不如用-1 。 # -1(自动调整)可以适应 placeholder中batch size 为None,测试集,验证集batch不匹配的问题(测试集batch为batchsize,验证集为整个集样本数) #reshaped = tf.reshape(pool2,[pool_shape[0], nodes]) # X的第一维size传入None时会报错 reshaped = tf.reshape(pool2,[-1, nodes]) #注意这里的中括号,与numpy.reshape()不同 #这里引入了dropout的概念。dropout在训练时会随机将部分节点的输出改为0。dropout可以避免过拟合(overfitting)问题 #dropout 一般只在全连接层使用 with tf.variable_scope('layer5_fc1', reuse =reuse): #权重 fc1_weights = tf.get_variable("weight", [nodes, FC1_SIZE, ], initializer = tf.truncated_normal_initializer(stddev = 0.1)) #只有全连接层的权重需要加入正则化,也是为了避免过拟合 if regularizer != None: tf.add_to_collection('losses', regularizer(fc1_weights)) #偏置 fc1_biases = tf.get_variable('bias', [FC1_SIZE], initializer=tf.constant_initializer(0.0)) #平均移动模型 if avg_class != None: fc1_weights = avg_class.average(fc1_weights) fc1_biases = avg_class.average(fc1_biases) #加权 fc1 = tf.matmul(reshaped, fc1_weights) fc1_biases #ReLU激活 fc1 = tf.nn.relu(fc1) if train: fc1 = tf.nn.dropout(fc1, 0.5) #每个神经元 以 50% 概率被抑制 #第六层,(第2个,最后一个)全连接层 #输出长度为10 (因为只有十个类别)的向量。之后无需引入激活函数。输出通过Softmax()就得到了分类的结果 with tf.variable_scope('layer6_fc2', reuse =reuse): #权重 fc2_weights = tf.get_variable("weight", [FC1_SIZE, NUM_LABELS, ], initializer = tf.truncated_normal_initializer(stddev = 0.1)) #只有全连接层的权重需要加入正则化,也是为了避免过拟合 if regularizer is not None: tf.add_to_collection('losses', regularizer(fc2_weights)) #偏置 fc2_biases = tf.get_variable('bias', [NUM_LABELS], initializer=tf.constant_initializer(0.0)) #平均移动模型 if avg_class is not None: fc2_weights = avg_class.average(fc2_weights) fc2_biases = avg_class.average(fc2_biases) #加权 logit = tf.matmul(fc1, fc2_weights) fc2_biases
#返回最后一层全连接层的输出 return logit # 通过tf.argmax()即可得到的分类结果
由于此卷积神经网络的待训练参数比较多,所以训练(卷积神经网络的训练下篇会介绍)起来比较慢。若是电脑性能不太好,可以适当减少参数数量,比如可以增大卷积层和池化层的过滤器的尺寸和移动步长,以及减少全连接层的节点数。