【CS20-TF4DL】02 Tensorflow Ops
约 6 分钟
这一讲我们来学习 Tensorflow 的基本操作。不出意外这是这门课程最枯燥的一节,因为我们需要学习很多基础的概念,但之后就会有趣很多!
更新历史
- 2019.08.05: 添加关于 Python Property 的文章链接
- 2019.08.04: 完成初稿
从简单的程序开始
代码地址,我们在上面代码做的操作有:
- 屏蔽 TF 的编译告警
- 写入 Graph 的结果,方便用 Tensorboard 展示
执行一下,然后打开 tensorboard 看看
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)
常量的相关代码参考 这里,以下是关键要点:
- 常量的广播 broadcasting 与 Numpy 类似
tf.zeros与numpy.zeros类似- 可以使用
tf.zeros_like函数生成与输入 tensor 形状一样的 tensor - 同样的道理适用于
tf.ones与tf.ones_like - 用指定值填充指定的形状
tf.fill(dims, value, name=None) - 生成数组可以用
tf.lin_space(start, stop, num, name=None)或者tf.range(start, limit=None, delta=1, dtype=None, name='range'),但是注意,这个数组并不可以用来遍历 - 随机生成常量的函数不少,具体见代码
- 如果要控制随机性,可以通过
tf.set_random_seed来固定种子,保证每次结果一致 - 常量会被保存在 graph 的定义中,滥用会使得载入 graph 变得很费时。所以只把 contant 用于基础类型,而数据集的部分,放到变量或者 reader 中,这样就可以用内存来存储,速度更快
Operations 操作
主要分以下几类
- 针对元素的数学操作: Add, Sub, Mul, Div, Exp, Log, Greater, Less, Equal, ...
- 数组操作: Concat, Slice, Split, Constant, Rank, Shape, Shuffle, ...
- 矩阵操作: MatMul, MatrixInverse, MatrixDeterminant, ...
- 状态操作: Variable, Assign, AssignAdd, ...
- 神经网络模块: SoftMax, Sigmoid, ReLU, Convolution2D, MaxPool, ...
- Checkpoint 操作: Save, Restore
- 队列和同步操作: Enqueue, Dequeue, MutexAcquire, MutexRelease, ...
- 流控制操作: Merge, Switch, Enter, Leave, NextIteration
数学运算操作
比较标准,与 numpy 很类似,如
- 绝对值
tf.abs - 取反
tf.negative - 返回数值的符号(就是判断正负)
tf.sign - 求倒数
tf.reciprocal - 求平方
tf.square - 取整
tf.round - 开平方
tf.sqrt - 求平方根的倒数
tf.rsqrt - 求幂
tf.pow - 求 e 的指数
tf.exp - 除法有花式
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 的数据类型,因为:
- 用 python 原始类型的话,TF 需要去推断 python 类型
- 对于 numpy array 来说,无法使用 GPU 加速计算
Variables 变量
代码参考 这里,以下是关键要点:
- 尽量使用
tf.get_variable而非tf.Variable来创建变量,因为tf.Variable和tf.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_add和assign_sub- 每个 session 中的变量是独立的,不会互相影响
- 可以通过
tf.Graph.control_dependencies(control_inputs)来控制哪些 operations 先执行
Placeholders 占位符
还记得一个 TF 程序的两个步骤吗:
- 组装一个 Graph
- 使用个 session 来执行 Graph 中的 operation
也就是说,我们是在不知道要计算什么值之前,就已经设定好了要如何计算这些值。这样一来,我们就可以之后再把要计算的值传过去。我们先来看一段代码:
# 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?就是指当真正用到这个对象的时候,才进行创建或初始化。
先看一个正常的例子:
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 外定义,省一行代码:
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 最终会非常大!
如何解决这个问题?请做好如下几点:
- 能提前定义的都提前定义好!这是所有计算图框架都需要注意的事情!
- Use Python property to ensure function is also loaded once the first time it is called.更多可以参考 这篇文章,写得非常棒!
下期预告
- Linear regression
- 控制流
- tf.data
- Optimizer
- Logistic regression on MNIST