一直以来没有系统学习过TF,为了更加高效的投入到近期的关系抽取比赛中,所以准备系统学习一下,系统学习的内容是李理老师的《Tensorflow简明教程》,地址为 http://fancyerii.github.io/books/tf 。以下为老师的TF教程,以及自己做的一些笔记和标注。
1. 概述
TensorFlow中计算的定义和计算的执行是分开的。这句话究竟怎么理解呢?可能指的是两者是在不同的context下完成的。
我们编写TensorFlow程序通常分为两步:定义计算图;使用session执行计算图。不过TensorFlow 1.5之后引入了Eager Execution,使得我们不需要定义计算图,直接就可以执行计算,从而简化代码尤其是简化调试。因为本课程不会用到Eager Execution,所以略过,有兴趣的读者可以参考Tensorflow官方文档。
2. Tensor
Tensor就是张量,0维数组表示一个数(scalar),1维数组表示一个向量(vector),二维数字表示一个矩阵(matrix),三维及其以上表示的是张量。
一个Tensor里的数据都是同一种类型的,比如tf.float32或者tf.string。数组的维度个数叫作rank,比如scalar的rank是0,矩阵的rank是2。数组每一维的大小组成的list叫作shape。比如下面是一些Tensor的例子:
3.0 # rank为0的tensor; 一个scalar,它的shape是[](没有shape信息),
[1., 2., 3.] # rank为1的tensor; 一个vector,它的shape是[3]
[[1., 2., 3.], [4., 5., 6.]] # 一个rank为2的tensor; 一个matrix,shape是[2, 3]
[[[1., 2., 3.]], [[7., 8., 9.]]] # rank为3的tensor,shape是[2, 1, 3]
TensorFlow使用numpy的ndarray来表示Tensor的值。有几种重要的特殊Tensor类型,包括:
- tf.Variable
- tf.constant
- tf.placeholder
- tf.SparseTensor
除了Variable,其它类型的Tensor都是不可修改的对象,因此在一次运算的执行时它只会有一个值。但是这并不是说每次执行是值是不变的,因为有些Tensor可能是有随机函数生成的,每次执行都会产生不同的值(但是在一次执行过程中只有一个值)。那什么是一次执行呢?
3. 数据流图
TensorFlow的计算图使用数据流图来表示。图中有两种类型的对象:
- Tensors 图中的边。Tensor在图中的“流动”表示了数据的变化和处理,这也是TensorFlow名字的由来。大部分TensorFlow函数都会返回Tensor。
- Operations(简称ops) 图中的点。Operation表示计算,它的输入和输出都是Tensor。
这部分的理解可参考语法树结构(参考程序员的自我修养)。不同的是把数据变成了边,运算变成了节点。
需要注意的是,tf.Tensor并不存储值,它只是数据流图中的节点,它表示一个计算,这个计算会产生一个Tensor。比如下面的例子(这句话是错的,其中tf.Tensor是边(edge),可参考链接https://www.tensorflow.org/guide/graphs)
a = tf.constant(3.0, dtype=tf.float32)
b = tf.constant(4.0) # 也是tf.float32,通过4.0推测出来的类型。
total = a + b
print(a)
print(b)
print(total)
它的运行结果为:
Tensor("Const:0", shape=(), dtype=float32)
Tensor("Const_1:0", shape=(), dtype=float32)
Tensor("add:0", shape=(), dtype=float32)
调用tf.constant(3.0) 创建了一个tf.Operation,它将会产生值3.0, 并将其添加到默认的数据流图中, and returns a tf.Tensor that represents the value of the constant。
print a,b和c并不会得到3,4和7。这里的a,b和c只是Graph中的Operation,执行这些Operation才会得到对应的Tensor值。每个Tensor都有一个数据类型dtype,tf.constant()函数会根据我们传入的值推测其类型,对于浮点数,默认类型是tf.float32。这和传统的编程语言有一些区别,对于c/c++/java语言来说,3.0这个字面量代表的是双精度浮点数(double或者TensorFlow的float64),而对于Python来说只有双精度浮点数(类型叫float)。因为对于大部分机器学习算法来说,单精度浮点数以及够用了,使用双精度浮点数需要更多的内存和计算时间,而且很多GPU的双精度浮点数计算速度要比单精度慢几十倍(不同的架构差别很大,比如Nvidia的GTX系列双精度慢很多,但是Nvidia的Tesla系列差别较小),因此TensorFlow默认会把3.0推测为tf.float32。
比如我们如下最简单的代码:
import tensorflow as tf
a = tf.add(3, 4)
我们使用TensorBoard(后面会介绍)可以看到实际的数据流图如下图所示。在数据流中每一个点表示一个Operation,比如add,每一条边表示一个Tensor。读者可能会奇怪,哪里来的x和y呢?x和y是TensorFlow自动为我们创建了两个Tensor 3和4。因为add函数会把两个Tensor加起来,它需要两个Tensor作为参数,但是我们传入的是两个数字,因此TensorFlow会自动的帮我们创建两个Constant,并且命名为x和y。因此下面的代码和上面的是等价的(结果是等价的,但是过程并不完全等价吧,后者是由节点生成边(多了这一步吧),然后两个边再和节点进行运算的):
x=tf.constant(3, name="x")
y=tf.constant(4, name="y")
a=tf.add(x,y)
注意constant不是一个Tensor,但是它内部保存了一个Tensor,当把它作为add的一个参数的时候,它们之间的边就表示把x内部的Tensor传给add。我们可以使用TensorBoard查看x的内容如下:
dtype {"type":"DT_INT32"}
value {"tensor":{"dtype":"DT_INT32","tensor_shape":{},"int_val":3}}
如果我们执行下面的代码:
import tensorflow as tf
a = tf.add(3, 4)
print(a)
有点读者可能期望得到结果7,但是实际结果却是:
Tensor("Add:0", shape=(), dtype=int32)
原因就是add返回的就是数据流图中的一个Operation,我们只是“定义”了一个计算图,但是目前还没有“执行”它。那怎么执行它呢?我们需要创建一个Session对象,然后用这个Session对象来执行图中的某些Operation。比如下面的代码就会定义出计算的结果7。
import tensorflow as tf
a = tf.add(3, 4)
sess = tf.Session()
print(sess.run(a))
sess.close()
这有些麻烦,使用Eager Execution会简单一些,但是目前它不能完全替代这种方法。上面创建Session,然后关闭Session的写法可以使用with,这样不会忘了关闭它。
import tensorflow as tf
a = tf.add(3, 4)
with tf.Session() as sess:
print(sess.run(a))
2. Operation
通常我们通过函数定义Operation之间的依赖关系,比如上面的代码,add产生的Operation会依赖a和b。但有的时候,我们需要某个Operation在其它的一些Operation之后再执行,但是它们并没有直接的函数关系,那么我们可以使用tf.Graph.control_dependencies来定义这种先后顺序关系。比如:
#graph g有5个ops: a, b, c, d, e
g = tf.get_default_graph()
with g.control_dependencies([a, b, c]):
# 只有当a b c都执行和才会执行d和e。
d = ...
e = ...
要注意的是只有在control_dependencies下创建的op才会建立这种依赖关系。比如下面的例子:
x = tf.Variable(0.0)
x_plus_1 = tf.assign_add(x, 1)
with tf.control_dependencies([x_plus_1]):
y = x
init = tf.initialize_all_variables()
with tf.Session() as session:
init.run()
for i in xrange(5):
print(y.eval())