本文的主要内容安排如下:
- 基本的操作
- 张量类型
- 导入数据
- lazy loading
我们首先介绍一下TensorBoard的使用,然后介绍TensorFlow的基本ops,之后介绍张量的数据类型,最后介绍一下如何将自己的输入导入模型。
1. TensorBoard
TensorBoard是TensorFlow的一个可视化工具,可以用于对TensorFlow模型的调试和优化。TensorBoard的外观大致如下:
当用户在TensorBoard激活的TensorFlow程序中执行某些操作时,这些操作将导出到事件日志文件中。 TensorBoard能够将这些事件文件转换为可视化文件,从而可以深入了解模型的结构及其运行时的行为。
让我们从一个小例子中,看看TensorBoard如何使用。下面的代码是不使用TensorBoard的计算a b的例子:
代码语言:javascript复制import tensorflow as tf
a = tf.constant(2)
b = tf.constant(3)
x = tf.add(a, b)
with tf.Session() as sess:
print(sess.run(x))
为了使用TensorBoard对程序进行可视化,我们需要将模型写入日志文件中。而日志文件的写入,需要依赖于一个filewriter,其生命代码如下:
代码语言:javascript复制writer = tf.summary.FileWriter([logdir], [graph])
[graph]
指当前模型的运算图。可以使用tf.get_default_graph()
获取模型的默认运算图,或者使用sess.graph
获取当前会话处理的运算图;后者要求sess会话已经被声明。值得注意的是,FileWriter的声明需要放在在运算图定义完成之后,否则TensorBoard对模型的结构显示不完整。
[logdir]
表明日志文件的存储位置。可以将[logdir]
命名为’./graphs’或者’./graphs/lecture02’.
import tensorflow as tf
a = tf.constant(2)
b = tf.constant(3)
x = tf.add(a, b)
writer = tf.summary.FileWriter('./graphs', tf.get_default_graph())
with tf.Session() as sess:
# writer = tf.summary.FileWriter('./graphs', sess.graph) # if you prefer creating your writer using session's graph
print(sess.run(x))
writer.close()
模型运行之后会将日志文件写入并存储到指定位置。接下来,到命令行窗口,运行如下代码:
代码语言:javascript复制$ python3 [my_program.py]
$ tensorboard --logdir="./graphs" --port 6006
如果运行报错:OSError:[Errno 22] Invalid argument,解决方法为:clickME
运行成果后,在浏览器中打开网址:http://localhost:6006就可以看到TensorBoard页面了。你可以看到运算图中有3个结点:2个constants和一个Add op。
其中Const、Const_1分别对应结点a和结点b,Add对应x。我们也可以自己对结点命名:
代码语言:javascript复制a = tf.constant(2, name="a")
b = tf.constant(3, name="b")
x = tf.add(a, b, name="add")
再次运行打开TensorBoard后,我们可以得到:
运算图定义了ops以及它们的依赖关系。我们可以通过点击结点来确定结点的值以及结点类型。
在了解TensorBoard之后,我们来看看TensorFlow中的各种op。
2. Constant op
TensorFlow中创建常量constant的方式很简单。
代码语言:javascript复制tf.constant(value, dtype=None, shape=None, name='Const', verify_shape=False)
# constant of 1d tensor(vector)
a = tf.constant([2, 2], name="vector")
# constant of 2x2 tensor (matrix)
b = tf.constant([[0, 1], [2, 3]], name="matrix")
你也可以创建特定维度、填充特定值的常量,具体操作和使用Numpy类似。
代码语言:javascript复制# 创建任意维度,内部元素全是0的constant常量
tf.zeros(shape, dtype=tf.float32, name=None)
# create a tensor of shape and all elements are zeros
tf.zeros([2,3], tf.int32) ==> [[0, 0, 0], [0, 0, 0]]
代码语言:javascript复制# 创建和input_tensor维度相同,全是0的常量
tf.zeros_like(input_tensor, dtype=None, name=None, optimize=True)
# create a tensor of shape and type (unless type is specified) as the input_tensor but all
elements are zeros.
# input_tensor [[0, 1], [2, 3], [4, 5]]
tf.zeros_like(input_tensor) ==> [[0, 0], [0, 0], [0, 0]]
代码语言:javascript复制# 创建任意维度,值全是1的常量
tf.ones(shape, dtype=tf.float32, name=None)
# create a tensor of shape and all elements are ones
tf.ones([2, 3], tf.int32) ==> [[1, 1, 1], [1, 1, 1]]
代码语言:javascript复制#创建和input_tensor维度相同,值全是1的常量
tf.ones_like(input_tensor, dtype=None, name=None, optimize=True)
# create a tensor of shape and type (unless type is specified) as the input_tensor but all
elements are ones.
# input_tensor is [[0, 1], [2, 3], [4, 5]]
tf.ones_like(input_tensor) ==> [[1, 1], [1, 1], [1, 1]]
代码语言:javascript复制# 创建任意维度,内部值都相同的常量
tf.fill(dims, value, name=None)
# create a tensor filled with a scalar value.
tf.fill([2, 3], 8) ==> [[8, 8, 8], [8, 8, 8]]
和Numpy类似,TF也可以创建constants序列,创建方法如下:
代码语言:javascript复制tf.lin_space(start, stop, num, name=None)
# 生成start到stop的封闭线性空间,总的个数为num;包含start和stop在内
tf.lin_space(10.0, 13.0, 4, name="linspace") ==> [10.0 11.0 12.0 13.0]
代码语言:javascript复制tf.range([start], limit=None, delta=1, dtype=None, name='range')
# 生成start到limit的子序列,包含start在内,不包含limit,delta控制步长
# 'start' is 3, 'limit' is 18, 'delta' is 3
tf.range(3, 18, delta) ==> [3, 6, 9, 12, 15]
# 'start' is 3, 'limit' is 1, 'delta' is -0.5
tf.range(start, limit, delta) ==> [3, 2.5, 2, 1.5]
# 'limit' is 5
tf.range(limit) ==> [0, 1, 2, 3, 4]
特别注意的是,和Numpy对象不同,tensor是不可以迭代的(iterable),所以下列操作是非法的:
代码语言:javascript复制for _ in tf.linspace(0.0, 10.0, 4): # TypeError
for _ in tf.range(4): # TypeError
可以使用随机方法生成常量constant,使用的函数如下:
代码语言:javascript复制tf.random_normal # 正态分布
tf.truncated_normal
tf.random_uniform
tf.random_shuffle
tf.random_crop
tf.multinomial
tf.random_gamma
tf.set_random_seed
3. Math op与数学运算相关的ops
TensorFlow中包含各种各样的数学ops,如加法tf.add, tf.add_n等。
TF常见ops如下:
4. 数据类型
4.1 Python 原生类型
TF可以使用Python的原生数据类型,如Boolean、数值型(整数、浮点数)、字符串。单个值可以被转换成0-d张量(标量)、列表可以被转换为1-d张量(向量)、二级列表可以被转换为2-d张量(矩阵)。
代码语言:javascript复制# 实数
t_0 = 19 # Treated as a 0-d tensor, or "scalar"
tf.zeros_like(t_0) # ==> 0
tf.ones_like(t_0) # ==> 1
# 列表
t_1 = [b"apple", b"peach", b"grape"] # treated as a 1-d tensor, or "vector"
tf.zeros_like(t_1) # ==> [b'' b'' b'']
tf.ones_like(t_1) # ==> TypeError
# 二级列表
t_2 = [[True, False, False],
[False, False, True],
[False, True, False]] # treated as a 2-d tensor, or "matrix"
tf.zeros_like(t_2) # ==> 3x3 tensor, all elements are False
tf.ones_like(t_2) # ==> 3x3 tensor, all elements are True
4.2 TF 原生类型
TF包含的数据类型如下:
4.3 Numpy数据类型与TF类型
TF在设计时要求能无缝集成Numpy,TF的数据类型基于Numpy对应的类型。事实上、np.int32 == tf.int32
结果为真。我们可以将numpy类型传送到TF ops中。
tf.ones([2,2], np.float32)
在TF中,numpy数组用于表示tensor的值。在tf.Session.run()中,如果fetches是tensor,返回值将是一个numpy数组。
代码语言:javascript复制a = tf.ones([2,3], np.int32)
sess = tf.Session()
print(a) ==> Tensor("ones:0", shape=(2, 3), dtype=int32)
print(type(a)) ==> <class 'tensorflow.python.framework.ops.Tensor'>
print(sess.run(a)) ==>[[1 1 1],[1 1 1]]
print(type(sess.run(a))) ==> <class 'numpy.ndarray'>
虽然,TF能支持Numpy数据类型,但我们建议尽可能地使用TF的数据类型,因为:
- 如果使用Numpy类型,必须引用numpy工具包
- 最重要的是,Numpy不兼容GPU
5. Variable变量
常量和变量之间的区别:
- 常量,顾名思义,是固定不变的。在模型训练过程中,我们希望模型的权重参数能不断优化,因此常量不适用于这种场景
- 常量的值作为graph定义的一部分被存储和序列化,每次graph加载时,常量的值都需要复制一份;变量是分开存储的,可能放在单独的参数服务器上。
因为常量的值将作为graph定义的一部分被存储和序列化,如果运算图中常量过多,就会导致graph的加载成本加大。这一点,我们可以通过打印graph的定义来确定,具体可以通过as_graph_def()
函数来实现:
my_const = tf.constant([1.0, 2.0], name="my_const")
with tf.Session()as sess:# 运算图中只有一个常量my_const
print(sess.graph.as_graph_def())
5.1 Variable的两种创建方法
tf.Variable()
和tf.get_variable()
,使用方法如下:
# create variables with tf.Variable
s = tf.Variable(2, name="scalar") # with scalar value
m = tf.Variable([[0, 1], [2, 3]], name="matrix") # with list value
# create variables with tf.get_variable
s = tf.get_variable("scalar", initializer=tf.constant(2))
m = tf.get_variable("matrix", initializer=tf.constant([[0, 1], [2, 3]]))
两种方法都可以创建tf.Variable对象,我们推荐使用第二种方法tf.get_variable()
,这种方法是对第一种方法的封装,可以更容易地进行共享。
为什么tf.constant是小写字母开头,tf.Variable为大写字母呢?因为tf.constant只是一个op,而tf.Variable是一个类,内部包含多个op:
代码语言:javascript复制x = tf.Variable(...)
x.initializer # init
x.value() # read op
x.assign(...) # write op
x.assign_add(...)
# and more
5.2 Variable初始化
Variable在使用之前必须先初始化。如果尝试使用一个未初始化的Variable会提示FailedPreconditionError: Attempting to use uninitialized value.
我们可以获取未被初始化的Variable列表:
print(sess.run(tf.report_uninitialized_variable()))
初始化所有Variables最简单的方法是使用:
代码语言:javascript复制with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
这里,我们fetches是一个变量初始化op,而不是一个获取tensor的op。
如果只想初始化一部分Variables,我们可以将variable变量送到tf.variables_initializer()
来实现:
with tf.Session() as sess:
# 只初始化a,b
sess.run(tf.variables_initializer([a, b]))
我们也可以将各个Variable的初始化工作分离,每个变量使用tf.Variable.initializer
来初始化:
with tf.Session() as sess:
sess.run(a.initializer)
我们还可以从文件中加载值来完成Variable的初始化。
5.3 打印、输出Variable的值
代码语言:javascript复制# V声明
V = tf.get_variable("normal_matrix", shape=(784, 10),
initializer=tf.truncated_normal_initializer())
with tf.Session() as sess:
# V初始化
sess.run(tf.global_variables_initializer())
# V打印、输出
print(sess.run(V))
也可以使用tf.Variable.eval()
来打印:
# W 声明
W = tf.Variable(tf.truncated_normal([700, 10]))
with tf.Session() as sess:
sess.run(W.initializer) #初始化
print(W.eval()) # Similar to print(sess.run(W))
tf.Variable.eval()
只是run的一个快捷方式等价于tf.get_default_session().run(t)
.
5.4 为变量赋值assign
可以使用tf.Variable.assign()
对variable进行赋值。
W = tf.Variable(10)
W.assign(100) # op
with tf.Session() as sess:
sess.run(W.initializer)
print(W.eval()) # >> 10
值得注意的是**tf.Variable.assign()**是一个op,为了让赋值操作起效,我们需要在session中运行它。
代码语言:javascript复制W = tf.Variable(10)
assign_op = W.assign(100)
with tf.Session() as sess:
sess.run(assign_op)
print(W.eval()) # >> 100
这里并没有对W使用初始化操作,因为assign函数会完成Variable的初始化。其实,变量的初始化op就是一个assign op,它将初始值赋值给变量对象.
代码语言:javascript复制# in the source code
self._initializer_op = state_ops.assign(self._variable, self._initial_value, validate_shape=validate_shape).op
如果assign op反复调用,效果将会叠加。
代码语言:javascript复制a = tf.get_variable('scalar', initializer=tf.constant(2))
a_times_two = a.assign(a * 2)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
sess.run(a_times_two) # >> 4
sess.run(a_times_two) # >> 8
sess.run(a_times_two) # >> 16
如果只想简单地对变量进行增减操作,可以使用tf.Variable.assign_add()
和tf.Variable.assign_sub()
方法。和tf.Variable.assign()
方法不同,这两个函数内部不能完成变量的初始化,因为它们依赖于变量的初始值。
W = tf.Variable(10)
with tf.Session() as sess:
sess.run(W.initializer)
print(sess.run(W.assign_add(10))) # >> 20
print(sess.run(W.assign_sub(2))) # >> 18
TF中每个Session维持一份Variable,相互之间互不干扰。
代码语言:javascript复制W = tf.Variable(10)
sess1 = tf.Session()
sess2 = tf.Session()
sess1.run(W.initializer)
sess2.run(W.initializer)
print(sess1.run(W.assign_add(10))) # >> 20
print(sess2.run(W.assign_sub(2))) # >> 8
print(sess1.run(W.assign_add(100))) # >> 120
print(sess2.run(W.assign_sub(50))) # >> -42
sess1.close()
sess2.close()
6. Interactive Session
Interactive Session和Session的区别在于InteractiveSession创建后,会自动设置为默认session,相当于执行了session.as_default_session。因此在调用run()
和eval()
方法的时候不需要显式的调用session。
sess = tf.InteractiveSession()
a = tf.constant(5.0)
b = tf.constant(6.0)
c = a * b
print(c.eval()) # we can use 'c.eval()' without explicitly stating a session
sess.close()
另外,tf.get_default_session()
方法可以用来获取当前线程的默认session。
7. Importing Data 导入数据
7.1 placeholders和feed_dict
定义placeholders,可以使用tf.placeholder(dtype, shape=None, name=None)
.
其中,shape可以是None,表示不指定shape,shape具体根据输入来确定。但是我们并不推荐这种方式,因为shape的不确定,会给模型debug增加难度。
代码语言:javascript复制a = tf.placeholder(tf.float32, shape=[3])
b = tf.constant([5, 5, 5], tf.float32)
# use the placeholder as you would a constant or a variable
c = a b # short for tf.add(a, b)
with tf.Session() as sess:
print(sess.run(c))
# InvalidArgumentError: a doesn’t an actual value
运行上述代码会抛出异常:InvalidArgumentError: a doesn’t an actual value
表示a的值不知道。因为我们没有对placeholder进行赋值。我们可以使用feed_dict来完成这项操作:feed_dict是一个字典,其中键为placeholder的对象名字(不是字符串),值为传送的值。
a = tf.placeholder(tf.float32, shape=[3])
b = tf.constant([5, 5, 5], tf.float32)
c = a b # short for tf.add(a, b)
with tf.Session() as sess:
print(sess.run(c, feed_dict={a: [1, 2, 3]}))
# the tensor a is the key, not the string ‘a’
# 输出为 >> [6, 7, 8]
placeholder也是有效的ops,tf.placeholer
返回的也是一个tf.Tensor
对象。
除了向placeholder进行值传送,我们也可以向非placeholder的tensor对象进行feed。确定tensor是否是可feed的,我们可以使用下面函数来确定:
代码语言:javascript复制tf.Graph.is_feedable(tensor)
代码语言:javascript复制a = tf.add(2, 5)
b = tf.multiply(a, 3)
with tf.Session() as sess:
print(sess.run(b)) # >> 21
# compute the value of b given the value of a is 15
print(sess.run(b, feed_dict={a: 15})) # >> 45
这种操作在测试的时候特别有用.假如一个graph太大,我们只想测试图的一个部分,就可以用这种方法提供值,节省不必要的计算时间。
7.2 tf.data
后续结合例子进行讲解。
8. Lazy loading懒加载
TensorFlow的一个常见的non-bug bugs(不报异常的异常)就是懒加载。懒加载指的是直到加载对象时才对它进行声明/初始化的编程模式(推迟声明和初始化)。在TensorFlow
中,它意味着直到你需要计算一个op时才对其进行创建。
我们先看一个正常的例子:
代码语言:javascript复制x = tf.Variable(10, name='x')
y = tf.Variable(20, name='y')
z = tf.add(x, y)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
writer = tf.summary.FileWriter('graphs/normal_loading', sess.graph)
for _ in range(10):
sess.run(z)
writer.close()
使用lazy loadding方式的代码:
代码语言:javascript复制x = tf.Variable(10, name='x')
y = tf.Variable(20, name='y')
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
writer = tf.summary.FileWriter('graphs/lazy_loading',sess.graph) for _ in range(10):
sess.run(tf.add(x, y))
print(tf.get_default_graph().as_graph_def())
writer.close()
这种方式看起来更为简洁,减少了z的创建语句。让我们看看两种运算图的定义有什么不同,使用语句
代码语言:javascript复制print(tf.get_default_graph().as_graph_def())
前一种方法内部只有一个add结点,后者会产生Add_1到Add_10共10个结点。如果循环10万次,会导致graph定义的极速膨胀。
我们应该避免使用Lazy Loading,方法是:
- 将op的定义和运行分别开来。
- 使用Python的property保证function仅在第一次调用时加载
9. References
CS 20: Tensorflow for Deep Learning Research Operations