算法实现过程的详细介绍

卷积神经网路最强大的地方还是对于图像问题的处理,现在就来处理在CIFAR10数据集上进行分类的一个任务,关于CIFAR10数据集介绍和CIFAR10数据集下载,可以在网上查阅相关资料。

  1. 载入一些常用库,比如Numpy和time,并载入Tensorflow Models中自动下载,读取CIFAR-10数据的类。其中下载的代码为:
git clone https://github.com/tensorflow/models.git
cd models/tutorials/image/cifar10

然后编写代码:

import cifar10, cifar10_input
import tensorflow as tf
import numpy as np
import time
  1. 接着定义batch_size,训练轮数max_steps,以及下载CIFAR-10数据的默认路径。
#定义batch_size,训练最大轮数max_steps,以及下载CIFAR-10数据的默认路径
max_steps = 3000
batch_size = 128
data_dir = 'C:\\Users\\xiaoyu\\PycharmProjects\\DeepLearning\\tmp\\cifar10_data\\cifar-10-batches-bin'
  1. 接下来定义初始化weight的函数,和之前一样使用tf.truncated_normal截断的正态分布来初始化权重。但是这里会给weight加一个L2的loss,相当于做了一个L2的正则化处理。在机器学习中,不管是分类还是回归任务,都有可能因为特征过多而导致过拟合,一般可以通过减少特征或者惩罚不重要的特征权重的,即特征的权重也会成为模型损失函数的一部分。可以理解为,为了使用某个特征,我们需要付出loss的代价,除非这个特征十分有效,否则会被loss上的增加覆盖效果。这样我们可以筛选出更有效的特征,减少特征权重防止过拟合。这也即是奥卡姆剃刀法则,越简单的东西越有效。一般来说L1正则会制造稀疏的特征,大部分无用特征的权重会被置0,而L2正则会让特征的权重不过大,使得特征的权重比较平均。我们使用w1控制L2 loss的大小,使用tf.nn.l2_loss函数计算weight的L2 loss,再使用tf.multiply让L2 loss乘以w1,得到最后的weight loss。接着,我们使用tf.add_to_collection把weight loss统一存到一个collection,这个collection命名为"losses",它会在后面计算神经网络的总体loss被用到。
#定义初始化weight的函数
def variable_with_weight_loss(shape, stddev, w1):
    var = tf.Variable(tf.truncated_normal(shape, stddev=stddev))
    if w1 is not None:
        weight_loss = tf.multiply(tf.nn.l2_loss(var), w1, name='weight_loss')
        tf.add_to_collection('losses', weight_loss)
    return var

下载CIFAR-10数据集:

cifar10.maybe_download_and_extract()
  1. 使用cifar10_input类中的distored_inputs函数产生训练需要使用的数据,包括特征及其对应的label,这里返回的是已经封装好的tensor,每次执行都会生成一个batch_size的数量的样本。这个函数里面还包括数据增强,多线程实现等等。
#使用cifar10_input类中的distored函数产生随机训练需要使用的数据(内部多线程实现)
images_train, labels_train = cifar10_input.distorted_inputs(data_dir=data_dir, batch_size=batch_size)
  1. 再使用cifar10_inputs函数生成测试数据,这里不需要进行太多的处理,不需要对图像进行翻转或者修改亮度,对比度,不过需要裁剪图片正中间的24*24大小的区块,并进行数据标准化操作。
#使用cifar10_input类中的distored函数生成测试数据
images_test, labels_test = cifar10_input.inputs(eval_data=True, data_dir=data_dir, batch_size=batch_size)
  1. 这里创建输入数据的placeholder,包括特征和label。在设定placeholder的数据尺寸时需要注意,因为batch_size在之后定义网络结构时时被用到了,所以数据尺寸中的第一个值即样本条数需要被预先设定,而不能像以前一样可以设置为None。而数据尺寸中的图片尺寸为24*24,即是裁剪后的大小,而颜色通道数则为3,代表图片是彩色有RGB三个通道。
#创建输入数据的placeholder
image_holder = tf.placeholder(tf.float32, [batch_size, 24, 24, 3])
label_holder = tf.placeholder(tf.int32, [batch_size])
  1. 做好了创建工作,接下来开始创建第一个卷积层。先使用之前写好的variable_with_weight_loss函数创建卷积核的参数并进行初始化。第一个卷积层使用55的卷积核大小,3个颜色通道,64个卷积核,同时设置weight初始化函数的标准差为0.05。我们不对第一个卷就曾的weight进行L2正则,因此w1这一项设为0.下面使用tf.nn.conv2d函数对输入数据的image_holder进行卷及操作,这里的步长stride设为1,padding模式为SAME。把这层的bias全部初始化为0,再将卷积的结果加上bias,最后使用一个ReLU激活函数进行非线性化。在ReLU激活函数之后,我们使用一个尺寸为33,且步长为2*2的最大池化层处理数据,注意这里最大池化的尺寸和步长不一样,这样可以增加数据的丰富性。再之后,我们使用tf.nn.lrn函数,即LRN对结果进行处理,LRN最早见于Alex那篇用CNN参加ImageNet比赛的论文,Alex在论文中解释LRN层模仿了生物神经系统中的“侧抑制”机制,对局部神经元的活动创建竞争环境,使得其中响应比较大的值变得更大,并抑制其他反馈比较小的神经元,增强了模型的泛化能力。Alex在ImageNet数据集上的实验表明,使用LRN层后CNN在Top1的错误率可以降低1.4%,因此在经典的AlexNet中使用了LRN层。LRN对ReLU这种没有上限边界的激活函数会比较有用,因为它会从附近的多个卷积核的响应中挑选比较大的反馈。但不适合Sigmoid这种有固定边界并且能抑制过大值得激活函数。
#创建第一个卷积层
weight1 = variable_with_weight_loss(shape=[5, 5, 3, 64], stddev=5e-2, w1=0.0)
kernel1 = tf.nn.conv2d(image_holder, weight1, [1, 1, 1, 1], padding='SAME')
bias1 = tf.Variable(tf.constant(0.0, shape=[64]))
conv1 = tf.nn.relu(tf.nn.bias_add(kernel1, bias1))
pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME')
norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001/9.0, beta=0.75)
  1. 现在来创建第2个卷积层,这里的步骤和第一步很像,区别如下。上一层的卷积核数量为64,所以本层卷积的输入通道也是64.这里的bias值全部初始化为0.1,并且交换了最大池化层和LRN层的顺序,先进行LRN层处理,再使用最大池化。
#创建第二个卷积层
weight2 = variable_with_weight_loss(shape=[5, 5, 64, 64], stddev=5e-2, w1=0.0)
kernel2 = tf.nn.conv2d(norm1, weight2, [1, 1, 1, 1], padding='SAME')
bias2 = tf.Variable(tf.constant(0.1, shape=[64]))
conv2 = tf.nn.relu(tf.nn.bias_add(kernel2, bias2))
norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001/9.0, beta=0.75)
pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME')
  1. 在2个卷积层之后,将使用一个全连接层,这里先要把第二个卷积层的输出结果flatten,使用tf.reshape函数将每个样本都变成一维向量。我们使用get_shape函数,获取数据扁平化之后的长度。接着使用variable_with_weights函数对全连接层的weight进行初始化,这里隐含层的节点数为384,正态分布的标准差设置为0.04,bias的值设置为0.1.需要注意的是我们希望这个全连接层不要过拟合,因此设置了一个非0的weight loss值为0.04,让这一层的所有参数都被L2正则约束。最后我们依然使用ReLU激活函数进行非线性化。
#创建第一个全连接层
reshape = tf.reshape(pool2, [batch_size, -1])
dim = reshape.get_shape()[1].value
weight3 = variable_with_weight_loss(shape=[dim, 384], stddev=0.04, w1=0.004)
bias3 = tf.Variable(tf.constant(0.1, shape=[384]))
local3 = tf.nn.relu(tf.matmul(reshape, weight3) + bias3)
  1. 接下来这个全连接层和上一层类似,只不过隐藏层的节点下降了一半,为192个。
#创建第二个全连接层
weight4 = variable_with_weight_loss(shape=[384, 192], stddev=0.04, w1=0.004)
bias4 = tf.Variable(tf.constant(0.1, shape=[192]))
local4 = tf.nn.relu(tf.matmul(local3, weight4) + bias4)
  1. 最后一层,依然先创建这一层的weight,其正态分布标准差设为上一层节点数的倒数,并且不计入L2的正则。需要注意的是,这里不像之前那样使用softmax输出最后的结果,这是因为我们把softmax的操作放在了计算loss的部分。我们不需要对inference的输出进行softmax处理就可以获得最终分类结果(直接比较inference输出的各类数值大小即可),计算softmax主要是为了计算loss,因此softmax操作整合到后面是比较合适的。
weight5 = variable_with_weight_loss(shape=[192, 10], stddev=1/192.0, w1=0.0)
bias5 = tf.Variable(tf.constant(0.0, shape=[10]))
  1. 完成了模型的inference部分,接下来计算CNN的loss。这里依然使用cross_entropy,需要注意的是我们把softmax的计算和cross_entropy loss的计算合在了一起,即是tf.nn.sparse_softmax_cross_entropy_with_logits。这里使用tf.reduce_mean对cross entropy计算均值再使用tf.add_to_collection把cross entropy的loss添加到整体的losses的collection中。最后,使用tf.add_n将整体的losses的collection中的全部loss求和,得到最终的loss。其中包括cross entropy loss,还有后面2个全连接层中weight的L2 loss。
#计算CNN的loss
def loss(logits, labels):
    labels = tf.cast(labels, tf.int64)
    #这个函数合并了softmax和交叉熵计算
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels, name='cross_entropy_per_example')
    cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
    tf.add_to_collection('losses', cross_entropy_mean)
    return tf.add_n(tf.get_collection('losses'), name='total_loss')
  1. 接着将logits节点和label_holder传入loss函数获得最终的loss。优化器依然选择Adam,学习率为1e-3,使用tf.nn.top_k函数求出输出结果中top k的准确率,默认为top 1,也就是输出得分最高的那一类的准确率。在井陉全局初始化和创建Seesion。
#接着将logits节点和label_holder传入loss函数获得最终的loss
loss = loss(logits, label_holder)
#定义优化器
train_op = tf.train.AdamOptimizer(1e-3).minimize(loss)
#使用tf.nn.top_k求出top-k准确率
top_k_op = tf.nn.in_top_k(logits, label_holder, 1)
#使用tf.InteractiveSession()创建Session,然后初始化
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()

14.启动图片数据增强县城队列,这里一共使用了16个线程来进行加速。注意,这里如果不启动线程,那么后续的inference和训练操作都是无法开始的。

#启动图片增强的线程队列
tf.train.start_queue_runners()
  1. 现在开始训练,我们需要使用session的run方法执行images_train,labels_train的计算,获得一个batch的训练数据,再将这个batch的数据传入train_op和loss计算。我们记录每一个step花费的时间,每隔10个step会计算并展示当前的loss,每秒能训练的样本数量,以及训练一个batch数据所花费的时间,这样就可以比较方便得监控整个训练过程,在GTX 1080上,每秒可以达到训练1800个样本,如果batch_size为128,则每个batch大约需要0.066s。损失函数在一开始大约为4.6,经过了3000步训练之后会降低到1.0附近。
#监控训练过程
for step in range(max_steps):
    start_time = time.time()
    image_batch, label_batch = sess.run([images_train, labels_train])
    _, loss_value = sess.run([train_op, loss], feed_dict={image_holder: image_batch, label_holder: label_batch})
    duration = time.time() - start_time
    if step % 10 == 0:
        example_per_sec = batch_size / duration
        sec_per_batch = float(duration)
        format_str = ('step %d, loss=%.2f (%.1f examples/sec; %.3f sec/batch)')
        print(format_str % (step, loss_value, example_per_sec, sec_per_batch))
  1. 评测模型在测试集上的表现。测试集一共有10000个样本,但是需要注意的是,我们仍然需要像训练那样使用固定的batch_size,然后一个batch一个batch地输入数据。我们先计算一共需要多少个batch才能将样本全部评测完。同时,在每一个step中使用session的run方法获取image_test,labels_test的batch,再执行top_k_op计算模型在这个batch的top1上预测正确的样本数,最后汇总所有预测正确的结果,求得全部测试样本中预测正确的数量,最后将准确率的评测结果计算并打印出来。
#测试模型
#测试模型
num_examples = 10000
import math
num_iter = int(math.ceil(num_examples / batch_size))
true_count = 0
total_sample_count = num_iter * batch_size
step = 0
while step < num_iter:
    image_batch, label_batch = sess.run([images_test, labels_test])
    predictions = sess.run([top_k_op], feed_dict={image_holder: image_batch, label_holder:label_batch})
    true_count += np.sum(predictions)
    step += 1
#打印准确率
precision = true_count / total_sample_count
print('precision @ 1 = %.3f' % precision)

算法完整代码

#coding=utf-8
import cifar10, cifar10_input
import tensorflow as tf
import numpy as np
import time
########################################################Inference#############################################################
#定义batch_size,训练最大轮数max_steps,以及下载CIFAR-10数据的默认路径
max_steps = 3000
batch_size = 128
data_dir = 'C:\\Users\\xiaoyu\\PycharmProjects\\DeepLearning\\tmp\\cifar10_data\\cifar-10-batches-bin'
#定义初始化weight的函数
def variable_with_weight_loss(shape, stddev, w1):
    var = tf.Variable(tf.truncated_normal(shape, stddev=stddev))
    if w1 is not None:
        weight_loss = tf.multiply(tf.nn.l2_loss(var), w1, name='weight_loss')
        tf.add_to_collection('losses', weight_loss)
    return var
#使用cifar10_input类中的distored函数产生随机训练需要使用的数据(内部多线程实现)
images_train, labels_train = cifar10_input.distorted_inputs(data_dir=data_dir, batch_size=batch_size)
#使用cifar10_input类中的distored函数生成测试数据
images_test, labels_test = cifar10_input.inputs(eval_data=True, data_dir=data_dir, batch_size=batch_size)
#创建输入数据的placeholder
image_holder = tf.placeholder(tf.float32, [batch_size, 24, 24, 3])
label_holder = tf.placeholder(tf.int32, [batch_size])
#创建第一个卷积层
weight1 = variable_with_weight_loss(shape=[5, 5, 3, 64], stddev=5e-2, w1=0.0)
kernel1 = tf.nn.conv2d(image_holder, weight1, [1, 1, 1, 1], padding='SAME')
bias1 = tf.Variable(tf.constant(0.0, shape=[64]))
conv1 = tf.nn.relu(tf.nn.bias_add(kernel1, bias1))
pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME')
norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001/9.0, beta=0.75)
#创建第二个卷积层
weight2 = variable_with_weight_loss(shape=[5, 5, 64, 64], stddev=5e-2, w1=0.0)
kernel2 = tf.nn.conv2d(norm1, weight2, [1, 1, 1, 1], padding='SAME')
bias2 = tf.Variable(tf.constant(0.1, shape=[64]))
conv2 = tf.nn.relu(tf.nn.bias_add(kernel2, bias2))
norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001/9.0, beta=0.75)
pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME')
#创建第一个全连接层
reshape = tf.reshape(pool2, [batch_size, -1])
dim = reshape.get_shape()[1].value
weight3 = variable_with_weight_loss(shape=[dim, 384], stddev=0.04, w1=0.004)
bias3 = tf.Variable(tf.constant(0.1, shape=[384]))
local3 = tf.nn.relu(tf.matmul(reshape, weight3) + bias3)
#创建第二个全连接层
weight4 = variable_with_weight_loss(shape=[384, 192], stddev=0.04, w1=0.004)
bias4 = tf.Variable(tf.constant(0.1, shape=[192]))
local4 = tf.nn.relu(tf.matmul(local3, weight4) + bias4)
#最后一个全连接层
weight5 = variable_with_weight_loss(shape=[192, 10], stddev=1/192.0, w1=0.0)
bias5 = tf.Variable(tf.constant(0.0, shape=[10]))
logits = tf.add(tf.matmul(local4, weight5), bias5)
###############################################################Backward##################################################
#计算CNN的loss
def loss(logits, labels):
    labels = tf.cast(labels, tf.int64)
    #这个函数合并了softmax和交叉熵计算
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels, name='cross_entropy_per_example')
    cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
    tf.add_to_collection('losses', cross_entropy_mean)
    return tf.add_n(tf.get_collection('losses'), name='total_loss')
#接着将logits节点和label_holder传入loss函数获得最终的loss
loss = loss(logits, label_holder)
#定义优化器
train_op = tf.train.AdamOptimizer(1e-3).minimize(loss)
#使用tf.nn.top_k求出top-k准确率
top_k_op = tf.nn.in_top_k(logits, label_holder, 1)
#使用tf.InteractiveSession()创建Session,然后初始化
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
#启动图片增强的线程队列
tf.train.start_queue_runners()
#监控训练过程
for step in range(max_steps):
    start_time = time.time()
    image_batch, label_batch = sess.run([images_train, labels_train])
    _, loss_value = sess.run([train_op, loss], feed_dict={image_holder: image_batch, label_holder: label_batch})
    duration = time.time() - start_time
    if step % 10 == 0:
        example_per_sec = batch_size / duration
        sec_per_batch = float(duration)
        format_str = ('step %d, loss=%.2f (%.1f examples/sec; %.3f sec/batch)')
        print(format_str % (step, loss_value, example_per_sec, sec_per_batch))
#测试模型
num_examples = 10000
import math
num_iter = int(math.ceil(num_examples / batch_size))
true_count = 0
total_sample_count = num_iter * batch_size
step = 0
while step < num_iter:
    image_batch, label_batch = sess.run([images_test, labels_test])
    predictions = sess.run([top_k_op], feed_dict={image_holder: image_batch, label_holder:label_batch})
    true_count += np.sum(predictions)
    step += 1
#打印准确率
precision = true_count / total_sample_count
print('precision @ 1 = %.3f' % precision)