在MNIST上只有91%正确率,实在太糟糕。在这个小节里,我们用一个稍微复杂的模型:卷积神经网络来改善效果。这会达到大概99.2%的准确率。虽然不是最高,但是还是比较让人满意。
为了创建这个模型,我们需要创建大量的权重和偏置项。这个模型中的权重在初始化时应该加入少量的噪声来打破对称性以及避免0梯度。由于我们使用的是ReLU神经元,因此比较好的做法是用一个较小的正数来初始化偏置项,以避免神经元节点输出恒为0的问题(dead neurons)。为了不在建立模型的时候反复做初始化操作,我们定义两个函数用于初始化。
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)
TensorFlow在卷积和池化上有很强的灵活性。我们怎么处理边界?步长应该设多大?在这个实例里,我们会一直使用vanilla版本。我们的卷积使用1步长(stride size),0边距(padding size)的模板,保证输出和输入是同一个大小。我们的池化用简单传统的2x2大小的模板做max pooling。为了代码更简洁,我们把这部分抽象成一个函数。
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')
现在我们可以开始实现第一层了。它由一个卷积接一个max pooling完成。卷积在每个5x5的patch中算出32个特征。卷积的权重张量形状是[5, 5, 1, 32]
,前两个维度是patch的大小,接着是输入的通道数目,最后是输出的通道数目。 而对于每一个输出通道都有一个对应的偏置量。
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
为了用这一层,我们把x
变成一个4d向量,其第2、第3维对应图片的宽、高,最后一维代表图片的颜色通道数(因为是灰度图所以这里的通道数为1,如果是rgb彩色图,则为3)。
x_image = tf.reshape(x, [-1,28,28,1])
We then convolve x_image
with the weight tensor, add the bias, apply the ReLU function, and finally max pool. 我们把x_image
和权值向量进行卷积,加上偏置项,然后应用ReLU激活函数,最后进行max pooling。
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
为了构建一个更深的网络,我们会把几个类似的层堆叠起来。第二层中,每个5x5的patch会得到64个特征。
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
现在,图片尺寸减小到7x7,我们加入一个有1024个神经元的全连接层,用于处理整个图片。我们把池化层输出的张量reshape成一些向量,乘上权重矩阵,加上偏置,然后对其使用ReLU。
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)
Dropout
为了减少过拟合,我们在输出层之前加入dropout。我们用一个placeholder
来代表一个神经元的输出在dropout中保持不变的概率。这样我们可以在训练过程中启用dropout,在测试过程中关闭dropout。 TensorFlow的tf.nn.dropout
操作除了可以屏蔽神经元的输出外,还会自动处理神经元输出值的scale。所以用dropout的时候可以不用考虑scale。
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
最后,我们添加一个softmax层,就像前面的单层softmax regression一样。
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)
这个模型的效果如何呢?
为了进行训练和评估,我们使用与之前简单的单层SoftMax神经网络模型几乎相同的一套代码,只是我们会用更加复杂的ADAM优化器来做梯度最速下降,在feed_dict
中加入额外的参数keep_prob
来控制dropout比例。然后每100次迭代输出一次日志。
cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
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, "float"))
sess.run(tf.initialize_all_variables())
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.2%。
目前为止,我们已经学会了用TensorFlow快捷地搭建、训练和评估一个复杂一点儿的深度学习模型。
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
import tensorflow as tf
sess = tf.InteractiveSession()
x = tf.placeholder("float", [None, 784])
y_ = tf.placeholder("float", shape=[None, 10])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
sess.run(tf.initialize_all_variables())
y = tf.nn.softmax(tf.matmul(x, W) + b)
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
for i in range(1000):
batch = mnist.train.next_batch(50)
train_step.run(feed_dict={x:batch[0], y_:batch[1]})
# 评估模型
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
#这里返回一个布尔数组。为了计算我们分类的准确率,我们将布尔值转换为浮点数来代表对、错,然后取平均值。例如:[True, False, True, True]变为[1,0,1,1],计算出平均值为0.75。
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
# 最后,我们可以计算出在测试数据上的准确率,大概是91%。
print(accuracy.eval(feed_dict={x:mnist.test.images, y_:mnist.test.labels}))
"""
权重初始化
"""
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)
"""
卷积和池化
"""
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')
# 第一层卷积
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
x_image = tf.reshape(x, [-1, 28, 28, 1])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
# 第二层卷积
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.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)
# dropout
keep_prob = tf.placeholder("float")
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_sum(y_*tf.log(y_conv))
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, "float"))
sess.run(tf.initialize_all_variables())
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}))
大致流程分为三步:
1、构建CNN网络结构;
2、构建loss function,配置寻优器;
3、训练、测试。
本教程中使用了两个卷积层+池化层,最后接上两个全连接层。
第一层卷积使用32个3x3x1的卷积核,步长为1,边界处理方式为“SAME”(卷积的输入和输出保持相同尺寸),激发函数为Relu,后接一个2x2的池化层,方式为最大化池化;
第二层卷积使用50个3x3x32的卷积核,步长为1,边界处理方式为“SAME”,激发函数为Relu, 后接一个2x2的池化层,方式为最大化池化;
第一层全连接层:使用1024个神经元,激发函数依然是Relu。
第二层全连接层:使用10个神经元,激发函数为softmax,用于输出结果。
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
#读取数据
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
sess=tf.InteractiveSession()
#构建cnn网络结构
#自定义卷积函数(后面卷积时就不用写太多)
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')
#设置占位符,尺寸为样本输入和输出的尺寸
x=tf.placeholder(tf.float32,[None,784])
y_=tf.placeholder(tf.float32,[None,10])
x_img=tf.reshape(x,[-1,28,28,1])
#设置第一个卷积层和池化层
w_conv1=tf.Variable(tf.truncated_normal([3,3,1,32],stddev=0.1))
b_conv1=tf.Variable(tf.constant(0.1,shape=[32]))
h_conv1=tf.nn.relu(conv2d(x_img,w_conv1)+b_conv1)
h_pool1=max_pool_2x2(h_conv1)
#设置第二个卷积层和池化层
w_conv2=tf.Variable(tf.truncated_normal([3,3,32,50],stddev=0.1))
b_conv2=tf.Variable(tf.constant(0.1,shape=[50]))
h_conv2=tf.nn.relu(conv2d(h_pool1,w_conv2)+b_conv2)
h_pool2=max_pool_2x2(h_conv2)
#设置第一个全连接层
w_fc1=tf.Variable(tf.truncated_normal([7*7*50,1024],stddev=0.1))
b_fc1=tf.Variable(tf.constant(0.1,shape=[1024]))
h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*50])
h_fc1=tf.nn.relu(tf.matmul(h_pool2_flat,w_fc1)+b_fc1)
#dropout(随机权重失活)
keep_prob=tf.placeholder(tf.float32)
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)
#设置第二个全连接层
w_fc2=tf.Variable(tf.truncated_normal([1024,10],stddev=0.1))
b_fc2=tf.Variable(tf.constant(0.1,shape=[10]))
y_out=tf.nn.softmax(tf.matmul(h_fc1_drop,w_fc2)+b_fc2)
#建立loss function,为交叉熵
loss=tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_out),reduction_indices=[1]))
#配置Adam优化器,学习速率为1e-4
train_step=tf.train.AdamOptimizer(1e-4).minimize(loss)
#建立正确率计算表达式
correct_prediction=tf.equal(tf.argmax(y_out,1),tf.argmax(y_,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
# 定义saver
saver = tf.train.Saver()
#开始喂数据,训练
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})
print("step %d,train_accuracy= %g"%(i,train_accuracy))
train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5})#这里才开始真正进行训练计算
# 模型储存位置
saver.save(sess, ".\\MNIST_data\\model.ckpt")
#训练之后,使用测试集进行测试,输出最终结果
print("test_accuracy= %g"% accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1}))
1.
#自定义卷积函数(后面就不用写太多)
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')
卷积步长为1,如要改成步长为2则strides=[1,2,2,1],只有中间两个是有效的(对于二维图来说),使用‘SAME’的padding方法(即输出与输入保持相同尺寸,边界处少一两个像素则自动补上);池化层的设置也类似,池化尺寸为2X2。
2.
#设置占位符,尺寸为样本输入和输出的尺寸
x=tf.placeholder(tf.float32,[None,784])
y_=tf.placeholder(tf.float32,[None,10])
x_img=tf.reshape(x,[-1,28,28,1])
设置输入输出的占位符,占位符是向一个会话中喂数据的入口,因为TensorFlow的使用中,通过构建计算图来设计网络,而网络的运行计算则在会话中启动,这个过程我们无法直接介入,需要通过placeholder来对一个会话进行数据输入。
占位符设置好之后,将x变形成为28x28是矩阵形式(tf.reshape()函数)。
3.
#设置第一个卷积层和池化层
w_conv1=tf.Variable(tf.truncated_normal([3,3,1,32],stddev=0.1))
b_conv1=tf.Variable(tf.constant(0.1,shape=[32]))
h_conv1=tf.nn.relu(conv2d(x_img,w_conv1)+b_conv1)
h_pool1=max_pool_2x2(h_conv1)
#设置第二个卷积层和池化层
w_conv2=tf.Variable(tf.truncated_normal([3,3,32,50],stddev=0.1))
b_conv2=tf.Variable(tf.constant(0.1,shape=[50]))
h_conv2=tf.nn.relu(conv2d(h_pool1,w_conv2)+b_conv2)
h_pool2=max_pool_2x2(h_conv2)
第一层卷积使用3x3x1的卷积核,一共有32 个卷积核,权值使用方差为0.1的截断正态分布(指最大值不超过方差两倍的分布)来初始化,偏置的初值设定为常值0.1。
第二层卷积和第一层类似,卷积核尺寸为3x3x32(32是通道数,因为上一层使用32个卷积核,所以这一层的通道数就变成了32),这一层一共使用50个卷积核,其他设置与上一层相同。
每一层卷积完之后接上一个2x2的最大化池化操作。
4.
#设置第一个全连接层
w_fc1=tf.Variable(tf.truncated_normal([7*7*50,1024],stddev=0.1))
b_fc1=tf.Variable(tf.constant(0.1,shape=[1024]))
h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*50])
h_fc1=tf.nn.relu(tf.matmul(h_pool2_flat,w_fc1)+b_fc1)
#dropout(随机权重失活)
keep_prob=tf.placeholder(tf.float32)
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)
#设置第二个全连接层
w_fc2=tf.Variable(tf.truncated_normal([1024,10],stddev=0.1))
b_fc2=tf.Variable(tf.constant(0.1,shape=[10]))
y_out=tf.nn.softmax(tf.matmul(h_fc1_drop,w_fc2)+b_fc2)
卷积层之后就是两个全连接层,第一个全连接层有1024个神经元,先将卷积层得到的2x2输出展开成一长条,使用Relu激活函数得到输出,输出为1024维。
Dropout:在这一层使用dropout(权值随机失活),对一些神经元突触连接进行强制的置零,这个trick可以防止神经网络过拟合。这里的dropout的保留比例是0.5,即随机地保留一半权值,删除另外一半(不要觉得可惜,为了保证在测试集上的效果,这是必须的)。Dropout比例通过placeholder来设置,因为训练过程中需要dropout,但是在最后的测试过程中,我们又希望使用全部的权值,所以dropout的比例要能够改变,所以这里使用placeholder。
第二个全连接层有10个神经元,分别对应0-9这10个数字(没错,这一层终于要得到最终的识别结果了),不过与之前的每一层不同的是,这里使用的激活函数是Softmax,关于softmax,我的个人理解是softmax是“以指数函数作为核函数的归一化操作”,softmax与一般归一化操作不同的是,指数函数能够放大一个分布内各个数值的差异,能够使各个数值的“贫富差距”变大,“两极分化”现象会更明显(对同一个分布进行一般的归一化得到的分布和softmax得到的分布,softmax得到的分布信息熵要更大)
5.
#建立loss function,为交叉熵
loss=tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_out),reduction_indices=[1]))
#配置Adam优化器,学习速率为1e-4
train_step=tf.train.AdamOptimizer(1e-4).minimize(loss)
#建立正确率计算表达式
correct_prediction=tf.equal(tf.argmax(y_out,1),tf.argmax(y_,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
建立loss function是很重要的一个过程,这里使用交叉熵来作为loss,交叉熵是用来衡量两个分布的相似程度的,两个分布越接近,则交叉熵越小。
使用Adam优化器来最小化loss,配置学习速率为1e-4。然后建立正确率的计算表达式(注意,仅仅是建立而已,现在还没有开始真正的计算),tf.argmax(y_,1),函数用来返回其中最大的值的下标,tf.equal()用来计算两个值是否相等。tf.cast()函数用来实现数据类型转换(这里是转换为float32),tf.reduce_mean()用来求平均(得到正确率)。
6.
#开始喂数据,训练
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})
print "step %d,train_accuracy= %g"%(i,train_accuracy)
train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5})#这里才开始真正进行训练计算
接下来就是喂数据了,对网络进行训练了,首先使用tf.global_variables_initializer().run()初始化所有数据,从mnist训练数据集中一次取50个样本作为一组进行训练,一共进行20000组训练,每100次就输出一次该组数据上的正确率。
进行训练计算的方式是:train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5}),通过feed_dict来对会话输送训练数据(以及其他一些想在计算过程中实时调整的参数,比如dropout比例)。
这段代码中可以看到,训练时dropout的保留比例是0.5,测试时的保留比例是1。
7.
#训练之后,使用测试集进行测试,输出最终结果
Print "test_accuracy= %g"%accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1})
最后输入测试数据集进行测试验证,把代码跑起来看看结果吧。
接下来就是喂数据了,对网络进行训练了,首先使用tf.global_variables_initializer().run()初始化所有数据,从mnist训练数据集中一次取50个样本作为一组进行训练,一共进行20000组训练,每100次就输出一次该组数据上的正确率。
进行训练计算的方式是:train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5}),通过feed_dict来对会话输送训练数据(以及其他一些想在计算过程中实时调整的参数,比如dropout比例)。
这段代码中可以看到,训练时dropout的保留比例是0.5,测试时的保留比例是1。
最后的运行结果长这样(测试集上的准确率为99.19%,还不错):
step 18800,train_accuracy= 0.98
step 18900,train_accuracy= 1
step 19000,train_accuracy= 0.98
step 19100,train_accuracy= 1
step 19200,train_accuracy= 1
step 19300,train_accuracy= 1
step 19400,train_accuracy= 1
step 19500,train_accuracy= 1
step 19600,train_accuracy= 1
step 19700,train_accuracy= 1
step 19800,train_accuracy= 1
step 19900,train_accuracy= 1
2018-09-21 03:31:26.823342: W T:\src\github\tensorflow\tensorflow\core\framework\allocator.cc:108] Allocation of 1003520000 exceeds 10% of system memory.
2018-09-21 03:33:03.734611: W T:\src\github\tensorflow\tensorflow\core\framework\allocator.cc:108] Allocation of 250880000 exceeds 10% of system memory.
2018-09-21 03:33:05.869754: W T:\src\github\tensorflow\tensorflow\core\framework\allocator.cc:108] Allocation of 392000000 exceeds 10% of system memory.
2018-09-21 03:34:56.028297: W T:\src\github\tensorflow\tensorflow\core\framework\allocator.cc:108] Allocation of 98000000 exceeds 10% of system memory.
test_accuracy= 0.9919
Process finished with exit code 0