本文参考
1)戈云飞 https://blog.youkuaiyun.com/geyunfei_/article/details/78782804
2)量化大司马 https://www.jianshu.com/p/696bde1641d8
3)《Tensorflow 实战Google深度学习框架》
4)zhuzuwei https://blog.youkuaiyun.com/zhuzuwei/article/details/78983801?utm_source=blogxgwz9
5)陈浅墨 https://blog.youkuaiyun.com/u011500062/article/details/51728830#commentBox
6)ciky齐 https://blog.youkuaiyun.com/c20081052/article/details/82961988
TensorFlow程序一般可以分为两个阶段,在第一个阶段需要定义图中所有的计算,第二个阶段为执行计算。
张量(Tensor)
可以先简单的将它理解为一个多维数组:
3 # 这个 0 阶张量就是标量,shape=[]
[1., 2., 3.] # 这个 1 阶张量就是向量,shape=[3]
[[1., 2., 3.], [4., 5., 6.]] # 这个 2 阶张量就是二维数组,shape=[2, 3]
[[[1., 2., 3.]], [[7., 8., 9.]]] # 这个 3 阶张量就是三维数组,shape=[2, 1, 3]
TensorFlow 内部使用tf.Tensor类的实例来表示张量,每个 tf.Tensor有三个属性:
- dtype Tensor 存储的数据的类型,可以为tf.float32、tf.int32、tf.string…
- shape Tensor 存储的多维数组中每个维度的数组中元素的个数,如上面例子中的shape
- 名字name
# 引入 tensorflow 模块
import tensorflow as tf
# 创建一个整型常量,即 0 阶 Tensor
t0 = tf.constant(3, dtype=tf.int32)
# 创建一个浮点数的一维数组,即 1 阶 Tensor
t1 = tf.constant([3., 4.1, 5.2], dtype=tf.float32)
# 创建一个字符串的2x2数组,即 2 阶 Tensor
t2 = tf.constant([['Apple', 'Orange'], ['Potato', 'Tomato']], dtype=tf.string)
# 创建一个 2x3x1 数组,即 3 阶张量,数据类型默认为整型
t3 = tf.constant([[[5], [6], [7]], [[4], [3], [2]]])
# 打印上面创建的几个 Tensor
print(t0)
print(t1)
print(t2)
print(t3)
t1、t2、t3是对tensor的引用。
>>> print(t0)
Tensor("Const:0", shape=(), dtype=int32)
>>> print(t1)
Tensor("Const_1:0", shape=(3,), dtype=float32)
>>> print(t2)
Tensor("Const_2:0", shape=(2, 2), dtype=string)
>>> print(t3)
Tensor("Const_3:0", shape=(2, 3, 1), dtype=int32)
在tensor中并没有真正保存数字,它保存的是如何得到这些数字的计算过程。 真正的运算是在它们被run的时候。
print 一个 Tensor 只能打印出它的属性定义,并不能打印出它的值,要想查看一个 Tensor 中的值还需要经过Session 运行一下
>>> sess = tf.Session()
>>> print(sess.run(t0))
3
>>> print(sess.run(t1))
[ 3. 4.0999999 5.19999981]
>>> print(sess.run(t2))
[[b'Apple' b'Orange']
[b'Potato' b'Tomato']]
>>> print(sess.run(t3))
[[[5]
[6]
[7]]
[[4]
[3]
[2]]]
变量
# 创建两个变量
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35), name="weights")
biases = tf.Variable(tf.zeros([200]), name="biases")
调用tf.Variable()会在计算图上添加这些节点:
变量在被使用前,必须被初始化。变量的形状是固定的。不过,Tensorflow提供了一些高级机制用于改变变量的形状。
- 一个变量节点,用于保存变量的值
- 一个初始化操作节点,用于将变量设置为初始值。它实际上是一个tf.assign节点。
- 初始值节点,例如例子中的zeros节点也会被加入到计算图中。
# 创建一个变量, 初始化为标量 0.
state = tf.Variable(0, name="counter")
# 创建一个 op, 其作用是使 state 增加 1
one = tf.constant(1)
new_value = tf.add(state, one)
update = tf.assign(state, new_value)
# 启动图后, 变量必须先经过`初始化` (init) op 初始化,
# 首先必须增加一个`初始化` op 到图中.
init_op = tf.initialize_all_variables()
# 启动图, 运行 op
with tf.Session() as sess:
# 运行 'init' op
sess.run(init_op)
# 打印 'state' 的初始值
print sess.run(state)
# 运行 op, 更新 'state', 并打印 'state'
for _ in range(3):
sess.run(update)
print sess.run(state)
# 输出:
# 0
# 1
# 2
# 3
使用tensorboard查看计算图
import tensorflow as tf
a = tf.constant([1, 2], name='a')
b = tf.constant([2.0, 3.0], name='b')
result = a + b
print(result)
TypeError: Input 'y' of 'Add' Op has type float32 that does not match type int32 of argument 'x'.
修改如下:
a = tf.constant([1, 2], name='a', dtype=tf.float32)
b = tf.constant([2.0, 3.0], name='b', dtype=tf.float32)
两个加数的类型不同会报错!如果不指定类型,TensorFlow会给出默认的类型,不带小数点的数会被默认为int32 ,带小数点的数会被默认为float32. 因为使用默认类型有可能会导致潜在的类型不匹配问题,所以一般建议通过指定dtype来明确指出变量或者常量的类型。TensorFlow支持14种不同的类型,主要包括了实数(tf.float32、tf.float64)、整数(tf.int8、tf.int16、tf.int32、tf.int64、tf.uint8)、布尔型(tf.bool)和复数(tf.complex64、tf.complex128)、字符串型(tf.string)。
数据流图(Dataflow Graph)
数据流图是由节点(nodes)和线(edges)构成的有向图:
- 节点(nodes) 表示计算单元,也可以是输入的起点或者输出的终点
- 线(edges) 表示节点之间的输入/输出关系
在 TensorFlow 中,每个节点都是用 tf.Tensor
的实例来表示的,即每个节点的输入、输出都是Tensor,如下图中 Tensor 在 Graph 中的流动,形象的展示 TensorFlow 名字的由来。
......Tensor 即可以表示输入、输出的端点,还可以表示计算单元......
每个节点可以有零个或多个输入,但只有一个输出。网络中的节点表示对象(张量和运算操作),边表示运算操作之间流动的张量。计算图定义神经网络的蓝图,但其中的张量还没有相关的数值。
TensorFlow 中的数据流图有以下几个优点:
- 可并行 计算节点之间有明确的线进行连接,系统可以很容易的判断出哪些计算操作可以并行执行
- 可分发 图中的各个节点可以分布在不同的计算单元(CPU、 GPU、 TPU等)或者不同的机器中,每个节点产生的数据可以通过明确的线发送的下一个节点中
- 可优化 TensorFlow 中的 XLA 编译器可以根据数据流图进行代码优化,加快运行速度
- 可移植 数据流图的信息可以不依赖代码进行保存,如使用Python创建的图,经过保存后可以在C++或Java中使用
TensorFlow 底层是使用C++实现,这样可以保证计算效率,并使用 tf.Session
类来连接客户端程序与C++运行时。上层的Python、Java等代码用来设计、定义模型,构建的Graph,最后通过tf.Session.run()
方法传递给底层执行。
Session
通过会话(session)来执行定义好的计算。使用会话对象来实现计算图的执行。会话对象封装了评估张量和操作对象的环境。这里真正实现了运算操作并将信息从网络的一层传递到另外一层。不同张量对象的值仅在会话对象中被初始化、访问和保存。在此之前张量对象只被抽象定义,在会话中才被赋予实际的意义。会话拥有并管理TensorFlow程序运行时的所有资源,当所有计算完成之后需要关闭会话来帮助系统回收资源,否则就可能出现资源泄露的问题。TensorFlow中使用会话的模式一般有两种,第一种模式需要明确调用会话生成函数和关闭会话函数。
使用这种模式时,在所有计算完成之后,需要明确调用Session.close函数来关闭会话并释放资源。然而,当程序因为异常而退出时,关闭会话的函数可能就不会被执行从而导致资源泄露。第二种通过python上下文管理器来使用会话,只要将所有的计算放在“with” 的内部就可以。当上下文管理器退出的时候自动释放所有资源。这样既解决了因为异常退出时资源释放的问题,同时也解决了忘记调用Session.close函数而产生的资源泄露。
TensorFlow会自动生成一个默认的计算图,如果没有特殊指定,运算会自动加入这个计算图中。
import tensorflow as tf
# 创建两个常量节点
node1 = tf.constant(3.2)
node2 = tf.constant(4.8)
# 创建一个 adder 节点,对上面两个节点执行 + 操作
adder = node1 + node2
# 打印一下 adder 节点
print(adder)
# 打印 adder 运行后的结果
sess = tf.Session()
print(sess.run(adder))
Tensor("add:0", shape=(), dtype=float32)
8.0
运算结果的值在 fetches 中提取;在示例中,提取的张量为 adder。run 方法将导致在每次执行该计算图的时候,都将对与 adder 相关的张量和操作进行赋值。如果抽取的不是 adder 而是 node1,那么最后给出的是向量 node1 的运行结果
上面使用tf.constant()
创建的 Tensor 都是常量,一旦创建后其中的值就不能改变了。有时我们还会需要从外部输入数据,这时可以用tf.placeholder
创建占位 Tensor,占位 Tensor 的值可以在运行的时候输入。
import tensorflow as tf
a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
adder_node = a + b
print(a)
print(b)
print(adder_node)
sess = tf.Session()
print(sess.run(adder_node, {a: 3, b: 4.5}))
print(sess.run(adder_node, {a: [1, 3], b: [2, 4]}))
Tensor("Placeholder:0", dtype=float32)
Tensor("Placeholder_1:0", dtype=float32)
Tensor("add:0", dtype=float32)
7.5
[ 3. 7.]
import tensorflow as tf
a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
adder_node = a + b
add_and_triple = adder_node*3 # 更复杂操作
print(a)
print(b)
print(adder_node)
sess = tf.Session()
print(sess.run(add_and_triple, {a: 3, b: 4.5})) # 当当当 输出22.5
print(sess.run(adder_node, {a: 3, b: 4.5}))
print(sess.run(adder_node, {a: [1, 3], b: [2, 4]}))
===============================================================================================
19.3.5补充
前面的代码分为以下三个主要部分:
- 第一部分 import 模块包含代码将使用的所有库,在目前的代码中只使用 TensorFlow,其中语句 import tensorflow as tf 则允许 Python 访问 TensorFlow 所有的类、方法和符号。
- 第二个模块包含图形定义部分...创建想要的计算图。在本例中计算图只有一个节点,tensor 常量消息由字符串“Welcome to the exciting world of Deep Neural Networks”构成。
- 第三个模块是通过会话执行计算图,这部分使用 with 关键字创建了会话,最后在会话中执行以上计算图。
该程序打印计算图执行的结果,计算图的执行则使用 sess.run() 语句,sess.run 求取 message 中所定义的 tensor 值;计算图执行结果输入到 print 函数,并使用 decode 方法改进,print 函数向 stdout 输出结果:
这里的输出结果是一个字节字符串。要删除字符串引号和“b”(表示字节,byte)只保留单引号内的内容,可以使用 decode() 方法。
简单小栗子
例1、
建立模型(Model)
如下为我们进行某项实验获得的一些实验数据:
我们将这些数据放到一个二维图上可以看的更直观一些,如下,这些数据在图中表现为一些离散的点:
我们需要根据现有的这些数据归纳出一个通用模型,通过这个模型我们可以预测其他的输入值产生的输出值。
如下图,我们选择的模型既可以是红线表示的鬼都看不懂的曲线模型,也可以是蓝线表示的线性模型,在概率统计理论的分析中,这两种模型符合真实模型的概率是一样的。
根据 “奥卡姆剃刀原则-若有多个假设与观察一致,则选最简单的那个,蓝线表示的线性模型更符合我们的直观预期。
如果用 x 表示输入, y 表示输出,线性模型可以用下面的方程表示:
y=W×x+b
如下图每条黄线代表线性模型计算出来的值与实际输出值之间的差值:
用表示实验得到的实际输出,则损失模型为:
显然,,损失模型里得到的loss越小,说明线性模型越准确。
import tensorflow as tf
# 创建变量W 和b节点,并设置初始值
W = tf.Variable([.1], dtype=tf.float32)
b = tf.Variable([-.1], dtype=tf.float32)
# 创建x 节点,用来输入实验中的输入数据
x = tf.placeholder(tf.float32)
# 创建线性模型
linear_model = W * x + b
# 创建y 节点,用来输入实验中得到的输入数据,用于损失模型计算
y = tf.placeholder(tf.float32)
# 创建损失模型
loss = tf.reduce_sum(tf.square(linear_model - y))
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
print(sess.run(linear_model, {x: [1, 2, 3, 6, 8]}))
print(sess.run(loss, {x: [1, 2, 3, 6, 8], y: [4.8, 8.5, 10.4, 21.0, 25.3]}))
fixW = tf.assign(W, [2.])
fixb = tf.assign(b, [1.])
sess.run([fixW, fixb])
print(sess.run(loss, {x: [1, 2, 3, 6, 8], y: [4.8, 8.5, 10.4, 21.0, 25.3]}))
[0. 0.1 0.20000002 0.5 0.7 ]
1223.0499
159.93999
我们需要不断调整变量W
和b
的值,找到使损失值最小的W
和b
。这肯定是一个very boring的过程,因此 TensorFlow 提供了训练模型的方法,自动帮我们进行这些繁琐的训练工作。
使用 TensorFlow 训练模型
TensorFlow 提供了很多优化算法来帮助我们训练模型。最简单的优化算法是梯度下降(Gradient Descent)算法,它通过不断的改变模型中变量的值,来找到最小损失值。
# 创建一个梯度下降优化器,学习率为0.001
optimizer = tf.train.GradientDescentOptimizer(0.001)
train = optimizer.minimize(loss)
# 用两个数组保存训练数据
x_train = [1, 2, 3, 6, 8]
y_train = [4.8, 8.5, 10.4, 21.0, 25.3]
# 训练10000次
for i in range(10000):
sess.run(train, {x: x_train, y: y_train})
# 打印
print('W:%s b:%s loss:%s' % (sess.run(W), sess.run(b), sess.run(loss, {x: x_train, y: y_train})))
整合一下
import tensorflow as tf
# 创建变量W 和b节点,并设置初始值
W = tf.Variable([.1], dtype=tf.float32)
b = tf.Variable([-.1], dtype=tf.float32)
# 创建x 节点,用来输入实验中的输入数据
x = tf.placeholder(tf.float32)
# 创建线性模型
linear_model = W * x + b
# 创建y 节点,用来输入实验中得到的输入数据,用于损失模型计算
y = tf.placeholder(tf.float32)
# 创建损失模型
loss = tf.reduce_sum(tf.square(linear_model - y))
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
# 创建一个梯度下降优化器,学习率为0.001
optimizer = tf.train.GradientDescentOptimizer(0.001)
train = optimizer.minimize(loss)
# 用两个数组保存训练数据
x_train = [1, 2, 3, 6, 8]
y_train = [4.8, 8.5, 10.4, 21.0, 25.3]
# 训练10000次
for i in range(10000):
sess.run(train, {x: x_train, y: y_train})
# 打印
print('W:%s b:%s loss:%s' % (sess.run(W), sess.run(b), sess.run(loss, {x: x_train, y: y_train})))
W:[2.982361] b:[2.0705438] loss:2.1294134
使用tensorboard
TensorFlow提供了一个可视化工具TensorBoard. 其可以有效的展示TensorFlow在运行过程中的计算图,各种指标随着时间的变化趋势以及训练中使用到的图像等信息。
import tensorflow as tf
# 定义一个简单的计算图,实现向量加法的操作
input1 = tf.constant([1.0, 2.0, 3.0], name='input1')
input2 = tf.Variable(tf.random_uniform([3]), name='input2')
output = tf.add_n([input1, input2], name='add')
# 生成一个写日志的writer,并将当前的Tensorflow计算图写入日志
writer = tf.summary.FileWriter('D:/path/to/log', tf.get_default_graph())
writer.close()
在命令行中运行:tensorboard --logdir=D:/path/to/log 在浏览器下输入127.0.0.1:6006就可以进入TensorBoard
TensorBoard可视化
TensorBoard可视化得到的图并不仅是将TensorFlow计算图中的节点和边直接可视化,它会根据每个TensorFlow计算节点的命名空间来整理可视化得到的效果图,使得神经网络的整体结构不会被过多的细节所淹没。 TensorBoard支持通过TensorFlow命名空间来整理可视化效果图上的节点。同一命名空间下的所有节点会被缩成一个点,只有顶层命名空间中的节点才会被显示在TensorBoard可视化效果图上。
import tensorflow as tf
# 将输入定义放在各自的命名空间中,从而使得tensorboard可以根据命名空间来整理
# 可视化效果图上的节点
with tf.name_scope('input1'):
input1 = tf.constant([1.0, 2.0, 3.0], name='input1')
with tf.name_scope('input2'):
input2 = tf.Variable(tf.random_uniform([3]), name='input2')
output = tf.add_n([input1, input2], name='add')
writer = tf.summary.FileWriter('D:/path/to/log', tf.get_default_graph())
writer.close()
import tensorflow as tf
# 创建变量W 和b节点,并设置初始值
W = tf.Variable([0], dtype=tf.float32, name='W')
b = tf.Variable([0], dtype=tf.float32, name='b')
# 创建x 节点,用来输入实验中的输入数据
x = tf.placeholder(tf.float32, name='x')
# 创建线性模型
linear_model = W * x + b
# 创建y 节点,用来输入实验中得到的输入数据,用于损失模型计算
y = tf.placeholder(tf.float32, name='y')
# 损失模型隐藏到loss-model模块
with tf.name_scope("loss-model"):
# 创建损失模型
loss = tf.reduce_sum(tf.square(linear_model - y))
tf.summary.scalar('loss', loss)
# 创建一个梯度下降优化器,学习率为0.001
optimizer = tf.train.GradientDescentOptimizer(0.001)
train = optimizer.minimize(loss)
# 用两个数组保存训练数据
x_train = [1, 2, 3, 6, 8]
y_train = [4.8, 8.5, 10.4, 21.0, 25.3]
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
# 调用 merge_all() 收集所有的操作数据
merged = tf.summary.merge_all()
# 模型运行产生的所有数据保存到 /tmp/tensorflow 文件夹供 TensorBoard 使用
writer = tf.summary.FileWriter('/tmp/tensor', sess.graph)
# 训练10000次
for i in range(10000):
summary, _ = sess.run([merged, train], {x: x_train, y: y_train})
# 收集每次训练产生的数据
writer.add_summary(summary, i)
# 打印
print('W:%s b:%s loss:%s' % (sess.run(W), sess.run(b), sess.run(loss, {x: x_train, y: y_train})))
评估模型
y=2.98x+2.07
但是这个我们自认为的最优模型是否会一直是最优的?我们需要通过一些新的实验数据来评估(evaluation)模型的泛化性能(generalization performance),如果新的实验数据应用到到这个模型中损失值越小,那么这个模型的泛化性能就越好,反之就越差。
tf.estimator
是TensorFlow提供的高级库,提供了很多常用的训练模型,可以简化机器学习中的很多训练过程,如:
- 运行训练循环
- 运行评估循环
- 管理训练数据集
前面我们构建了一个线性模型,通过训练得到一个线性回归方程。tf.estimator
中也提供了线性回归的训练模型tf.estimator.LinearRegressor
,下面的代码就是使用LinearRegressor
训练并评估模型的方法:
例2、
一、数据的准备
Kaggle里包含了42000份训练数据和28000份测试数据(和谷歌准备的MNIST数据,在数量上有所不同)。
Kaggle的数据都是表格形式的,和MNIST给的图片不一样。但实际上只是对图片的信息进行了处理,把一个28*28的图片信息,变成了28*28=784的一行数据。
MNIST的图片信息:
它每份的图片都是被规范处理过的,是一张被放在中间部位的灰度图。
每一个图片均为28×28像素,我们可以将其理解为一个二维数组的结构:
28*28 = 784,也就是说,这个二维数组可以转为一个784个数字组成的一维数组。
扁平化会丢失图片的二维结构信息,好的图形结构算法都会利用二维结构信息,但是为了简化过程便于理解,这里先使用这种一维结构来进行分析。
这样,上面的训练数据和测试数据,都可以分别转化为[42000,769]和[28000,768]的数组。为什么训练数据会多一列呢?因为有一列存的是这个图片的结果。好我们继续来看图片:
这个图片上我们可以看出来,第一列是存的结果,后面784列存的是图片的像素信息。
二、模型的设计
1)使用一个最简单的单层的神经网络进行学习
2)用SoftMax来做为激活函数
目前主流的几个激活函数是:sigmoid,tanh,ReLU。
sigmoid:采用S形函数,取值范围[0,1]
tanh:双切正切函数,取值范围[-1,1]
ReLU:简单而粗暴,大于0的留下,否则一律为0。
SoftMax:我们知道max(A,B),是指A和B里哪个大就取哪个值,但我们有时候希望比较小的那个也有一定概率取到,怎么办呢?我们就按照两个值的大小,计算出概率,按照这个概率来取A或者B。比如A=9,B=1,那取A的概率是90%,取B的概率是10%。
这个看起来比max(A,B)这样粗暴的方式柔和一些,所以叫SoftMax(?)
3)用交叉熵来做损失函数
比如,你想把乾坤大挪移练到第七层大圆满,你现在是第五层,那你还差两层,这个两层就是你和大圆满之间的距离。交叉熵通俗的讲就是现在的训练程度和圆满之间的距离,我们希望距离越小越好,所以交叉熵可以作为一个损失函数,来衡量和目标之间的距离。
4)用梯度下降来做优化方式
三、代码实现
1)在写代码的过程中,数据的预处理是最大的一块工作,做一个项目,60%以上的代码在做数据预处理。
2)建立神经网络,设置损失函数,设置梯度下降的优化参数
3)初始化变量,设置好准确度的计算方法,在Session中运行
import tensorflow as tf
import pandas as pd
import numpy as np
train = pd.read_csv('data/train.csv')
images_train = train.iloc[:, 1:].values
labels_train = train.iloc[:, 0].values
test = pd.read_csv('data/test.csv')
images_test = test.iloc[:, :].values
# 对输入进行处理
images_train = images_train.astype(np.float)
images_train = np.multiply(images_train, 1.0 / 255.0)
images_test = images_test.astype(np.float)
images_test = np.multiply(images_test, 1.0 / 255)
images_size = images_train.shape[1]
images_width = images_height = np.ceil(np.sqrt(images_size)) # ceil 取整
labels_count = np.unique(labels_train).shape[0]
# 进行one-hot编码 ????
def dense_to_one_hot(labels_dense, num_classes):
num_labels = labels_dense.shape[0]
index_offset = np.arange(num_labels) * num_classes
labels_one_hot = np.zeros((num_labels, num_classes))
labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1
return labels_one_hot
labels = dense_to_one_hot(labels_train, labels_count)
labels = labels.astype(np.uint8)
batch_size = 64 # 一批大小
n_batch = int(len(images_train) / batch_size) # 多少批
x = tf.placeholder('float', shape=[None, images_size])
y = tf.placeholder('float', shape=[None, labels_count])
weights = tf.Variable(tf.zeros([784, 10]))
biases = tf.Variable(tf.zeros([10]))
result = tf.matmul(x, weights) + biases
predictions = tf.nn.softmax(result)
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=predictions))
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
for epoch in range(50):
for batch in range(n_batch - 1):
batch_x = images_train[batch * batch_size:(batch + 1) * batch_size]
batch_y = labels[batch * batch_size:(batch + 1) * batch_size]
sess.run(train_step, feed_dict={x: batch_x, y: batch_y})
batch_x = images_train[n_batch * batch_size:]
batch_y = labels[n_batch * batch_size:]
sess.run(train_step, feed_dict={x: batch_x, y: batch_y})
myPrediction = sess.run(predictions, feed_dict={x: images_test})
label_test = np.argmax(myPrediction, axis=1)
pd.DataFrame(label_test).to_csv('simpleNN1.csv')
持久化代码
我们在用tensorflow训练模型时,可能需要训练很长很长一段时间,为了方便下次使用,应该将模型保存起来。在sklearn中,我们可以使用pickle模块进行模型保存;而在tensorflow中,我们可以使用它自带的Saver()类进行模型的保存。
Saver类是用于保存和恢复变量的。
保存模型
tf.train.Saver.save(sess, save_path, global_step=None, latest_filename=None, meta_graph_suffix='meta',
write_meta_graph=True, write_state=True)
- sess: 表示当前会话,当前会话记录了当前的变量值。
- save_path: String类型,用于指定训练结果的保存路径。
- global_step:表示当前是第几步。如果提供的话,这个数字会添加到save_path后面,用于构建checkpoint文件。这个参数有助于我们区分不同训练阶段的结果。
注意:
# 需要在本python脚本文件下存在model目录
# 否则提示错误 ValueError: Parent directory of Saved_model/model.ckpt doesn't exist, can't save.
虽然上面的程序只指定了一个文件路径,但是在这个文件目录下会出现4个文件
- model.ckpt.meta 保存了计算图的结构
.meta 文件以 “protocol buffer”格式保存了整个模型的结构图,模型上定义的操作等信息。
- model.ckpt.index
- model.ckpt.data-00000-of-00001
.data-00000-of-00001 文件和 .index 文件合在一起组成了 ckpt 文件,保存了网络结构中所有 权重和偏置 的数值。
.data文件保存的是变量值,.index文件保存的是.data文件中数据和 .meta文件中结构图之间的对应关系(Mabey)。
一个文本文件,记录了训练过程中在所有中间节点上保存的模型的名称,首行记录的是最后(最近)一次保存的模型名称。我们可以指定保存最近的N个Checkpoints文件。
- checkpoint
加载已经保存的模型
tf.train.Saver.restore(sess, save_path)
- sess: 表示当前会话,之前保存的结果将被加载入这个会话
- save_path: 同保存模型时用到的save_path参数
两段代码唯一不同的是,在加载模型的代码中没有运行变量的初始化过程,而是将变量的值通过已经保存的模型加载进来。如果不希望重复定义图上的运算,也可以直接加载已经持久化的图。
一个稍微复杂一些的例子:
import tensorflow as tf
import numpy as np
x = tf.placeholder(tf.float32, shape=[None, 1])
y = 4 * x + 4
w = tf.Variable(tf.random_normal([1], -1, 1))
b = tf.Variable(tf.zeros([1]))
y_predict = w * x + b
loss = tf.reduce_mean(tf.square(y - y_predict))
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)
isTrain = True
train_steps = 100
checkpoint_steps = 50 # 表示训练多少次保存一下checkpoints
checkpoint_dir = 'model/'
saver = tf.train.Saver() # defaults to saving all variables - in this case w and b
x_data = np.reshape(np.random.rand(10).astype(np.float32), (10, 1))
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
if isTrain:
for i in range(train_steps):
sess.run(train, feed_dict={x: x_data})
if (i + 1) % checkpoint_steps == 0:
saver.save(sess, checkpoint_dir + 'model.ckpt', global_step=i + 1)
else:
ckpt = tf.train.get_checkpoint_state(checkpoint_dir)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess, ckpt.model_checkpoint_path)
else:
pass
print(sess.run(w))
print(sess.run(b))
查看TensorFlow中checkpoint内变量的几种方法:
查看ckpt中变量的方法有三种:
- 在有model的情况下,使用tf.train.Saver进行restore
- 使用tf.train.NewCheckpointReader直接读取ckpt文件,这种方法不需要model。
- 使用tools里的freeze_graph来读取ckpt
注意:
- 如果模型保存为.ckpt的文件,则使用该文件就可以查看.ckpt文件里的变量。ckpt路径为 model.ckpt
- 如果模型保存为.ckpt-xxx-data (图结构)、.ckpt-xxx.index (参数名)、.ckpt-xxx-meta (参数值)文件,则需要同时拥有这三个文件才行。并且ckpt的路径为 model.ckpt-xxx
2. 使用tf.train.NewCheckpointReader直接读取ckpt文件里的变量,使用tools.inspect_checkpoint里的print_tensors_in_checkpoint_file函数打印ckpt里的东西
#使用NewCheckpointReader来读取ckpt里的变量
from tensorflow.python import pywrap_tensorflow
checkpoint_path = os.path.join(model_dir, "model.ckpt")
reader = pywrap_tensorflow.NewCheckpointReader(checkpoint_path) #tf.train.NewCheckpointReader
var_to_shape_map = reader.get_variable_to_shape_map()
for key in var_to_shape_map:
print("tensor_name: ", key)
#print(reader.get_tensor(key))
#使用print_tensors_in_checkpoint_file打印ckpt里的内容
from tensorflow.python.tools.inspect_checkpoint import print_tensors_in_checkpoint_file
print_tensors_in_checkpoint_file(file_name, #ckpt文件名字
tensor_name, # 如果为None,则默认为ckpt里的所有变量
all_tensors, # bool 是否打印所有的tensor,这里打印出的是tensor的值,一般不推荐这里设置为False
all_tensor_names) # bool 是否打印所有的tensor的name
#上面的打印ckpt的内部使用的是pywrap_tensorflow.NewCheckpointReader所以,掌握NewCheckpointReader才是王道
3.使用tools里的freeze_graph来读取ckpt
from tensorflow.python.tools import freeze_graph
freeze_graph(input_graph, #=some_graph_def.pb
input_saver,
input_binary,
input_checkpoint, #=model.ckpt
output_node_names, #=softmax
restore_op_name,
filename_tensor_name,
output_graph, #='./tmp/frozen_graph.pb'
clear_devices,
initializer_nodes,
variable_names_whitelist='',
variable_names_blacklist='',
input_meta_graph=None,
input_saved_model_dir=None,
saved_model_tags='serve',
checkpoint_version=2)
#freeze_graph_test.py讲述了怎么使用freeze_grapg。