以線性迴歸爲例,深入理解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結構:

 

在原來的理解中,聲明的變量比如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,既沒有直接創建邊啊,比如有一個變量,如下:

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類的定義

關鍵屬性:
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

在恢復模型的時候,我們遍歷圖中的所有節點,使用的語句爲:

 tensor_name_list = [tensor.name for tensor in graph.as_graph_def().node]# 得到當前圖中所有節點的名稱

2.2 Node和NodeDef

tensorflow中python接口沒有提供顯示Node定義,都是通過NodeDef來實現的,另外NodeDef只在tensorflow1.x版本中,它有幾個常用的屬性:

Attributes:
device: 該節點所在的設備
input: 該節點的輸入節點
name: string,名稱
op: 該節點的Op(operation)

2.3 Op和OpDef

(1)tf.Operation類

它有一些常見的屬性,如下:

屬性
control_inputs
device
graph
inputs
name
node_def
op_def
outputs
type

常見的一些方法如下:

方法
colocation_groups()
get_attr(name)
run(feed_dict=None,session=None)
values()

三、如何獲取node_name和tensor_name

前面說了,本質上graph的組成是節點(node,即operation)和邊(tensor),而tensor是依賴於每一個節點的輸出值的,到底怎麼去獲取節點名稱和張量名稱呢?

3.1 先看下面的例子:

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

總結如下:

(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的一般格式爲: "<op_name>:<output_index>"

如上面的 a:0、 b:0、Const:0、Const_1:0、x:0、y:0 等等,爲什麼要這樣做,在每一個node_name後面再添加一個新的index呢?

3.2 爲什麼tensor_name的格式爲 "<op_name>:<output_index>"

實際上是因爲,TensorFlow中自己所創建的節點node完全可以同名稱,如果是兩個節點名稱相同,那我就沒辦法光通過node_name來區分不同的tensor了,如下所示

創建兩個同名稱的佔位符

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、等等,如下:

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)建議:編寫神經網絡的時候,爲了便於管理各個變量,權重、偏置、輸入、輸出等等,最好起一個易於辨識的名稱,不要使用默認名稱,這樣跟方便實用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章