本文梳理了TensorFlow的基本概念:数据流图、Tensor、Operation、Variable、Placeholder、Session、Optimizer、minimize。并对每个概念进行了详细的讲解和扩展。
阅读本文需要对深度学习有一定了解,并知道tensorflow是做什么的。
声明式编程:做什么
命令式编程:怎么做
TensorFlow采用了声明式编程范式。
声明式编程的优点:
1.代码可读性强:以目标而非过程为导向。
2.支持引用透明:函数的调用语句可以由它的返回值取代。
3.提供预编译优化能力:先构建出数据流图,无依赖逻辑的并行化计算、无效逻辑去除、公共逻辑提取等。
数据流图定义:用节点和有向边描述数学运算的有向无环图。
数据流图中的节点代表各类操作,包括数学运算、数据填充、结果输出和变量读写等等,每个节点的操作都需要分配到具体的物理设备(cpu、gpu)上执行。有向边描述了节点间的输入输出关系。
前向图中的节点有:
后向图中的节点有:
多数用来传输数据,少数用来控制依赖。
数据流图执行顺序的实现参考了拓扑排序的设计思想。
当我们使用tensorflow执行指定数据流图时,其执行过程可分为以下4个步骤:
在tensorflow中,张量(Tensor)是数据流图上的数据载体,通常是多维数组。还有一种叫SpareseTensor,存放稀疏数据(0的数量远多于非0元素),旨在减少稀疏数据的内存占用。
在物理实现时,它含有指向张量数据的内存缓冲区的指针,当它不被任何操作依赖时,会释放该内存缓冲区。
1.创建
一般tensor都不是直接创建的,而是定义常量和代数计算操作而间接创建的。
import tensorflow as tf
a = tf.constant(1, name="a", dtype=tf.int32)
b = tf.constant(2, name="b", dtype=tf.int32)
c = tf.add(a, b, name="c")
print(a)
print(b)
print(c)
Tensor("a:0", shape=(), dtype=int32)
Tensor("b:0", shape=(), dtype=int32)
Tensor("c:0", shape=(), dtype=int32)
2.求解
如果想要求解某个张量的值,则需要创建会话,然后执行张量的eval方法或会话的run方法。
with tf.Session() as sess:
print(c.eval())
print(sess.run(c))
3
3
3.成员方法
张量公共成员方法:
方法名称 | 功能说明 |
---|---|
eval | 取出张量值 |
get_shape | 取出张量形状 |
srt_shape | 修改张量形状 |
consumers | 获取直接依赖此张量的所有操作 |
import tensorflow as tf
a = tf.constant(1, name="a", dtype=tf.int32)
b = tf.constant(2, name="b", dtype=tf.int32)
c = tf.add(a, b, name="c")
print(a.consumers())
[<tf.Operation 'c' type=Add>] # 可以看出'c'这个操作依赖tensor'a'
4.操作
tensorflow提供了大量操作,以便构建数据流图,实现算法模型。
操作类型 | 典型操作 |
---|---|
一元代数操作 | abs、neg和invert |
二元代数操作 | add、multiply和sub(也可以用+ - * /) |
形状操作 | chip、reshape、slice和shuffle |
规约操作 | reduce_mean和reduce_sum |
神经网络操作 | conv、pool、softmax和relu |
条件操作 | cond |
数据流图由节点和邮箱边组成,每个节点代表一种操作,因此操作是模型功能的实际载体。数据流图中的节点按功能不同可以分为以下三种:
计算操作的基本属性:
属性名称 | 功能说明 |
---|---|
name | 名称 |
type | 操作的类型名称,比如加法是add |
inputs | 输入张量的列表 |
contril_inputs | 输入控制依赖向量 |
outputs | 输出的张量列表 |
device | 操作执行用的设备名称 |
graph | 操作所属的数据流图 |
traceback | 操作实例化时的调用栈 |
并不需要直接定义计算节点,只要你用了类似tf.add这种操作,它就自动构造了Operation实例。下面整理了TensorFlow Python API提供的典型操作:
操作类型 | 典型操作 |
---|---|
基础算术 | add、multiply、mod、sqrt、sin、trace、fft、argmin |
数组运算 | size、rank、split、reverse、cast、one_hot、quantize |
梯度裁剪 | clip_by_value、clip_by_norm、clip_by_global_norm |
逻辑控制和调试 | identity、logical_and、equal、less、is_finite、is_nan |
数据流控制 | enqeue、dequeue、size、take_grad、apply_grad |
初始化操作 | zeros_initializer、random_normal_initializer、orthogonal_initializer |
神经网络运算 | convolution、pool、bias_add、softmax、dropout、erosion2d |
随机运算 | random_normal、random_shuffle、multinomial、random_gamma |
字符串运算 | string_to_hash_bucket、reduce_join、substr、encode_base64 |
图像运算处理 | encode_png、resize_images、rot90、hsv_to_rgb、adjust_gamma |
作用:多次执行数据流图时存储特定的参数。
属性名称 | 功能说明 |
---|---|
name | 名称 |
dtype | 数据类型 |
shape | 变量形状 |
initial_value | 变量的初始值 |
initizlizer | 计算前为变量赋值的初始化操作 |
device | 操作执行用的设备名称 |
graph | 操作所属的数据流图 |
op | 变量操作 |
构建变量时必须给定一个值。
变量值要初始化tf.global_variables_initializer()
之后才能看到。
# 清空图
import tensorflow as tf
aa = tf.Variable([1,2,3], name="aa")
print(aa.dtype) # 输出其数据类型
print(aa.initial_value) # 输出初始化值
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(aa)) # 输出初始化之后的变量值
<dtype: 'int32_ref'>
Tensor("aa/initial_value:0", shape=(3,), dtype=int32)
[1 2 3]
数据流图在填充数据之前不会执行任何计算,通常,我们在定义模型之前就已经明确了输入数据的类型和形状等属性,而模型的第一步计算很可能就要用到这些输入数据。数据节点Placeholder的作用就是定义好待输入数据的类型和形状,先完成模型的构建,最后数据流图要执行的时候再把数据**填充(feed)**到Placehoder中来。
属性名称 | 功能说明 |
---|---|
name | 名称 |
dtype | 数据类型 |
shape | 变量形状 |
import tensorflow as tf
a = tf.constant([[1,2],[3,4]], dtype=tf.int32, name="a")
b = tf.placeholder(shape=[2,2], dtype=tf.int32, name="b")
c = a + b
with tf.Session() as sess:
print(sess.run(c, feed_dict={b:[[1,1],[1,1]]}))
[[2 3]
[4 5]]
tensorflow数据流图描述了要计算的拓扑结构和所需的数据属性,但它仅仅是个壳。还需要向图中填充数据、选择待求解的张量、执行各种操作才能得到结果。而tensorflow的会话提供了这个计算过程的运行环境,它的本质是维护一段运行时间的上下文。
会话通过提取和切分数据流图、调度并执行操作节点,来将数据流图在计算机上执行。
流程:创建会话、运行会话、关闭会话。
sess = tf.Session() # 创建会话
sess.run(...) # 运行会话
sess.close() # 关闭会话
# 或者:
with tf.Session() as sess:# 自动关闭
sess.run(...)
参数名称 | 功能说明 |
---|---|
target | 会话连接的执行引擎 |
graph | 会话加载的数据流图 |
config | 会话启动时的配置项 |
target在执行单机任务时不用管它。
graph指向默认图,当有多个数据流图时需要显式指定。
config描述会话的配置信息。
配置的内容可以包括并行线程数、GPU分配策略、运算超时时间等参数,最常用的几个参数:
参数 | 描述 |
---|---|
allow_soft_placement=True |
允许tensorflow自动分配设备 |
log_device_placement=True |
记录每个节点分配到哪个设备上日志 |
gpu_options.allow_growth=True |
用于动态申请显存,从少到多慢慢增加gpu容量 |
gpu_options.per_process_gpu_memory_fraction |
用于限制gpu使用率,=0.3表示拿出30%给进程使用 |
用法:
config = tf.ConfigProto(allow_soft_placement=True,
log_device_placement=True)
config.gpu_options.allow_growth=True
config.gpu_options.per_process_gpu_memory_fraction = 0.3
with tf.Session(config = config,...)
优化器是tensorflow实现优化算法的载体,它实现了自动求梯度并更新参数的功能。优化器根据模型结构和损失函数,利用链式求导法则求出每个参数的梯度并更新它们,以完成一次完整的训练。
tensorflow提供了很多种Optimizer,定义在tf.train中。
使用minimize方法训练模型
# minimize的参数:
minimize(self, loss, global_step=None, var_list=None,
gate_gradients=GATE_OP, aggregation_method=None,
colocate_gradients_with_ops=False, name=None,
grad_loss=None)
实现原理参见 源码:tensorflow/python/training/optimizer.py
其中常用的参数:
参数 | 功能 | 数据类型 |
---|---|---|
loss | 损失值 | Tensor |
global_step | 全局训练步数,自增 | Variable |
name | 优化器的名称 | String |
训练模型实例:
# 定义模型
...
# 定义损失函数
loss = ...
# 优化器
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01) # 选择优化方法,给定学习率
golbal_step = tf.Variable(0, name='global_step', trainable=False)
train = optimizer.minimize(loss, global_step=global_step)
with tf.Session() as sess:
sess.run(global_variables_initializer()) # 初始化参数
for step in 10000:
sess.run(train, feed_dict={...}) # 一步训练
[1] 深入理解TensorFlow框架设计与实现原理 第三章