1 計算模型 —— 計算圖(Graph)
更多參考:數據流圖
TensorFlow 中的所有計算都會被轉化為計算圖上的節點。TensorFlow 是一個通過計算圖的形式來表述計算的編程系統。TensorFlow中的每個計算都是計算圖的一個節點,而節點之間的邊描述了計算之間的依賴關系。
import sys
sys.path.append('E:/zlab/')
from plotnet import draw_feed_forward, DynamicShow
TensorFlow 的計算模型是有向圖,采用數據流圖 (Data Flow Graphs),其中每個節點代表一些函數或計算,而邊代表了數值、矩陣或張量。
數據流圖
數據流圖是用於定義計算結構的。在 TensorFlow 中,數據流圖本質上是一組鏈接在一起的函數,每個函數都會將其輸出傳遞給 \(0\) 個、\(1\) 個或多個位於這個級聯鏈上的其他函數。
with DynamicShow((6, 3), '計算圖.png') as d: # 隱藏坐標軸
seq_list = draw_feed_forward(d.ax, num_node_list=[2, 1, 1])
seq_list[0][0].text('$1$')
seq_list[0][1].text('$2$')
seq_list[1][0].text('$+$')
seq_list[2][0].text('$3$')

如上圖,我們使用數據流圖表示了 \(1 + 2 = 3\) 這一個運算。

我們也可以將其抽象化:
with DynamicShow((6, 3), '計算圖1.png') as d: # 隱藏坐標軸
seq_list = draw_feed_forward(d.ax, num_node_list=[2, 1, 1])
seq_list[0][0].text('$a$')
seq_list[0][1].text('$b$')
seq_list[1][0].text('$c$')
seq_list[2][0].text('$d$')

我們也可以將上述過程簡化為:

將節點 \(c\) 與 \(d\) 合並,若 \(c\) 表示求和運算,\(d\) 表示非線性變換,則上圖可以看作是一個神經元。
除了節點和邊的概念,數據流圖還有一個十分關鍵的概念:依賴關系。

我們一般地,像 \(z_0^{(0)}\) 與 \(z_0^{(1)}\) 直接連接,稱為直接依賴,而像 \(z_0^{(0)}\) 與 \(z_0^{(2)}\),稱為間接依賴。即 \(z_0^{(1)}\) 直接依賴於 \(z_0^{(0)}\) 和 \(z_1^{(0)}\),而 \(z_0^{(2)}\) 間接依賴依賴於 \(z_0^{(0)}\) 和 \(z_1^{(0)}\)。

1.1 計算圖的使用
- 定義計算圖的所有節點;
- 執行計算。
1.1.1 使用默認圖
在TensorFlow程序中,系統會自動維護一個默認的計算圖,通過tf.get_default_graph()函數可以獲取當前默認的計算圖。
通過a.graph可以查看張量所屬的計算圖。
import tensorflow as tf
a = tf.constant([1., 2.], name = 'a')
b = tf.constant([2., 3.], name = 'b')
result = a + b
a.graph is tf.get_default_graph()
True
1.1.2 tf.Graph函數可以生成新的計算圖
不同計算圖上的張量和運算均不會共享。
g1 = tf.Graph()
with g1.as_default():
# 在計算圖 g1 中定義變量 “v” ,並設置初始值為 0。
v = tf.get_variable("v", [1], initializer = tf.zeros_initializer()) # 設置初始值為0,shape 為 1
g2 = tf.Graph()
with g2.as_default():
# 在計算圖 g2 中定義變量 “v” ,並設置初始值為 1。
v = tf.get_variable("v", [1], initializer = tf.ones_initializer()) # 設置初始值為1
# 在計算圖 g1 中讀取變量“v” 的取值
with tf.Session(graph = g1) as sess:
tf.global_variables_initializer().run()
with tf.variable_scope("", reuse=True):
print(sess.run(tf.get_variable("v")))
# 在計算圖 g2 中讀取變量“v” 的取值
with tf.Session(graph = g2) as sess:
tf.global_variables_initializer().run()
with tf.variable_scope("", reuse=True):
print(sess.run(tf.get_variable("v")))
[ 0.]
[ 1.]
TensorFlow的計算圖不僅僅可以用來隔離張量和計算,它還提供了管理張量和計算的機制。
計算圖可以通過 tf.Graph.device函數來指定運行計算的設備。
g = tf.Graph()
# 指定計算運行的設備
with g.device('/gpu:0'):
result = a + b
有效整理TensorFlow 程序的資源也是計算圖的一個重要功能。
在一個計算圖中,可以通過集合(collection)來管理不同類別的資源。比如通過tf.add_to_collection函數可以將資源加入一個
或多個集合中,然后通過tf.get_collection獲取一個集合里面的所有資源(如張量,變量,或者運行TensorFlow程序所需的隊列資源等等)
TensorFlow中維護的集合列表
| 集合名稱 | 集合內容 | 使用場景 |
|---|---|---|
tf.GraphKeys.VARIABLES |
所有變量 | 持久化TensorFlow模型 |
tf.GraphKeys.TRAINABLE_VARIABLES |
可學習的變量(一般指神經網絡中的參數) | 模型訓練、生成模型可視化內容 |
tf.GraphKeys.SUMMARIES |
日志生成相關的張量 | TensorFlow計算可視化 |
tf.GraphKeys.QUEUE_RUNNERS |
處理輸入的QueueRunner | 輸入處理 |
tf.GraphKeys.MOVING_AVERAGE_VARIABLES |
所有計算了滑動平均值的變量 | 計算變量的滑動平均值 |
2 數據模型——張量(Tensor)
在TensorFlow程序中,所有的數據都是通過張量的形式來表示的。
從功能角度來看張量可理解為多維數組。但是張量在TensorFlow的實現並不是直接采用數組的形式,它只是對TensorFlow中運算結果的引用。在張量中並沒有真正保存數字,它保存的是如何得到這些數字的計算過程。
import tensorflow as tf
# tf.constant 是一個計算,這個計算的結果為一個張量,保存在變量 a 中
a = tf.constant([1., 2.], name = 'a')
b = tf.constant([3., 4.], name = 'b')
result = tf.add(a, b, name = 'add')
print(result)
Tensor("add_3:0", shape=(2,), dtype=float32)
張量的屬性值主要有三個:名字(name)、維度(shape)和類型(type)
name
- 張量的唯一標識符;
- 給出了張量是如何計算出來的。
張量和計算圖上的每一個節點所有代表的結果是對應的。張量的命名:node:src_output。
其中node為節點的名稱,src_output表示當前張量來自節點的第幾個輸出。
type
每一個張量會有一個唯一的類型。
import tensorflow as tf
a = tf.constant([1, 2], name = 'a', dtype = tf.float32)
b = tf.constant([3., 4.], name = 'b')
result = tf.add(a, b, name = 'add')
print(result)
Tensor("add_4:0", shape=(2,), dtype=float32)
TensorFlow支持14種類型:
實數(tf.float32, tf.float64)、整數(tf.int8, tf.int16, tf.int32, tf.int64, tf.uint8)、布爾型(tf.bool)、復數(tf.complex64, tf.complex128)。
張量的用途
對中間結果的引用,提高代碼可讀性。
# 使用張量記錄中間結果
a = tf.constant([1, 2], name = 'a', dtype = tf.float32)
b = tf.constant([3., 4.], name = 'b')
result = a + b
# 直接計算向量的和,這樣可讀性很差
result = tf.constant([1., 2.], name = 'a') + tf.constant([3., 4.], name = 'b')
<tf.Tensor 'add_5:0' shape=(2,) dtype=float32>
result.get_shape() # 獲取張量的維度信息
TensorShape([Dimension(2)])
當計算圖構造完成后,張量可用來獲取計算結果。
通過run計算結果;
或者在會話(Session())中運行。
3 運行模型——會話(Session)
執行已經定義好的運算。
會話擁有並管理TensorFlow程序運行時的所有資源。當所有計算完成后需要關閉會話來幫助系統回收資源,否則會出現資源泄露現象。
模式一
# 創建一個會話
sess = tf.Session()
# 使用這個創建好的會話得到關心的運算結果
sess.run(result)
# 關閉會話
sess.close()
模式二:通過Python上下文管理機制
with tf.Session() as sess:
sess.run(result)
# 當上下文退出時會話關閉和資源可以被釋放。
指定默認會話(默認會話不會自動生成),通過 tf.Tensor.eval 函數計算張量取值。
sess = tf.Session()
with sess.as_default():
print(result.eval())
[ 4. 6.]
以下代碼實現相同的功能:
sess = tf.Session()
# 下面的兩個命令有相同的功能
print(sess.run(result))
print(result.eval(session = sess))
[ 4. 6.]
[ 4. 6.]
使用tf.InteractiveSession構建會話
在交互的環境下(如Jupyter Notebook)可以直接構建默認會話,即使用tf.InteractiveSession函數(此函數會自動將生成的會話注冊為默認會話)。
sess = tf.InteractiveSession()
print(result.eval())
sess.close()
[ 4. 6.]
通過ConfigProto配置會話
config=tf.ConfigProto(allow_soft_placement=True, log_device_placement=True)
sess1 = tf.InteractiveSession(config=config)
sess2 = tf.Session(config=config)
allow_soft_placement=True 在下面的任意一個條件成立,GPU的運算可以放到CPU上進行:
- 運算無法在GPU上執行;
- 沒有GPU資源(比如運算被指定在第二個GPU上運行,但是機器只有一個GPU);
- 運算輸入包含對CPU計算結果的引用。
log_device_placement=True日志中將會記錄每個節點被安排在了哪個設備上以方便調試。
