iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 3
1
AI & Data

深度學習裡的冰與火之歌 : Tensorflow vs PyTorch系列 第 3

Day 3 靜態計算圖:Tensorflow

Tensorflow 1.x 如何使用靜態計算圖

計算

  1. 將計算的順序以一張靜態計算圖(Graph)代表,又被稱為 ComputationalGraph
  2. 若要執行計算圖,則需要在 Session 物件的 context 內。
  3. 一個 Session 物件將會 ComputationGraph 的節點分配到適當的 Device (如 GPU 或 CPU) 節點.
  4. 一個 Session 物件將會呼叫相應的實踐方法來執行計算圖的計算。(如,使用 in-memory 的 numpy.ndarray 作為資料或是 C/C++ 版本的 tensorflow::Tensor instance)

資料

  1. 在計算圖 Tensorflow 將資料以 Tensor 類別物件封裝。包括了,tf.Tensortf.placeholder。除了 tf.constant 持有不可更改的資料外,tf.placeholder如它的
  2. 計算結果會儲存在 Variable 物別中。(Variable 可以視為內在狀態可改變的 Tensor 物件)
  3. 呼叫 feed 或 fetch 方法透過 Session 物件餵入或取得資料。
  4. 根據 ComputationalGraph 完成的計算結果可以透過 fetch 方法取得資料。

相關 API:

以下 tfimport tensorflow as tf 的執行結果,也是 tensorflow 的簡稱。

  1. tf.Graph: 代表計算圖的物件,通常包括了三個元件: tf.constant, tf.Variabletf.Operation
  2. tf.Variable: 具有狀態的變數。該類別下的物件的內在狀態,或所持有的資料是允許被修改的(透過 tf.Variable().assign() ),甚至不在 Session 物件的 context 內。tf.constant 是一個內在狀態不得被更改的 tf.Tensor
  3. tf.Operation: 代表計算圖的運算元,有以下四大類運算
    1. Utility Ops:包括了 tf.Variable.assign()
    2. 計算(Arithmetic Ops): 包括了 reduce 類型的 operation,reduce 類型的 operation 會導致輸出的維度比輸入小 (如 reduce_mean 等等) 。
    3. 初始化(Initialization Ops): 包括全域的初始化運算元(global initializer)初始化全域可見的 tf.Variable 或局部域的初始化運算元(local initializer)
    4. 和類神經網路相關的運算元: 見 tf.nn module

更多關於 tf.Graph

tf.Graph 有下列兩個主要的元素:

1.圖形結構(Graph Structure):Tensorflow 中使用的計算圖是一個數據流計算圖,其節點為 tf.Operation相對於 PyTorch 的 Function 物件),而圖的連結(edges)代表的是數據流動的方向,如被某一 tf.Operation當做輸入而消耗,或當作輸出而產生。使用者可以檢查 Graph 結構(透過視覺化工具 tensorboard)以了解如何計算值。但是,它缺少運行時資料的訊息,如包含 NaN 的值。

  1. 圖形集合(Graph Collection):,任何 tf.Variable(或 tf.Tensor)都可以放入任何現有或新創建的集合(透過呼叫 tf.add_to_collection)。可以使用 tf.GraphKeys 中定義的鍵查找當前存在的集合(透過呼叫 tf.get_collection)。使用集合的好處是可以為 tf.Savertf.Optimizer 等操作量(見下)。

Namescope of tf.Graph:

tf.Graph 可以應用命名空間於子圖,為它們提供有意義的名稱。這可以通過呼叫 module function tf.name_scope 來完成。

Control dependencies

因為 tf.Graph 有上述集合的概念,所以可以直接提取集合中多個 tf.Variable 物件。這個設計可以反映在 tf.Optimizer 在實例化物件時,不須像 PyTorch torch.nn.optim 在實例化 Optimizer 的時候需要傳遞訓練變數。

相對地,一個 tf.Optimizer可以直接提取 Graph collection 中的 ops.GraphKeys.TRAINABLE_VARIABLES(想像靜態計算圖,是個無所不知的天神),而只需要將正向傳播完成的損失傳入 tf.Optimizer().minimize() 函式中。

但,有些變數本身並沒有計算關係,在靜態計算圖中沒有可以到達的路徑,我們可以將他們的聚集起來,並使用 tf.control_dependencies(control_inputs) context 來將 context 內所執行的運算元與 tf.control_dependencies 輸入相關。

更多關於 tf.Variable

tf.Variable() 在實體化時,可以透過給予初始值(initial_value)或給予變數定義(variable_def)。若要以變數定義來實體化,則需要遵循 protobuf 的規則來撰寫定義的部分。另外,tf.Variable還有一個常用參數,那就是 trainabletrainable 參數接受布林值,若為 True 則會將該變數放置在 tf.GraphKeys.TRAINABLE_VARIABLES的集合裏,反之(為False),則不會。預設的 tf.Variable() 通常放置於 global 和 trainable 的 collections 中。

Sharing and Variable scope:

如果要讓兩個變數要共享其中一個變數的值,在實作上通常讓另一個 tf.Variable 物件的參考指向共享的tf.Variable 上。 共享參數在需要實踐 tie weight 的需求,如 autoencoder 讓 decoder 的權重變數為 encoder 權重的 transpose。

  1. 如我們經常在 python 上做的,透過參數傳遞的方法來 explicitly 傳遞 f.Variable 的參考。
  2. 透過 tf.variable_score 作 implicit 分享

Senarios of implicit sharing:

以下的原始碼出自於 tensorflow variable guide。主要目的是使用一個如 factory 的函式,能夠重複呼叫建立不同的卷積層。 在下列的程式碼中,使用 tf.get_variable 來建立 tf.Variable 物件,該方法會在現有的 scope 或 tf.variable_scope 指定的 scope 尋找同名的變數,若沒有尋得則建立一個新的變數。

由下面的原始碼,可以看到若不使用 tf.variable_scope,則會出現ValueError(完整的錯誤訊息原始碼底下)

def conv_relu(input, kernel_shape, bias_shape):
    # Create variable named "weights".
    weights = tf.get_variable("weights", kernel_shape,
        initializer=tf.random_normal_initializer())
    # Create variable named "biases".
    biases = tf.get_variable("biases", bias_shape,
        initializer=tf.constant_initializer(0.0))
    conv = tf.nn.conv2d(input, weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv + biases)
    
input1 = tf.random_normal([1,10,10,32])
input2 = tf.random_normal([1,20,20,32])
x = conv_relu(input1, kernel_shape=[5, 5, 32, 32], bias_shape=[32])
x = conv_relu(x, kernel_shape=[5, 5, 32, 32], bias_shape = [32])  # This fails.    
"""
ValueError: Variable weights already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope? 
"""

如果要解決以上 name conflict 問題,就要利用 tf.variable_scope 在每一次呼叫都建立新的命名空間。

def my_image_filter(input_images):
    with tf.variable_scope("conv1"):
        # Variables created here will be named "conv1/weights", "conv1/biases".
        relu1 = conv_relu(input_images, [5, 5, 32, 32], [32])
    with tf.variable_scope("conv2"):
        # Variables created here will be named "conv2/weights", "conv2/biases".
        return conv_relu(relu1, [5, 5, 32, 32], [32])

倘若我們想要 re-using 同樣的 tf.Variable,如,我們之前所說的 implicitly sharing 或 tie weight case。我們仍舊可以運用 tf.variable_scope 函後將 reuse 參數設為 True。這個參數也可以在 tf.get_variable 找到,但一旦將該參數設為 True,若在指定或所在的命名空間找不到該名稱的變數,就不會再建立新的 tf.Variable 而丟出錯誤訊息。

# passing true to enforce reuse at
# constructing time
with tf.variable_scope("model"):
  output1 = my_image_filter(input1)
with tf.variable_scope("model", reuse=True):
  output2 = my_image_filter(input2)

# using variable_scope to enforce reuse
with tf.variable_scope("model") as scope:
  output1 = my_image_filter(input1)
  # below is equivalent to ```
  # with tf.variable_scope(scope, reuse=True):
  scope.reuse_variables()
  output2 = my_image_filter(input2)

上一篇
Day 2 動態計算圖:PyTorch's autograd
下一篇
Day 4 偽動態計算圖: Tensorflow 的 Eager Mode
系列文
深度學習裡的冰與火之歌 : Tensorflow vs PyTorch31

尚未有邦友留言

立即登入留言