tensorflow-编程基础之编程模型

TensorFlow的命令来源于本身的运行原理。Tensor(张量)意味着N维数组,Flow(流)意味着基于数据流图的计算。TensorFlow是张量从图像的一端流动到另一端的计算过程,这也是TensorFlow的编程模型。

模型的运行机制

TensorFlow的运行机制属于’定义’与’运行’相分离。从操作层面可以抽象成两种:模型构建和模型运行。

名称 含义
张量(tensor) 数据,即某一类型的多维数组
变量(Variable) 常用语定义模型中的参数,是通过不断训练得到的值
占位符(placeholder) 输入变量的载体,也可以理解成定义函数时的参数
图中的节点操作(operation, OP) 即一个OP获得0个或者多个tensor,执行计算,输出额外的0个或者多个tensor

关于’图’的理解:

  1. 一个’图’代表一个计算任务
  2. 在模型运行的环节中,’图’会在回话(session)里被启动。
  3. session将图的OP分发到如CPU或GPU之类的设备上,同时提供执行OP的方法。这些方法执行后,将产生的tensor返回。在python语言中,返回的tensor是numpy ndarray对象;在C和C++语言中,返回的tensor是TensorFlow::Tensor实例。
    session与图的工作关系如下:

在实际环境中,这种运行情况会有3种应用场景,分别是训练场景、测试场景与使用场景。在训练场景下图的运行方式与其他两种不同,具体如下:

  1. 训练场景
    实现模型从无到有的过程,通过样本的学习训练,调整学习参数,形成最终的模型。其过程是将给定的样本和标签作为输入节点,通过大量的循环迭代,将图中的正向运算(从输入的样本通过OP运算得到输出的方向)得到的输出值,再进行反向运算(从输出到输入的方向),以更新模型中的学习参数,最终使模型产生的正向结果最大化的接近样本标签。这样就得到了一个可以拟合样本规律的模型。
  2. 测试场景和使用场景
    测试场景是利用图的正向运算得到的结果与真实值进行比较的差别;使用场景也是利用图的正向运算得到结果,并直接使用。所以二者的运算过程是一样的。对于该场景下的模型与正常编程用到的函数特别相似。在函数中,可以分为实参、形参、函数体与返回值。同样在模型中,实参就是输入的样本,形参就是占位符,运算过程就相当于函数体,得到的结果相当于返回值。

session与图的交互过程中定义了两种数据的流向机制:

  • 注入机制(feed): 通过占位符向模式中传入数据
  • 取回机制(fetch): 从模式中得到结果

session示例

1
2
3
4
5
6
7
8
# -*- coding: utf-8 -*-
import tensorflow as tf
hello = tf.constant('Hello, TensorFlow!') # 定义一个常量
sess = tf.Session() # 建立一个session
print(sess.run(hello)) # 通过session里面run函数来运行结果
sess.close() # 关闭session
输出:
b'Hello, TensorFlow!'

计算两个变量(3和4)的相加与相乘值

1
2
3
4
5
6
7
8
9
10
11
# -*- coding: utf-8 -*-
import tensorflow as tf
a = tf.constant(3) # 定义常量3
b = tf.constant(4) # 定义常量4
with tf.Session() as sess:
print('相加:%i' % sess.run(a + b))
print('相乘:%i' % sess.run(a * b))
输出:
相加:7
相乘:12

占位符使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding: utf-8 -*-
import tensorflow as tf
a = tf.placeholder(tf.int16) # 定义占位符a
b = tf.placeholder(tf.int16) # 定义占位符b
add = tf.add(a, b) # 相加
mul = tf.multiply(a, b) # 相乘
with tf.Session() as sess:
print('相加: %i' % sess.run(add, feed_dict={a: 3, b: 4})) # 通过feed_dict把具体的值放到占位符里
print('相乘: %i' % sess.run(mul, feed_dict={a: 3, b: 4})) # 通过feed_dict把具体的值放到占位符里
print(sess.run([mul, add], feed_dict={a: 3, b: 4})) # 一次将多个节点取出(使用注入机制获取节点)
输出:
相加: 7
相乘: 12
[12, 7]

建立session还有以下两种方式:

  • 交互式session方式:一般在Jupyter环境下使用较多,具体用法与前面的with session类似
  • 使用Supervisor方式:该方式会更高级一些,使用起来也更加复杂,可以自动来管理session中的具体任务,例如载入/栽出检查点文件、写入TensorBoard等,另外该方法还支持分布式训练的部署:
    1
    sess = tf.InteractiveSession()

保存和载入模型

保存模型

首先需要创建一个saver,然后再session中通过saver的save方法即可将模型保存起来:

1
2
3
4
5
saver = tf.train.Saver() # 生产saver
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # 对模型初始化
# 中间省却训练步骤
saver.save(sess, 'save_path/file_name') # 训练完成以后,使用saver.save来保存, 如果file_name不存在,会自动创建

载入模型

将模型保存以后,在session中通过saver.restore()函数来载入模型:

1
2
3
4
5
saver = tf.train.Saver()
with tf.Session() as sess:
# 参数可以进行初始化,也可以不进行初始化。即使初始化了,初始化的值也会被restore的值给覆盖掉
sess.run(tf.global_variables_initializer())
saver.restore(sess, 'save_path/file_name')

示例

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
# -*- coding: utf-8 -*-
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
train_X = np.linspace(-1, 2, 100)
train_Y = 2 * train_X + np.random.randn(*train_X.shape) * 0.3 # y=2x 加入噪声
plt.plot(train_X, train_Y, 'ro', label='Original data') # 显示模拟数据点
plt.legend()
plt.show()
# 创建模型
# placeholder定义占位符(定义输入节点)
X = tf.placeholder('float')
Y = tf.placeholder('float')
# 模型参数
W = tf.Variable(tf.random_normal([1]), name='weight')
b = tf.Variable(tf.zeros([1]), name='bias')
# 前向结构
z = tf.multiply(X, W) + b
# 反向优化
cost = tf.reduce_mean(tf.square(Y - z)) # 生成值与真实值的平方差
# 定义一个学习率,代表调整参数的速度。这个值一般小于1.这个值越大,表明调整的速度越大,但不精确;值越小,表明调整的精确度越高,速度越慢。
learning_rate = 0.01
# 梯度下降 GradientDescentOptimizer函数是一个封装好的梯度下降算法,参数learning_rate叫做学习率,用来指定参数调节的速度。
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
# 训练模型
# 初始化所有变量
init = tf.global_variables_initializer()
# 定义参数
training_epochs = 20 # 设置迭代次数20
display_step = 2
saver = tf.train.Saver()
save_dir = 'log/'
# 启动session tensorflow中的任务是通过session来进行的
with tf.Session() as sess:
sess.run(init)
plotdata = {'batchsize': [], 'loss': []} # 存放批次值和损失值
# 向模型输入数据
for epoch in range(training_epochs):
for x, y in zip(train_X, train_Y):
# 通过sess.run 来进行网络节点运算,通过feed机制将真实数据灌到占位符对应的位置
sess.run(optimizer, feed_dict={X: x, Y: y})
# 显示训练中的详细信息
if epoch % display_step == 0:
loss = sess.run(cost, feed_dict={X: train_X, Y: train_Y})
print('Epoch:', epoch + 1, 'cost=', loss, 'W=', sess.run(W), 'b=', sess.run(b))
if not loss == 'NA':
plotdata['batchsize'].append(epoch)
plotdata['loss'].append(loss)
print('Finished!')
saver.save(sess, save_dir + 'linermodel.cpkt')
# 通过结果我们发现,随着 W 和 b的调整, cost值整体在不断变小
print('cost=', sess.run(cost, feed_dict={X: train_X, Y: train_Y}), 'W=', sess.run(W), 'b=', sess.run(b))
# 图形显示
plt.plot(train_X, train_Y, 'ro', label='Original data')
plt.plot(train_X, sess.run(W) * train_X + sess.run(b), label='Fittedline')
plt.legend()
plt.show()
#载入模型
#with tf.Session() as sess:
# sess.run(tf.global_variables_initializer())
# saver.restore(sess, save_dir + 'linermodel.cpkt')
# print('x=0.2, z=', sess.run(z, feed_dict={X: 0.2}))

运行完成后,在代码同级目录下log文件夹里生成了几个文件:

  • checkpoint
  • linermodel.cpkt.data-00000-of-00001
  • linermodel.cpkt.index
  • linermodel.cpkt.meta

把载入模型的代码块注释打开,就能从保存的模型成功载入,并计算出正确的值了。

获取模型内存

上面模型保存了,但仍然对我们不透明,如何知道保存了哪些东西?

1
2
3
4
5
6
7
8
9
from tensorflow.python.tools.inspect_checkpoint import print_tensors_in_checkpoint_file
print_tensors_in_checkpoint_file(save_dir + 'linermodel.cpkt', None, True)
输出:
tensor_name: bias
[0.02912794]
tensor_name: weight
[1.9792577]
可以看到,tensor_name 后面跟的就是创建的变量名,接着是它的数值

保存模型的其他方法

  1. 指定变量名字与变量的对应关系
    1
    saver = tf.train.Saver({'weight': W, 'bias': b})
1
2
saver = tf.train.Saver([W, b]) # 放到一个list里
saver = tf.train.Saver({v.op.name: v for v in [W, b]}) # 将op的名字当做key

检查点(CheckPoint)

保存检查点

保存模型并不限于在训练之后,在训练之中也需要保存,因为TensorFlow训练模型时难免会出现中断的情况。我们自然希望能够将辛苦得到的中间参数保留下来,否则下次又要重新看似是。这种在训练中保存模型,习惯上称之为保存检查点。

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
# -*- coding: utf-8 -*-
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
plotdata = {'batchsize': [], 'loss': []}
# 定义生成loss可视化的函数
def moving_average(a, w=10):
if len(a) < w:
return a[:]
return [val if idx < w else sum(a[(idx - w): idx]) / w for idx, val in enumerate(a)]
# 生成模拟数据
train_X = np.linspace(-1, 1, 100)
train_Y = 2 * train_X + np.random.randn(*train_X.shape) * 0.3 # y = 2x, 但是加入了噪声
# 图形显示
plt.plot(train_X, train_Y, 'ro', label='Original data')
plt.legend()
plt.show()
tf.reset_default_graph()
# 创建模型
# 占位符
X = tf.placeholder('float')
Y = tf.placeholder('float')
# 模型参数
W = tf.Variable(tf.random_normal([1]), name='weight')
b = tf.Variable(tf.zeros([1]), name='bias')
# 前向结构
z = tf.multiply(X, W) + b
# 反向优化
cost = tf.reduce_mean(tf.square(Y - z))
learning_rate = 0.01
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost) # 梯度下降
# 初始化所有变量
init = tf.global_variables_initializer()
# 定义学习参数
training_epochs = 20
display_step = 2
saver = tf.train.Saver(max_to_keep=1) # 生成saver, max_to_keep=1只保留一个文件,新生成的模型会覆盖以前的模型
save_dir = 'log/'
# 启动图
with tf.Session() as sess:
sess.run(init)
# 向模型中输入数据
for epoch in range(training_epochs):
for x, y in zip(train_X, train_Y):
sess.run(optimizer, feed_dict={X: x, Y: y})
# 显示训练中的详细信息
if epoch % display_step == 0:
loss = sess.run(cost, feed_dict={X: train_X, Y: train_Y})
print('Epoch:', epoch + 1, 'cost=', loss, 'W=', sess.run(W), 'b=', sess.run(b))
if not loss == 'NA':
plotdata['batchsize'].append(epoch)
plotdata['loss'].append(loss)
saver.save(sess, save_dir + 'linermodel.cpkt', global_step=epoch)
print('Finished!')
print('cost=', sess.run(cost, feed_dict={X: train_X, Y: train_Y}), 'W=', sess.run(W), 'b=', sess.run(b))
# 显示模型
plt.plot(train_X, train_Y, 'ro', label='Original data')
plt.plot(train_X, sess.run(W) * train_X + sess.run(b), label='Fitted Wline')
plt.legend()
plt.show()
plotdata['avgloss'] = moving_average(plotdata['loss'])
plt.figure(1)
plt.subplot(211)
plt.plot(plotdata['batchsize'], plotdata['avgloss'], 'b--')
plt.xlabel('Minibatch number')
plt.ylabel('Loss')
plt.title('Minibatch run vs. Training loss')
plt.show()
# 重启sess,载入检查点
load_epoch = 18
with tf.Session() as sess2:
sess2.run(tf.global_variables_initializer())
saver.restore(sess2, save_dir + 'linermodel.cpkt-' + str(load_epoch))
print('x=0.2, z=', sess2.run(z, feed_dict={X: 0.2}))

如果觉得通过指定迭代次数比较麻烦,还有一个好方法可以快速获取到检查点文件:

1
2
3
ckpt = tf.train.get_checkpoint_state(ckpt_dir)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess, ckpt.model_checkpoint_path)

还可以更简洁一些:

1
2
3
kpt = tf.train.latest_checkpoint(save_dir)
if kpt != None:
saver.restore(sess, kpt)

更简便的保存检查点

通过tf.train.MonitoredTrainingSession函数。该函数可以直接实现保存载入检查点模型的文件。这里按照训练时间来保存。通过指定save_checkpoint_secs参数的具体秒数,来设置没训练多久保存一次检查点。

1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding: utf-8 -*-
import tensorflow as tf
tf.reset_default_graph()
global_step = tf.train.get_or_create_global_step() # 这种方式必须定义global_step变量,否则会报错
step = tf.assign_add(global_step, 1)
# 设置检查点路径为 log/checkpoints
with tf.train.MonitoredTrainingSession(
checkpoint_dir='log/checkpoints',
save_checkpoint_secs=2) as sess: # 如果不设置save_checkpoint_secs参数,默认的保存时间间隔为10分钟(更适用于大型数据集来训练复杂模型的情况)
print(sess.run([global_step]))
while not sess.should_stop(): # 启用死循环,当sess不结束时就不停止
i = sess.run(step)
print(i)

将程序停止,可以看到log/checkpoints下面生成了监测点文件model.ckpt-73647.meta,再次运行程序可以看到程序自动载入检查点文件是从第72748次开始运行的。

TensorBoard可视化

TensorFlow还提供了一个可视化工具TensorBoard。它可以将训练过程中的各种绘制数据展示出来,包括标量(Scalars)、图片(Images)、音频(Audio)、计算图(Graph)、数据分布、直方图(Histograms)和嵌入式向量。可以通过网页来观察模型的结构和训练过程中各个参数变化。
当然,TensorBoard不会自动把代码展示出来,其实他是一个日志展示系统,需要在session中运算图时,将各种类型的数据汇总并输出到日志文件中。然后启动TensorBoard服务,TensorBoadr读取这些日志文件,并开启6006端口提供Web服务,让用可以在浏览器中查看数据。

可视化函数

TensorFlow提供了一系列API来生成这些数据:

函数 说明
tf.summary.scalar(tags, values, collections=None, nameNone) 标量数据汇总, 输出protobuf
tf.summary.histogram(tag, values, collections=None, name=None) 记录变量var的直方图,输出带有直方图的汇总的protobuf
tf.summary.image(tag, tensor, max_images=3, collections=None, name=None) 图像数据汇总,输出protobuf
tf.summary.merge(inputs, collections=None, name=None) 合并所有的汇总日志
tf.summary.FileWriter 创建一个SummaryWriter
Class SummaryWriter: add_summary(), add_sessionlog(), add_event(), add_graphy() 将protobuf写入文件的类

示例

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
# -*- coding: utf-8 -*-
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
plotdata = {'batchsize': [], 'loss': []}
# 定义生成loss可视化的函数
def moving_average(a, w=10):
if len(a) < w:
return a[:]
return [val if idx < w else sum(a[(idx - w): idx]) / w for idx, val in enumerate(a)]
# 生成模拟数据
train_X = np.linspace(-1, 1, 100)
train_Y = 2 * train_X + np.random.randn(*train_X.shape) * 0.3 # y = 2x, 但是加入了噪声
# 图形显示
plt.plot(train_X, train_Y, 'ro', label='Original data')
plt.legend()
plt.show()
tf.reset_default_graph()
# 创建模型
# 占位符
X = tf.placeholder('float')
Y = tf.placeholder('float')
# 模型参数
W = tf.Variable(tf.random_normal([1]), name='weight')
b = tf.Variable(tf.zeros([1]), name='bias')
# 前向结构
z = tf.multiply(X, W) + b
tf.summary.histogram('z', z) # 将预测值以直方图形式显示
# 反向优化
cost = tf.reduce_mean(tf.square(Y - z))
tf.summary.scalar('loss_funmction', cost) # 将损失以标量形式显示
learning_rate = 0.01
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost) # 梯度下降
# 初始化所有变量
init = tf.global_variables_initializer()
# 定义学习参数
training_epochs = 20
display_step = 2
saver = tf.train.Saver(max_to_keep=1) # 生成saver, max_to_keep=1只保留一个文件,新生成的模型会覆盖以前的模型
save_dir = 'log/'
with tf.Session() as sess:
sess.run(init)
merged_summary_op = tf.summary.merge_all() # 合并所有summary
# 创建summary_writer,用于写文件
summary_writer = tf.summary.FileWriter('log/mnist_with_summaries', sess.graph)
# 向模型中输入数据
for epoch in range(training_epochs):
for x, y in zip(train_X, train_Y):
sess.run(optimizer, feed_dict={X: x, Y: y})
# 生成summary
summary_str = sess.run(merged_summary_op, feed_dict={X: x, Y: y})
summary_writer.add_summary(summary_str, epoch) # 将summary 写入文件
# 显示训练中的详细信息
if epoch % display_step == 0:
loss = sess.run(cost, feed_dict={X: train_X, Y: train_Y})
print('Epoch:', epoch + 1, 'cost=', loss, 'W=', sess.run(W), 'b=', sess.run(b))
if not loss == 'NA':
plotdata['batchsize'].append(epoch)
plotdata['loss'].append(loss)
saver.save(sess, save_dir + 'linermodel.cpkt', global_step=epoch)
print('Finished!')
print('cost=', sess.run(cost, feed_dict={X: train_X, Y: train_Y}), 'W=', sess.run(W), 'b=', sess.run(b))

然后到log目录下执行:

1
tensorboard --logdir mnist_with_summaries # 这里不能进入到目录mnist_with_summaries里面执行

然后浏览器:localhost:6006 就能看到数据图

参考:<深度学习之TensorFlow: 入门、原理与进阶实战>