tensorflow学习笔记——使用TensorFlow操做MNIST数据(1)html
如下定义了三个隐藏层的全链接方式的神经网络样例代码:java
import tensorflow as tf l1 = tf.matmul(x, w1) l2 = tf.matmul(l1, w2) y = tf.matmul(l2,w3)
激活函数有多种,例如经常使用的 tf.nn.relu tf.nn.tanh tf.nn.sigoid tf.nn.elu,下面显示了几种经常使用的非线性激活函数的函数图像:python
样例代码:git
import tensorflow as tf a = tf.nn.relu(tf.matmul(x, w1) + biase1) y = tf.nn.relu(tf.matmul(a, w2) + biase2)
经典损失函数,交叉熵(cross entropy)用于计算预测结果矩阵 Y 和实际结果矩阵 Y_ 之间的距离样例代码:正则表达式
import tensorflow as tf cross_entropy = - tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)))
import tensorflow as tf v = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) tf.reduce_mean(tf.clip_by_value(v, 0.0, 10.0))
对于分类问题,一般把交叉熵与softmax回归一块儿使用算法
import tensorflow as tf cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y, y_)
对于回归问题,一般使用mse(均方偏差函数)计算损失函数编程
import tensorflow as tf mse_loss = tf.reduce_mean(tf.square(y_ - y)) # 与如下函数计算结果彻底一致 dataset_size = 1000 mse_loss = tf.reduce_sum(tf.pow(y_ - y, 2)) / dataset_size
自定义条件化的损失函数数组
import tensorflow as tf loss_less = 10 loss_more = 1 loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more, (y_ - y) * loss_less)) train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
通常优化器的目标是优化权重W和误差biases,最小化损失函数的结果,如下优化器会不断优化W和 biases。网络
Adam优化算法是一个全局最优势的优化算法,引入了二次方梯度校订,相比于基础SGD算法,不容易陷入局部优势,并且速度更快。数据结构
本质上是带有动量项的RMSprop,它利用梯度的一阶矩估计和二阶矩估计动态调整每一个参数的学习率。Adam的优势主要在于通过偏置校订后,每一次迭代学习率都有个肯定范围,使得参数比较平稳。
import tensorflow as tf LEARNING_RATE = 0.001 mse_loss = tf.reduce_mean(tf.square(y_ - y)) train_op = tf.train.AdamOptimizer(LEARNING_RATE).minimize(mse_loss)
在训练神经网络时,须要设置学习率(learning rate)控制参数更新的速度,学习率决定了参数每次更新的幅度,学习率设置过大可能致使没法收敛,在极优值的两侧来回移动。学习率设置太小虽然能保证收敛性,但可能致使收敛过慢。
为了解决学习率的问题,Tensorflow 提供了一种更加灵活的学习率设置方法——指数衰减法。tf.train.exponential_decay 函数实现了指数衰减学习率。经过这个函数,能够先使用较大的学习率来快速获得一个比较优的解,而后随着迭代的继续逐步减小学习率,使得模型在训练后期更加稳定。exponential_decay 函数会指数级地减小学习率,它实现了一下代码的功能:
decayed_learning_rate = learning_rate * decay_rate ^(global_step / decay_steps)
其中 decayed_learning_rate 为每一轮优化时使用的学习率,learning_rate 为事先设定的初始学习率,decay_rate 为衰减系数,decay_steps 为衰减速度。tf.train.exponential_decay 函数能够经过设置参数 staircase 选择不一样的衰减方式。
import tensorflow as tf global_step = tf.Variable(0) learning_rate = tf.train.exponential_decay( learning_rate=0.1, global_step=global_step, decay_steps=100, decay_rate=0.96, staircase=True, name=None ) train_op = tf.train.AdamOptimizer(learning_rate).minimize(loss, global_step=global_step)
避免训练出来的模型过度复杂,即模型记住了全部数据(包括噪声引发的偏差),所以须要引入正则化函数叠加的方式,避免模型出现过拟合
import tensorflow as tf v_lambda = 0.001 w = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1)) y = tf.matmul(x, w) mse_loss = tf.reduce_mean(tf.square(y_ - y) + tf.contrib.layers.l2_regularizer(v_lambda)(w))
用于控制模型的变化速度,能够控制权重W以及误差 biases 例如 :avg_class.average(w) avg_avergae(biases)
import tensorflow as tf v1 = tf.Variable(0, dtype=tf.float32) step = tf.Variable(0, trainable=False) ema = tf.train.ExponentialMovingAverage(decay=0.99, num_updates=step) # 每一次操做的时候,列表变量[v1]都会被更新 maintain_averages_op = ema.apply([v1]) with tf.Session() as sess: # 初始化 init_op = tf.global_variables_initializer() sess.run(init_op) print(sess.run([v1, ema.average(v1)])) # 更新step和v1的取值 sess.run(tf.assign(step, 10000)) sess.run(tf.assign(v1, 10)) sess.run(maintain_averages_op) print(sess.run([v1, ema.average(v1)]))
gfile模块定义在tensorflow/python/platform/gfile.py,但其源代码实现主要位于tensorflow/tensorflow/python/lib/io/file_io.py,那么gfile模块主要功能是什么呢?
google上的定义为:
翻译过来为:
没有线程锁的文件I / O操做包装器
tf.gfile模块的主要角色是:
C ++ FileSystem API支持多种文件系统实现,包括本地文件,谷歌云存储(以gs://开头)和HDFS(以hdfs:/开头)。 TensorFlow将它们导出为tf.gfile,以便咱们可使用这些实现来保存和加载检查点,编写TensorBoard log以及访问训练数据(以及其余用途)。可是,若是全部文件都是本地文件,则可使用常规的Python文件API而不会形成任何问题。
以上为google对tf.gfile的说明。
下面将分别介绍每个gfile API!
拷贝源文件并建立目标文件,无返回,其形参说明以下:
建立一个目录,dirname为目录名字,无返回。
删除文件,filename即文件名,无返回。
递归删除全部目录及其文件,dirname即目录名,无返回。
判断目录或文件是否存在,filename可为目录路径或带文件名的路径,有该目录则返回True,不然False。
查找匹配pattern的文件并以列表的形式返回,filename能够是一个具体的文件名,也能够是包含通配符的正则表达式。
判断所给目录是否存在,若是存在则返回True,不然返回False,dirname是目录名。
罗列dirname目录下的全部文件并以列表形式返回,dirname必须是目录名。
以递归方式创建父目录及其子目录,若是目录已存在且是可覆盖则会建立成功,不然报错,无返回。
重命名或移动一个文件或目录,无返回,其形参说明以下:
返回目录的统计数据,该函数会返回FileStatistics数据结构,以dir(tf.gfile.Stat(filename))获取返回数据的属性以下:
递归获取目录信息生成器,top是目录名,in_order默认为True指示顺序遍历目录,不然将无序遍历,每次生成返回以下格式信息(dirname, [subdirname, subdirname, ...], [filename, filename, ...])。
获取文本操做句柄,相似于python提供的文本操做open()函数,filename是要打开的文件名,mode是以何种方式去读写,将会返回一个文本操做句柄。
tf.gfile.Open()是该接口的同名,可任意使用其中一个!
该函数与tf.gfile.GFile的差异仅仅在于“无阻塞”,即该函数会无阻赛以较快的方式获取文本操做句柄
TFRecord 文件中的数据都是经过 tf.train.Example Protocol Buffer 的格式存储的。如下为 tf.train.Example 的数据结构:
message Example { Features features = 1; }; message Features{ map<string, Feature> feature = 1; }; message Feature { oneof kind { BytesList bytes_list = 1; FloatList float_list = 1; Int64List int64_list = 3; } };
tf.train.Example 中包含了一个从属性名称到取值的字典。其中属性名称为一个字符串,属性的取值能够为字符串(BytesList),实数列表(FloatList)或者整数列表(Int64List)。
# _*_coding:utf-8_*_ import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data import os import numpy as np # 生成整数的属性 def _int64_feature(value): return tf.train.Feature(int64_list=tf.train.Int64List(value=[value])) # 生成字符串型的属性 def _bytes_feature(value): return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value])) def create_records(): ''' 实现将 MNIST 数据集 转化为records 注意:读取的图像数据默认是uint8,而后转化为tf 的字符串型BytesList 保存, :return: ''' # 导入MNIST数据集 mnist = input_data.read_data_sets('data/', dtype=tf.uint8, one_hot=True) images = mnist.train.images labels = mnist.train.labels # 训练图像的分辨率,做为example的属性 pixels = images.shape[1] num_examples = mnist.train.num_examples # 存储TFRecord文件的地址 filename = 'record/output_mnist.tfrecords' # 建立一个writer来写TFRecord 文件 writer = tf.python_io.TFRecordWriter(filename) # 将每张图片都转化为一个Example for i in range(num_examples): # 将图像转为字符串 image_raw = images[i].tostring() # 将一个样例转化为Example Protocol Buffer 并将全部信息写入这个数据结构 example = tf.train.Example(features=tf.train.Features( feature={ 'pixels': _int64_feature(pixels), 'label': _int64_feature(np.argmax(labels[i])), 'image_raw': _bytes_feature(image_raw) } )) # 将Example写入TFRecord 文件 writer.write(example.SerializeToString()) print("data processing success") writer.close() if __name__ == '__main__': dir_name = 'record' if not os.path.exists(dir_name): os.mkdir(dir_name) create_records()
上面程式能够将MNIST数据集中全部的训练数据存储到一个TFRecord 文件中。当数据量较大时,也能够将数据写入多个 TFRecord 文件。TensorFlow对从文件列表中读取数据提供了很好的支持。
#_*_coding:utf-8_*_ import tensorflow as tf def read_tfrecord(): """ 读取tfrecord文件 :return: """ filename = 'record/output.tfrecords' # 建立一个队列来维护输入文件列表 filename_queue = tf.train.string_input_producer([filename]) # 建立一个reader来读取TFRecord文件中Example reader = tf.TFRecordReader() # 从文件中读取一个Example _, serialized_example = reader.read(filename_queue) # 用FixedLenFeature 将读入的Example解析成 tensor features = tf.parse_single_example( serialized_example, features={ 'image_raw': tf.FixedLenFeature([], tf.string), 'pixels': tf.FixedLenFeature([], tf.int64), 'label': tf.FixedLenFeature([], tf.int64) } ) # tf.decode_raw 将字符串解析成图像对应的像素数组 images = tf.decode_raw(features['image_raw'], tf.uint8) labels = tf.cast(features['label'], tf.int32) pixels = tf.cast(features['pixels'], tf.int32) init_op = tf.global_variables_initializer() with tf.Session() as sess : sess.run(init_op) # 启动多线程处理输入数据 coord = tf.train.Coordinator() threads = tf.train.start_queue_runners(sess=sess, coord=coord) # 每次运行读出一个Example,当全部样例读取完以后,在此样例中程序会重头读取 for i in range(10): # 在会话中取出image 和 label image, label = sess.run([images, labels]) print(label) coord.request_stop() coord.join(threads) if __name__ == '__main__': read_tfrecord()
在神经网络的结构上,深度学习一方面须要使用激活函数实现神经网络模型的去线性化,另外一方面须要一个或多个隐藏层使得神经网络的结构更深,以解决复杂问题,在训练神经网络时,咱们学习了使用带指数衰减的学习率设置,使用正则化来避免过分拟合,以及使用滑动平均模型来使得最终模型更加健壮。如下代码给出了一个在MNIST数据集上实现这些功能的完整的TensorFlow程序。
#_*_coding:utf-8_*_ import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data # MNIST 数据集相关的常数 # 输入层的节点数,对于MNIST数据集,这个就等于图片的像素 INPUT_NODE = 784 # 输出层的节点数,这个等于类别的数目,由于在MNIST数据集中须要区分的 # 是0-9这个10个数字,因此这里输出层的节点数为10 OUTPUT_NODE = 10 # 配置神经网络的参数 # 隐藏层节点数,这里使用只有一个隐藏层的网络结构做为样例 # 这个隐藏层有500个节点 LAYER1_NODE = 500 # 一个训练batch中的训练数据个数,数字越小时,训练过程越接近随机梯度降低 # 数字越大时,训练越接近梯度降低 BATCH_SIZE = 100 # 基础的学习率 LEARNING_RATE_BASE = 0.8 # 学习率的衰减率 LEARNING_RATE_DECAT = 0.99 # 描述模型复杂度的正则化项在损失函数中的系数 REGULARIZATION_RATE = 0.0001 # 训练轮数 TRAINING_STEPS = 30000 # 滑动平均衰减率 MOVING_AVERAGE_DECAY = 0.99 # 一个辅助函数,给定神经网络的输入和全部参数,计算神经网络的前向传播结果 # 在这里定义了一个使用ReLU激活函数的三层全链接神经网络 # 经过加入隐藏层实现了多层网络结构,经过ReLU激活函数实现了去线性化 # 在这个函数中也支持传入用于计算参数平均值的类,这样方便在测试时使用滑动平均模型 def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2): # 当没有提供滑动平均类时,直接使用参数当前的取值 if avg_class == None: # 计算隐藏层的前向传播结果,这里使用了ReLU激活函数 layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1) # 计算输出层的前向传播结果,由于在计算损失函数时会一并计算softmax函数 # 因此这里不须要加入激活函数,并且不加入softmax不会影响预测结果 # 由于预测时使用的是不一样类型对应节点输出值的相对大小, # 有没有softmax层对最后分类的结果没有影响 # 因而在计算整个神经网络的前向传播时能够不加入最后的softmax层 return tf.matmul(layer1, weights2) + biases2 else: # 首先使用avg_class.average 函数来计算得出变量的滑动平均值 # 而后再计算相应的神经网络前向传播结果 layer1 = tf.nn.relu( tf.matmul(input_tensor, avg_class.average(weights1)) + avg_class.average(biases1) ) return tf.matmul(layer1, avg_class.average(weights2)) + avg_class.average(biases2) # 训练模型的过程 def train(mnist): x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input') y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y-input') # 生成隐藏层的参数 weights1 = tf.Variable( tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1)) biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE])) # 生成输出层的参数 weights2 = tf.Variable( tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1)) biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE])) # 计算在当前参数下神经网络前向传播的结果。这里给出的用于计算滑动平均的类为None # 因此函数不会使用参数的滑动平均值 y = inference(x, None, weights1, biases1, weights2, biases2) # 定义存储训练轮数的变量,这个变量不须要计算滑动平均值 # 这里指定这个变量为不可训练的变量(trainable=False) # 在使用TensorFlow训练神经网络时,通常会将表明训练轮数的变量指定为不可训练的参数 global_step = tf.Variable(0, trainable=False) # 给定滑动平均衰减率和训练轮数的变量,初始化滑动平均类。 # 当给定训练轮数的变量能够加快训练早期变量的更新速度 varibale_averages = tf.train.ExponentialMovingAverage( MOVING_AVERAGE_DECAY, global_step ) # 在全部表明神经网络参数的变量上使用滑动平均。 # 其余辅助变量(好比global_step)就不须要了 # tf.trainable_variables 返回的就是图上集合GraphKeys.TRAINABLE_VARIABLES中的元素 # 这个集合的元素就是全部没有指定trainable=False 的参数 varibale_averages_op = varibale_averages.apply(tf.trainable_variables()) # 计算使用了滑动平均以后的前向传播结果。由于滑动平均不会改变变量自己的额取值 # 而是会委会一个影子变量来记录其滑动平均值,因此当须要使用这个滑动平均值时,须要明确调用average函数 average_y = inference(x, varibale_averages, weights1, biases1, weights2, biases2) # 计算交叉熵做为刻画预测值和真实值之间差距的损失函数,这里使用了TensorFlow中提供的 # sparse_softmax_cross_entropy_with_logits函数来计算交叉熵 # 当分类问题中只有一个正确答案时,可使用这个函数来加速交叉熵的计算 # MNIST问题的图片中只包含了一个0-9中的一个数字,因此可使用这个函数来计算交叉熵损失 # 这个函数的第一个参数是神经网络不包含softmax层的前向传播结果,第二个是训练数据的正确答案 #由于标准答案是一个长度为10的一位数组,而该函数须要提供了一个正确的答案数字 #因此须要使用 tf.argmax函数来获得正确答案对应的类别编号 cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1)) # 计算在当前batch中全部样例的交叉熵平均值 cross_entropy_mean = tf.reduce_mean(cross_entropy) # 计算L2正则化损失函数 regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE) # 计算模型的正则化损失,通常只计算神经网络边上权重的正则化损失,而不使用偏置项 regularization = regularizer(weights1) + regularizer(weights2) # 总损失等于交叉熵损失和正则化损失的和 loss = cross_entropy_mean + regularization # 设置指数衰减的学习率 learning_rate = tf.train.exponential_decay( LEARNING_RATE_BASE, #基础的学习率,随着迭代的进行,更新变量时使用的 # 学习率在这个基础上递减 global_step, # 学习率在这个基础上递减 mnist.train.num_examples / BATCH_SIZE, # 过完全部的训练数据须要的迭代次数 LEARNING_RATE_DECAT, # 学习率衰减速度 staircase=True ) # 使用tf.train.GradientDescentOptimizer优化算法来优化损失函数 # 注意这里损失函数包含了交叉熵损失和L2正则化损失 train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step) # 在训练神经网络模型时,没过一遍数据即须要经过反向传播来更新神经网络中的参数 # 又要更新每个参数的滑动平均值,为了一次完成多个操做,西面两行程序和下面代码是等价的 # train_op = tf.group(train_step, varibale_averages_op) with tf.control_dependencies([train_step, varibale_averages_op]): train_op = tf.no_op(name='train') # 检验使用了滑动平均模型的神经网络前向传播结果是否正确。tf.argmax(average_y, 1) # 计算每个样例的预测答案,其中average_y 是一个 batch_size*10的二维数组, # 每一行表示一个样例的前向传播结果。tf.argmax的第二个参数“1”表示选取最大值的的操做 # 仅在第一个维度中进行,也便是说,只有每一行选取最大值对应的下标 # 因而获得的结果是一个长度为batch的一维数组,这个一维数组中的值就表示了 # 每个样例对应的数字识别结果 # tf.rqual 判断两个张量的每一维是否相等,若是相等返回True,不然返回False。 correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1)) # 这个运算首选将一个布尔型的数值转换为实数型,而后计算平均值,这个平均值 # 就是模型在这一组数据上的正确率 accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) # 初始化会话并开始训练过程 with tf.Session() as sess: tf.global_variables_initializer().run() # 准备验证数据,通常在神经网络的训练过程当中会经过验证数据来判断 # 大体判断中止的条件和评判训练的效果 validate_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): # 每1000轮输出一次在验证数据集上的测试结果 if i % 1000 == 0: # 计算滑动平均模型在验证数据上的结果,由于MNIST数据集比较小, # 因此一次能够处理全部的验证数据 # 当神经网络模型比较复杂或者验证数据比较大,太大的batch会致使计算时间过长 # 甚至发生内存溢出的错误 validate_acc = sess.run(accuracy, feed_dict=validate_feed) print('After %d training step(s), validation accuracy using average model is %g'%(i, validate_acc)) # 产生这一轮使用的一个batch的训练数据,并运行训练过程 xs, ys = mnist.train.next_batch(BATCH_SIZE) sess.run(train_op, feed_dict={x:xs, y_:ys}) # 在训练结束后,在测试数据集上检测神经网络模型的最终正确率 test_acc = sess.run(accuracy, feed_dict=test_feed) print('After %d training step(s), test accuracy using average model is %g'%(TRAINING_STEPS, test_acc)) # 主程序入口 def main(argv=None): # 声明处理MNIST数据集的类,这个类在初始化的时候会自动下载数据 mnist = input_data.read_data_sets('data', one_hot=True) train(mnist) # Tensorflow 提供的一个主程序入口,tf.app.run会调用上面定义的main函数 if __name__ == '__main__': # tf.app.run(main=None) main()
运行上面的程序,获得的结果以下:
Extracting data\train-images-idx3-ubyte.gz Extracting data\train-labels-idx1-ubyte.gz Extracting data\t10k-images-idx3-ubyte.gz Extracting data\t10k-labels-idx1-ubyte.gz After 0 training step(s), validation accuracy using average model is 0.143 After 1000 training step(s), validation accuracy using average model is 0.9776 After 2000 training step(s), validation accuracy using average model is 0.9816 After 3000 training step(s), validation accuracy using average model is 0.9832 After 4000 training step(s), validation accuracy using average model is 0.9838 After 5000 training step(s), validation accuracy using average model is 0.9836 ... ... After 27000 training step(s), validation accuracy using average model is 0.9846 After 28000 training step(s), validation accuracy using average model is 0.9844 After 29000 training step(s), validation accuracy using average model is 0.9844 After 30000 training step(s), test accuracy using average model is 0.9842
从上面的结果能够看出,在训练初期,随着训练的进行,模型在验证数据集上的表现愈来愈好。从第4000轮开始,模型在验证数据集上的表现就开始波动,这说明模型已经接近极小值了。因此迭代也就结束了。
这些代码均来自与《TensorFlow实战:Google深度学习框架》这本书,缘由是TensorFlow的版本相对书上的版本比较高,因此一些API接口都变了。因此有些函数在书中的程序是错误的,致使程序在运行的时候就会报错。
错误1以下:
ValueError: Only call `sparse_softmax_cross_entropy_with_logits` with named arguments (labels=..., logits=..., ...)
解决:这个缘由是函数的API发生了变化,咱们须要添加labels 和 logits。
修改代码以下:
原代码: # 计算交叉熵及其平均值 cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(y,tf.argmax(y_, 1)) 修改后的代码: cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y,logits=tf.argmax(y_, 1)) ***********出现新的错误************* ValueError: Rank mismatch: Rank of labels (received 2) should equal rank of logits minus 1 (received 1). 咱们将代码修改以下: labels=tf.argmax(y_, 1),logits=y
后面的缘由是由于计算交叉熵的时候,比较的两个几率分布放反了。由于交叉熵是衡量一个几率分布区表达另一个几率分布的难度,值越低越好,因此是用预测的结果来表达正确的标签。
当运行完例子,会报以下错误:
Traceback (most recent call last): File "E:\PyCharm 2019.1.3\helpers\pydev\pydevconsole.py", line 221, in do_exit import java.lang.System File "E:\PyCharm 2019.1.3\helpers\pydev\_pydev_bundle\pydev_import_hook.py", line 21, in do_import module = self._system_import(name, *args, **kwargs) ModuleNotFoundError: No module named 'java' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<input>", line 1, in <module> File "E:\PyCharm 2019.1.3\helpers\pydev\_pydev_bundle\pydev_umd.py", line 197, in runfile pydev_imports.execfile(filename, global_vars, local_vars) # execute the script File "E:\PyCharm 2019.1.3\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile exec(compile(contents+"\n", file, 'exec'), glob, loc) File "E:/backup/pycode.py", line 180, in <module> tf.app.run(main=None) File "E:\Anaconda3\envs\python36\lib\site-packages\tensorflow\python\platform\app.py", line 48, in run _sys.exit(main(_sys.argv[:1] + flags_passthrough)) File "E:\PyCharm 2019.1.3\helpers\pydev\pydevconsole.py", line 226, in do_exit os._exit(args[0]) TypeError: an integer is required (got type NoneType)
小小分析一波,咱们发现已经执行完代码了。执行完后会报错误提示:
TypeError: an integer is required (got type NoneType)
经查看,是TensorFlow版本问题,在r0.11以前的版本里,tf.app.run的代码以下:
def run(main=None): f = flags.FLAGS flags_passthrough = f._parse_flags() main = main or sys.modules['__main__'].main sys.exit(main(sys.argv[:1] + flags_passthrough))
没有argv 参数,argv参数是在 r0.12后加入的。因此须要升级版本。其实不升级版本也能够,咱们这样执行便可。
if __name__ == '__main__': # 咱们这里不使用TensorFlow提供的主程序入口 tf.app.run函数 # tf.app.run(main=None) main()
在上面程序中咱们使用了神经网络解决MNIST问题,在程序的开始设置了初始学习率,学习率衰减率,隐藏层节点数量,迭代轮数等七种不一样的参数。那么如何设置这些参数的取值呢?在大部分状况下,配置神经网络的这些参数都是须要经过实验来调整的。虽然一个神经网络模型的效果最终是经过测试数据来判断的,可是咱们不能直接经过模型在测试数据上的效果来选择参数,使用测试数据来选取参数可能会致使神经网络模型过分拟合测试数据,从而失去对未知数据的预判能力。由于一个神经网络模型的最终目标是对未知数据提供判断,因此为了评估模型在未知数据上的效果,须要保证测试数据在训练过程当中是不可见的。只有这样才能保证经过测试数据评估出来的效果和真实应用场景下模型对未知数据预判的效果是接近的。因而,为了评测神经网络模型在不一样参数取值下的效果,通常会从训练数据中抽取一部分做为验证数据。使用验证数据就能够评判不一样参数取值下模型的表现。除了使用验证数据,还能够采用交叉验证(cross validation)的方式来验证模型效果。但由于神经网络训练时间自己就比较长,采用 cross validation 会花费大量的时间,因此在海量数据的状况下,通常会更多的采用验证数据集的形式来评测模型的效果。
为了说明验证数据在必定程度上能够做为模型效果的评判标准,咱们将对比在不一样迭代轮数的状况下,模型在验证数据和测试数据上的正确率。为了同时获得同一个模型在验证数据和测试数据上的正确率,能够在每1000轮的输出中加入在测试数据集上的正确率。当咱们再上门代码中加入下面代码,就能够获得每1000轮迭代后,使用了滑动平均的模型在验证数据和测试数据上的正确率。
if i % 1000 == 0: validate_acc = sess.run(accuracy, feed_dict=validate_feed) # 计算滑动平均模型在测试数据和验证数据上的正确率 test_acc = sess.run(accuracy, feed_dict=test_feed) print('After %d training step(s), validation accuracy using average model is %g,' 'test accuracy using average model is %g'%(i, validate_acc, test_acc))
下图给出了上面代码获得的每1000轮滑动平均模型在不一样数据集上的正确率曲线,其中灰色曲线表示随着迭代轮数的增长,模型在验证数据上的正确率,黑色曲线表示在测试数据上的正确率。从图中能够看出虽然两条曲线不会彻底重合,可是这两条曲线的趋势基本同样,并且他们的相关关系(correlation coeddicient)大于0.9999。这意味着在MNIST问题上,彻底能够经过模型在验证数据上的表现来判断一个模型的优劣。
固然,以上结论是针对MNIST这个数据集的额,对其它问题,还须要具体问题具体分析。不一样问题的数据分布式不同的。若是验证数据分布不能很好地表明测试数据分布。那么模型在这两个数据集上的表现就有可能会不同。因此,验证数据的选取方法是很是重要的,通常来讲选取的验证数据分布越接近测试数据分布,模型在验证数据上的表现越能够体现模型在测试数据上的表现。经过上面的实验,至少能够说明经过神经网络在验证数据上的效果来选取模型的参数是一个可行的方案。
下面经过MNIST数据集来比较值钱提到的不一样优化方法对神经网络模型正确率的影响。下面将使用神经网络模型在MNIST测试数据集上的正确率做为评价不一样优化方法的标准。而且一个模型在MNIST测试数据集上行的正确率简称为“正确率”。以前提到了设计神经网络时的五种优化方法。在神经网络结构的设计上,须要使用激活函数和多层隐藏层。在神经网络优化时,可使用指数衰减的学习率,加入正则化的损失函数以及滑动平均模型,在下图中给出了在相同神经网络参数下,使用不一样优化方法啊,通过3000轮训练迭代后,获得的最终模型的正确率。下图的结果包含了使用全部优化方法训练获得的模型和不用其中某一项优化方法训练获得的模型。经过这种方式,能够有效验证每一项优化方法的效果。
从图中能够很明显的看出,调整神经网络的结构对最终的正确率有很是大的影响。没有隐藏层或者激活函数时,模型的正确率只有大约92.6%,这个数字要远远小于使用了隐藏层和激活函数时能够达到的大约98.4%的正确率。这说明神经网络的结构对最终模型的效果有本质性的影响。
从上图的结果能够发现使用滑动平均模型,指数衰减的学习率和使用正则化带来的正确率的提高并非特别明显。其中使用了全部优化算法的模型和不使用滑动平均的模型以及不适用指数衰减的学习率的模型均可以达到大约98.4%的正确率。这是由于滑动平均模型和指数衰减的学习率在必定程度上都是限制神经网络中参数更新的速度,然而在MNIST数据上,由于模型收敛的速度很快,因此这两种优化对最终模型的影响不大。从以前的结果能够看出,当模型迭代达到4000轮的时候正确率就已经接近最终的正确率了。而在迭代的早期,是否使用滑动平均模型或者指数衰减的学习率对训练的结果影响相对较小,下图显示了不一样迭代轮数时,使用了全部优化方法的模型的正确率与平均绝对梯度的变化趋势。
下图显示了不一样迭代轮数时,正确率与衰减以后的学习率的变化趋势:
从图中能够看出前4000轮迭代对模型的改变是最大的。在4000轮以后,由于梯度自己比较小,因此参数的改变也就是比较缓慢了。因而滑动平均模型或者指数衰减的学习率的做用也就没有那么突出了。从上图能够看到,学习率曲线呈现出阶梯状衰减,在前4000轮时,衰减以后的学习率和最初的学习率差距并不大。那么,这是否能说明这些优化方法做用不大呢?答案是否认的,当问题更加复杂时,迭代不会这么快接近收敛,这时滑动平均模型和指数衰减的学习率能够发挥更大的做用。好比在Cifar-10 图像分类数据集上,使用滑动平均模型能够将错误率下降11%,而使用指数衰减的学习率能够将错误率下降7%。
相对滑动平均模型和直属衰减学习率,使用加入正则化的损失函数给模型效果带来的提高要相对显著。使用了正则化损失函数的神经网络模型能够下降大约6%的错误率(从1.69%下降到1.59%)。下图给出了正则化给模型优化过程带来的影响。而且下面两个图给出了不一样损失函数的神经网络模型。一个模型只最小化交叉熵损失,另外一个模型优化的是交叉熵和L2正则化的损失的和。下面先给出这个模型优化函数的声明语句:
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1)) # 计算在当前batch中全部样例的交叉熵平均值 cross_entropy_mean = tf.reduce_mean(cross_entropy) #********************************************************* # 只最小化交叉熵损失 train_step = tf.train.GradientDescentOptimizer(learning_rate ).minimize(cross_entropy_mean,global_step=global_step) #********************************************************* # 优化交叉熵和L2正则化损失的和 loss = cross_entropy_mean + regluaraztion train_step = tf.train.GradientDescentOptimizer(learning_rate ).minimize(loss, global_step=global_step)
下图灰色和黑色的实线给出了两个模型正确率的变化趋势,虚线给出了在当前训练batch上的交叉熵损失:
从图中能够看出,只优化交叉熵的模型在训练数据上的交叉熵损失(灰色虚线)要比优化总损失的模型更小(黑色虚线)。然而在测试数据上,优化总损失的模型(褐色实线)却要比只优化交叉熵的模型(灰色实线)。这其中缘故,就是过拟合问题,只优化交叉熵的模型能够更好地拟合训练数据(交叉熵损失更小),可是却不能很好的挖掘数据中潜在的规律来判断未知的测试数据,因此在测试数据上的正确率低。
下图显示了不一样模型的损失函数的变化趋势,左侧显示了只优化交叉熵的模型损失函数的变化规律,能够看到随着迭代的进行,正则化损失是在不断加大的。由于MNIST问题相对比较简单,迭代后期的梯度很小,因此正则化损失的增加也不快。若是问题更加复杂,迭代后期的梯度更大,就会发现总损失(交叉熵损失加上正则化损失)会呈现出一个U字形,在图的右侧,显示了优化总损失的模型损失函数的变化规律,从图中能够看出,这个模型的正则化损失部分也能够随着迭代的进行愈来愈小,从而使得总体的损失呈现一个逐步递减的趋势。
总的来讲,经过MNIST数据集有效地验证了激活函数,隐藏层能够给模型的效果带来质的飞跃。因为MNIST问题自己相对简单,滑动平均模型,指数衰减的学习率和正则化损失对最终正确率的提高效果不明显。可是经过进一步分析实验的结果,能够得出这些优化方法确实能够解决神经网络优化过程当中的问题。当须要解决的问题和使用到的神经网络模型更加复杂时,这些优化方法将更有可能对训练效果产生更大的影响。
上面咱们将计算神经网络前向传播结果的过程抽象成了一个函数。经过这种方式在训练和测试的过程当中能够统一调用同一个函数来得模型的前向传播结果。
在上面代码中,这个函数定义以下:
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
在定义中能够看到,这个函数的参数中包含了神经网络中的全部参数。然而,当神经网络的结构更加复杂,参数更多时,就须要一个更好的方式来传递和管理神经网络中的参数了。TensorFlow提供了经过变量名称来建立或者获取一个变量的机制。经过这个机制,在不一样的函数中能够直接经过变量的名字来使用变量,而不须要将变量经过 tf.get_variable 和 tf.get.variable_scope 函数实现的。下面将分别介绍如何使用这两个函数。
前面学习了经过tf.Variable 函数来建立一个变量。除了tf.Variable函数,TensorFlow还提供了tf.get_variable函数来建立或者获取变量。当 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.get.variable_scope 函数建立变量的过程基本是一致的。tf.get_variable 函数调用时提供的维度(shape)信息以及初始化方法(initializer)的参数和tf.Variable 函数调用时提供的初始化过程当中的参数也相似。TensorFlow中提供的initializer 函数和以前提到的随机数以及常量生成函数大部分是一一对应的。好比,在上面的样例程序中使用到的常数初始化函数 tf.constant_initializer 和常数生成函数 tf.constant 功能上就是一直的。TensorFlow 提供了七种不一样的初始化函数,下表总结了他们的功能和主要参数。
tf.get_varibale 函数与 tf.Variable 函数最大的区别在于指定变量名称的参数。对于 tf.Variable 函数,变量名称是一个可选的参数,经过 name='v' 的形式给出。可是对于 tf.get_variable 函数,变量名称是一个必填的参数。tf.get_variable会根据这个名字去建立或者获取变量。在上面的样例程序中,tf.get_variable首先会视图去建立一个名字为 v 的参数,若是建立失败(好比已经有同名的参数),那么这个程序就会报错。这是为了不无心识的变量复用形成的错误。好比在定义神经网络参数时,第一层网络的权重已经叫 weights 了,那么在建立第二层神经网络时,若是参数名仍然叫 weights,就会触发变量重用的错误。不然两层神经网络共用一个权重会出现一些比较难以发现的错误。若是须要经过 tf.get_variable 获取一个已经建立的变量,须要经过 tf.variable_scope 函数来生成一个上下文管理器,并明确指定在这个上下文管理器中, tf.get_variable将直接获取已经生成的变量。下面给出一段代码来讲明如何经过tf.variable_scope 函数来控制 tf.get_variable 函数获取已经建立过的变量。
# 在名字为foo的命名空间内建立名字为 v 的变量 with tf.variable_scope('foo'): v = tf.get_variable( 'v', [1], initializer=tf.constant_initializer(1.0) ) # 由于在命名空间foo中已经存在名字为 v 的变量,因此下面的代码将会报错 with tf.variable_scope('foo'): v = tf.get_variable('v', [1]) ''' 报错以下: ValueError: Variable foo/v already exists, disallowed. Did you mean to set reuse=True in VarScope? ''' # 在生成上下文管理器时,将参数reuse设置为True,这样tf.get_variable函数将直接获取已经声明的函数 with tf.variable_scope('foo', reuse=True): v1 = tf.get_variable('v', [1]) print(v == v1) # 输出为True,表明v,v1表明的是相同的TensorFlow中变量 # 将参数reuse设置为True时,tf.variable_scope将只能获取已经建立过的变量 # 由于在命名空间bar中尚未建立变量 v 因此下面的代码将会报错 with tf.variable_scope('bar', reuse=True): v = tf.get_variable('v', [1]) ''' 报错以下: ValueError: Variable bar/v does not exist, or was not created with tf.get_variable(). Did you mean to set reuse=None in VarScope?'''
上面的样例简单地说明了经过 tf.variable_scope函数能够控制 tf.get_variable函数的语义。当 tf.variable_scope函数使用参数 reuse=True生成上下文管理器时,这个上下文管理器内全部的tf.get_variable函数会直接获取已经建立的变量。若是变量不存在,则 tf.get_variable函数将会报错;相反,若是 tf.variable_scope函数使用参数 reuse=None或者 reuse=False建立上下文管理器,tf.get_variable操做将建立新的变量。若是同名的变量已经存在,则 tf.get_variable函数将报错,TensorFlow中 tf.variable_scope 函数是能够嵌套的,下面程序说明了当 tf.varibale_scope 函数嵌套时,reuse参数的取值是如何肯定的:
with tf.variable_scope('root'): # 能够经过td.get_varable_scope().reuse函数来获取当前上下文管理器中reuse参数的取值 print(tf.get_variable_scope().reuse) # 输出为False 即最外层reuse是False # 新建一个嵌套的上下文管理器,并制定reuse为True with tf.variable_scope('foo', reuse=True): print(tf.get_variable_scope().reuse) # 输出为True,由于咱们设置了其reuse为True # 新建一个嵌套的上下文管理器但不指定reuse,这时候reuse的取值会和上一层层保持一致 with tf.variable_scope('bar'): print(tf.get_variable_scope().reuse) # 输出为True,没有设置的话,将和上一层保持一致 print(tf.get_variable_scope().reuse) # 这里输出为False。退出reuse设置为True的上下文以后,reuse的值又回到了False
tf.variable_scope函数生成的上下文管理器也会建立一个TensorFlow中的命名空间,在命名空间内建立的变量名称都会带上这个命名空间名做为前缀。因此 tf.variable_scope函数除了能够控制tf.get_variable 执行的功能以外,这个函数也提供了一个管理变量命名空间的方式,如下代码显示了如何经过tf.variable_scope来管理变量的名称:
v1 = tf.get_variable('v', [1]) print(v1.name) # 输出 v:0 # 其中 v 为变量的名称 0表示这个变量是生成变量这个运算的第一个结果 with tf.variable_scope('foo'): v2 = tf.get_variable('v', [1]) print(v2.name) # 输出 foo/v:0 # 在tf.variable_scope中建立的变量,名称前面会加入命名空间的名称 # 并经过/来分隔名称空间的名称和变量的名称 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) # 输出 foo/v1:0 # 当命名空间退出以后,变量名称也就不会再被加入其前缀了 # 建立一个名称为空的命名空间,并设置reuse=True with tf.variable_scope("", reuse=True): v5 = tf.get_variable('foo/bar/v', [1]) # 能够直接经过带命名空间名称的变量名来获取其余命名空间下的变量 # 好比这里指定名称 foo/bar/v 来获取在命名空间 foo/bar/中建立的变量 print(v5==v3) # 输出结果为 : True v6 = tf.get_variable('foo/v1', [1]) print(v6==v4) # 输出结果为 : True
经过tf.variable_scope 和 tf.get_variable 函数,下面代码对计算前向传播结果的函数 作了一些改进:
def inference(input_tensor, reuse=False): # 定义第一层神经网络的变量和前向传播过程 with tf.variable_scope('layer1', reuse=reuse): # 根据传进来的reuse来判断是建立新变量仍是使用已经建立好的 # 在第一次构造网络时须要建立新的变量,之后每次调用这个函数直接使用reuse=True # 在以后,就不须要每次将变量传进来了。 weights = tf.get_variable('weights', [INPUT_NODE, LAYER1_NODE], initializer=tf.truncated_normal_initializer(stddev=0.1)) biases = tf.get_variable('biases', [OUTPUT_NODE], initializer=tf.constant_initializer(0.0)) layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases) # 相似的定义第二层神经网络的变量和前向传播过程 with tf.variable_scope('layer2', reuse=reuse): weights = tf.get_variable('weights', [LAYER1_NODE, OUTPUT_NODE], initializer=tf.truncated_normal_initializer(stddev=0.1)) biases = tf.get_variable("biases", [OUTPUT_NODE], initializer=tf.constant_initializer(0.0)) layer2 = tf.matmul(layer1, weights) + biases # 返回最后的前向传播结果 return layer2 x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input') y = inference(x) # 在程序中须要使用训练好的神经网络进行推导时,能够直接调用 inference(new_x, True) # 若是须要使用滑动平均模型能够参考以前的代码,把计算滑动平均的类传到inference函数中 # 获取或者建立变量的部分不须要改变 new_x = ... new_y = inference(new_x, True)
使用上面的代码,就不须要再将全部的变量都做为参数传递到不一样的函数中了。当神经网络结构更加复杂,参数更多时,使用这种变量管理的方式将大大提升程序的可读性。
在上面已经给出了一个完整的TensorFlow程序来解决MNIST问题,然而这个程序的可扩展性并很差,由于计算前向传播的函数须要将全部变量都传入,当神经网络结构变得更加复杂,参数更多时,程序可读性会变得很是差。并且这种方式会致使程序中有大量的冗余代码,下降编程的效率。而且上面程序并无持久化训练好的模型。当程序退出时,训练好的模型也就没法被使用了,这致使获得的模型没法被重用。更严重的是,通常神经网络模型训练的时候都比较长,少则几个小时,多则几天甚至几周。若是在训练过程当中程序死机了,那么没有保存训练的中间结果会浪费大量的时间和资源。因此,在训练的过程当中须要每隔一段时间保存一次模型训练的中间结果。
结合变量管理机制和TensorFlow模型持久化机制,咱们将学习一个TensorFlow训练设计网络的最佳实践。将训练和测试分红两个独立的程序,这可使得每个组件更加灵活。好比训练神经网络的程序能够持续输出训练好的模型,而测试程序能够每隔一段时间检测最新模型的正确率,若是模型效果更好,则将这个模型提供给产品使用。除了将不一样功能模型分开,咱们还将前向传播的过程抽象成一个单独的库函数,由于神经网络的前向传播过程在训练和测试的过程当中都会用到,因此经过库函数的方式使用起来便可更加方便,又能够保证训练和测试过程当中使用的前向传播方法必定是一致的。
下面将重构以前的程序来解决MNIST问题,重构以后的代码将会被拆成3个程序,第一个是 mnist_inference.py,它定义了前向传播的过程以及神经网络中的参数,第二个是 mnist_train.py 它定义了神经网络的训练过程,第三个是 mnist_eval.py ,它定义了测试过程。
下面给出 mnist_inference.py中的的内容:
#_*_coding:utf-8_*_ import tensorflow as tf # 定义神经网络结构相关参数 INPUT_NODE = 784 OUTPUT_NODE = 10 LAYER1_NODE = 500 # 经过get_variable函数来获取变量。在训练神经网络时会建立这些变量 # 在测试时会经过保存的模型加载这些变量的取值 # 由于能够在变量加载时将滑动平均变量重命名,因此能够经过一样的名字在训练时使用变量自己 # 并且在测试时使用变量的滑动平均值,在这个函数中也会将变量的正则化损失加入损失集合 def get_weight_variable(shape, regularizer): weights = tf.get_variable( 'weights', shape, initializer=tf.truncated_normal_initializer(stddev=0.1) ) # 当给出正则化生成函数时,将当前变量的正则化损失计入名字为losses的集合 #这里使用add_to_collection函数将一个张量加入一个集合,而这个集合的的名称为losses # 这是自定义的集合,再也不TensorFlow自主管理的集合列表中 if regularizer != None: tf.add_to_collection('losses', regularizer(weights)) return weights # 定义神经网络的前向传播过程 def inference(input_tensor, regularizer): # 声明第一层神经网络的变量并完成前向传播过程 with tf.variable_scope('layer1'): # 这里经过 tf.get_variable或tf.Variable没有本质区别 # 由于在训练或者是测试中没有在同一个程序中屡次调用这个函数 # 若是在同一个程序中屡次调用,在第一次调用以后就须要将reuse参数设置为True weights = get_weight_variable( [INPUT_NODE, LAYER1_NODE], regularizer ) biases = tf.get_variable( 'biases', [LAYER1_NODE], initializer=tf.constant_initializer(0.0) ) layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases) # 相似的声明第二层神经网络的变量并完成前向传播过程 with tf.variable_scope('layer2'): weights = get_weight_variable( [LAYER1_NODE, OUTPUT_NODE], regularizer ) biases = tf.get_variable( 'biases', [OUTPUT_NODE], initializer=tf.constant_initializer(0.0) ) layer2 = tf.matmul(layer1, weights) + biases # 返回最后前向传播的结果 return layer2
在这段代码中定义了神经网络的前向传播算法,不管是训练时仍是测试时,均可以直接调用 Inference 这个函数,而不用关心具体的神经网络结构,使用定义好的前向传播过程,下面代码给出了神经网络的训练程式 mnist_train.py的代码:
#_*_coding:utf-8_*_ import os import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data # 加载 mnist_inference.py 中定义的常量和前向传播的函数 import mnist_inference # 配置神经网络的参数 BATCH_SIZE = 100 LEARNING_RATE_BASE = 0.8 LEARNING_RATE_DECAY = 0.99 REGULARAZTION_RATE = 0.0001 TRAINING_STEPS = 30000 MOVING_AVERAGE_DECAY = 0.99 # 模型保存的路径和文件名 MODEL_SAVE_PATH = 'model1/' MODEL_NAME = 'model.ckpt' def train(mnist): # 定义输入输出 placeholder x = tf.placeholder( tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input' ) y_ = tf.placeholder( tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input' ) regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE) # 直接使用bookmnost_inference.py中定义的前向传播过程 y = bookmnist_inference.inference(x, regularizer) global_step = tf.Variable(0, trainable=False) # 定义损失函数,学习率,滑动平均操做以及训练过程 variable_averages = tf.train.ExponentialMovingAverage( MOVING_AVERAGE_DECAY, global_step ) variable_averages_op = variable_averages.apply( tf.trainable_variables() ) 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) loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses')) learning_rate = tf.train.exponential_decay( LEARNING_RATE_BASE, global_step, mnist.train.num_examples / BATCH_SIZE, LEARNING_RATE_DECAY ) train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize( loss, global_step=global_step ) with tf.control_dependencies([train_step, variable_averages_op]): train_op = tf.no_op(name='train') # 初始化TensorFlow持久化类 saver = tf.train.Saver() with tf.Session() as sess: tf.global_variables_initializer().run() # 在训练过程当中再也不测试模型在验证数据上的表现 # 验证和测试的过程都将会有一个独立的程序完成 for i in range(TRAINING_STEPS): xs, ys = mnist.train.next_batch(BATCH_SIZE) _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys}) # 每1000轮保存一次模型 if i % 1000 == 0: # 输出当前的训练状况,这里只输出了模型在当前训练batch上的损失函数大小 # 经过损失函数的大小能够大概了解训练的状况,在验证集上正确率信息会有一个单独的程序来生成 print("Afer %d training step(s), loss on training batch is %g"%(step, loss_value)) # 保存当前模型,注意这里给出的global_step参数,这样可让每一个被保存模型的文件名末尾加上训练点额轮数 saver.save( sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step ) def main(argv=None): mnist = input_data.read_data_sets('data', one_hot=True) train(mnist) if __name__ == '__main__': main()
运行上面的程式,能够获得相似下面的代码:
Extracting data\train-images-idx3-ubyte.gz Extracting data\train-labels-idx1-ubyte.gz Extracting data\t10k-images-idx3-ubyte.gz Extracting data\t10k-labels-idx1-ubyte.gz Afer 1 training step(s), loss on training batch is 2.97567 Afer 1001 training step(s), loss on training batch is 0.228694 Afer 2001 training step(s), loss on training batch is 0.161067 Afer 3001 training step(s), loss on training batch is 0.148354 Afer 4001 training step(s), loss on training batch is 0.121623 Afer 5001 training step(s), loss on training batch is 0.106996 Afer 6001 training step(s), loss on training batch is 0.104219 Afer 7001 training step(s), loss on training batch is 0.112429 Afer 8001 training step(s), loss on training batch is 0.077253 ... ... Afer 29001 training step(s), loss on training batch is 0.03582
在新的训练代码中,再也不将训练和测试跑一块儿,训练过程当中,每1000轮输出一次在当前训练batch上损失函数的大小来大体估计训练的效果。在上面的程式中,每1000轮保存一次训练好的模型,这样能够经过一个单独的测试程序,更加方便的在滑动平均模型上作测试,下面给出测试程序 mnist_eval.py
#_*_coding:utf-8_*_ import time import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data # 加载 mnist_inference.py 和 mnist_train.py中定义的常量和函数 import mnist_inference import mnist_train # 每10秒加载一次最新的模型,并在测试数据上测试最新模型的正确率 EVAL_INTERVAL_SECS = 10 def evalute(mnist): with tf.Graph().as_default() as g: # 定义输入输出格式 x = tf.placeholder( tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input' ) y_ = tf.placeholder( tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input' ) validate_feed = {x: mnist.validation.images, y_: mnist.validation.labels} # 直接经过调用封装好的函数来计算前向传播的额结果 #由于测试时不关注正则化损失的值,因此这里用于计算正则化损失函数被设置为None y = mnist_inference.inference(x, None) # 使用前向传播的结果计算正确率,若是须要对未知的样本进行分类, # 那么使用 tf.argmax(y, 1)就能够获得输入样例的预测类别了 correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) # 经过变量重命名的方式来加载模型,这样在前向传播的过程当中就不须要调用求滑动平均的函数获取平均值 # 这样就能够彻底共享以前mnist_inference.py中定义的前向传播过程 variable_averages = tf.train.ExponentialMovingAverage( mnist_train.MOVING_AVERAGE_DECAY ) variables_to_restore = variable_averages.variables_to_restore() saver = tf.train.Saver(variables_to_restore) # 每隔EVAL_INTERVAL_SECS 秒调用一次计算正确率的过程以检测训练过程当中正确率的变化 while True: with tf.Session() as sess: # tf.train.get_checkpoint_state函数会经过checkpoint文件 # 自动找到目录中最新模型的文件名 ckpt = tf.train.get_checkpoint_state( mnist_train.MODEL_SAVE_PATH ) if ckpt and ckpt.model_checkpoint_path: # 加载模型 saver.restore(sess, ckpt.model_checkpoint_path) # 经过文件名获得模型保存时迭代的轮数 global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1] accuracy_score = sess.run(accuracy, feed_dict=validate_feed) print("After %s training step(s) , validation accuracy ='%g"%(global_step, accuracy_score)) else: print("No checkpoint file found") return time.sleep(EVAL_INTERVAL_SECS) def main(argv=None): mnist = input_data.read_data_sets('data', one_hot=True) evalute(mnist) if __name__ == '__main__': main()
上面的代码会每隔10秒运行一次,每次运行都是读取最新保存的模型,并在MNIST验证数据上计算模型的正确率。若是须要离线预测未知数据的类别(好比这个样例程序能够判断手写数字图片中所包含的数字),只须要将计算正确率的部分改成答案输出便可。运行上面代码能够获得相似下面的结果,注意由于这个程序每10秒会自动运行一次,而训练程序不必定每10秒输出一个新模型,因此下面的结果中会发现有些模型被测试了屡次,通常在解决真实问题时,不会这么频繁的运行评测程序。
Extracting data\train-images-idx3-ubyte.gz Extracting data\train-labels-idx1-ubyte.gz Extracting data\t10k-images-idx3-ubyte.gz Extracting data\t10k-labels-idx1-ubyte.gz After 29001 training step(s) , validation accuracy ='0.9856 After 29001 training step(s) , validation accuracy ='0.9856 After 29001 training step(s) , validation accuracy ='0.9856 After 29001 training step(s) , validation accuracy ='0.9856 After 29001 training step(s) , validation accuracy ='0.9856 After 29001 training step(s) , validation accuracy ='0.9856 After 29001 training step(s) , validation accuracy ='0.9856 ... ...