深入理解tensorflow的基本概念:Graph、Operation、Tensor、Node的区别

随着对Tensorflow的不断深入使用,愈加觉得对Tensorflow设计理念和细节了解越多越有好处,特写一篇总结方便后续回顾和后续进一步深入学习

声明,以下内容主要参考两篇精品博客,推荐有空可以好好读一下:
以线性回归为例,深入理解tensorflow的Operation、Tensor、Node的区别
Tensorflow基本概念

一、概念总述

深度学习(神经网络)之所以具备智能,就是因为它具有反馈机制。深度学习具有一套对输出所做的评价函数(损失函数),损失函数在对神经网络做出评价后,会通过某种方式(梯度下降法)更新网络的组成参数,以期望系统得到更好的输出数据。
在这里插入图片描述
由此可见,神经网络的系统主要由以下几个方面组成:

  • 输入
  • 系统本身(神经网络结构),以及涉及到系统本身构建的问题:如网络构建方式、网络执行方式、变量维护、模型存储和恢复等等问题
  • 损失函数
  • 反馈方式:训练方式
  • 定义好以上的组成部分,我们就可以用流程化的方式将其组合起来,让系统对输入进行学习,调整参数。因为该系统的反馈机制,所以,组成的方式肯定需要循环。

而对于Tensorflow来说,其设计理念肯定离不开神经网络本身。所以,学习Tensorflow之前,对神经网络有一个整体、深刻的理解也是必须的。如下图:Tensorflow的执行示意。
在这里插入图片描述
上图展示了Tensorflow的核心工作原理,即训练闭环由 Graph, target(output)、Loss、Variable 变量(Tensorflow中也是Variable)

那么对于以上所列的几点,什么才是最重要的呢?我想肯定是有关系统本身所涉及到的问题。即如何构建、执行一个神经网络? 在Tensorflow中,用计算图来构建网络,用会话来具体执行网络。深入理解了这两点,我想,对于Tensorflow的设计思路,以及运行机制,也就略知一二了。

  • 图(tf.Graph):计算图,主要用于构建网络,本身不进行任何实际的计算。计算图的设计启发是高等数学里面的链式求导法则的图。可以将计算图理解为是一个计算模板或者计划书。
  • 会话(tf.session):会话,主要用于执行网络。所有关于神经网络的计算都在这里进行,它执行的依据是计算图或者计算图的一部分,同时,会话也会负责分配计算资源和变量存放,以及维护执行过程中的变量。

另外,我们需要对tensorflow内部API有一个整体了解,tensorflow API 结构一览:
在这里插入图片描述
在上图中, 左边的部分是高层API, 高层API在牺牲部分灵活性的情况下可以帮助你快速的搭建模型; 右边的部分属于底层API, 灵活性较高,但模型的实现较为复杂,一个简单模型往往需要上千行代码。

接下来,我们主要从计算图开始,看一看Tensorflow是如何构建、执行网络的。

数据流图
tensorflow 定义的数据流图如下所示:
在这里插入图片描述
从上面可以看出,tensorflow的Graph明确定义为:由节点和有向边描述数学运算的有向无环图。例如

  • 调用 tf.constant(42.0) 可创建单个 tf.Operation,该操作可以生成值 42.0,将该值添加到默认图中,并返回表示常量值的 tf.Tensor。
  • 调用 tf.matmul(x, y) 可创建单个 tf.Operation,该操作会将 tf.Tensor 对象 x 和 y 的值相乘,将其添加到默认图中,并返回表示乘法运算结果的 tf.Tensor。
  • 执行 v = tf.Variable(0) 可向图添加一个 tf.Operation,该操作可以存储一个可写入的张量值,该值在多个 tf.Session.run 调用之间保持恒定。tf.Variable 对象会封装此操作,并可以像张量一样使用,即读取已存储值的当前值。tf.Variable 对象也具有 assignassign_add 等方法,这些方法可创建 tf.Operation对象,这些对象在执行时将更新已存储的值。(请参阅变量了解关于变量的更多信息。)
  • 调用 tf.train.Optimizer.minimize 可将操作和张量添加到计算梯度的默认图中,并返回一个 tf.Operation,该操作在运行时会将这些梯度应用到一组变量上。

这里有一个很重要的概念:什么是节点,什么事Tensor?

前向图中的节点分为三类:

  • Operation,主要为数学函数和表达式。比如图中的MatMul,BiasAdd和Softmax,绝大多数节点都属于此类。
  • 存储模型参数的张量(Variable):比如图中的和。
  • 占位符(placeholder):比如图中的Input和Class Labels,通常用来描述输入、输出数据的类型和形状。

后向图中的节点同样分为三类

  • 梯度值:经过前向图计算出的模型参数的梯度,比如图中的Gradients
  • 更新模型参数的操作:比如图中和,它们定义了如何将梯度值更新到对应的模型参数。
  • 更新后的模型参数,比如图中SGD Trainer内的W和b,与前向图中的模型参数一一对应,但参数值得到了更新,用于模型的下一轮训练。

有向边:定义了节点之间的关系。分为两类:一类用于传输数据,绝大部分流动着张量的边都是此类,在图中使用实线表示,简称数据边。还有一种特殊边,一般画为虚线边,称为控制依赖,可以用于控制操作的运算,这被用来确保happens-before关系,这类边上没有数据流过,但源节点必须在目的节点开始执行前完成执行。例如tf.global_variables_initializer()形成的边。

二、线性回归的完整实例

本文以一个两层神经网络来实现线性回归,代码如下:
下面,以线性回归为例,深入理解tensorflow的Operation、Tensor、Node的区别

前言:在使用tensorflow的时候,常常会被Operation、Tensor、Op_name、tensor_name等等概念搞混淆,本文专门通过一个简单的例子来深入讲解他们之间的区别于本质,并且如何在tensorboard中进行查看。

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结构:
在这里插入图片描述
在这里插入图片描述

三、底层概念与实现

这一节主要讨论一下一些东西,即Graph、GraphDef、Node、NodeDef、Op、OpDef等概念
3.1 Graph与GraphDef

(1)tf.Graph类的定义

关键属性:
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中有

四个属性
library: FunctionDefLibrary library
node: repeated NodeDef node(graph中所有的节点定义)
version: int32 version
versions: VersionDef versions

在这里插入图片描述
在这里插入图片描述

四、如何获取node_name和tensor_name

前面说了,本质上graph的组成是节点(node,即operation)和边(tensor),而tensor是依赖于每一个节点的输出值的,到底怎么去获取节点名称和张量名称呢?

例子:

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)

运行结果:

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

在这里插入图片描述
为什么tensor_name的格式为 “<op_name>:<output_index>”

实际上是因为,TensorFlow中自己所创建的节点node完全可以同名称,如果是两个节点名称相同,那我就没办法光通过node_name来区分不同的tensor了,如下所示

创建两个同名称的占位符

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)建议:编写神经网络的时候,为了便于管理各个变量,权重、偏置、输入、输出等等,最好起一个易于辨识的名称,不要使用默认名称,这样跟方便实用。

(5)模型的核心是图,而图的核心是节点和Tensor,那么如果能理解这个原理就能对图的加载和切分做很灵活的操作

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值