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