卷积神经网络(Convolutional Neural Network,CNN),一般卷积神经网路由多个卷积层构成,每个卷积层中通常会进行如下几个操作:
(1)图像通过多个不同的卷积核的滤波,并加偏置(bias),提取出局部特征,每一个卷积核会映射出一个新的2D图像。
(2)将前面卷积核的滤波输出结果,进行非线性的激活函数处理,目前最常见的是ReLu函数,而以前Sigmoid函数用的比较多。
(3)对激活函数的结果再进行池化操作(即降采样6,比如2x2的图片降维1x1的图片),目前一般是使用最大池化,保留最显著的特征,并提升模型的畸变容忍能力。
一个卷积层中可以有多个不同的卷积核,而每一个卷积核都对应一个滤波后映射出的新图像,同一个新图像中每一个像素都来自完全相同的卷积核,这就是卷积核的权值共享。卷积核的大小,一般为3X3,5X5,7X7,1x1。卷积操作是局部连接方式,比如整个图像是1000x1000像素,卷积核是10x10,那图像的维度就是100,一个卷积核对应一个隐藏节点,隐藏层节点就是1W个。如果只有一个卷积核,就只能提取一种卷积核滤波的结果,即只能提出一种图片特征。我么可以增加卷积核的数量多提取一些特征。每一个卷积核滤波得到的图像就是一类特征的映射,即一个Feature Map.
卷积的好处是,不管图片尺寸如何,我们需要训练的权值数量只跟卷积核大小、卷积核数量有关,我们可以使用非常少的参数量处理任意大小的图片。隐藏节点的数量只跟卷积的步长有关,如果步长为1,那么隐藏节点的数量和输入的图像的像素数量一致;如果步长为5,那么每5X5的像素才需要一个隐藏节点,我们隐藏节点的数量就是输入像素数量的1/25.
总结一下,卷积神经网络的要点就是局部连接、权值共享和池化层中的降采样。其中局部连接和权值共享降低了参数量,使训练复杂度大大下降,并减轻了过拟合。同时,权值共享还赋予了卷积网络对平移的容忍性,而池化曾降采样则进一步降低了输出参数量,并赋予模型对轻度形变的容忍性,提高了模型的泛化能力。
======================================================================================
Tensorflow实现简单的卷积神经网络
这个简单的卷积神经网络使用两个卷积层加一个全连接层构建一个简单但是非常有代表性的卷积神经网络。
首先载入MNIST数据集,并创建默认的interactive Session
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
mnist=input_data.read_data_sets('/tmp/data',one_hot=True)
sess=tf.InteractiveSession()
接下来是实现这个卷积神经网络会有很多权重和偏置需要创建,因此我们先定义好初始化函数以便重复使用。我们需要给权重制造一些随机的噪声来打破完全对称,比如截断的正太分布噪声,标准差为0.1.同时因为我们使用ReLu函数,也给偏置增加一些小的正值(0.1)用来避免死亡节点。
def weight_variable(shape):
initial=tf.truncated_normal(shape,stddev=0.1)
return tf.Variable(initial)
def bias_variable(shape):
initial=tf.constant(0.1,shape=shape)
return tf.Variable(initial)
w是卷积参数,比如[5,5,1,32]:前面两个数字代表卷积核的尺寸;第三个数字代表有多少个channel。因为我们只有灰度单色,所以是1,如果是彩色的RGB图片,这里应该是3;最后一个数字代表卷积核的数量,也就是这个卷积层会提取多少类的特征(多少个不同的卷积核)。
strides代表卷积模板移动的步长,都是1代表会不遗漏地划过图片的每一个点。strides[0]
和strides[3]
的两个1是默认值,中间两个1代表padding时在x方向运动一步,y方向运动一步。
Padding代表边界的处理方式,这里SAME代表边界加上Padding让卷积的输出和输入保持同样的尺寸。
tf.nn.max_pool是Tensorflow中最大池化函数,这里使用2x2的最大池化,即讲一个2X2的像素块降维1x1的像素。最大池化会保留原始像素块中灰度值最高的哪一个像素,即保留最显著的特征。池化的核函数大小为2X2.
因为希望整体上缩小图片尺寸,因此池化层的strides也设为横竖两个方向以2为步长。
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')
在正式设计卷积神经网络结构之前,先定义输入的placeholder,x是特征,y_是真实的label。因为卷积神经网络利用到空间结构信息,因袭需要将1维的输入向量转为2维的图片结构,即从1X784->28X28,同时因为只有一个颜色通道,故最终尺寸为[-1,28,28,1],前面的-1代表样本数量不固定,最后的1代表颜色通道数量。这里使用的tensor变形函数是tf.reshape
x=tf.placeholder(tf.float32,[None,784])
y_=tf.placeholder(tf.float32,[None,10])
x_image=tf.reshape(x,[-1,28,28,1])
接下来定义我们的第一个卷积层。我们先使用前面写好的函数进行参数初始化,包括weights和bias。这里的[5,5,1,32],代表卷积核尺寸为5X5,1个颜色通道,32个不同的卷积核。然后使用conv2d函数进行卷积操作,并加上偏置,接着再使用ReLu激活函数进行非线性处理。最后,使用最大池化函数max_pool_2x2对卷积的输出结果进行池化操作。
w_conv1=weight_variable([5,5,1,32])
b_conv1=bias_variable([32])
h_conv1=tf.nn.relu(conv2d(x_image,w_conv1)+b_conv1)
h_pool1=max_pool_2X2(h_conv1)
现在定义第二个卷积层,这个卷积层基本和第一个卷积层一样,唯一的不同是卷积核的数量编程了64,也就是说这一层的卷积会提取64中 特征。
上一层的卷积核数量为32(即输出32个通道),所以本卷积和尺寸的第三个维度即输入的通道数也需要调整为32
w_conv2=weight_variable([5,5,32,64])
b_conv2=bias_variable([64])
h_conv2=tf.relu(conv2d(h_pool1,w_conv2)+b_conv2)
h_pool2=max_pool_2X2(h_conv2)
w_fc1=weight_variable([7*7*64,1024])
b_fc1=bias_variable([1024])
h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*64])
h_fc1=tf.nn.relu(tf.matmul(h_pool2_flat,w_fc1)+b_fc1)
keep_prob=tf.placeholder(tf.float32)
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)
w_fc2=weight_variable([1024,10])
b_fc2=bias_variable([10])
y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop,w_fc2)+b_fc2)
cross_entropy=tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_conv),reduction_indices=[1]))
train_step=tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction=tf.equal(tf.argmax(y_conv,1),tf.argmax(y_,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
tf.global_variables_initializer().run()
for i in range(20000):
batch=mnist.train.next_batch(50)
if i%100==0:
train_accuracy=accuracy.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0})
print 'step %d,training accuracy %g'%(i,train_accuracy)
train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5})
print 'test accuracy %g'% accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1.0})
全部训练完成后,我们再最终的测试集上进行全面测试,得到整体的分类准确率。========99.16%
=====================================================================================
Tensorflow实现进阶的卷积神经网络
使用的数据集是CIFAR-10
先下载tensorflow models库,以便使用其中提供的CIFAR-10的数据类
git clone https://github.com/tensorflow/models.git
cd models/tutorials/image/cifar10
然后我们载入一些常用库,并载入Tensorflow models中自动下载。读取CIFAR-10数据的类。
import cifar10,cifar10_input
import tensorflow as tf
import numpy as np
import time
接着定义batch_size、训练轮数max_steps,以及下载CIFAR-10数据的默认路径。
max_steps=3000
batch_size=128
data_dir='/tmp/cifar10_data/cifar-10-batches-bin'
在机器学习中,因特征过多而导致过拟合,一般可以通过减少特征或者惩罚不重要特征的权重来缓解这个问题。但是通常我们并不知道该惩罚哪些特征的权重,而正则化就是版主我们惩罚特征权重的,即特征的权重也会成为模型损失函数的一部分。可以理解为,为了使用某个特征,我们需要付出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时被用上。
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.maybe_download_and_extract()
需要注意的是我们队数据进行了data augmentation(数据增强)。具体细节可查看cifar10_input.distorted_inputs函数,其中数据增强操作包括随机的水平翻转(tf.image.random_flip_left_right)、随机剪切一块24x24代销的图片(tf.random_crop)、设置随机的亮度和对比度(tf.image.random_brightness,tf.image.random_contrast),以及对数据进行标准化tf.image.per_image_whitening(对数据减去均值,除以方差,保证数据0均值,方差为1)。通过这些操作,我们可以获得更多的样本(带噪声的),原来的一张图片样本可以变为多张图片,相当于扩大样本量,对提高准确率非常有帮助。需要注意的是,我们对图像进行数据增强的操作需要耗费大量的CPU时间,因此distorted_inputs使用了16个独立的县城来加速任务,函数内部会产生线程池,在需要使用时会通过tensorflow queue进行调度。
images_train.labels_train=cifar10_input.distorted_inputs(data_dir=data_dir,batch_size=batch_size)
images_test,labels_test=cifar10_input.inputs(eval_data=True,data_dir=data_dir,batch-size=batch_size)
image_holder=tf.placeholder(tf.float32,[batch_size,24,24,3])
label_holder=tf.placeholder(tf.int32,[batch_size])
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.constent(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.04)
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.004,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)
layer名称 | 描述 |
conv1 | 卷积层和Relu激活函数 |
pool1 | 最大池化 |
norm1 | LRN |
conv2 | 卷积层和relu激活函数 |
norm2 | LRN |
pool2 | 最大池化 |
local3 | 全连接层和relu激活函数 |
local4 | 全连接层和relu函数 |
logits | 模型inference的输出结果 |
完成了模型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 ,还有两个全连接层中的weight 的L2 loss.
def loss(logits,labels):
labels=tf.cast(labels,tf.int64)
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)
优化器选择Adan optimizer,学习速率设为1e-3
train_op=tf.train.AdamOptimizer(1e-3).minimize(loss)
使用tf.nn.in_top_k函数求输出结果中top k 的准确率,默认使用top1,也就是输出分数最高的哪一类的准确率。
top_k_op=tf.nn.in_top_k(logits,label_holder,1)
使用tf.InteractiveSession创建默认的session,接着初始化全部模型参数
sess=tf.InteractiveSession()
tf.global_variables_initializer().run()
这一步是启动前面提到的图片数据增强的线程队列,这里一共使用了16个县城来进行加速。注意,如果这里不启动县城,那么后续的inference及训练的操作都是无法开始的
tf.train.start_queue_runners()
现在正是开始训练。在每一个step的训练过程中,我们需要先使用session的run方法执行images_train,labels_train的计算,获得一个batch的训练数据,再将这个batch的数据传入train_op和loss计算。我们记录每一个step花费的时间,每隔10个step会计算并展示当前的loss、每秒能训练的样本数量,以及训练一个batch数据所花费的时间,这样就可以比较方便地监控整个训练过程。
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 stap%10==0:
examples_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,examples_per_sec,sec_per_batch)
接下来评测模型在测试集上的准确率。需要注意的是,我们依然要像训练时那样使用固定的batch_size,然后一个batch一个batch地输入测试数据。我们先计算一共要多少个batch才能将全部样本评测完。同时,在每一个step中使用session的run方法获取images_test,labels_test的batch,再执行top_k_op计算模型在这个batch的top 1上预测正确地样本数,最后汇总所有预测正确地结果,求得全部测试样本中预测正确的数量。
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
最后将准确率的评测结果计算并打印出来
precision=true_count/total_sample_count
print 'precision @1=%.3f' % precision
最终,在cifar-10数据集上,通过一个算时间小迭代次数的训练,可以达到大致73%准确率。持续增加max_steps,可以期望准确率逐渐增加。如果max_steps比较大,则推荐使用学习速率衰减(decay)的SGD进行训练,这样训练过程中能达到的准确率峰值会比较高,大致接近86%,而其中L2正则以及LRN层的使用都对模型准确率有所提升。