0.简介
此札记的目的还是, 通过输出: 1.理解概念, 2.加深印象, 3.融会贯通.
首先, 先看一段Python中Tensorflow的代码:
1 | import tensorflow as tf |
在上面的注释中, 我反复添加了注释1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上面的例子直接使用了复杂的训练和优化流程来解释tf图的概念, 下面用一个简单点的例子, 同样也是tf图的概念, 只不过数据没那么复杂了:
```python
# 创建一个变量, 初始化为标量 0.
state = tf.Variable(0, name="counter")
# 创建一个 op, 其作用是使 state 增加 1
one = tf.constant(1)
new_value = tf.add(state, one)
update = tf.assign(state, new_value)
# 启动图后, 变量必须先经过`初始化` (init) op 初始化,
# 首先必须增加一个`初始化` op 到图中.
init_op = tf.initialize_all_variables()
# 启动图, 运行 op
with tf.Session() as sess:
sess.run(init_op) # 运行 'init' op
print sess.run(state) # 打印 'state' 的初始值
for _ in range(3): # 运行 op, 更新 'state', 并打印 'state'
sess.run(update)
print sess.run(state)
1 | # 输出为: |
1.基本使用
好了, 尽管有上面的例子, 但它们只是代码(没从中看出tf图的样子), 下面, 开始解释代码背后的tf图逻辑(不准确解释为”编译树”, 这才是tf图, 眼睛看不见只有脑子能”看见”的那种图).
在Tensorflow中:
使用图(graph)来表示计算任务;
(p.s. 说到图, 就隐含背后计算的依赖)
在被称之为会话(Session)的上下文(context)中执行图;
(p.s. 图中的数学计算当然要消耗计算资源, 而Python中的Session便是管理这种上下文(1.进入Session代码块:使用资源, 2.执行别的代码块:结束Session代码块中对OS资源的占用)的, 故要在Session中执行图的计算)
使用tensor表示数据;
(p.s. 不必紧张, tensor根本不是能用于真正计算的数据类型(比如
```float```等才是), 它只是一种数据类别的强制要求(Google说在我的tf中必须要用tensor来包装一切数据, 就当这样便于管理tf图吧), 这个关系好比Class与Class中基本数据类型的关系一样, 只不过这里是tf, 那里是一般编程语言的Class概念). 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47+ 通过**变量(Variable)**维护状态;
+ 使用**feed**和**fetch**可以为任意的操作(arbitrary operation)赋值或者从其中获取数据.
<br><br>
## 2.综述
Tensorflow是一个编程系统, 使用图来表示计算任务, 图中的节点被称之为**op**(operation的缩写, 意为具体某种计算操作, 如加法), 如下图中的'绿/红/黄/蓝色'节点. 一个op获得0个或多个Tensor数据, 执行计算, 产生0个或多个Tensor结果. 每个Tensor是一个类型化的多维数组, 例如, 你可以将一小组图像集表示为一个四维浮点数数组([图像个数x图像的三维]), 这四个维度分别是[batch, height, width, channels].

*图自[SRC](http://www.sohu.com/a/203650826_314987).*
一个tf图描述了计算的过程(这个过程即计算的依赖关系). 为了进行计算, 图必须在'会话'里被启动(在Session代码块中进行). 会话将图的op分发到诸如CPU或GPU之类的设备上, 同时提供执行op的方法(e.g. 具体操作, 如加法, 还是乘法, 还是卷积, etc.). 这些方法执行后, 将产生的tensor结果返回. 在Python中, tensor结果是```numpy```的```ndarray```对象; 在C和C++语言中, tensor结果是```tensorflow::Tensor```实例对象.
<br><br>
## 3.计算图
Tensorflow的程序通常被组织成: 一个**1.构建阶段**和一个**2.执行阶段**(我理解成**定义**和**使用**tf图).
在构建阶段, op的执行步骤被描述成一个tf图. 在执行阶段, 使用会话执行tf图中的op. **例如**, 通常在构建阶段创建一个图来表示和训练神经网络, 然后在执行阶段反复执行图中的训练op(即```train```变量).
(p.s. TensorFlow支持C, C++, Python编程语言. 目前, TensorFlow的Python库更加易用, Python库提供了大量的辅助函数来简化构建图的工作, 但在C和C++中, 它们尚未被支持, 但三种语言的会话库(session libraries)是一致的)
<br><br>
## 4.构建图('变量声明'与'依赖')
举例来说, 构建图(如构建AlexNet的tf图), 就是在Python的TF代码中定义好变量的调用关系(如池化层的函数将卷积层的变量当做输入, 这就是一个调用关系, 即图的依赖), 这个AlexNet的输入, 5个Conv层和3个FC层的依赖关系, 就体现在Python的TF代码的变量调用关系中(而继续深究下去, 可能要查找'编译树'了).
构建图的第一步, 是创建'**源op**'(source op). 源op不需要任何输入, 例如常量(Constant), 它的输出被传递给其它op做进一步的运算. 在代码中(如下), op构造器的返回值代表被该op的输出, 这些返回值可以传递给其它op构造器作为输入(返回值=输出, 这不是废话吗..).
TF的Python库有一个默认图(default graph), op构造器可以为其增加节点, 这个默认图对许多程序来说已经足够用了(代码中并没有相应代码语句的表示, 只不过由Python解释器默认使用). (阅读'Graph类'的文档来了解如何管理多个图)
```python
import tensorflow as tf
# 创建一个常量op, 产生一个1x2矩阵, 这个 op 被作为一个节点加到默认图中.
# 构造器(指'tf.constant()')的返回值代表该常量op的返回值.
matrix1 = tf.constant([[3., 3.]])
# 创建另外一个常量op, 生成一个2x1矩阵.
matrix2 = tf.constant([[2.],[2.]])
# 创建一个'矩阵乘法matmul'op, 把'matrix1'和'matrix2'作为输入.
# 返回值'product'代表矩阵乘法的结果.
product = tf.matmul(matrix1, matrix2)
# 至此, 变量的调用关系, 也即tf图(依赖)的构建, 完成了;
默认图现在有三个节点, 两个constant() op, 和一个matmul() op. 为了真正进行矩阵相乘运算, 并得到矩阵乘法的 结果, 你必须在会话里启动这个图.
(p.s. 这好像有点杀鸡用牛刀了: 矩阵相乘, 我不能用普通的Python代码?! Don’t worry, 这只是个举例, 牛刀能杀鸡, 也能宰牛)
5.启动图(在会话中启动)
构造阶段完成后, 才能启动图(p.s. 要不然没意义啊, 没构造完如何进行有意义的计算). 启动图的第一步是创建一个Session对象(这是语法机制), 如果无任何创建参数(传给1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
```python
# 启动默认图(无参数传入);
sess = tf.Session()
# 1.调用sess的'run()'方法来执行'矩阵乘法op', 传入'product'作为该方法的参数;
# 2.在'4节'提到, 'product'代表了'矩阵乘法op'的输出,
# 传入它是向方法表明, 我们希望取回'矩阵乘法op'的输出;
# 3.整个执行过程是自动化的, (!!!)会话负责传递op所需的全部输入(!!!), op通常是并发执行的(Mark: 如何并发?!);
# 4.函数调用'run(product)'触发了图中三个op(两个常量op和一个矩阵乘法op)的执行(!!!);
# 5.返回值'result'是一个numpy的`ndarray`对象; (tf的返回值都是ndarray对象)
result = sess.run(product) # (!!!)前面构建好了图, 才有这一步的启动和计算;
print result # ==> [[ 12.]]
# 任务完成, (需)关闭会话;
sess.close()
Session对象在使用完后需要关闭以释放资源, 除了显式调用1
2
3
4
5
```python
with tf.Session() as sess:
result = sess.run([product])
print result
关于CPU和GPU(等设备)
在实现上, TF将图形定义转换成分布式执行的操作, 以充分利用可用的计算资源(如CPU或GPU). TF能自动检测是使用CPU还是GPU, 一般不需要人为显式指定, 如果检测到GPU, TF会尽可能地利用找到的第一个GPU来执行操作.
如果机器上有超过一个可用的GPU, 除第一个外的其它GPU默认不参与计算. 如果要让TF使用这些GPU, 必须将op明确指派给它们执行. 1
2
3
4
5
6
7
8
```python
with tf.Session() as sess:
with tf.device("/gpu:1"):
matrix1 = tf.constant([[3., 3.]])
matrix2 = tf.constant([[2.],[2.]])
product = tf.matmul(matrix1, matrix2)
...
CPU和GPU等设备用字符串进行标识, 目前支持的设备包括:
- “/cpu:0“: 机器的CPU;
- “/gpu:0“: 机器的第一个GPU, 如果有的话;
- “/gpu:1“: 机器的第二个GPU, 以此类推.
(阅读使用GPU章节, 了解 TensorFlow GPU 使用的更多信息)
6.交互式使用
文档中的Python示例使用一个’会话Session’来启动图, 并调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(上面的话的意思是: 我就不```sess.run()```了, 你在代码中声明一下```InteractiveSession ```, 然后我就告诉Python解释器关于资源的使用方式, 交由你的```Tensor.eval()```和```Operation.run()```来使用, 但它(Python解释器)还是会管理的)
```python
# 进入一个交互式 TensorFlow 会话.
import tensorflow as tf
sess = tf.InteractiveSession()
x = tf.Variable([1.0, 2.0])
a = tf.constant([3.0, 3.0])
# 使用初始化器 initializer op 的 run() 方法初始化 'x'
x.initializer.run()
# 增加一个'减法sub op', 从'x'减去'a'. 运行'减法op', 输出结果;
sub = tf.sub(x, a)
print sub.eval() # 直接eval()一下好了, 就不要sess.run()了;
# ==> [-2. -1.]
7.Tensor
TF程序使用’Tensor数据结构’来代表所有的数据, 计算图中, 操作间传递的数据都是Tensor(你可以把TF的Tensor看作是一个n维的数组或列表). 一个Tensor包含一个静态类型rank, 和一个shape.
(想了解TF是如何处理这些概念的, 参见Rank, Shape, 和Type)
8.变量(More Details)
变量维护图执行过程中的*状态信息*, 下面的例子演示了如何使用变量实现一个简单的计数器. (参见’变量’章节了解更多细节)
1 | ###### 创建图 ###### |
1 | # 输出: |
代码中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<br><br>
## 9.Fetch('sess.run()的结果')
为了取回'op的输出内容', 可以在使用`Session`对象的`run()`调用执行图时, **<del>传入一些Tensor</del>**(原文写得这么不明白?!)声明一个Tensor(即Python变量), 这些Tensor会帮助你<del>取回结果</del>保存结果Tensor到等号左边的变量中. 在之前的例子里, 我们只取回了单个节点的'state'值, 但也可以取回多个Tensor(如下例):
```python
input1 = tf.constant(3.0)
input2 = tf.constant(2.0)
input3 = tf.constant(5.0)
intermed = tf.add(input2, input3)
mul = tf.mul(input1, intermed)
with tf.Session():
result = sess.run([mul, intermed])
print result
# 输出('mul'一个结果, 'intermed'一个结果):
# [array([ 21.], dtype=float32), array([ 7.], dtype=float32)]
(需要获取的多个Tensor值,在’result op’的一次运行中一起获得(而不是逐个去获取))
10.Feed(即’Placeholder’)
上述示例在计算图中引入了Tensor, 它们以常量或变量的形式存储. TF还提供了’feed机制’, 该机制可以临时替代图中的任意操作中的Tensor, 可以对图中任何op提交补丁(补丁: Excuse Me?!), 在后续中插入具体的Tensor值. Feed使用一个Tensor值临时替换一个操作的输出结果. 你可以提供Feed数据作为run()
调用的参数. Feed只在调用它的方法内有效, 方法结束, Feed就会消失. (讲得真绕, Feed之意就是在某处Feed数据给需要被Feed的东西)
最常见的用例是将某些特殊的操作指定为’feed’操作, 标记的方法是使用tf.placeholder()
为这些操作创建占位符.
1 | input1 = tf.placeholder(tf.types.float32) # Feed; |
(如果没有正确提供Feed, placeholder()
op将会报错误. MNIST全连通Feed教程(source code)给出了一个更大规模的使用Feed的例子)
(Note Ends, Keep For Future Fine-Tuning, :))
References
1.参考CNBLOGS博文
2.参考TENSORFLY社区