底层 API 编程介绍
这篇指南将帮助你使用底层的 TensorFlow 核心 API 进行编程,会涉及到以下几个部分:
- 管理你的 TensorFlow 程序(一张 TensorFlow 计算图,
tf.Graph
)以及 TensorFlow 运行时(一个 TensorFlow 会话,tf.Session
),而不是依赖 Estimators 来管理它们 - 使用
tf.Session
运行 TensorFlow 操作 - 在底层环境中使用高级的组件(datasets, layers, 以及 特征列)
- Premade Estimators
我们推荐尽量使用更高层次的 API 来构建模型,但了解 TensorFlow 核心 API 有以下几个优点:
- 使用低级 TensorFlow 的操作能帮你更加切中肯綮地进行实验和 debug
* 在使用高层次 API 的时候,你能够知道其内部是如何运作的
配置
理解本指南的大部分内容需要你有以下知识储备:
- 会使用 Python 进行编程
- 关于数组的一些知识
- 最好了解一些机器学习的内容
启动 python
然后跟着一起来实操吧!
运行下列代码来配置你的 Python 环境:
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import numpy as np
import tensorflow as tf
张量值
TensorFlow 核心的数据单元是张量(Tensor),一个张量由一个基本数据类型的多维数组来表示。张量的秩指的是它的维度数量,它的形状是一个整数元组,指定了各个维度的数组长度。下面一些张量值的例子:
3. # 秩为 0 的张量,形为 [] 的标量
[1., 2., 3.] # 秩为 1 的张量,形为 [3] 的向量
[[1., 2., 3.], [4., 5., 6.]] # 秩为 2 的张量,形为 [2, 3] 的矩阵
[[[1., 2., 3.]], [[7., 8., 9.]]] # 秩为 3 的张量,形为 [2, 1, 3]
TensorFlow 使用 numpy 数组来表示张量的值。
TensorFlow 核心 API 实战
使用 TensorFlow 核心 API 编程由以下两部分组成:
计算图
计算图是一组定义在图中的 TensorFlow 操作,该图由两种类型的对象组成:
重点:tf.Tensors
并不包含值,而只是操作图中各个部分的工具。
让我们来构建一张简单的计算图。图中最基础的操作就是常量操作,tf.constant()
这个 Python 函数以一个张量值作为输入,其产生结果不需要额外的输入。当我们调用它时,它会输出传递给构造器的那个值。
我们可以创建两个浮点类型的常量 a
和 b
如下列代码所示:
a = tf.constant(3.0, dtype=tf.float32)
b = tf.constant(4.0) # 隐式的创建一个浮点类型
total = a + b
print(a)
print(b)
print(total)
print
语句的结果如下:
Tensor("Const:0", shape=(), dtype=float32)
Tensor("Const_1:0", shape=(), dtype=float32)
Tensor("add:0", shape=(), dtype=float32)
注意,张量的打印结果并不是如你所想的 3.0
,4.0
和 7.0
。上述语句只是构建了计算图,这些 tf.Tesor
对象只代表了将要进行的操作的结果。
计算图中的每个操作都会被赋予一个唯一的名称,这个名称和 Python 对象的名称无关。张量的命名规则是"操作名 + 输出序号"
,如上面的 "add:0"
。
TensorBoard
TensorFlow 提供了一个名为 TensorBoard 的工具。TensorBoard 的功能之一就是可视化一张计算图,几个简单的命令就能实现它。
首先,需要将计算图存为 TensorBoard 的总结文件,代码如下所示:
writer = tf.summary.FileWriter('.')
writer.add_graph(tf.get_default_graph())
你会在当前目录下得到一个 .event
后缀的文件,其命名格式如下:
events.out.tfevents.{timestamp}.{hostname}
接下来,在一个新的终端里,使用下面的命令启动 TensorBoard:
tensorboard --logdir .
你可以在你的浏览器里打开 TensorBoard 的计算图页面,你应该会看到和下面类似的一张图
会话
下面的代码创建了一个 tf.Session
对象,并且调用了它的 run
方法来对我们先前创建的 total
张量求值:
sess = tf.Session()
print(sess.run(total))
当你使用 Session.run
方法来获取一个节点的输出时,TensorFlow 回溯计算图,并先计算出所获取节点需要的输入(如果是某个节点的输出,则计算之),然后输出结果。所以打印的结果是 7.0:
7.0
你可以同时传递给 tf.Session.run
多个张量,run
方法会自动处理任意元组或者是字典的组合,如下面的例子所示:
print(sess.run({'ab':(a, b), 'total':total}))
方法返回的结果和输入具有相同的结构:
{'total': 7.0, 'ab': (3.0, 4.0)}
在调用 tf.Seeion.run
的过程中,每一个 tf.Tensor
张量对象具有唯一的值。比如,下面的代码调用了 tf.random_uniform
函数来产生一个服从均一分布的 3 个元素的随机向量(值介于 [0, 1]
):
vec = tf.random_uniform(shape=(3,))
out1 = vec + 1
out2 = vec + 2
print(sess.run(vec))
print(sess.run(vec))
print(sess.run((out1, out2)))
结果显示了每次调用 run
,都会产生不同的结果,但是在一次调用中,vec
的值保持不变(out1
和 out2
接收同一个随机的输入):
[ 0.52917576 0.64076328 0.68353939]
[ 0.66192627 0.89126778 0.06254101]
(
array([ 1.88408756, 1.87149239, 1.84057522], dtype=float32),
array([ 2.88408756, 2.87149239, 2.84057522], dtype=float32)
)
一些 TensorFlow 函数会返回 tf.Operations
对象而不是 tf.Tensors
,在一个 Operation(操作) 上调用 run
的结果是 None
。运行一个操作的结果不会获取值,但是会产生一系列的副作用,像初始化以及训练操作,这些内容会在后面的部分谈到。
赋值
如果计算图始终只能产生常量结果的话,那就没什么意思了。计算图能够通过使用占位符(placeholder)来接收外部的输入,占位符像是一个函数的参数,能够在之后的操作中进行赋值。
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
z = x + y
print(sess.run(z, feed_dict={x: 3, y: 4.5}))
print(sess.run(z, feed_dict={x: [1, 3], y: [2, 4]}))
结果的输出如下:
7.5
[ 3. 7.]
需要注意,feed_dict
参数可以用来覆盖计算图中的任何张量。占位符和 tf.Tensors
的唯一区别就在于如果占位符没有被赋值,则使用时会抛出错误。
数据集
tf.data.Dataset.make_one_shot_iterator
my_data = [
[0, 1,],
[2, 3,],
[4, 5,],
[6, 7,],
]
slices = tf.data.Dataset.from_tensor_slices(my_data)
next_item = slices.make_one_shot_iterator().get_next()
while True:
try:
print(sess.run(next_item))
except tf.errors.OutOfRangeError:
break
如果 Dataset
依赖于有状态操作,那么使用它之前,你可能需要初始化迭代器,如下所示:
r = tf.random_normal([10,3])
dataset = tf.data.Dataset.from_tensor_slices(r)
iterator = dataset.make_initializable_iterator()
next_row = iterator.get_next()
sess.run(iterator.initializer)
while True:
try:
print(sess.run(next_row))
except tf.errors.OutOfRangeError:
break
网络层
网络层封装了变量和作用其上的操作。比如,全连接层)对输入进行一个加权求和的操作,并且可以作用一个可选择的激活函数。连接的权重和偏置都由网络层对象来管理。
创建网络层
x = tf.placeholder(tf.float32, shape=[None, 3])
linear_model = tf.layers.Dense(units=1)
y = linear_model(x)
网络层根据输入来决定内部的变量的形状大小,所以我们必须设置 x
这一占位符的形状,从而能够让网络层推断出正确的参数形状。
定义了输出的操作 y
之后,在我们运行之前,还有一点需要注意。
初始化网络层
网络层中的变量在使用之前,必须要初始化。你可以逐一的初始化每个变量,同样也能够和下面一样,简单地用一行代码初始化计算图中所有的变量:
init = tf.global_variables_initializer()
sess.run(init)
重点:调用 tf.global_variables_initializer
创建并返回一个 TensorFlow 操作的句柄,当使用 tf.Session.run
来运行它时,这个操作将会初始化所有的全局变量。
还有一点需要注意,global_variables_initializer
只会初始化已经在计算图中定义的变量。所以初始化操作应该是构建计算图的最后一步操作。
执行网络层
网络层初始化之后,和其他张量一样,我们可以对 linear_model
的输出张量进行求值。如下面的代码所示:
print(sess.run(y, {x: [[1, 2, 3],[4, 5, 6]]}))
结果是含有两个元素的向量:
[[-3.41378999]
[-9.14999008]]
网络层函数快捷键
x = tf.placeholder(tf.float32, shape=[None, 3])
y = tf.layers.dense(x, units=1)
init = tf.global_variables_initializer()
sess.run(init)
print(sess.run(y, {x: [[1, 2, 3], [4, 5, 6]]}))
特征列
tf.feature_column.indicator_column
features = {
'sales' : [[5], [10], [8], [9]],
'department': ['sports', 'sports', 'gardening', 'gardening']}
department_column = tf.feature_column.categorical_column_with_vocabulary_list(
'department', ['sports', 'gardening'])
department_column = tf.feature_column.indicator_column(department_column)
columns = [
tf.feature_column.numeric_column('sales'),
department_column
]
inputs = tf.feature_column.input_layer(features, columns)
运行 inputs
会把 features
变成一系列向量。
var_init = tf.global_variables_initializer()
table_init = tf.tables_initializer()
sess = tf.Session()
sess.run((var_init, table_init))
内部状态初始化之后,就可以和其他张量一样运行 inputs
:
print(sess.run(inputs))
这展示了特征列是如何对输入的向量进行编码:“部门”属性使用 one-hot 进行编码,占据每一个向量的前面两个索引([1., 0.] 代表”sports",[0., 1.] 代表“gardening”),“销量”属性则由第三个索引来表示:
[[ 1. 0. 5.]
[ 1. 0. 10.]
[ 0. 1. 8.]
[ 0. 1. 9.]]
训练
熟悉了 TensorFlow 的核心操作之后,一起来动手训练一个小的回归模型吧。
定义数据
先来定义一些输入,x
,以及对应的输出 y_true
:
x = tf.constant([[1], [2], [3], [4]], dtype=tf.float32)
y_true = tf.constant([[0], [-1], [-2], [-3]], dtype=tf.float32)
定义模型
接下来,构建一个简单的线性模型,输出为一个值:
linear_model = tf.layers.Dense(units=1)
y_pred = linear_model(x)
你可以对模型的预测进行求值,如下面的代码所示:
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
print(sess.run(y_pred))
模型还没有经过训练,所以四个预测值和实际值相去甚远。下面是我们的结果,你的结果可能有些不一样:
[[ 0.02631879]
[ 0.05263758]
[ 0.07895637]
[ 0.10527515]]
损失函数
为了优化一个模型,你需要先定义损失函数。我们使用经常应用在回归问题中的平方差作为损失函数。
loss = tf.losses.mean_squared_error(labels=y_true, predictions=y_pred)
print(sess.run(loss))
这会打印出损失函数的值,比如:
2.23962
训练
tf.train.GradientDescentOptimizer
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)
上面的代码构建了优化过程所需的所有计算图的组件,并且返回了一个训练操作。运行这一操作,计算图中的所有变量会被更新,你可以像下面一样运行:
for i in range(100):
_, loss_value = sess.run((train, loss))
print(loss_value)
因为 train
是一个操作,而不是一个张量,所以运行的结果并不会返回值。我们可以运行 loss_value
张量来获知训练的过程,结果如下:
1.35659
1.00412
0.759167
0.588829
0.470264
0.387626
0.329918
0.289511
0.261112
0.241046
...
完整程序
x = tf.constant([[1], [2], [3], [4]], dtype=tf.float32)
y_true = tf.constant([[0], [-1], [-2], [-3]], dtype=tf.float32)
linear_model = tf.layers.Dense(units=1)
y_pred = linear_model(x)
loss = tf.losses.mean_squared_error(labels=y_true, predictions=y_pred)
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
for i in range(100):
_, loss_value = sess.run((train, loss))
print(loss_value)
print(sess.run(y_pred))
接下来
想要学习更多关于使用 TensorFlow 构建模型的内容,你可以参考一下内容:
如果你想要学习更多 TensorFlow 内部的工作原理,你可以参考下列文档,其中对我们涉及的话题有更深入的介绍: