前言:在使用tensorflow的时候,常常会被Operation、Tensor、Op_name、tensor_name等等概念搞混淆,本文专门通过一个简单的例子来深入讲解他们之间的区别于本质,并且如何在tensorboard中进行查看。
一、线性回归的完整实例
本文以一个两层神经网络来实现线性回归,代码如下:
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 48 49 50 51 52 53 54 | import tensorflow as tf import numpy as np import matplotlib.pyplot as plt #使用numpy生成200个随机点 x_data=np.linspace(-0.5,0.5,200)[:,np.newaxis] noise=np.random.normal(0,0.02,x_data.shape) y_data=np.square(x_data)+noise #定义两个placeholder存放输入数据 x=tf.placeholder(tf.float32,[None,1],name="model_input") y=tf.placeholder(tf.float32,[None,1],name="model_output") #定义神经网络中间层 Weights_L1=tf.Variable(tf.random_normal([1,10]),name="Dense1_weights") biases_L1=tf.Variable(tf.zeros([1,10]),name="Dense1_bias") #加入偏置项 Wx_plus_b_L1=tf.matmul(x,Weights_L1)+biases_L1 L1=tf.nn.tanh(Wx_plus_b_L1,name="Dense1_output") #加入激活函数 #定义神经网络输出层 Weights_L2=tf.Variable(tf.random_normal([10,1]),name="Dense2_weights") biases_L2=tf.Variable(tf.zeros([1,1]),name="Dense2_bias") #加入偏置项 Wx_plus_b_L2=tf.matmul(L1,Weights_L2)+biases_L2 prediction=tf.nn.tanh(Wx_plus_b_L2,name="Dense2_ouput") #加入激活函数 #定义损失函数(均方差函数) loss=tf.reduce_mean(tf.square(y-prediction),name="loss_function") #定义反向传播算法(使用梯度下降算法训练) optimizer = tf.train.GradientDescentOptimizer(0.1).minimize(loss,name="optimizer") # tensorboard loss_scaler = tf.summary.scalar('loss',loss) merge_summary = tf.summary.merge_all() # 模型保存 saver = tf.train.Saver() gpu_options = tf.GPUOptions() gpu_options.visible_device_list = "1" gpu_options.allow_growth = True config = tf.ConfigProto(gpu_options = gpu_options) with tf.Session(config=config) as sess: #变量初始化 sess.run(tf.global_variables_initializer()) writer=tf.summary.FileWriter('./tensorboard/linear_regression',graph=sess.graph) #训练2000次 for i in range(2000): opti,loss_,merge_summary_ = sess.run([optimizer,loss,merge_summary],feed_dict={x:x_data,y:y_data}) writer.add_summary(merge_summary_,global_step=i) if i%100==0: print(f"now is training {i+1} batch,and loss is {loss_} ") save_path = saver.save(sess,"./linear_model/linear_model.ckpt") print(f"model have saved in {save_path} ") |
运行之后,会得到权重保存文件checkpoint,以及tensorboard日志文件,现在打开tensorboard,我们可以看到如下面的graph结构:
在原来的理解中,声明的变量比如a=tf.Variable()这应该是一个tensor,而进行的操作比如tf.add()应该才是operation,后面发现这种理解是错误的,
我们可以发现几个问题,现在总结如下:
- (1)声明的占位符placeholder,比如上面的model_input,是一个节点node,对应的是operation,用椭圆符号表示;
- (2)声明的变量,把比如上面的Dense1_weights,本质上也是一个node,虽然用的是圆角矩形(namespace),将其双击展开,发现里面包含了3个节点node;
总结:
- 常量、占位符、变量声明、操作函数本质上都是节点node,都是一种操作operation;
- 只有在节点之间流动的箭头才表示的是tensor,实现箭头表示有tensor的流动,虚线箭头表示节点之间具有依赖关系;
graph的组成由两部分组成,即节点node和边tensor,但是我们并没有直接创建tensor,既没有直接创建边啊,比如有一个变量,如下:
1 | x = tf.random_normal([2, 3], stddev=0.35, name = "weights") |
我们常常说创建了一个(2,3)的tensor,但是实际上在graph中又是一个节点node,这到底是怎了区分和理解呢?
可以这样理解:
Tensor 可以看做一种符号化的句柄,指向操作节点(node)的运算结果,在执行后返回这个节点运算得到的值,在graph中的表现来看,就是这个节点运算之后输出的边,用一个带箭头的边来表示,这样个人觉得比较好理解。
二、底层概念与实现
这一节主要讨论一下一些东西,即Graph、GraphDef、Node、NodeDef、Op、OpDef等概念
2.1 Graph与GraphDef
(1)tf.Graph类的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 关键属性: collections 关键方法: add_to_collection add_to_collections as_default as_graph_def clear_collection control_dependencies create_op with g.device finalize get_all_collection_keys get_collection get_collection_ref get_name_scope get_name_scope get_operation_by_name get_operations get_tensor_by_name |
在我们恢复模型的时候,要预测模型,需要知道模型的输入与输出的名称,就需要用到两个方法
get_operation_by_name
get_tensor_by_name
那怎么区分“tensor_name”和“operation_name”这两个概念呢?后面会讲到。
(2)tf.GraphDef类的定义——仅在tensorflow1.x中有
1 2 3 4 5 | 四个属性 library: FunctionDefLibrary library node: repeated NodeDef node(graph中所有的节点定义) version: int32 version versions: VersionDef versions |
在恢复模型的时候,我们遍历图中的所有节点,使用的语句为:
1 | tensor_name_list = [tensor.name for tensor in graph.as_graph_def().node]# 得到当前图中所有节点的名称 |
2.2 Node和NodeDef
tensorflow中python接口没有提供显示Node定义,都是通过NodeDef来实现的,另外NodeDef只在tensorflow1.x版本中,它有几个常用的属性:
1 2 3 4 5 | Attributes: device: 该节点所在的设备 input: 该节点的输入节点 name: string,名称 op: 该节点的Op(operation) |
2.3 Op和OpDef
(1)tf.Operation类
它有一些常见的属性,如下:
1 2 3 4 5 6 7 8 9 10 | 属性 control_inputs device graph inputs name node_def op_def outputs type |
常见的一些方法如下:
1 2 3 4 5 | 方法 colocation_groups() get_attr(name) run(feed_dict=None,session=None) values() |
三、如何获取node_name和tensor_name
前面说了,本质上graph的组成是节点(node,即operation)和边(tensor),而tensor是依赖于每一个节点的输出值的,到底怎么去获取节点名称和张量名称呢?
3.1 先看下面的例子:
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 | import tensorflow as tf print(tf.__version__) a = tf.constant([1], name = 'a') # 创建两个自己命名的常量 b = tf.constant([2], name = 'b') aa = tf.constant([3]) # 创建两个使用默认名称的常量 bb = tf.constant([4]) x = tf.Variable(initial_value=[1,2,3],name="x") # 创建两个命名的变量 y = tf.Variable(initial_value=[3,2,1],name="y") # 创建三个操作 a_b = tf.add(a, b, name = "a_add_b") aa_bb = tf.add(aa, bb) x_y = tf.add(x,y,name="x_add_y") # 会话GPU的相关配置 gpu_options = tf.GPUOptions() gpu_options.visible_device_list = "1" gpu_options.allow_growth = True config = tf.ConfigProto(gpu_options = gpu_options) with tf.Session(config=config) as sess: print('a tensor name is : %s, Op name is : %s' %(a.name, a.op.name)) print('b tensor name is : %s, Op name is : %s' % (b.name, b.op.name)) print('aa tensor name is : %s, Op name is : %s' %(aa.name, aa.op.name)) print('bb tensor name is : %s, Op name is : %s' % (bb.name, bb.op.name)) print('x tensor name is : %s, Op name is : %s' % (x.name, x.op.name)) print('y tensor name is : %s, Op name is : %s' % (y.name, y.op.name)) print('a_b tensor name is : %s, Op name is : %s' % (a_b.name, a_b.op.name)) print('aa_bb tensor name is : %s, Op name is : %s' % (aa_bb.name, aa_bb.op.name)) print('x_y tensor name is : %s, Op name is : %s' % (x_y.name, x_y.op.name)) # 每个操作节点(Op Node)是一个 NodeDef 对象,包含 name、op、input、device、attr 等属性 print("======================================================================") # 遍历图中的所有节点 node_name_list = [node.name for node in tf.get_default_graph().as_graph_def().node] for node_name in node_name_list: print(node_name) |
运行结果如下:
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 | a tensor name is : a:0, Op name is : a b tensor name is : b:0, Op name is : b aa tensor name is : Const:0, Op name is : Const bb tensor name is : Const_1:0, Op name is : Const_1 x tensor name is : x:0, Op name is : x y tensor name is : y:0, Op name is : y a_b tensor name is : a_add_b:0, Op name is : a_add_b aa_bb tensor name is : Add:0, Op name is : Add x_y tensor name is : x_add_y:0, Op name is : x_add_y ====================================================================== a b Const Const_1 x/initial_value # x是一个变量,变量在graph中是圆角矩形,里面可以展开成一个字图subgraph,里面又包含一些子节点node x x/Assign x/read y/initial_value y y/Assign y/read a_add_b Add x_add_y |
总结如下:
(1)我们所声明的不管是常量、变量、占位符、操作函数,本质上都是节点node,即operation
(2)操作节点的名称,即node_name为 variable.op.name .命名规则遵循如果是显示指定了name参数,那就是这个指定的参数名称,如果是没有显示指定,则会是常量使用Const,加法操作是Add,如果存在多个使用默认名称的,那么遵循在后面添加一个后缀数字的方法,如
- Const、Const_1、Const_2、Const_3...依次下去
- Add、Add_1、Add_2、Add_3、Add_4、...依次下去
(3)张量名称,tensor_name为 variable.name ,即tensor_name的一般格式为: "
: " 如上面的 a:0、 b:0、Const:0、Const_1:0、x:0、y:0 等等,为什么要这样做,在每一个node_name后面再添加一个新的index呢?
3.2 为什么tensor_name的格式为 "
实际上是因为,TensorFlow中自己所创建的节点node完全可以同名称,如果是两个节点名称相同,那我就没办法光通过node_name来区分不同的tensor了,如下所示
创建两个同名称的占位符
1 2 | data = tf.placeholder(tf.float32, [None, 28*28], name='data') label = tf.placeholder(tf.float32, [None, 10], name='data') |
因为他们的 op.name 均是相同的所以,output_index就变得十分重要,他表示是同名变量的第几个。
但是实际上,在tensorflow后面的版本中,已经有所更改,
既不允许有同名节点,也就不会有同名tensor了,当自己声明的两个node同名称时,会在后面默认追加数字,1、2、3、等等,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import tensorflow as tf print(tf.__version__) data = tf.placeholder(tf.float32, [None, 28*28],name="data") label = tf.placeholder(tf.float32, [None, 10],name="data") with tf.Session() as sess: print('data tensor name is : %s, Op name is : %s' %(data.name, data.op.name)) print('label tensor name is : %s, Op name is : %s' % (label.name, label.op.name)) print("======================================================================") # 遍历图中的所有节点 node_name_list = [node.name for node in tf.get_default_graph().as_graph_def().node] for node_name in node_name_list: print(node_name) ''' data tensor name is : data:0, Op name is : data label tensor name is : data_1:0, Op name is : data_1 ====================================================================== data data_1 ''' |
四、全文总结:
(1)注意区分node(operation)与tensor的本质区别
(2)注意tensor_name与node_name的区别,注意tensor_name的格式:“node_name:index”
(3)注意tensor_name与node_name的命名规则,特别是出现了同名operation的时候怎么处理
(4)建议:编写神经网络的时候,为了便于管理各个变量,权重、偏置、输入、输出等等,最好起一个易于辨识的名称,不要使用默认名称,这样跟方便实用。