TensorFlow执行

Session

Session:TF中OPs对象执行、Tensor对象计算的封装环境

  • Session管理图、OPs

    • 所有图必须Session中执行
    • 将图中OPs、其执行方法分发到CPU、GPU、TPU等设备上
    • 为当前变量值分配内存
  • Session只执行Data/Tensor Flow中OPs,忽略不相关节点

    • 即通往fetches的flow中的OPs构成子图才会被计算
  • 各Session中各值独立维护,不会相互影响

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
class Session:
def __init__(self,
target=str,
graph=None/tf.Graph,
config=None/tf.ConfigProto)

# 获取Session中载入图
self.graph

# 关闭会话,释放资源
def close(self):
pass

# 支持上下文语法
def __enter__(self):
pass
def __exit__(self):
pass

# 执行TensorFlow中节点,获取`fetches`中节点值
# 返回值若为Tensor
# python中:以`np.ndarray`返回
# C++/C中:以`tensorflow:Tensor`返回
# 可直接调用`.eval()`获得单个OP值
def run(self,
fetches=tf.OPs/[tf.OPs],
feed_dict=None/dict,
options=None,
run_metadata=None)

tf.InteractiveSession

tf.InteractiveSession:开启交互式会话

1
2
3
4
5
6
7
8
9
10
11
 # 开启交互式Session
sess = tf.InteractiveSession()
a = tf.constant(5.0)
b = tf.constant(6.0)
c = a * b
x = tf.Variable([1.0, 2.0])
# 无需显式在`sess.run`中执行
# 直接调用`OPs.eval/run()`方法得到结果
x.initializer.run()
print(c.eval())
sess.close()

Session执行

  • Fetch机制:sess.run()执行图时,传入需取回的结果, 取回操作输出内容

  • Feed机制:通过feed_dict参数,使用自定义tensor值替代图 中任意feeable OPs的输出

    • tf.placeholder()表示创建占位符,执行Graph时必须 使用tensor替代
    • feed_dict只是函数参数,只在调用它的方法内有效, 方法执行完毕则消失
  • 可以通过feed_dict feed所有feedable tensor,placeholder 只是指明必须给某些提供值

    1
    2
    3
    4
    a = tf.add(2, 5)
    b = tf.multiply(a, 3)
    with tf.Session() as sess:
    sess.run(b, feed_dict={a: 15}) # 45

config参数选项

  • log_device_placement:打印每个操作所用设备
  • allow_soft_placement:允许不在GPU上执行操作自动迁移到 CPU上执行
  • gpu_options
    • allow_growth:按需分配显存
    • per_process_gpu_memory_fraction:指定每个GPU进程 使用显存比例(无法对单个GPU分别设置)
  • 具体配置参见tf.ConfigProto

Graph

Graph:表示TF中计算任务,

  • operation/node:Graph中节点,包括:operatorvariableconstant

    • 获取一个、多个Tensor执行计算、产生、返回tensor
    • 不能无法对其值直接进行访问、比较、操作
    • 图中节点可以命名,TF会自动给未命名节点命名
  • tensor:Graph中边,n维数组

    • TF中所有对象都是Operators
    • tensor是OPs执行结果,在图中传递/流动
  • 图计算模型优势

    • 优化能力强、节省计算资源
      • 缓冲自动重用
      • 常量折叠,只计算取得目标值过程中必须计算的值
      • 方便并行化
      • 自动权衡计算、存储效率
    • 易于部署
      • 可被割成子图(即极大连通分量),便于自动区分
      • 子图可以分发到不同的设备上分布式执行,即模型并行
      • 许多通用ML模型是通过有向图教学、可视化的
  • 图计算模型劣势

    • 难以debug
      • 图定义之后执行才会报错
      • 无法通过pdb、打印状态debug
    • 语法繁复

构建图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Graph:
def __init__(self):
pass

# 将当前图作为默认图
# 支持上下文语法
def as_default(self):
pass

# 强制OPs依赖关系(图中未反映),即优先执行指定OPs
# 支持上下文语法
def as_default(self):
def control_dependencies(self,
?ops=[tf.OPs])
pass

# 以ProtoBuf格式展示Graph
def as_graph_def(self):
pass

# 判断图中节点是否可以被feed
def is_feedable(self,
?op=tf.OPs):
pass
  • 可以通过tf.Graph创建新图,但最好在是在一张图中使用多个 不相连的子图,而不是多张图

    • 充分性:Session执行图时忽略不必要的OPs
    • 必要性
      • 多张图需要多个会话,每张图执行时默认尝试使用所有 可能资源
      • 不能通过python/numpy在图间传递数据(分布式系统)
  • 初始化即包含默认图,OP构造器默认为其增加节点

    • 通过tf.get_default_graph()获取

图相关方法

  • 获取TF初始化的默认图

    1
    2
    def tf.get_default_graph():
    pass

命名空间

1
2
3
4
5
6
7
8
 # 均支持、利用上下文语法,将OPs定义于其下
def tf.name_scope(name(str)):
pass
def tf.variable_scope(
name(str),
reuse=tf.AUTO_REUSE
):
pass
  • tf.name_scope:将变量分组
    • 只是将变量打包,不影响变量的重用、可见性等
    • 方便管理、查看graph
  • tf.variable_scope
    • 对变量有控制能力
      • 可设置变量重用性
      • 变量可见性局限于该variable scope内,即不同 variable scope间可以有完全同名变量 (未被TF添加顺序后缀)
    • 会隐式创建name scope
    • 大部分情况是用于实现变量共享
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def fully_connected(x, output_dim, scope):
# 设置variable scope中变量自动重用
# 或者调用`scope.reuse_variables()`声明变量可重用
with tf.variable_scope(scope, reuse=tf.AUTO_REUSE) as scope:
# 在当前variable scope中获取、创建变量
w = tf.get_variable("weights", [x.shape[1]], output_dim,
initializer=tf.random_normal_initializer())
b = tf.get_variable("biases", [output_dim],
initializer=tf.constant_initizer(0.0))
return tf.matmul(x, w) + b

def two_hidden_layers(x):
h1 = fully_connected(x, 50, "h1")
h2 =-fully_connected(h1, 10, "h2")

with tf.variable_scope("two_layers") as scope:
logits1 = two_hidden_layers(x1)
logits2 = two_hidden_layers(x2)

Lazy Loading

Lazy Loading:推迟声明/初始化OP对象至载入图时 (不是指TF的延迟计算,是个人代码结构问题,虽然是TF延迟图计算 模型的结果)

  • 延迟加载容易导致向图中添加大量重复节点,影响图的载入、 传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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):
    # 延迟加载节点,每次执行都会添加`tf.add`OP
    sess.run(tf.add(x, y))
    print(tf.get_default_graph().as_graph_def())
    writer.close()
  • 解决方案

    • 总是把(图的搭建)OPs的定义、执行分开
    • python利用@property装饰器,使用单例模式函数 封装变量控制,保证仅首次调用函数时才创建OP

Eager Execution

1
2
3
4
import tensorflow as tf
import tensorflow.contrib.eager as tfe
# 启用TF eager execution
tfe.enable_eager_execution()
  • 优势

    • 支持python debug工具
    • 提供实时报错
    • 支持python数据结构
    • 支持pythonic的控制流

      1
      2
      3
      i = tf.constant(0)
      whlile i < 1000:
      i = tf.add(i, 1)
  • eager execution开启后

    • tensors行为类似np.ndarray
    • 大部分API和未开启同样工作,倾向于使用
      • tfe.Variable
      • tf.contrib.summary
      • tfe.Iterator
      • tfe.py_func
      • 面向对象的layers
      • 需要自行管理变量存储
  • eager execution和graph大部分兼容

    • checkpoint兼容
    • 代码可以同时用于python过程、构建图
    • 可使用@tfe.function将计算编译为图

示例

  • placeholder、sessions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 普通TF
    x = tf.placholder(tf.float32, shape=[1, 1])
    m = tf.matmul(x, x)
    with tf.Session() as sess:
    m_out = sess.run(m, feed_dict={x: [[2.]]})

    # Eager Execution
    x = [[2.]]
    m = tf.matmul(x, x)
  • Lazy loading

    1
    2
    3
    4
    5
    x = tf.random_uniform([2, 2])
    for i in range(x.shape[0]):
    for j in range(x.shape[1]):
    # 不会添加多个节点
    print(x[i, j])

Device

设备标识

  • 设备标识:设备使用字符串进行标识

    • /cpu:0:所有CPU都以此作为名称
    • /gpu:0:第一个GPU,如果有
    • /gpu:1:第二个GPU
    1
    2
    3
    4
    5
    6
    # 为计算指定硬件资源
    with tf.device("/gpu:2"):
    a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0], name="a")
    b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0], name="b")
    c = tf.multiply(a, b)
    # creates a graph
  • 环境变量:python中既可以修改os.environ,也可以直接设置 中设置环境变量

    • CUDA_VISIBLE_DEVICES:可被使用的GPU id

设备执行

设备执行:TF将图形定义转换成分布式执行的操作以充分利用计算 资源

  • TF默认自动检测硬件配置,尽可能使用找到首个GPU执行操作

  • TF会默认占用所有GPU及每个GPU的所有显存(可配置)

    • 但只有一个GPU参与计算,其他GPU默认不参与计算(显存 仍然被占用),需要明确将OPs指派给其执行
    • 指派GPU数量需通过设置环境变量实现
    • 控制显存占用需设置Session config参数
  • 注意:有些操作不能再GPU上完成,手动指派计算设备需要注意

tf.ConfigProto

  • Session参数配置

    1
    2
    3
    4
    5
     # `tf.ConfigProto`配置方法
    conf = tf.ConfigProto(log_device_placement=True)
    conf.gpu_options.allow_growth=True
    sess = tf.Session(config=conf)
    sess.close()

Python执行模型

综述

Code Blocks

代码块:作为一个单元执行的一段python文本,代码块构成python 程序

  • 模块、函数体、类定义
  • 交互式输入的每条命令
  • 作为标准输入、命令行参数发送给解释器的脚本文件
  • 脚本命令
  • 传递给eval()exec()的字符串参数
  • 代码块在执行帧中执行,执行帧包含某些用于调试的管理信息 并决定代码块执行完成后操作

NamingBinding

Binding of Names

  • 名称:用于指代对象,通过名称绑定操作引入
  • 名称绑定方法

    • 传递参数
    • import语句
      • from ... import *会绑定被导入模块中定义的所有 公有名称(仅在模块层级上被使用)
    • 类、函数定义
    • 以标识符为目标的赋值
    • for循环开头、withexcept子句的as之后

    • del语句的目标也被视为绑定,虽然实际语义为解除名称 绑定

  • 变量类型

    • 局部变量:绑定在代码块中的名称,且未声明为nonlocal 、或global
    • 全局变量:绑定在模块层级的名称
      • 模块代码块中变量既为全局变量、也是局部变量
    • 自由变量:在代码块中使用但未在其中定义的变量
  • 自由变量不同于闭包变量,函数闭包变量__closure__要求 自由变量来自于父函数作用域

Scope

作用域:定义了代码块中名称的可见性

  • 名称的作用域包含

    • 定义该变量代码块
    • 定义变量代码块所包含的代码块 (除非被包含代码块中引入对该名称不同绑定)
  • 名称作用域虽然包括定义变量代码块所包含的代码块

    • 可以直接访问变量
    • 但修改变量必须使用globallocal等声明
  • 在代码块内任何位置进行名称绑定,则代码块内所有对该名称 的使用被认为是对代码块的引用

    • python没有声明语法,则代码块中局部变量可通过,在整个 代码块文本中扫描名称绑定来确定
  • 模块作用域在模块第一次被导入时创建

Resolution of Names

  • 若名称在代码块中被使用,会由包含它的最近作用域来解析

  • 若名称完全无法找到将raise NameError

  • 若当前作用域为函数作用域,且名称指向局部变量,若名称被 绑定值前被被使用将raise UnboundLocalErrorUnboundLocalErrorNameError子类)

  • (代码块)环境:对代码块可见的所作用域集合

global

  • global指定名称的使用是对最高层级命名空间中该名称 绑定的引用

    • 全局命名空间:包含代码块的模块命名空间
    • 内置命名空间:builtins模块的命名空间
  • global语句与同一代码块中名称绑定具有相同作用域

    • 若自由变量最近包含作用域中有global语句,其也会被 当作全局变量
  • global语句须在名称使用之前声明

nonlocal

  • nonlocal使得相应名称指向最近包含函数作用域的中绑定的 变量

  • 指定名称不存在于任何包含函数作用域则raise SyntaxError

内置命名空间

  • 与代码块执行相关联的内置命名空间实际上是通过其在全局命名 空间中搜索名称__builtins__找到,一般是字典、模块 (此时使用模块的命名空间字典)

  • 默认情况下

    • __main__模块中:__builtins__为内置模块builtins
    • __main__模块中:__buitlins__builtins模块 自身的字典别名

动态特性

  • 自由变量的名称解析

    1
    2
    3
    4
    5
    6
    i = 0
    def f():
    print(i)
    i = 42
    f()
    # 打印`42`
    • 发生在运行时,而不是编译时
    • 在全局命名空间中,而不是最近包含命名空间中

eval()exec()

  • eval()exec()没有对完整环境的访问权限来解析名称

    • 有可选参数用于重载全局、局部命名空间
    • 若只指定一个命名空间,则会同时作用于两者

  • 类中名称解析遵守大部分情况下同普通规则,但 未绑定局部变将在全局命名空间中搜索

  • 类定义的命名空间__dict__会成为类的属性字典

  • 类代码块中定义的名称的作用域会被限制在类代码块中,不会 扩展到方法代码块中,包括推导式、生成器表达式

    1
    2
    3
    class A:
    a = 42
    b = list(a + i for i in range(10))

Exception

异常:中断代码块的正常控制流程以便处理错误、其他异常条件的 方式

  • 在错误被检测到的位置引发

    • 解释器检测到运行时错误时错误
    • raise语句显式引发
  • 可被当前包围代码块、任何直接或间接主调代码块捕获、处理

    • try...except指定错误处理,finally子句清理代码
    • 异常通过实例标识、根据器类型选择执行except子句
  • 错误处理采用“终止”模型

    • 异常处理器可以找出发生的问题,在外层继续执行
    • 但不能修复错误的根源,并重试失败操作
    • 异常完全未被处理时,解释器会终止程序执行、或返回交互 模式循环,并打印栈回溯信息,除非为SystemExit异常
  • 异常实例可以携带关于异常状态的额外信息

    • 异常信息不是python API一部分,内容可能在不同python 版本间不经警告的改变