写下您的评论...
TensorFlow 代码模版-基础篇
本文的代码整理自郑泽宇等著作的《TensorFlow 实战 Google 深度学习框架(第 2 版)》这本书,个人觉得这本书还是很不错的,除了 TensorFlow,还讲到了很多原理和深度学习的知识点,只是书中的对应的 TensorFlow 版本稍微有点旧了(1.4.0,这应该归咎于 TensorFlow 版本变化太快了,API 变动频繁),我测试使用的 TensorFlow 版本是 1.12,代码运行完好。
本文在书中源代码基础上,联系上下文,将一些关键知识点也作了详细的说明补充,完全可以作为模版来学习和使用。
以下代码涉及的知识点关键词:
前向传播、变量命名空间、变量初始化、全连接神经网络、分类、激活函数、正则化、自定义集合、学习率指数衰减、参数滑动平均模型、参数以及滑动平均值的反向传播更新、交叉熵损失、模型的持久化和恢复、验证和测试。
inference
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | #! /usr/bin/env python # -*- coding: UTF-8 -*- """ @Author: lumingdong.cn @Reference: TensorFlow实战Google深度学习框架(第2版) @Project: TF @File: mnist_inference.py @TF Version: 1.12.0 @Description: Now is better than never. ——The Zen of Python """ import tensorflow as tf # 定义神经网络结构相关的参数。 INPUT_NODE = 784 # 输入层的节点数。对于MNIST数据集,这个就等于图片的像素。 OUTPUT_NODE = 10 # 输出层的节点数,这个等于类别的数目。因为在MNIST数据集中要区分的是O~9这10个数宇,所以这里输出层的节点数为10。 LAYER1_NODE = 500 # 隐藏层节点数。这里使用只有一个隐藏层的网络结构作为样例。这个隐藏层有500个节点。 """ 通过tf.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 is not None: # 计算模型的正则化项。一般只计算神经网络边上权重的正则化损失,而不使用偏置项。 tf.add_to_collection('losses', regularizer(weights)) return weights """ 一个辅助函数,给定神经网络的输入和所有参数,计算神经网络的前向传播结果。在这里定义了一个使用ReLU激活函数的三层全连接神经网络。 通过加入隐藏层实现了多层网络结构,通过ReLU激活函数实现了去线性化。 """ # 定义神经网络的前向传播过程。 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 |
train
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | #! /usr/bin/env python # -*- coding: UTF-8 -*- """ @Author: lumingdong.cn @Reference: TensorFlow实战Google深度学习框架(第2版) @Project: TF @File: mnist_train.py @TF Version: 1.12.0 @Description: Now is better than never. ——The Zen of Python """ import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data # 加载 mnist_inference.py 中定义的常量和前向传播的函数 。 import mnist_inference import os # 配置神经网络的参数。 BATCH_SIZE = 100 # 一个训练batch中训练数据的个数。数字越小时,训练过程越接近随机梯度下降(SGD);数字越大时,训练越接近批量梯度下降(BGD)。 LEARNING_RATE_BASE = 0.8 # 基础的学习率。 LEARNING_RATE_DECAY = 0.99 # 学习率的衰减率。 REGULARIZATION_RATE = 0.0001 # 描述模型复杂度的正则化项在损失函数中的系数。 TRAINING_STEPS = 30000 # 训练轮数,有时也用epochs命名变量。 MOVING_AVERAGE_DECAY = 0.99 # 滑动平均衰减率。 # 模型保存的路径和文件名。 MODEL_SAVE_PATH = "MNIST_model/" MODEL_NAME = "mnist_model" 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') # 计算L2正则化损失的函数。 regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE) # 直接使用mnist_inference.py中定义的前向传播过程。 y = mnist_inference.inference(x, regularizer) """ 定义存储当前训练轮次的变量。这个变量不需要计算滑动平均值,所以这里指定这个变量为不可训练的变量(trainable=False)。 在使用TensorFlow训练神经网络时,-般会将代表训练轮数的变量指定为不可训练的参数。 """ global_step = tf.Variable(0, trainable=False) # 定义损失函数、学习率、滑动平均操作以及训练过程。 """ 滑动平均模型:一种可以使模型在测试数据上更健壮(robust)的方法。 在采用随机梯度下降算法训练神经网络时,使用滑动平均模型在很多应用中都可以在一定程度提高最终模型在测试数据上的表现。 TensorFlow提供了滑动平均类,通常需要传入两个参数,一个滑动平均衰减率,一个是训练轮数。 滑动平均衰减率:控制模型更新的速度; 训练轮数:这里的训练轮数其实是模拟训练神经网络中的迭代轮数,可以用于动态控制衰减率, 给定训练轮数的变量可以加快训练早期变量的更新速度。 """ # 给定滑动平均哀减率和训练轮数的变量,初始化滑动平均类。 variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step) # 定义一个更新变量滑动平均的操作。这里需要给定一个列表,每次执行这个操作时这个列表中的变量都会被更新。 variables_averages_op = variable_averages.apply(tf.trainable_variables()) # 计算交叉熵损失,这里使用的sparse,label是非One-Hot形式 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) # 从自定义losses集合获取权重的正则化数值并加和,模型的总损失等于交叉熵损失和正则化损失的和。 loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses')) # 通过指数衰减方式生成学习率。 learning_rate = tf.train.exponential_decay( LEARNING_RATE_BASE, # 初始学习率 global_step, # 当前训练轮次,epoch mnist.train.num_examples / BATCH_SIZE, # 定义衰减周期,跟参数staircase配合,可以在decay_step个训练轮次内保持学习率不变。 LEARNING_RATE_DECAY, # 衰减率系数 staircase=True # 定义衰减方式为阶梯型衰减方式(默认为连续型衰减) ) """ 在minimize函数中传入global_step将自动更新global_step参数,从而使得学习率也得到相应更新。 """ train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step) """ 在训练神经网络模型时,每过一遍数据既需要通过反向传播来更新神经网络中的参数,又要更新每一个参数的滑动平均值。 为了一次完成多个操作,TensorFlow提供了tf.control_dependencies和tf.group两种机制来实现。 下面两行程序和 train_op = tf.group(train_step, variables_averages_op) 是等价的。 """ with tf.control_dependencies([train_step, variables_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("After %d training step(s), loss on training batch is %g." % (step, loss_value)) # 保存当前的模型。 """ 注意这里给出了 global_step 参数,这样可以让每个被保存模型的文件名末尾加上训练的轮数, 比如 "model.ckpt-1000" 表示训练1000轮之后得到的模型。 """ saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step) def main(argv=None): # 载入MNIST数据集,如果指定路径没有已经下载好的数据, mnist = input_data.read_data_sets("./datasets/MNIST_data/", one_hot=True) train(mnist) if __name__ == '__main__': tf.app.run() |
eval
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | #! /usr/bin/env python # -*- coding: UTF-8 -*- """ @Author: lumingdong.cn @Reference: TensorFlow实战Google深度学习框架(第2版) @Project: TF @File: mnist_eval.py @TF Version: 1.12.0 @Description: Now is better than never. ——The Zen of Python """ 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 evaluate(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)就可以得到输入样例的预测类别了。 这里需要注意,inference函数返回的y其实是logit值,也就是说,在测试的时候并没有使用softmax层, 其实softmax的作用就是将前向传播的结果转换成一个概率分布,通常来说,logit值最大的那个,转换成的概率也是最大的, 所以在测试阶段免去了softmax层,而使用tf.argmax函数来得到logit值最大索引位置,从而得到输入样例的预测类别, 也很大程度减轻了计算量。 """ correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) # 定义滑动平均操作 variable_averages = tf.train.ExponentialMovingAverage(mnist_train.MOVING_AVERAGE_DECAY) """ 在TensorFlow中,每一个变量的滑动平均值是通过影子变量维护的,在申明滑动平均模型之后,TensorFlow会自动生成一个影子变量, 所以要获取变量的滑动平均值实际上就是获取这个影子变量的取值。保存模型的时候,除了保存每一个变量,如果该变量有影子变量, 也会保存下来。如果在加载模型时直接将影子变量映射到变量自身,那么在使用训练好的模型时就不需要再调用函数来获取变量的滑动平均值了。 这样大大方便了滑动平均模型的使用。因此可以通过变量重命名的方式来加载模型,这样在前向传播的过程中就不需要调用 求滑动平均的函数来获取平均值了。这样就可以完全共用 mnist_inference.py 中定义的前向传播过程。 tf.train.ExponentialMovingAverage类提供了variables_to_restore函数来生成tf.train.Saver类所需要的变量重命名字典。 相当于 saver = tf.train.Saver({"v/ExponentialMovingAverage": v})中的字典(v的影子变量-->v)不需要手动指定了, 手动映射需要建立每一个变量重命名映射字典,不方便使用。 """ 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("./datasets/MNIST_data/", one_hot=True) mnist = input_data.read_data_sets("/root/workspace/tf/pycharm/data/", one_hot=True) evaluate(mnist) if __name__ == '__main__': main() |
© 除特别注明外,本站所有文章均为卢明冬的博客原创 , 转载请注明作者和文章链接。
© 本文链接:https://lumingdong.cn/tensorflow-code-template-foundation.html
© 本文链接:https://lumingdong.cn/tensorflow-code-template-foundation.html