本文默认的一些名称叫法:
1. “Tensor实例”、”Tensor对象”、”tensor”都是在说一个Tensor实例。同样,”Operation实例”、”Operation对象”、”operation”都是在说一个Operation实例。
2. 单独的”Tensor”和”Operation”都表示一个Python class
3. “operation”有时简写为”op”或”Op”,”tensor”有时简写为”t”
代码位置:tensorflow/tensorflow/python/framework/ops.py
1. 要点
tf.get_default_graph()
显式地访问这个图,也可以不理会这个图,因为调用任一个operation函数时,如调用constant op,c=tf.constant(4.0)
,一个表示operation的节点会自动添加到这个图上,此时c.graph
就指这个默认图。as_default()
方法,并得到一个Python中的上下文管理器(context manager),来管理临时默认图的生命周期,即with ...
下的代码区域。g = tf.Graph()
with g.as_default():
# 此时定义的operation和tensor都自动添加到图g上
c = tf.constant(30.0)
小提示:组装图阶段,Graph类不是线程安全的,添加operations最好在单线程内完成。
2. Graph的属性
内部属性:
_nodes_by_id
:dict( op的id => op ),按id记录所有添加到图上的op_nodes_by_name
:dict( op的name => op ),按名字记录所有添加到图上的op_next_id_counter
:int,自增器,创建下一个op时用的id_version
:int,记录所有op中最大的id_default_original_op
:有些op需要附带一个original_op
,如replica op需要指出它要对哪个op进行复制_attr_scope_map
:dict( name scope => attr ),用于添加一组额外的属性到指定scope中的所有op_op_to_kernel_label_map
:dict( op type => kernel label ),kernel可能是指operation中更底层的实现_gradient_override_map
:dict( op type => 另一个op type ),把一个含自定义gradient函数的注册op,用在一个已存在的op上_name_stack
:字符串,嵌套的各个scopes的名字拼成的栈,用带间隔符”/”的字符串表示_names_in_use
: dict( name scope => 使用次数 )_device_function_stack
: list,用来选择device的函数栈,每个元素是一个device_function(op)
,用来获取op所在device_control_flow_context
:一个context对象,表示当前控制流的上下文,如CondContext
对象,WhileContext
对象,定义在ops/control_flow_ops.py。实际上,控制流也是一个op,用来控制其他op的执行,添加一些条件依赖的关系到图中,使执行某个operation前先查看依赖_control_dependencies_stack
:list,一个控制器栈,每个控制器是一个上下文,存有控制依赖信息,表明当执行完依赖中的operations和tensors后,才能执行此上下文中的operations_unfeedable_tensors
:set,定义不能feed的tensors_unfetchable_ops
:set,定义不能fetch的ops_handle_feeders
:dict( tensor handle placeholder => tensor dtype )_handle_readers
:dict( tensor handle => 它的read op )_handle_movers
:dict( tensor handle => 它的move op )_handle_deleters
:dict( tensor handle => 它delete op )_seed
:当前图内使用的随机种子_collections
:dict( collection name => collection ),相当于图中的一块缓存,每个collection可看成一个list,可以存任何对象_functions
:定义图内使用中的一些函数_container
:资源容器resource container,用来存储跟踪stateful operations,如:variables,queues_registered_ops
:注册的所有操作_finalized
:布尔值,真表示Graph属性都已确定,不再做修改_lock
:保证读取Graph某些属性(如:_version
)时尽可能线程安全_graph_def_version
:图定义的版本_building_function
:该图是否表示一个函数_colocation_stack
:保存共位设置(其他op都与指定op共位)的栈对外属性:
tf.Graph.version
,也就是self._version
,记录最新的节点version,即图中最大op id,但是与GraphDef的version无关tf.Graph.graph_def_versions
,也就是self._graph_def_versions
,GraphDef版本,定义在tensorflow/tensorflow/core/framework/graph.prototf.Graph.seed
,也就是self._seed
,此图内使用的随机种子tf.Graph.building_function
,也就是self._building_function
tf.Graph.finalized
,也就是self._finalized
,表明组装图阶段是否完成3. Graph的主要方法
构造方法:
tf.Graph.__init__()
:创建一个空图获取图元素(tensors, operations)的方法:
tf.Graph.as_graph_def(from_version=None, add_shapes=False)
:返回该graph对应的GraphDef表示,使用了protocol buffer,见下面的message GraphDef。该方法是线程安全的。 from_version
表明包括的节点version(即op id)的范围,from_version
之前的节点都不要;(2) add_shapes
如果为真,则每个节点都要添加输出tensors的形状信息到_output_shapes
messsage GraphDef {
// 图中的所有节点,参见下面message NodeDef
repeated NodeDef node = 1;
// 图的版本,不同于TensorFlow版本
VersionDef versions = 4;
// 丢弃
int32 version = 3 [deprecated = true];
// Experimental. 提供用户自定义的函数
FunctionDefLibrary library = 2;
}
tf.Graph.as_graph_element(obj, allow_tensor=True, allow_operation=True)
:该获取信息的方法实际上完成了一个验证加转换的工作,给定一个obj,看它能否对应到图中的元素,可以是一个operation,也可以是一个tensor,如果对应,则以operation或tensor的身份返回它自己。该方法可以被多个线程同时调用。 obj
:可以是一个Tensor对象,或一个Operation对象,或tensor名,或operation名,或其他对象;(2) allow_tensor
:真表示obj可以是tensor;(3) allow_operation
:真表示obj可以是operationtf.Graph.get_operation_by_name(name)
:根据名字获取某个operationtf.Graph.get_tensor_by_name(name)
:根据名字获取某个tensortf.Graph.get_operations()
:获取所有operationstf.Graph.is_feedable(tensor)
tf.Graph.is_fetchable(tensor_or_op)
tf.Graph.prevent_feeding(tensor)
tf.Graph.prevent_fetching(op)
添加节点组装图的方法:
tf.Graph.unique_name(name, mark_as_used=True)
:为operation name构造一个唯一名,唯一名可能包含分隔符"/"
。传参mark_as_used
表示构造的唯一名,只是用来看看,还是要被创建出来使用的,将其传给方法create_op()
来创建一个operation。tf.Graph.create_op(op_type, inputs, dtypes, ...)
:这是一个低级别接口,开发者一般用不到,因为只用具体op的构造函数,如tf.constant()
,即可实现向图添加op节点。此方法返回一个Operation实例,却有很多传入参数,包括: op_type
:创建的op类型,也就是操作方法名,如”MatMul”,对应OpDef.name字段;(2) inputs
:op的输入,是一个由Tensor对象组成的列表;(3) dtypes
:op输出的tensors的数据类型,是一个由DType对象组成的列表input_types
:op输入的tensors的类型,是一个由DType对象组成的列表,默认使用inputs
中的tensors自带的Dtype;(2) name
:op做节点的名字,默认基于op_type
构造出;(3) attrs
:dict( 属性名 => operation的属性),在NodeDef proto中有定义;(4) op_def
:OpDef proto,是一个描述operation操作方法的protocol buffer;(5) compute_shapes
:布尔值,是否计算输出的tensors的形状;(6) compute_device
:布尔值,是否执行device_function来获取operation的device结束组装图的定稿方法
tf.Graph.finalize()
:结束组装图,以后图只能读不能写,不能再添加新operation节点。此方法用在图要在多个线程间共享的场景下,如用于QueueRunner
切换默认图的方法:
tf.Graph.as_default()
:让当前图取代默认图。一般来说,不常使用,因为当前线程会自动提供一个全局默认图,也就是说,全局默认图是当前线程的一个属性,新建一个线程后全局默认图就变了。除非你在同一个进程内创建了多个图,才会有用这个方法的需求。 # 两种等价方法
# 方法一:
g = tf.Graph()
with g.as_default():
...
# 方法二:
with tf.Graph().as_default() as g: # 当前上下文就是刚创建的g
...
返回新上下文的方法:
tf.Graph.control_dependencies(control_inputs)
:返回一个控制依赖的上下文,使得上下文内的新加入op都有此依赖。控制依赖的意思是,若想执行下步的op,必须先完成依赖中的input。此方法的上下文就是一个控制器controller,controller内保存了新建的控制依赖,同时controller加入一个控制器栈controller stack,以支持嵌套的控制依赖上下文。 control_inputs
是一个operation或tensor的列表with g.control_dependencies([a, b]):
# 这里新建的op在a,b后执行
with g.control_dependencies([c, d]):
# 这里新建的op在a,b,c,d后执行
with g.control_dependencies(None):
# 因为依赖链断掉,这里新建的op不需等待a,b,c,d
with.control_dependencies([e, f])
# 这里新建的op在e,f后执行
tf.Graph.device(device_name_or_function)
:返回一个默认device的上下文,使得上下文内的新加入op都被分派到该device上。此方法的上下文就是一个device function,它被压入一个device function stack,以支持嵌套。 device_name_or_function
可以是一个表示device名的字符串,或一个返回device名的函数,或Nonev.assign()
将随它的Variable对象v放在一起def matmul_on_gpu(node):
if node.type == "MatMul": # node.type就是指op type
return "/gpu:0"
else:
return "/cpu:0"
with g.device(matmul_on_gpu):
# 此处新建的所有type为"MatMul"的op将放在GPU 0上,其他则放在CPU 0上
with g.device('/gpu:0'):
# 此处新建的所有op都将放在GPU 0上
device的命名格式:
/job:
/replica: /task: /device: :
-:标识id,为一个字符串,形如
[a-zA-Z][_a-zA-Z]*
,比如/job:param_server
为一个名为”param_server”的job
-:支持的设备类型,如”cpu”或”gpu”
-,
,
:小的非负整数
举例:
(1)/job:w/replica:0/task:0/device:gpu:*
:位于job w的replica 0, task 0上的任何gpu devices
(2)/job:*/replica:*/task:*/device:cpu:*
:位于任何job/task/replica的任何cpu devices
tf.Graph.name_scope(name)
:返回一个层级命名operation的上下文。一个图维护一个命名域(或叫“命名空间”)的栈self._name_stack
,此方法的传入参数name会被压入该栈,支持嵌套。 name
可以是一个字符串,用于创建一个新的name scope;也可以是一个已有的name scope,用于重新进入这个已存在的scope;也可以是None或空字符,此时表示顶层的name scopec = tf.constant(1.0, name="c") # c.op.name为"c"
c_1 = tf.constant(2.0, name="c") # c_1.op.name为"c_1"
with g.name_scope("nested") as scope:
nested_c = tf.constant(3.0, name="c") # nested_c.op.name为"nested/c"
with g.name_scope("inner"):
nested_inner_c = tf.constant(4.0, name="c") # nested_inner_c.op.name为"nested/inner/c"
with g.name_scope("inner"): # 因为此域下已有"inner",所以用"inner_1"
nested_inner_1_c = tf.constant(5.0, name="c") # nested_inner_1_c.op.name为"nested/inner_1/c"
with g.name_scope(scope): # 无论现在嵌套哪里,都转换成scope,即"nested/"
nested_c_1 = tf.constant(6.0, name="c") # nested_c_1.op.name为"nested/c_1"
with g.name_scope(""): # 变成顶级scope
c_2 = tf.constant(7.0, name="c") # c_2.op.name为"c_2"
tf.Graph.gradient_override_map(op_type_map)
:返回一个改写gradient函数的上下文,使得针对某些operation,我们可以使用自己的gradient函数。一个图维护这样一个映射关系self._gradient_override_map
.# 先注册一个gradient函数
@tf.RegisterGradient("CustomSquare")
def _custom_square_grad(op, grad):
# ...
with tf.Graph().as_default() as g:
c = tf.constant(5.0)
s_1 = tf.square(c) # 使用tf.square默认的gradient
with g.gradient_override_map({"Sqaure": "CustomSquare"}):
s_2 = tf.square(s_2): # 使用自定义的_custom_square_grad函数来计算s_2的梯度
tf.Graph.colocate_with(op, ignore_existing=False)
:返回一个共用给定op的位置的上下文,使得上下文内的新加入op都共用这个位置。传参ignore_existing
为真则表示忽略以前所有的共位设置。a = tf.Variable([1.0])
with g.colocate_with(a):
# 下面的b,c与a共位
b = tf.constant(1.0)
c = tf.add(a, b)
tf.Graph.container(container_name)
:返回一个带资源容器的上下文,服务于带状态的operations,如:varaibles,queues,用来存储跟踪它们的状态。可使用tf.Session.reset()
清除资源容器中保存的信息。with g.container('experiment0'):
v = tf.Variable([1.0]) # 将存到资源容器"experiment0"
with g.container('experiment1'):
q = tf.FIFOQueue(10, tf.float32) # 将存到资源容器"experiment1"
with g.container(''):
v2 = tf.Variable([2.0]) # 将存到默认的资源容器
与graph collections相关的方法:
一个图中可以有多个collections,也称为graph collections,每个collection都有一个名字,用来存储一组相关的对象,可以把一个collection看成一个list或array。它的标准名字有:GLOBAL_VARIABLES
,LOCAL_VARIABLES
,MODEL_VARIABLES
等,定义在GraphKeys
类里。
tf.Graph.add_to_collection(name, value)
:把value存到名为name的collection中tf.Graph.add_to_collections(names, value)
:把value存到名字在names上的所有collections中tf.Graph.get_collection(name, scope=None)
:根据名为name的collection,返回它中所有的values,即一个values的列表。传参scope
充当一个过滤器,筛出指定scope中的values。tf.Graph.get_collection_ref(name)
:同上,不同之处是此方法返回对collection本身的引用,而不是复制一份,故在上面的修改会起作用。tf.Graph.get_all_collection_keys()
:返回一个collections的listf.Graph.clear_collection(name)
:清除名为name的collection上所有的values1. 要点
c=tf.matmul(a,b)
,则创建一个表示矩阵乘操作的op节点,其中a, b, c都为tensor,a和b作输入,c作输出Graph.create_op()
run()
op.run()
,这实际上是tf.get_default_session().run(op)
的简写2. Operation的属性
_node_def
为一个NodeDef对象,_op_def
为一个OpDef对象_id_value
为op在图中的id_graph
为op所在图_inputs
为输入op的tensors列表,_outputs
为输出op的tensors列表_input_types
为输入op的tensors的数据类型列表,_output_types
为输出op的tensors的数据类型列表_control_inputs
为执行op前的控制依赖_original_op
为当前op需要的一个原op,如replica op还需要一个op,称为原op_traceback
为创建op时的调用栈call stack_control_flow_context
为包含当前op的当前控制流上下文tf.Operation.name
:该operation的全名tf.Operation.type
:该operation的type,如MatMul
tf.Operation.inputs
:该operation的输入,是一个Tensor对象的列表tf.Operation.outputs
:该operation的输出,也是一个Tensor对象的列表tf.Operation.control_inputs
:是一个Operation对象的列表,执行当前operation前,需要保证此列表中的所有Operation对象都已执行完毕tf.Operation.graph
:该operation所在的graphtf.Operation.device
:该operation所在的device,表示为一个字符串tf.Operation.traceback
:自该operation创建以来的调用栈tf.Operation.node_def
:该operation对应的NodeDef表示,使用了protocol buffer,见下面的message NodeDef
tf.Operation.op_def
:该operation的type对应的OpDef表示,使用了protocol buffer,见下面的message OpDef
// 定义图中的一个节点
message NodeDef {
// 本operator名,在一个图中是唯一的,可看成当前节点名
string name = 1;
// operation名,在一个图中可有重复,可看成操作的方法名
string op = 2;
// input列表,每个input表示为字符串":",表明来自哪个op的哪个output索引
repeated string input = 3;
// 本节点所在device,举例为:
// 1) "@other/node":与另一个节点"other/node"共位置
// 2) "/job:worker/replica:0/task:1/gpu:3":全路径
// 3) "/job:worker/gpu:3":部分路径
// 4) "":无
string device = 4;
// 应包含OpDef的所有attrs
map<string, AttrValue> attr = 5;
}
// 定义一个操作
message OpDef {
// operation名,等于NodeDef中的"op",名字采用CameCase格式,若首字符为"_"则为内部保留的操作
string name = 1;
// 定义一个argument message,用作一个input或output
message ArgDef {
// 当前input或output的名
sting name = 1;
// 给人读的描述
string description = 2;
// 当前input或output,可以接受一到多个tensors
// (1)当接受一个tensors时,要么设置type字段,要么设置type_attr字段,指向一个类型为"type"的attr
// (2)当接受多个type相同的tensors时,要设置number_attr字段,指向一个类型为"int"的attr,表示tensors的数目,当然还要设置type或type_attr字段
// (3)当接受多个type不同的tensors时,要设置type_list_attr字段,指向一个类型为"list(type)"的attr,不用设置type、type_attr和number_attr字段
DataType type = 3;
string type_attr = 4;
string number_attr = 5;
string type_list_attr = 5;
// 当前input或output是否为ref
bool is_ref = 16;
}
// 当前操作的所有输入
repeated ArgDef input_arg = 2;
// 当前操作的所有输出
repeated ArgDef output_arg = 3;
// 定义一个attr message
message AttrDef {
string name = 1;
string type = 2; // 如:"string", "list(string)", "int"
AttrValue default_value = 3;
string description = 4;
// 对于"int"型,有下面两字段
bool has_minimum = 5;
int64 minimum = 6;
AttrValue allowed_values = 7;
}
// 在op中定义的attr会加入NodeDef
repeated AttrDef attr = 4;
// 其他
OpDeprecation deprecation = 8;
string summary = 5;
string description = 6;
// 操作是否满足交换律
bool is_commutative = 18;
// 操作可接受2个以上的inputs,得出1个同类型的output,需满足交换律和结合律
bool is_aggregate = 16;
// 操作是否带状态,stateful ops不能在devices间移动,除非状态也能移动
bool is_stateful = 17; // 如:variables, queue
// 默认情况下,所有op的inputs必须是初始化后的tensors
bool allows_uninitialized_input = 19; // 如:assign
}
3. Operation的主要方法
构造方法:
tf.Operation.__init__(node_def, g, inputs=None, ...)
:创建一个Operation实例,传入参数有很多,包括: node_def
为一个node_def_pb2.NodeDef
实例,包含了描述operation的属性,有name
、op
、device
但没有input
,因为input
是生成模型时才有的;(2) g
为所在的图inputs
为当前operation的输入,是一个Tensor对象的列表;(2) output_types
当前operation的输出的类型,为是一个DType对象的列表;(3) control_inputs
执行当前operation的前提,为一个operations或tensors的列表;(4) input_types
为输入的类型,默认为[x.dtype.base_dtype for x in inputs]
;(5) original_op
为一个关联的原op,如复制op的replica op,要给出那个op;(6) op_def
为当前operation代表的op type,如”matmul”,定义在op_def_pb2.OpDef
执行operation的方法:
tf.Operation.run(feed_dict=None, session=None)
:在session中运行当前operation,会一级级触发那些给当前operation提供inputs的所有直接或间接的operations。实际上,最后调用的是session.run(operation, feed_dict)
。 feed_dict
是一个dict( Tensor对象或tensor名 => 具体值 ),具体值可以是list、numpy ndarray、TensorProto或string;(2) session
若没指定,则用当前线程的默认session。获取operation信息的方法:
tf.Operation.get_attr(name)
:根据名字返回当前operation的某个属性值。一个operation会有多个属性,定义在self._node_def.attr
上。tf.Operation.colocation_groups()
:返回当前operation的共位置组列表,格式为["loc:@<节点名即_node_def.name>", ...]
1. 要点
Session.run()
或t.eval()
来计算。t.eval()
实际上是tf.get_default_session().run(t)
。# 组网过程,下面的c、d、e都是Tensor实例,即一个符号,而不是具体值
c = tf.constant([[1.0, 2.0], [3.0, 4.0]]) # constant op的输入是一个二维数组常量
d = tf.constant([[1.0, 1.0], [0.0, 1.0]])
e = tf.matmul(c, d)
# 启动session来执行图
sess = tf.Session()
result = sess.run(e) # 这里result是一个numpy array,负责存储具体值
2. Tensor的属性
_op
:以该tensor作输出的op_value_index
:在op输出中的索引_dtype
:数据类型_shape
:tensor形状_consumers
:使用该input做输入的operations列表,方便在图中游走_handle_shape
,_handle_dtype
:用于C++形状推断tf.Tensor.dtype
:该tensor中元素的DTypetf.Tensor.name
:该tensor的名字,为”op名:输出索引”tf.Tensor.op
:该tensor所在的operation,tensor作它的输出tf.Tensor.value_index
:该tensor位于它所在operation的输出列表中的索引tf.Tensor.graph
:该tensor所在的图,也是它的op的图tf.Tensor.device
:该tensor所在的devicetf.Tensor.shape
:该tensor的形状,是一个TensorShape对象,如TensorShape([Dimension(3), Dimension(4)])
3. Tensor的主要方法
tf.Tensor.__init__(op, value_index, dtype)
:创建一个Tensor实例,op
为以它做输出的operation,value_index
为它在输出中的索引,dtype
为它的元素数据类型tf.Tensor.eval(feed_dict=None, session=None)
:启动session后,如果session的图与该tensor的图相同,则在session中对该tensor求值,会触发计算它的operation以及图中所依赖的前面operations。实际上,最终调用的是session.run(tensors, feed_dict)
。 feed_dict
是一个dict( Tensor对象或tensor名 => 具体值 ),具体值可以是list、numpy ndarray、TensorProto或string;(2) session
若没指定,则用当前线程的默认session。tf.Tensor.get_shape()
:获取该tensor的形状,返回是一个TensorShape对象,推断形状(shape inference)的过程不用启动session,但在operation中需注册一个用于推断形状的函数。比如,c=tf.constant([[1.0,2.0,3.0],[4.0,5.0,6.0]])
,则调用c.get_shape()
得到一个TensorShape([Dimension(2), Dimension(3)])
tf.Tensor.set_shape(shape)
:设置或更新该tensor的形状,如image.set_shape([28, 28, 3])
tf.Tensor.consumers()
:返回以该tensor做输入的所有operations4. Tensor重载的Python operators
__(r)add__
“+”,__(r)sub__
“-“,__(r)mul__
“*”,__(r)div__
“/”, __(r)floordiv__
“//”,__(r)truediv
“/”,__(r)mod__
“mod”,__neg__
“-“,__(r)pow__
“pow(x,y)”,__abs__
“| |”__(r)and__
“&”,__(r)or__
“|”,__invert__
“~”,__(r)xor__
“^”__eq__
“==”,__ge__
“>=”,__gt__
“>”,__le__
“<=”,__lt__
“<”__getitem__
“[ ]”,__hash__
默认都是元素级(element-wise)操作,除了__(r)mul__
,源码注释这样说:# Dispatches cwise mul for "DenseDense" and "DenseSparse"
# __getitem__:限定到子tensor
foo = tf.constant([[1,2,3], [4,5,6], [7,8,9]])
print(foo[::2, ::-1].eval()) # => [[3,2,1], [9,8,7]]