MNIST是手写体识别数据集,是大多数深度学习教程的入门资料。本文结合这个数据集,利用TensorFlow搭建神经网络。介绍了变量重用和名称空间的问题。
以下的代码以及内容,基本上来自Tensorflow:实战Google深度学习框架
1 MNIST介绍
MNIST是一个手写体数字识别数据集,它包含了60000张图片作为训练数据,10000张图片作为测试数据。每张图片代表0-9中的其中一个数字,图片大小为28x28,下图显示了一张数字图片以及其数字矩阵
以下代码给出了利用TensorFlow处理MNIST数据集的例子程序
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
#载入MNIST数据,如果路径下没有已经下载好的数据,则会自动下载
mnist = input_data.read_data_sets("/path/to/MNIST_data")
#程序会自动分好train,validation,test的数据集
print("data size = %d" %(mnist.train.num_examples))
print("validation size = %d" %(mnist.validation.num_examples))
print("test size = %d" %(mnist.test.num_examples))
#打印其中一个样本的矩阵,样本是一个长度为784的数组
#标签是0-9的其中一个数
print("train data "+ str(mnist.train.images[0]))
print("train data label " + str(mnist.train.labels[0]))
#为了方便使用随机梯度下降,函数含定义了next_batch函数,可以从
#数据集中读取一部分数据用于训练
batch_size = 100
xs,ys = mnist.train.next_batch(batch_size)
print("x shape = "+str(xs.shape))
print("y shape = "+str(ys.shape))
2 TensorFlow训练神经网络
以下给出一个利用TensorFlow搭建一个只有1个隐藏层的神经网络来识别MNIST数据集的数字。其中使用了指数衰减的学习率和L2正则化避免过拟合。以下是完整程序。
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
INPUT_NODE = 784 # 输入节点
OUTPUT_NODE = 10 # 输出节点
LAYER1_NODE = 500 # 隐藏层节点数
BATCH_SIZE = 100 # 每次batch打包的样本个数
# 模型相关的参数
LEARNING_RATE_BASE = 0.8 #初始化学习率
LEARNING_RATE_DECAY = 0.99 #学习率衰减率
REGULARAZTION_RATE = 0.0001 #正则化的lambda
TRAINING_STEPS = 5000 #总训练次数
#正向传播算法
#函数参数为输入矢量、连接到隐藏层和输出层的w1 b1, w2 b2,返回输出层结果
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
layer1 = tf.nn.relu(tf.matmul(input_tensor,weights1)+biases1)
return tf.matmul(layer1,weights2)+biases2
def train(mnist):
# 定义输入输出的placeholder
x = tf.placeholder(dtype=tf.float32,shape=[None,784],name='x-input')
y_= tf.placeholder(dtype=tf.float32,shape=[None,10],name='y-output')
#初始化传输矩阵
weight1 = tf.Variable(tf.truncated_normal([INPUT_NODE,LAYER1_NODE],stddev=0.1))
biases1 = tf.Variable(tf.constant(0.1,shape=[LAYER1_NODE]))
weight2 = tf.Variable(tf.truncated_normal([LAYER1_NODE,OUTPUT_NODE],stddev=0.1))
biases2 = tf.Variable(tf.constant(0.1,shape=[OUTPUT_NODE]))
# 计算不前向传播结果
y = inference(x,None,weight1,biases1,weight2,biases2)
# 定义训练轮数,用于学习率衰减
global_step = tf.Variable(0,trainable=False)
# 计算交叉熵及其平均值
#tf.nn.sparse_softmax_cross_entropy_with_logits中
#labels要求输入正确样本的下标
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)
# 损失函数的计算,添加正则化项
regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)
regularization = regularizer(weight1)+regularizer(weight2)
loss = cross_entropy_mean + regularization
# 设置指数衰减的学习率。
#设置为训练集过完一遍,学习率再下降
learning_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE,
global_step,
mnist.train.num_examples / BATCH_SIZE,
LEARNING_RATE_DECAY,
staircase=True)
# 优化损失函数,利用Adam的话结果不收敛,原因暂时不明确
train_step = tf.train.MomentumOptimizer(learning_rate,0.5).minimize(loss,global_step=global_step)
# 计算正确率
#correct_prodiction返回的是bool
#tf.cast函数将bool值转化为tf.float值,用于计算正确率
correct_prediction = tf.equal(tf.argmax(y_,1),tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
# 初始化会话,并开始训练过程。
# 每1000轮利用validation set进行一次正确率测试
# 训练完成后利用test set进行正确率测试
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
validation_feed = {x:mnist.validation.images,y_:mnist.validation.labels}
test_feed = {x:mnist.test.images,y_:mnist.test.labels}
for i in range(TRAINING_STEPS):
if i%1000==0:
validation_acc = sess.run(accuracy,feed_dict=validation_feed)
print("learning rate = " + str(sess.run(learning_rate)))
print("i = %d,accuracy = %g" %(i,validation_acc))
xs,ys=mnist.train.next_batch(BATCH_SIZE)
sess.run(train_step,feed_dict={x:xs,y_:ys})
test_acc = sess.run(accuracy,feed_dict=test_feed)
print("test accuracy = %g" %(test_acc))
#主函数
def main(argv=None):
mnist = input_data.read_data_sets("/path/to/data"
, one_hot=True)
train(mnist)
if __name__=='__main__':
main()
3 变量管理
在上方的例子中的inference函数中:
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
layer1 = tf.nn.relu(tf.matmul(input_tensor,weights1)+biases1)
return tf.matmul(layer1,weights2)+biases2
需要传入神经网络里的所有权重参数进去,才能计算输出值。当网络的层数少的时候,这种方法问题不大,但是如果层数很多,就需要一个更好的方法来传输这些网络变量了。
TensorFlow提供了通过变量名称来创建或者获取变量,并且通过上下文管理器,使名称相同的变量能够在不同的区域独立存在。实现的机制主要是通过tf.get_variable 和tf.variable_scope。下边介绍一下。
当用tf.get_variable 来创建变量时,其效果跟tf.Variable 是基本等价的:
v = tf.get_variable("v", shape=[1],initializer=tf.constant_initializer(1.0))
v = tf.Variable(tf.constant(1.0,shape=[1],name='v'))
两者的区别在于,tf.get_variable中的变量名称是一个必填的参数,而tf.Variable的变量名称可选。在tf.get_variable中如果变量名称在之前已经声明过的话,会创建失败并报错,防止两个变量名称重复。
如果要用tf.get_variable来获取变量,需要上下文管理器tf.scope函数
with tf.variable_scope("foo"):
v=tf.get_variable("v",shape=[1],initializer=tf.constant_initializer(1))
#以上代码在foo名称空间中创建了名字为v的变量
#因此在以下代码中尝试再次创建时会报错
with tf.variable_scope("foo"):
v=tf.get_variable("v",shape=[1],initializer=tf.constant_initializer(1))
#在声明名称空间时,如果reuse声明为true,则可以获取已经声明的变量
with tf.variable_scope("foo",reuse=True):
v=tf.get_variable("v",shape=[1],initializer=tf.constant_initializer(1))
#但是如果尝试获取没被声明的变量时,则会报错
#例如下方v变量在bar的名称空间中没被事先声明,因此报错
with tf.variable_scope("bar",reuse=True):
v=tf.get_variable("v",shape=[1],initializer=tf.constant_initializer(1))
#名称空间还能够嵌套使用
with tf.variable_scope("root"):
print tf.get_variable_scope().reuse #打印false
with tf.variable_scope("foo", reuse=True):
print tf.get_variable_scope().reuse #打印true
with tf.variable_scope("bar"):
print tf.get_variable_scope().reuse#打印False
print tf.get_variable_scope().reuse #打印False
#tf.variable_scope()还有管理变量名称的功能,在不同的名称空间中,变量的名字是不同的
v1 = tf.get_variable("v", [1])
print v1.name #打印v:0 v为变量名称,:0表示这个变量是运算得出的第一个结果
with tf.variable_scope("foo",reuse=True):
v2 = tf.get_variable("v", [1])#打印foo/v:0
print v2.name
with tf.variable_scope("foo"):
with tf.variable_scope("bar"):
v3 = tf.get_variable("v", [1])
print v3.name #打印foo/bar/v:0
v4 = tf.get_variable("v1", [1])
print v4.name #打印v1:0
#tf.get_variable还支持利用带名称空间的变量名字来获取变量
#以下输出都是True
with tf.variable_scope("",reuse=True):
v5 = tf.get_variable("foo/bar/v", [1])
print v5 == v3
v6 = tf.get_variable("v1", [1])
print v6 == v4
通过上下文管理器,我们可以优化刚开始构建的神经网络,通过更改inference函数,可以令其更简洁。
def inference(input_tensor):
with tf.variable_scope('layer1'):
weights = tf.get_variable('weights',[INPUT_NODE,LAYER1_NODE],initializer=tf.truncated_normal_initializer(stddev=0.1))
bias = tf.get_variable('bias',[LAYER1_NODE],initializer=tf.constant_initializer(0.0))
layer1 = tf.nn.relu(tf.matmul(input_tensor,weights)+bias)
with tf.variable_scope('layer2'):
weights = tf.get_variable('weights',[LAYER1_NODE,OUTPUT_NODE],initializer=tf.truncated_normal_initializer(stddev=0.1))
bias = tf.get_variable('bias',[OUTPUT_NODE],initializer=tf.constant_initializer(0.0))
layer2 = tf.matmul(layer1,weights)+bias
return layer2
#同时y的获得方式也要改变
y = inference(x)
现在神经网络的参数声明以及其运算过程都在函数内完成,即使网络变大,也能够保证程序可读性。