第一章:神经网络理论知识
从本篇内容开始作为从<神经网路基础到量化投资应用>系列笔记的第二部分,主要用于神经网络的实现,由于自己不是计算机专业,所以许多语言的知识就不再细说,写博客的目的就是最终可以写完一个完整的模型,可以使用,并且知道为什么这么写。本篇内容主要想写4部分,第一节全连接神经神经网络,第二节卷积神经网络(CNN),第三节循环神经网络(RNN),第四节为一些扩展和问题。在学习的过程中看了好多Tensorflow的文章,都是在讲其中的结构,而本篇博客将直接从代码出发,直接粘贴复制就可以使用。我的重点不是写TensorFlow的内部架构,而是讲写代码的逻辑。每一篇博客将直接是上代码,解释,上代码,解释,,,这样的结构。
Tensorflow的配置和安装计算模型储存模型数据模型等相关知识这里一概不再讲解,直接上代码。
这个例子是TensorFlow书上的一个经典例子,即MNIST_DATA分类,是学习TF所必须经过的数据集,它包含了60000张图片作为训练数据,10000张图片作为测试数据。在MNIST数据集中的每一张图片都代表了手写体的0-9中的一个数字。我们就是要设计一个模型可以对其进行分类。
直接上码:
代码1可以直接下载运行。文章末尾有代码资源,下面是一个只有一个隐藏层的神经网络,是我自己对代码的一个解剖。
第一部分、导入相关的模块
import tensorflow as tf
# 导入tensorflow框架
from tensorflow.examples.tutorials.mnist import input_data
# 导入MNIST_DATA数据
第二部分、参数设置
# MNIST数据集相关的常数
INPUT_NODE = 784 # 输入层的节点数。对于MNIST数据集,这个就等于图片的像素
# 在MNIST_Data数据集中每一张图片的长和宽都是28像素的,所以一张图片共有28x28=784个像素点。
OUTPUT_NODE = 10
# 输入层的节点数。这个等于类别的数目。因为在MNIST数据集中,
#需要区分的是0-9这10个数字,所以这里输出层的节点数为10
# 配置神经网络的参数
LAYER1_NODE = 500
#隐藏层节点数。这是使用只有一个隐藏层的网络结构作为样例。
BATCH_SIZE = 100
#一个训练batch中的训练数据个数,数字越小,训练过程越接近随机梯度下降:
# 数字越大,训练越接近梯度下降。
# 相当于我们把60000张图片分批进行计算,每一批为100张,
LEARNING_RATE_BASE = 0.8 #基础的学习率
参数更新过程:
w : = w − α d w b : = b − α d b 基 础 学 习 率 即 为 这 里 的 α w:=w-\alpha dw \\ b:=b-\alpha db \\ 基础学习率即为这里的 \alpha w:=w−αdwb:=b−αdb基础学习率即为这里的α
LEARNING_RATE_DECAY = 0.99 # 学习率衰减速度。
在神经网络的学习过程中,如果学习率设置的太大,那么模型将在最优点的位置
徘徊,不能到达最优点,如果学习率设置的太小,那么模型将收敛速度将会大幅降低,
所以,设置学习率的指数衰减,即学习率随着迭代次数的增加而变小(衰减),从而
即不浪费计算资料也可使得模型可以很好的收敛到最优点附近。
指数衰减公式:
α ′ = α ∗ d e c a y r a t e g l o b a l s t e p d e c a y s t e p s α : 为 学 习 率 ( L E A R N I N G _ R A T E _ B A S E ) d e c a y r a t e : 为 衰 减 速 率 , 即 为 这 里 的 L E A R N I N G _ R A T E _ D E C A Y g l o b a l s t e p : 为 当 前 的 迭 代 轮 数 d e c a y s t e p : 通 常 为 e x a m p l e s n u m B A T C H _ S I Z E , 即 训 练 完 所 有 数 据 需 要 的 迭 代 次 数 \alpha' = \alpha * decay_{rate}^{\frac{global_{step}}{decay_{steps}}} \\ {}\\ \alpha: 为学习率({LEARNING\_RATE\_BASE})\\ {}\\ decay_{rate}:为衰减速率,即为这里的LEARNING\_RATE\_DECAY\\ {}\\ global_{step}:为当前的迭代轮数\\ {}\\ decay_{step}:通常为\frac{examples_{num}}{BATCH\_SIZE},即训练完所有数据需要的迭代次数 α′=α∗decayratedecaystepsglobalstepα:为学习率(LEARNING_RATE_BASE)decayrate:为衰减速率,即为这里的LEARNING_RATE_DECAYglobalstep:为当前的迭代轮数decaystep:通常为BATCH_SIZEexamplesnum,即训练完所有数据需要的迭代次数
在该公式中decay_steps为一个常数,decay_rate是一个小于1的数,所以随着迭代次数(global_step)的增加,学习率将会指数级的进行衰减。
REGULARIZATION_RATE = 0.001 # 描述模型复杂度的正则化项在损失函数中的系数。
正则化
模型的过拟合现象就是由于模型在训练集上的损失函数过于小而导致泛化能力大幅减弱,
如果我们用R(w)来表示模型的复杂度,那么如果模型发生过拟合,就说明R(w)会变得过大。解决方法就是不要使得R(w)过大,做法就是将R(w)和损失函数J(θ)放在一起训练,它俩是一对矛盾,其中一个减小另外一个一定增大。所以,损失函数用J(θ)+λR(w)来代替,λ表示复杂度项在整体损失的权重。
常见的R(w)有:
R ( w ) = ∣ ∣ w ∣ ∣ 1 = ∑ i ∣ w i ∣ R ( w ) = ∣ ∣ w ∣ ∣ 2 2 = ∑ i ∣ w i 2 ∣ R ( w ) = ∑ i α ∣ w i ∣ + ( 1 − α ) w i 2 J ( θ ) + λ R ( w ) R(w)=||w||_1=\sum_i|w_i|\\ R(w)=||w||_2^2 = \sum_i|w_i^2|\\ R(w)=\sum_i \alpha|w_i|+(1-\alpha)w_i^2\\ J(\theta)+\lambda R(w) R(w)=∣∣w∣∣1=i∑∣wi∣R(w)=∣∣w∣∣22=i∑∣wi2∣R(w)=i∑α∣wi∣+(1−α)wi2J(θ)+λR(w)
这里的REGULARIZATION_RATE即为上式中的λ。
TRAINING_STEPS = 30000 # 训练论数
MOVING_AVERAGE_DECAY = 0.99 # 滑动平均衰减率
滑动平均衰减
滑动平均区别去动量梯度下降法,滑动平均是对训练过程中的参数进行移动平均计算,动量梯度下降法是改进了梯度下降的方法,所以这是两个频道的事情。
滑动平均是对参数进平均,而对参数进行移动平均的时候,并不是直接改变参数,而是在参数的基础上设置一个影子变量来记录参数的移动平均值。影子变量的更新过程:
s h a d o w _ v a r i a b l e = d e c a y × s h a d o w _ v a r i a b l e + ( 1 − d e c a y ) × v a r i a b l e shadow\_variable = decay \times shadow\_variable + (1-decay) \times variable shadow_variable=decay×shadow_variable+(1−decay)×variable
为了在训练的前期可以更新得更快:设置衰减率
d e c a y = m i n { d e c a y , 1 + g l o b a l _ s t e p 10 + g l o b a l _ s t e p } decay = min\{decay,\frac{1+global\_step}{10+global\_step}\} decay=min{decay,10+global_step1+global_step}
MOVING_AVERAGE_DECAY 即为上式中的decay,衰减率通常设置为接近1的数。(0.9,0.99…)
第三部分,前向传播
# 一个辅助函数,给定神经网络的输入和所有参数,计算神经网络的前向传播结果。在这里定义了一个使用ReLU激活函数的三层全链接神经网络。通过
# 加入隐藏层实现多层网络结构,通过ReLU的就哦函数实现了去线性化。在这个函数中也支持传入用于计算参数平均值的类,这样方便在测试使用滑动平均模型
def inference(input_tensor,avg_class,weights1,biases1,weights2,biases2):
# 当没有提供滑动平均类时,直接使用参数当前的取值。
if avg_class is None:
# 计算隐藏层的前向传播结果,这里使用了ReLU激活函数。
layer1 = tf.nn.relu(tf.matmul(input_tensor,weights1)+biases1)
a [ 1 ] = g ( a [ 0 ] ∗ w 1 + b 1 ) g 为 R e L U 激 活 函 数 a^{[1]}=g(a^{[0]}*w_1+b_1) \\ {}\\ g为ReLU激活函数 a[1]=g(a[0]∗w1+b1)g为ReLU激活函数
# 计算输出层的前向传播结果。因为在计算损失函数时会一并计算softmax函数,所以这里不需要加入激活函数。
# 而且不加入softmax不会影响预测结果。因为预测时使用的是不同类别对应节点输出值的相对大小,有没有softmax层对最后分类结果的计算没有影响。
# 于是在计算整个神经网络的前向传播时可以不加入最后的softmax层。
return tf.matmul(layer1,weights2) + biases2
如果不进行滑动平均,则单一隐藏层模型的前向传播结果为:
a [ 2 ] = a [ 1 ] ∗ w 2 + b 2 a^{[2]}=a^{[1]}*w_2 + b_2 a[2]=a[1]∗w2+b2
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')
定义x和预测值 y ^ \hat{y} y^,tf.placeholder 占位符函数。
# 生成隐藏层的参数
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]))
第一层(隐藏层): a [ 0 ] a^{[0]} a[0]也就是输入数据X是None行,INPUT_NODE列的矩阵, w 1 w_1 w1为INPUT_NODE行,LAYER1_NODEl列的, b 1 b_1 b1是一个常数,通过python的广播机制进行相加,所以 a [ 1 ] = g ( a [ 0 ] ∗ w 1 + b 1 ) a^{[1]}=g(a^{[0]}*w_1+b_1) a[1]=g(a[0]∗w1+b1),所以根据矩阵的乘法 a [ 1 ] a^{[1]} a[1]为None行,LAYER1_NODE列。
第二层(输出层): a [ 2 ] a^{[2]} a[2]也就是output即 y ^ \hat{y} y^。 a [ 2 ] = a [ 1 ] ∗ w 2 + b 2 a^{[2]}=a^{[1]}*w_2+b_2 a[2]=a[1]∗w2+b2有矩阵的乘法和python的广播机制, a [ 1 ] a^{[1]} a[1]为None行,LAYER1_NODE列, w 2 w_2 w2为LAYER1_NODE 行,OUTPUT_NODE列, b 1 b_1 b1为常数,所以 a [ 2 ] a^{[2]} a[2]也就是output也就是 y ^ \hat{y} y^为None行,OUTPUT_NODE列。
代码中的y_和 y ^ \hat{y} y^并不是同一个意思,代码中y_表示的真实的labels;而 y ^ \hat{y} y^表示的预测值,在代码中为y(没有滑动平均)或者是average_y(带有滑动平均)。
# 计算当前参数下神经网络前向传播的结果,这里给出的用于计算滑动平均的类为None,所以函数不会使用参数的滑动平均值
y = inference(x,None,weights1,biases1,weights2,biases2)
不进行滑动平均的前向传播结果。
# 定义存储训练轮数的变量。这个变量不需要计算活动平均值,所以这里指定这个变量为不可训练的变量
#(trainable=Fasle).再使用TensorFlow训练神经网络时,一般会将代表训练轮数的变量指定为不可训练的参数。
global_step = tf.Variable(0,trainable=False)
# 给定滑动平均衰减率和训练轮数的变量,初始化滑动平均类。在上一部分中介绍过给定训练轮数的变量可以加快
# 训练早期变量的更新速度。
variable_averages = tf.train.ExponentialMovingAverage(
MOVING_AVERAGE_DECAY,global_step
)
# 在所有待变神经网络参数的变量上使用滑动平均。其他辅助变量(比如global_step)就不需要了。
# tf.trainbale_variables 返回的就是图上的集合GraphKeys.TRAINABLE_VARIABLES中的元素。
# 这个集合的元素就是所有没有指定trainable=False的参数。
Variable_averages_op = variable_averages.apply(
tf.trainable_variables()
)
上述代码为对所有可以训练的参数进行滑动平均。
# 计算使用了滑动平均之后的前向传播结果。在上一部分中介绍过滑动平均不会改变变量本身的取值,而是会维护一个影子
# 变量来记录器滑动平均值。所以当需要使用这个滑动平均值时,需要明确调用average函数
average_y = inference(
x,variable_averages,weights1,biases1,weights2,biases2
)
滑动平均之后的前向传播结果。
# 计算交叉熵,作为刻画预测值和真实值之间差距的损失函数。这里使用了Tensorflow中提供的sparse_softmax_cross_entropy_with_logists
# 函数来计算交叉熵。当分类问题只有一个正确答案时,可以使用这个函数来进行交叉熵的计算。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)
softmax 激活函数:
t = e z [ l ] t=e^{z^{[l]}} t=ez[l]
a [ l ] = e z [ l ] ∑ i t i a^{[l]}=\frac{e^{z^{[l]}}}{\sum_i t_i} a[l]=∑itiez[l]
tf.nn.sparse_softmax_cross_entropy_with_logits()该函数用于每个数据只有一个正确答案的情况,首先对y进行softmax层计算,然后再进行交叉熵的计算。所以之前的前向传播并没有进行softmax计算。
# 计算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_DECAY # 学习衰减速度。
)
上述代码为学习率的衰减
# 使用tf.train.GradientDescentOptimizer 优化算法来优化损失函数。注意这里损失函数包含了交叉熵损失和LL2正则化损失。
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,Variable_averages_op]):
train_op = tf.no_op(name='train')
上述代码为梯度下降法的执行
# 检验使用了滑动平均模型的神经网络前向传播结果是否正确。tf.argmax(average_y,1)计算每一个样例
# 的预测答案。其中average_y是一个batch_size*10的二维数组,每一行表示一个样例的前向传播结果。tf.agmax
# 的第二个参数“1”表示选取最大值的操作仅在第一个维度进行,也就是说,只在每一行选取最值对应的下标。于是得到
# 的结果是一个长度为batch的一维数组,这个一维数组中的值就表示了每一个样例对应的数组识别结果。
# tf.equal判断两个张脸的每一个维是否相等。如果相等返回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:
sess.run(tf.global_variables_initializer())
# 准便验证数组。一般在神经网络的训练过程中会通过验证数据来大致判断停止的条件和评判训练的效果。
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。当神经网络模比较复杂或者验证数据比较
# 大时,太大的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("MNIST_data",one_hot=True)
train(mnist)
# Tensorflow 提供的一个主程序入口,tf.app.run会调用上面定义的main函数。
if __name__ == '__main__':
tf.app.run()
主程序入口
上述代码为一个py, 为了代码的可读性的便捷性,现在将这个例子拆分为3个程序,第一个是mnist_inference.py,它定义了前向传播的过程以及神经网络的参数。第二个是mnist_train.py它定义了神经网络的训练过程。第三个为mnist_eval.py,它定义了测试过程。
第一部分,导入模块
# -*- coding:utf-8 -*-
import tensorflow as tf
第二部分,设置参数
这些参数在第一节的时候已经进行解释,不再赘述。
#定义神经网络结构相关的参数
INPUT_NODE = 784
OUTPUT_NODE = 10
LAYERA1_NODE = 500
第三部分,参数管理
在第一节的时候,由于模型只有一个隐藏层和一个输出层,所以之后w_1和w_2两个参数,参数较少,正则化可以直接对其进行操作。如果参数过多,设置一个参数生成函数,便于正则化计算,如果需要正则化,则可以直接将正则项添加到集合当中。
'''
通过tf.get_variable函数来获取变量。在训练神经网络时会创建这些变量:则测试时会通过
保存的模型加载这些变量的取值。而且更加方便的是因为可以在变量加载时将滑动平均变量
重命名,所以可以直接通过同样的名字在训练时使用变量自身,而在测试时使用变量的滑动平均值。
在这个函数中也会将变量的正则化损失加入损失集合。
'''
def get_weight_variable(shape,regularizer):
weights = tf.get_variable(
"weights",shape,
initializer=tf.truncated_normal_initializer(stddev=0.1)
)
'''
当给出了正则化生成函数时,将当前变量的正则化损失加入名字为名字为lossses的集合。在这里使用了
add_to_collection 函数将一个张量加入一个集合,而这个集合的名称为losses。这是自定义的集合,不在
Tensorflow自动管理的集合列表中。
'''
if regularizer is not None:
tf.add_to_collection('losses',regularizer(weights))
return weights
第四部分,前向传播过程
# 定义神经网络的前向传播过程。
def inference(input_tensor,regularizer):
# 声明第一层神经网络的变量并完成前向传播过程。
定义一个命名空间layer1,专门管理第一层中的参数。
with tf.variable_scope('layer1'):
'''
这里通过tf.get_variable 或tf.Variable 没有本质区别,因为在训练或是测试中
没有在同一个程序中多次调用这个函数。如果在同一个程序中多次调用,在第一次调用
之后需要将reuse参数设置为True
'''
weights = get_weight_variable(
[INPUT_NODE,LAYERA1_NODE],regularizer
)
biases = tf.get_variable(
"biases",[LAYERA1_NODE],
initializer=tf.constant_initializer(0.0)
)
layer1 = tf.nn.relu(tf.matmul(input_tensor,weights)+biases)
定义命名空间layer2,专门管理第二层的参数。
# 类似的声明第二层神经网络的变量并完成前向传播过程。
with tf.variable_scope("layer2"):
weights = get_weight_variable(
[LAYERA1_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
第一部分,导入模块
#-*- coding:utf-8 -*-
import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# 加载mnist_inference.py中的定义的常量和前向传播的函数。
import mnist_inference
第二部分,设置参数
这些参数以及做过解释,多了两个路径,用来保存模型。在mnist_train.py运行过程中会把训练好的数据保存下来,然后再运行mnist_eval.py从中调取模型中文件。
# 配置神经网络的参数
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARAZTION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AEVERAGE_DECAY = 0.99
# 模型保存的路径和文件名
MODEL_SAVE_PATH = "model/"
MODEL_NAME = "model.ckpy"
反向传播训练过程
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)
#直接使用mnist_inference.py中定义的前向传播过程。
y = mnist_inference.inference(x, regularizer)
global_step = tf.Variable(0,trainable=False)
# 和5.2.1节样例中类似地定义损失函数、学习率、滑动平均操作以及训练过程。
variable_averages = tf.train.ExponentialMovingAverage(
MOVING_AEVERAGE_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("After %d training step(s),loss on training"
"batch is %g."%(step,loss_value))
'''
保存当前的模型。这一这里给吃了global_step参数,这样可以让每一被保存模型的文件名末尾
加上训练的论数,比如“model.ckpy-1000”表示训练1000轮之后得到的模型
'''
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("MNIST_data",one_hot=True)
train(mnist)
if __name__ == '__main__':
tf.app.run()
# -*- 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 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)就可以得到输入样例的预测类别了。
'''
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_AEVERAGE_DECAY
)
variable_to_restore = variable_averages.variables_to_restore()
saver = tf.train.Saver(variable_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("MNIST_data",one_hot=True)
evaluate(mnist)
if __name__ == '__main__':
tf.app.run()
在上一部分当中神经网络的前向传播过程抽象成了一个函数。
def inference(input_tensor,avg_class,weights1,biases1,weight2,biases2)
从定义种可以看出这个函数当中包含了前向传播的所有参数,但是如果网络比较大,参数特别多,显然这种方法是不可取的,所以我们通过上下文管理器来生成一个命名空间,从而实现对参数的统一管理。
其中涉及到函数tf.get_variable(),函数tf.get_variable()即可以创建参数也可以获取创建好的参数
代码举例
# 在名字为foo的命名空间内创建名字为v的变量
with tf.variable_scope("foo"):
v = tf.get_variable("v",[1],initializer = tf.constant_initializer(1.0))
# 因为在命名空间foo中已经存在名字为v的变量,所以以下代码将会报错:
# variabel foo/v already exists, disallowed.Did you mean to set reuse = True in VarScope?
with tf.variable_scope("foo"):
v = tf.get_variable("v",[1])
# 在生成上下文管理器时,将参数reuse设置为True。这样tf.get_variable函数将直接获取已经声明的变量。
with tf.variable_scope("foo"):
v1 = tf.get_variable("v",[1])
print(v==v1) # 输出为True,代表v,v1代表的时相同的TensorFlow中变量。
# 将参数reuse设置为True时,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])
print(v5==v3) # 输出True
v6 = tf.get_variable("foo/v1",[1])
print(v6==v4) # 输出True
2.1模型的保存
import tensorflow as tf
# 声明两个变量并计算它门的和。
v1 = tf.Variable(tf.constant(1.0,shape=[1]),name="v1")
v2 = tf.Variable(tf.constant(2.0,shape=[1]),name="v2")
return = v1+v2
init_op = tf.global_variables_initializer()
# 声明tf.train.Saver类用于保存模型。
saver = tf.train.Saver()
with tf.Session() as sess:
sess.run(init_op)
# 将模型保存到model/model.ckpt文件。
saver.save(sess,"model/model.ckpt")
2.2加载模型
import tensorflow as tf
# 直接加载持久化的图。
saver = tf.train.import_meta_graph("model/model.ckpt.meta")
with tf.Session() as sess:
saver.restore(sess,"model/./")
# 通过张量的名称来获取张量。
print sess.run("add:0")
# 输出[3.]
上述代码为import_meta_graph即计算图中全部的参数,但是在实际中我们不一定需要导入全部的参数,有可能只需要导入部分的参数,例如在迁移学习当中。那么我们可以通过列表[]来选择我们想要导入的参数。
saver = tf.train.Saver([v1])
2.3重命名
从模型导入参数,我们可以通过重命名来实现对参数的导入,
v1 = tf.Variable(tf.constant(1.0,shape=[1]),name="other-v1")
v2 = tf.Varibale(tf.constant(2.0,shape=[1]),name="other-v2")
saver=tf.train.Saver({"v1":v1,"v2":v2})
将v1重命名了other-v1,v2重命名为other-v2。
滑动平均模型的应用
滑动平均模型是通过影子参数实现对模型的健化。
所以我们可以直接通过影子参数的重命名从而实现对模型参数的导入。
保存滑动平均模型
import tensorflow as tf
v = tf.Variable(0,dtype=tf.floar32,name="v")
# 在没有声明滑动平均模型时只有一个变量v,所以以下语句指挥输出“v:0”。
for variables in tf.gobal_variable():
print(variables.name)
ema = tf.train.ExponentialMovingAverage(0.99)
maintain_average_op = ema.apply(tf.global_varibales())
# 在声明滑动平均模型之后,Tensorflow会自动生成一个影子变量
# 于是下列语句会输出:“v:0”和"v/ExpoenentialMovingAverage:0".
for variables in tf.global_variables():
print(variables.name)
Saver = tf.train.Saver()
with tf.Session() as sess:
init_op = tf.global_varibales_initializer()
sess.run(init_op)
sess.run(tf.assign(v,10))
sess.run(maintain_average_op)
# 保存时,TensorFlow会将v:0和v/ExpontialMoving Average:0两个变量都保存下来。
saver.save(sess,"model/model.ckpt")
print(sess.run([v,ema.average(v)])) #输出[10.0,0.099999905]
读取变量的滑动平均值
通过这个方法,就可以使用完全一样的代码来计算滑动平均模型的前向传播结果。
v = tf.variable(0,dtype=tf.float32,name="v")
saver = tf.train.Saver({"v/ExponentialMovingAverage":v})
with tf.Session() as sess:
saver.restore(sess,"model/model.ckpt")
print sess.run(v) #输出0.099999905
为了方便加载时重名命滑动平均变量,tf.train.ExponentialMovingAverage类提供了variables_to_restore函数生成tf.train.Saver类所想要的变量重命名字典。以下代码给出了variables_to_restore函数使用样例。
import tensorflow as tf
v = tf.Variable(0,dtype=tf.floar32,name="v")
ema = tf.train.ExponentialMovingAverage(0.99)
# 通过使用varibales_to_restore函数可以直接生成上面代码中提供的字典{"v/ExponentialMovingMovingAverage":v}
# 以下代码会输出:{"v/ExponentialMovingAverage":}其中后面的variable类就代表了变量v。
print(ema.variables_to_restore())
saver = tf.train.Saver(ema.variables_to_restore())
with tf.Session() as sess:
saver.restore(sess,"model.ckpt")
print(sess.run(v)) # 输出0.99999905.
2.4迁移学习的准备
使用tf.train.Saver会保存运行TensorFlow程序所需要的全部信息,然而有时并不需要某些信息。比如在测试或者离线预测时,只需要知道如何从从神经网络的输入层经过前向传播计算得到输出层即可,而不需要类似于变量初始化、模型保存等辅助节点的信息。于是Tensorflow提供了convert_variables_to_constants函数,通过这个函数可以将计算图中的变量及其取值通过常量的方式保存,这样整个Tensor flow计算图可以统一存放在一个文件中。以下为一个案例。
import tensorflow as tf
trom tensorflow.python.framework import graph_util
v1 = tf.Variable(tf.constant(1.0,shape=[1]),name="v1")
v2 = tf.varialbe(tf.constant(2.0,shape=[1]),name="v2")
result = v1+v2
init_op = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init_op)
# 导出当前计算图的GraphDef部分,只需要这一部分就可完成从输入层到输出层的计算过程。
graph_def = tf.get_default_graph().as_graph_def()
#将图中的变量及其取值转化为常量,同时将图中不必要的节点去掉。在下面一行代码中,最后一个参数['add']给出了需要保存的节点名称。add节点既是上面定义的加法
output_graph_def = graph_util.convert_variables_to_constants(sess,graph_def,[add])
# 将导出的模型存入文件。
with tf.gf.ile.GFile("model/combined_model.pd","wb") as f:
f.write(output_graph_def.SerializeToString())
加载模型
直接计算定义的加法运算的结果。
import tensorflow as tf
from tensorflow.python.platfrom import gfile
with tf.Session() as sess:
model_filename="model/combined_model.pd"
# 读取保存的模型文件,并将文件解析成对应的GraphDef protocol Buffer
with gfile.FastGFile(model_filename,'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
# 将graph_def中保存的图加载到当前的图中,return_elements=["add:0"]给出了返回的张量的名称。哎保存的时候给出的时计算节点的名称,所以为"add"。在加载的时候给出的时张量的名称,所以是add:0
result = tf.import_graph_def(graph_def,return_elements=["add:0"])
print(sess.run(result)) # 输出[3.0]