基础知识
下面就自底向上详细介绍一下 TensorFlow 的系统架构。最下层是网络通信层和设备管理层。网络通信层包括 gRPC(google Remote Procedure Call Protocol)和远程直接数据存取(Remote Direct Memory Access, RDMA),这都是在分布式计算时需要用到的。设备管理层包括 TensorFlow 分别在 CPU、 GPU、 FPGA 等设备上的实现,也就是对上层提供了一个统一的接口,使上层只需要处理卷积等逻辑,而不需要关心在硬件上的卷积的实现过程。
其上是数据操作层,主要包括卷积函数、激活函数等操作。再往上是图计算层,也是我们要了解的核心,包含本地计算图和分布式计算图的实现。再往上是 API 层和应用层
Tensorflow的特点
TensorFlow是符号式编程。将图的定义和图的运行完全分开。因此, TensorFlow 被认为是一个“符号主义”的库。相比于命令式编程来说不易理解和调试,但是运行速度相对很快。
符号式计算一般是先定义各种变量,然后建立一个数据流图,在数据流图中规定各个变量之间的计算关系,最后需要对数据流图进行编译,但此时的数据流图还是一个空壳儿,里面没有任何实际数据,只有把需要运算的输入放进去后,才能在整个模型中形成数据流,从而形成输出值。
TensorFlow 中涉及的运算都要放在图中,而图的运行只发生在会话(session)中。开启会话后,就可以用数据去填充节点,进行运算;关闭会话后,就不能进行计算了。因此,会话提供了操作运行和 Tensor 求值的环境。
import tensorflow as tf
# 创建图
a = tf.constant([1.0, 2.0])
b = tf.constant([3.0, 4.0])
c = a * b
# 创建会话
sess = tf.Session()
# 计算 c
print sess.run(c) # 进行矩阵乘法,输出[3., 8.]
sess.close()
Tensorflow的运行原理(编程模型)
下面这张图是一个简单的回归模型。图中包含输入(input)、塑形(reshape)、 Relu 层(Relu layer)、 Logit 层(Logit layer)、 Softmax、交叉熵(cross entropy)、梯度(gradient)、 SGD 训练(SGD Trainer)等部分。
它的计算过程是,首先从输入开始,经过塑形后,一层一层进行前向传播运算。 Relu 层(隐藏层)里会有两个参数,即 Wh1和 bh1,在输出前使用ReLu(Rectified Linear Units)激活函数做非线性处理。然后进入 Logit 层(输出层),学习两个参数 Wsm和 bsm。用 Softmax 来计算输出结果中各个类别的概率分布。用交叉熵来度量两个概率分布(源样本的概率分布和输出结果的概率分布)之间的相似性。然后开始计算梯度,这里是需要参数 Wh1、 bh1、 Wsm和 bsm,以及交叉熵后的结果。随后进入 SGD 训练,也就是反向传播的过程,从上往下计算每一层的参数,依次进行更新。也就是说,计算和更新的顺序为 bsm、 Wsm、 bh1和 Wh1。
Tensor(张量)代表了数据流图中的边,而 Flow(流动)这个动作就代表了数据流图中节点所做的操作。
边
TensorFlow 的边有两种连接关系:数据依赖和控制依赖。实线边表示数据依赖,代表数据,即张量。在机器学习算法中,张量在数据流图中从前往后流动一遍就完成了一次前向传播(forword propagation),而残差从后向前流动一遍就完成了一次反向传播(backword propagation)。
虚线边,称为控制依赖。可以用于控制操作的运行,这被用来确保happens-before 关系,这类边上没有数据流过,但源节点必须在目的
节点开始执行前完成执行。常用代码如下:
tf.Graph.control_dependencies(control_inputs)
节点
节点又称为算子,它代表一个操作(operation, OP),一般用来表示施加的数学运算,也可以表示数据输入(feed in)的起点以及输出(push out)的终点.或者是读取/写入持久变量(persistent variable)的终点
这些代码在C:\Users\14192\Anaconda3\Lib\site-packages\tensorflow\python\ops中。(因为我安装了anaconda,所以包的路径都是anaconda的子路径)
其余概念
- 图:操作任务描述成有向无环图。首先创建节点,然后创建节点之间的关联
- 会话: 会话是启动图的第一步,提供执行操作的一些方法。过程是:建立会话,此时会生成一张空图,在会话中添加节点和边,形成一张图,然后执行。相关代码在C:\Users\14192\Anaconda3\Lib\site-packages\tensorflow\python\client\session.py中。
import tensorflow as tf
# 创建一个常量运算操作,产生一个 1×2 矩阵
matrix1 = tf.constant([[3., 3.]])
# 创建另外一个常量运算操作,产生一个 2×1 矩阵
matrix2 = tf.constant([[2.],[2.]])
# 创建一个矩阵乘法运算 ,把 matrix1 和 matrix2 作为输入
# 返回值 product 代表矩阵乘法的结果
product = tf.matmul(matrix1, matrix2)
#会话:
#传入一些 Tensor,这个过程叫填充(feed);
#返回的结果类型根据输入的类型而定,这个过程叫取回(fetch)。
with tf.Session() as sess:
result = sess.run([product])
print( result)
- 设备:一块可以用来运算并且拥有自己的地址空间的硬件,如 GPU 和 CPU。TensorFlow 为了实现分布式执行操作,充分利用计算资源,可以明确指定操作在哪个设备上执行。
with tf.Session() as sess:
# 指定在第二个 gpu 上运行
with tf.device("/gpu:1"):
matrix1 = tf.constant([[3., 3.]])
matrix2 = tf.constant([[2.],[2.]])
product = tf.matmul(matrix1, matrix2)
- 变量
变量(variable)是一种特殊的数据,它在图中有固定的位置,不像普通张量那样可以流动。例如,创建一个变量张量,使用 tf.Variable()构造函数,这个构造函数需要一个初始值,初始值的形状和类型决定了这个变量的形状和类型。
state = tf.Variable(0, name="counter")
TensorFlow 还提供了填充机制,可以在构建图时使用 tf.placeholder()临时替代任意操作的张量,在调用 Session 对象的 run()方法去执行图时,使用填充数据作为调用的参数,调用结束后,填充数据就消失。
input1 = tf.placeholder(tf.float32)
input2 = tf.placeholder(tf.float32)
output = tf.multiply(input1, input2)
with tf.Session() as sess:
print sess.run([output], feed_dict={input1:[7.], input2:[2.]})
# 输出 [array([ 14.], dtype=float32)]
- 内核:可以理解为进程。操作(operation)是对抽象操作(如 matmul 或者 add)的一个统称,而内核(kernel)则是能够运行在特定设备(如 CPU、 GPU)上的一种对操作的实现。因此,同一个操作可能会对应多个内核。
常用API汇总
可视化时需要在程序中给必要的节点添加摘要(summary),摘要会收集该节点的数据,并标记上第几步、时间戳等标识,写入事件文件(event file)中。 tf.summary.FileWriter 类用于在目录中创建事件文件,并且向文件中添加摘要和事件,用来在 TensorBoard 中展示。