Tensorflow學習筆記: 變數及共享變數

劉美利發表於2018-08-29

TensorFlow中變數主要用來表示機器學習模型中的引數,變數透過 tf.Variable 類進行操作。tf.Variable 表示張量,透過執行 op 可以改變它的值。與 tf.Tensor 物件不同,tf.Variable 存在於單個 session.run 呼叫的上下文之外。

在內部,tf.Variable 儲存持久張量。具體 op 允許您讀取和修改此張量的值。這些修改在多個 tf.Session 之間是可見的,因此對於一個 tf.Variable,多個工作器可以看到相同的值。

1. tf.Variable 建立變數

tf.Variable的初始化函式如下所示

__init__(
    initial_value=None,
    trainable=True,
    collections=None,
    validate_shape=True,
    caching_device=None,
    name=None,
    variable_def=None,
    dtype=None,
    expected_shape=None,
    import_scope=None,
    constraint=None
)

其中引數

  1. initial_value 表示初始化值,用Tensor表示

  2. trainable 表示變數是否被訓練,如果被訓練,將加入到tf.GraphKeys.TRAINABLE_VARIABLES集合中,TensorFlow將計算其梯度的變數

  3. collections 表示一個graph collections keys的集合,這個建立的變數將被新增到這些集合中,預設集合是[GraphKeys.GLOBAL_VARIABLES].

  4. name: 變數的命名,預設是'Variable'

  5. dtype 表示型別

例如我們建立一個變數,並且檢視其name和shape

import tensorflow as tfw1 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights")b1 = tf.Variable(tf.zeros([200]),name="biases")w2 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights") # 名稱相同b2 = tf.Variable(tf.zeros([200]),name="biases") # 名稱相同print w1.name, w1.shapeprint b1.name, b1.shapeprint w2.name, w2.shapeprint b2.name, b2.shape************************************輸出**************************************weights:0 (784, 200)biases:0 (200,)weights_1:0 (784, 200)biases_1:0 (200,)

可以看到在命名的時候,如果指定的name重複,那麼w2就會被命名為"name_1:0" 這樣累加下去。

2. 變數集合 collections

預設情況下,每個tf.Variable都放置在以下兩個集合中:*tf.GraphKeys.GLOBAL_VARIABLES- 可以在多個裝置共享的變數,*tf.GraphKeys.TRAINABLE_VARIABLES- TensorFlow 將計算其梯度的變數。

2.1 檢視集合變數列表

要檢視放置在某個集合中的所有變數的列表,可以採用如下方式

import tensorflow as tfw1 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights")b1 = tf.Variable(tf.zeros([200]),name="biases")w2 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights") # 名稱相同b2 = tf.Variable(tf.zeros([200]),name="biases") # 名稱相同print tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)************************************輸出**************************************[<tf.Variable 'weights:0' shape=(784, 200) dtype=float32_ref>, <tf.Variable 'biases:0' shape=(200,) dtype=float32_ref>, <tf.Variable 'weights_1:0' shape=(784, 200) dtype=float32_ref>, <tf.Variable 'biases_1:0' shape=(200,) dtype=float32_ref>]

可以看到輸出結果是所有變數的列表

2.2 建立變數集合

如果您不希望變數被訓練,可以將其新增到 tf.GraphKeys.LOCAL_VARIABLES 集合中。例如,以下程式碼段展示瞭如何將名為 my_local 的變數新增到此集合中:

my_local = tf.get_variable("my_local", shape=(),
collections=[tf.GraphKeys.LOCAL_VARIABLES])

或者,您可以指定 trainable=False 為 tf.get_variable 的引數:

my_non_trainable = tf.get_variable("my_non_trainable",
                                   shape=(),
                                   trainable=False)

我們測試效果如下所示,可以看到b2的trainable=False,那麼輸出collection沒有b2

import tensorflow as tfw1 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights")b1 = tf.Variable(tf.zeros([200]),name="biases")w2 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights") # 名稱相同b2 = tf.Variable(tf.zeros([200]),name="biases", trainable=False) # 名稱相同print tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)************************************輸出**************************************[<tf.Variable 'weights:0' shape=(784, 200) dtype=float32_ref>, <tf.Variable 'biases:0' shape=(200,) dtype=float32_ref>, <tf.Variable 'weights_1:0' shape=(784, 200) dtype=float32_ref>]

您也可以使用自己的集合。集合名稱可為任何字串,且您無需顯式建立集合。建立變數(或任何其他物件)後,要將其新增到集合,請呼叫 tf.add_to_collection。例如,以下程式碼將名為 my_local 的現有變數新增到名為 my_collection_name 的集合中:

tf.add_to_collection("my_collection_name", my_local)

3. 共享變數

我們檢視下面的程式碼,表示一個卷積神經網路,其中包括conv1_weights, conv1_biases, conv2_weights, conv2_biases四個引數,也就是4個變數

def my_image_filter(input_images):
    conv1_weights = tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv1_weights")
    conv1_biases = tf.Variable(tf.zeros([32]), name="conv1_biases")
    conv1 = tf.nn.conv2d(input_images, conv1_weights,
        strides=[1, 1, 1, 1], padding='SAME')
    relu1 = tf.nn.relu(conv1 + conv1_biases)
    conv2_weights = tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv2_weights")
    conv2_biases = tf.Variable(tf.zeros([32]), name="conv2_biases")
    conv2 = tf.nn.conv2d(relu1, conv2_weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv2 + conv2_biases)

假設我們利用這個函式對兩張圖片進行相同的操作,也就是呼叫兩次,那麼每次都會建立4個變數,假設我們在函式內對變數進行了最佳化求解,那麼每次都會重新建立變數,這樣就無法複用引數,導致訓練過程無效

# 第一次執行方法建立4個變數result1 = my_image_filter(image1)# 第二次執行再建立4個變數result2 = my_image_filter(image2)ValueError: Variable weight already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope? Originally defined at:

TensowFlow透過變數範圍(variable scope)和tf.get_variable方法解決了共享變數(引數)的問題。

3.1 tf.variable_scope和tf.get_variable

tf.Variable()方法每次被呼叫都會建立新的變數,這樣就無法解決共享變數的問題,而tf.get_variable結合作用域即可表明我們是想建立新的變數,還是共享變數,變數作用域允許在呼叫隱式建立和使用變數的函式時控制變數重用。作用域還允許您以分層和可理解的方式命名變數。tf.get_variable()的機制跟tf.Variable()有很大不同,如果指定的變數名已經存在(即先前已經用同一個變數名透過get_variable()函式例項化了變數),那麼get_variable()只會返回之前的變數,否則才創造新的變數。我們舉例進行說明。

例如上面的例子中有兩個卷積層,我們先來編寫一個函式建立一個卷積/relu層,這個函式使命的變數名稱是'weights'和'biases'

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)

在真實模型中需要多個卷積層,我們透過變數域來區分不同層的變數,不同的變數域下的變數名車為:scope_name/variable_name, 如下所示,第一個卷積層的變數名稱是'conv1/weights', 'conv1/biases', 第二個卷積層的變數名稱是 'conv2/weights', 'conv2/biases'。

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])

但即便這樣,如果多次呼叫該函式,也會丟擲異常,

result1 = my_image_filter(image1)result2 = my_image_filter(image2)# Raises ValueError(... conv1/weights already exists ...)

因為用get_variable()建立兩個相同名字的變數是會報錯的,預設上它只是檢查變數名,防止重複,如果要變數共享,就需要指定在哪個域名內可以共享變數。

開啟共享變數有兩種方式

方法1

採用scope.reuse_variables()觸發重用變數,如下所示

with tf.variable_scope("model") as scope:
  output1 = my_image_filter(input1)
  scope.reuse_variables()
  output2 = my_image_filter(input2)

方法2

使用reuse=True 建立具有相同名稱的作用域

with tf.variable_scope("model"):
  output1 = my_image_filter(input1)with tf.variable_scope("model", reuse=True):
  output2 = my_image_filter(input2)

3.2 理解variable_scope

理解變數域的工作機理非常重要,我們對其進行梳理,當我們呼叫tf.get_variable(name, shape, dtype, initializer)時,這背後到底做了什麼

首先,TensorFlow 會判斷是否要共享變數,也就是判斷 tf.get_variable_scope().reuse 的值,如果結果為 False(即你沒有在變數域內呼叫scope.reuse_variables()),那麼 TensorFlow 認為你是要初始化一個新的變數,緊接著它會判斷這個命名的變數是否存在。如果存在,會丟擲 ValueError 異常,否則,就根據 initializer 初始化變數:

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])assert v.name == "foo/v:0"

而如果 tf.get_variable_scope().reuse == True,那麼 TensorFlow 會執行相反的動作,就是到程式裡面尋找變數名為 scope name + name 的變數,如果變數不存在,會丟擲 ValueError 異常,否則,就返回找到的變數:

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])with tf.variable_scope("foo", reuse=True):
    v1 = tf.get_variable("v", [1])assert v1 is v


變數域可以多層重疊,例如,下面的變數上有兩層的變數域,那麼變數名是'foo/var/v:0'

with tf.variable_scope("foo"):
    with tf.variable_scope("bar"):
        v = tf.get_variable("v", [1])
assert v.name == "foo/bar/v:0"

在同一個變數域中,如果需要呼叫同名變數,那麼需要重用變數即可,例如v1和v兩個變數時相同的,因為變數名都是'foo/v'

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])
    tf.get_variable_scope().reuse_variables()
    v1 = tf.get_variable("v", [1])
assert v1 is v

總結

  1. tf.get_variable()預設上它只檢查變數名,如果變數名重複,那麼就會報錯;tf.Variable()每次被呼叫都建立相應的變數,即便變數名重複,也會建立新的變數,因此無法共享變數名。

  2. 如果scope中開啟共享變數,那麼呼叫tf.get_variable()就會查詢相同變數名的變數,如果有,就直接返回該變數,如果沒有,就建立一個新的變數;

  3. 如果scope沒有開啟共享變數(預設模式),那麼 呼叫tf.get_variable()發現已有相同變數名的變數,就會報錯,如果沒有,就建立一個新的變數。

  4. 要重用變數,需要在scope中開啟共享變數,有兩種方法,推薦第一種

【本文轉載自:知乎,作者:於翔宇,原文連結:】

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31542119/viewspace-2213121/,如需轉載,請註明出處,否則將追究法律責任。

相關文章