0%

【CS20-TF4DL】02 Tensorflow Ops

这一讲我们来学习 Tensorflow 的基本操作。不出意外这是这门课程最枯燥的一节,因为我们需要学习很多基础的概念,但之后就会有趣很多!


更新历史

  • 2019.08.05: 添加关于 Python Property 的文章链接
  • 2019.08.04: 完成初稿

从简单的程序开始

代码地址,我们在上面代码做的操作有:

  1. 屏蔽 TF 的编译告警
  2. 写入 Graph 的结果,方便用 Tensorboard 展示

执行一下,然后打开 tensorboard 看看

1
2
3
4
python 1_basic_operation.py
# 启动 tensorboard
tensorboard --logdir=data/1-graphs/
# 访问 localhost:6006

Contants 常量

定义为 tf.constant(value, dtype=None, shape=None, name='Const', verify_shape=False)

常量的相关代码参考 这里,以下是关键要点:

  1. 常量的广播 broadcasting 与 Numpy 类似
  2. tf.zerosnumpy.zeros 类似
  3. 可以使用 tf.zeros_like 函数生成与输入 tensor 形状一样的 tensor
  4. 同样的道理适用于 tf.onestf.ones_like
  5. 用指定值填充指定的形状 tf.fill(dims, value, name=None)
  6. 生成数组可以用 tf.lin_space(start, stop, num, name=None) 或者 tf.range(start, limit=None, delta=1, dtype=None, name='range'),但是注意,这个数组并不可以用来遍历
  7. 随机生成常量的函数不少,具体见代码
  8. 如果要控制随机性,可以通过 tf.set_random_seed 来固定种子,保证每次结果一致
  9. 常量会被保存在 graph 的定义中,滥用会使得载入 graph 变得很费时。所以只把 contant 用于基础类型,而数据集的部分,放到变量或者 reader 中,这样就可以用内存来存储,速度更快

Operations 操作

主要分以下几类

  1. 针对元素的数学操作: Add, Sub, Mul, Div, Exp, Log, Greater, Less, Equal, …
  2. 数组操作: Concat, Slice, Split, Constant, Rank, Shape, Shuffle, …
  3. 矩阵操作: MatMul, MatrixInverse, MatrixDeterminant, …
  4. 状态操作: Variable, Assign, AssignAdd, …
  5. 神经网络模块: SoftMax, Sigmoid, ReLU, Convolution2D, MaxPool, …
  6. Checkpoint 操作: Save, Restore
  7. 队列和同步操作: Enqueue, Dequeue, MutexAcquire, MutexRelease, …
  8. 流控制操作: Merge, Switch, Enter, Leave, NextIteration

数学运算操作

比较标准,与 numpy 很类似,如

  1. 绝对值 tf.abs
  2. 取反 tf.negative
  3. 返回数值的符号(就是判断正负) tf.sign
  4. 求倒数 tf.reciprocal
  5. 求平方 tf.square
  6. 取整 tf.round
  7. 开平方 tf.sqrt
  8. 求平方根的倒数 tf.rsqrt
  9. 求幂 tf.pow
  10. 求 e 的指数 tf.exp
  11. 除法有花式 div,具体参考代码

Data Types 数据类型

代码参考 这里,以下是关键要点:

  • 直接用 Python 原生的类型有:boolean, numeric(int, float), string
  • 标量会被认为是 0 维张量
  • 1 维数组会被认为是 1 维张量
  • 2 维数字会被认为是 2 维张量
  • TF 和 Numpy 无缝对接 tf.int32 == np.int32 => True
  • 可以直接向 TF 传递 numpy 的数据类型
  • 在 Session 中,获取 Tensor 会返回 numpy ndarray

可能的话尽可能使用 TF 的数据类型,因为:

  1. 用 python 原始类型的话,TF 需要去推断 python 类型
  2. 对于 numpy array 来说,无法使用 GPU 加速计算

Variables 变量

代码参考 这里,以下是关键要点:

  • 尽量使用 tf.get_variable 而非 tf.Variable 来创建变量,因为 tf.Variabletf.Constant 不同,并不是一个 operation,而是一个类,包含了多个 operation,所以建议用更加像访问类的方式来访问
  • 在使用变量之前,一定要进行初始化,比较省事的方式就是 sess.run(tf.global_variables_initializer(),注意,initializer 是一个 operation,所以需要用 sess 来执行
  • 也可以使用 sess.run(tf.variables_initializer([s, m])) 来初始化部分变量
  • 也可以使用 sess.run(W.initializer) 只初始化一个变量
  • 可以用 eval 函数来获取值
  • tf.Variable.assign 是一个 operation,需要保存下来然后再 sess 中执行才可以生效,其他的操作还有 assign_addassign_sub
  • 每个 session 中的变量是独立的,不会互相影响
  • 可以通过 tf.Graph.control_dependencies(control_inputs) 来控制哪些 operations 先执行

Placeholders 占位符

还记得一个 TF 程序的两个步骤吗:

  1. 组装一个 Graph
  2. 使用个 session 来执行 Graph 中的 operation

也就是说,我们是在不知道要计算什么值之前,就已经设定好了要如何计算这些值。这样一来,我们就可以之后再把要计算的值传过去。我们先来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# tf.placeholder(dtype, shape=None, name=None)
a = tf.placeholder(tf.float32, shape=[3])
b = tf.constant([5, 5, 5], tf.float32)

# 相当于 c = tf.add(a, b)
c = a + b
with tf.Session() as sess:
# 这样会报错!InvalidArgumentError: a doesn't an actual value
# 也就是 a 实际上不是一个值,只是一个 placeholder
print(sess.run(c))
# 需要把具体要计算的放进去,才可以计算
print(sess.run(c, feed_dict={a:[1, 2, 3]}))
# 如果想要传入多个值,那么需要一个一个来
for a_value in list_of_values_for_a:
print(sess.run(c, {a:a_value}))

这里有一点要注意下,shape=None 表示该位置可以接受任何的值,好处在于构建 graph 的时候很轻松,但是,但是,在调试的时候,会很让人摸不着头脑。更重要的是,这样会导致后面的 shape 推断无法进行,进而导致很多 operations 无法进行。

Lazy Loading 的陷阱

啥是 Lazy Loading?就是指当真正用到这个对象的时候,才进行创建或初始化。

先看一个正常的例子:

1
2
3
4
5
6
7
8
9
10
11
x = tf.Variable(10, name='x')
y = tf.Variable(20, name='y')
z = tf.add(x, y) # 在执行 graph 之前就创建 node

writer = tf.summary.FileWriter('./graphs/normal_loading', tf.get_default_graph())
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for _ in range(10):
sess.run(z)
writer.close()
# Add 这个 node 只会被添加一次

然后我们比较一下 lazy loading 的版本,这里只是没有把 z 放到 session 外定义,省一行代码:

1
2
3
4
5
6
7
8
9
10
x = tf.Variable(10, name='x')
y = tf.Variable(20, name='y')

writer = tf.summary.FileWriter('./graphs/normal_loading', tf.get_default_graph())
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for _ in range(10):
sess.run(tf.add(x, y))
writer.close()
# Add 这个节点,循环多少次就会添加多少次,graph 最终会非常大!

如何解决这个问题?请做好如下几点:

  1. 能提前定义的都提前定义好!这是所有计算图框架都需要注意的事情!
  2. Use Python property to ensure function is also loaded once the first time it is called.更多可以参考 这篇文章,写得非常棒!

下期预告

  1. Linear regression
  2. 控制流
  3. tf.data
  4. Optimizer
  5. Logistic regression on MNIST