1. 程式人生 > >Tensorflow深度學習之十四:Tensorflow變數管理

Tensorflow深度學習之十四:Tensorflow變數管理

宣告:本篇文章參考《Tensorflow實戰Google深度學習框架》一書

Tensorflow提供了通過變數名稱來建立或者獲取一個變數的機制。通過這個機制,在不同的函式中可以直接通過變數的名字來使用變數,而不需要將變數通過引數的形式到處傳遞。

Tensorflow中通過便令名稱獲取變數的機制主要是通過tf.get_variable和tf.variable_scope函式實現的。

除了tf.Variable函式,Tensorflow還提供了tf.get_variable函式串講或者獲取變數。當tf.get_variable用於建立變數時,它和tf.Variable的功能是基本等價的。

以下是通過這兩個函式建立同一個變數的樣例:

v = tf.get_variable("v", shape=[1], initializer=tf.constant_initializer(1.0))
v = tf.Variable(tf.constant(1.0, dtype=tf.float32, shape=[1], name="v"))

從上面的程式碼中可以看出,通過tf.get_variable和tf.Variable函式建立變數的過程基本是相同的。tf.get_variable函式呼叫時提供的維度(shape)資訊以及初始化方法(initializer)的引數和tf.Variable函式呼叫的提供的初始化過程中的引數也類似。

Tensorflow中提供的initializer函式和Tensorflow中的隨機數以及常量生成函式大部分是一一對應的。

比如,在上述程式碼中使用的常數初始化函式tf.constant_initializer和常數生成函式tf.constant功能上是一致的。

下面是常用的變數初始化函式

初始化函式 功能 主要引數
tf.constant_initializer 將變數初始化為給定常量 常量的取值
tf.random_normal_initializer 將變數初始化為滿足正態分佈的隨機值 正態分佈的均值和標準差
tf.truncated_normal_initializer 將變數初始化為滿足正態分佈的隨機值,但如果隨機出來的值偏離平均值超過2個標準差,那麼這個數將會被重新隨機 正態分佈的均值和標準差
tf.random_uniform_initializer 將變數初始化為滿足平均分佈的隨機值 最大,最小值
tf.uniform_unit_scaling_initializer 將變數初始化為滿足平均分佈但不影響輸出數量級的隨機值 factor(產生隨機值是乘以的係數)
tf.zeros_initializer 將變數設定為全0 變數維度
tf.ones_initializer 將變數設定為全1 變數維度

tf.get_variable和tf.Variable兩個函式最大的區別在於指定變數名稱的引數。對於tf.Variable函式,變數名稱是一個可選的引數,通過name = “v” 的形式給出。但是對於tf.get_variable函式,變數名稱是一個必填的引數。tf.get_variable會根據這個名字去建立或者獲取變數。

在上面的樣例程式碼中,tf.get_variable首先會去試圖建立一個名字為v的變數,如果建立失敗(比如已經有了同名的引數),那麼這個程式就會報錯。這是為了避免無意識的變數複用造成的錯誤。

比如在定義神經網路引數時,第一層網路的權重已經叫weights了,那麼在建立第二層神經網路時,如果引數名稱仍然叫weights,就會觸發變數重用的錯誤。

如果需要通過tf.get_variable獲取一個已經建立的變數,需要通過tf.variable_scope函式來生成一個上下文管理器,並且明確指定在這個上下文管理器中,tf.get_variable將直接獲取已經生成的變數。

下面給出一段程式碼說明如何通過tf.variable_scope函式控制tf.get_variable函式獲取已經建立過的變數。

import tensorflow as tf
import cv2

# Tensorflow預設會話
sess = tf.InteractiveSession()

# 在名稱空間foo內建立名字為v的變數。
with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1], initializer=tf.constant_initializer(1.0))

# 將下面的兩行程式碼註釋去掉,執行程式,控制檯會輸出:
# ValueError: Variable foo/v already exists, disallowed. Did you mean to set reuse=True in VarScope? Originally defined at:
#   ……
#       v = tf.get_variable("v", [1], initializer=tf.constant_initializer(1.0))
#
# 因為在名稱空間foo中已經存在名字為v變數,所以下面兩行程式碼會報錯。
# with tf.variable_scope("foo"):
#     v = tf.get_variable("v", [1])

# 在生成上下文管理器時,將引數reuse設定為True,這樣tf.get_variable函式將直接獲取已經宣告的變數
with tf.variable_scope("foo", reuse=True):
    v1 = tf.get_variable("v", [1])
    print(v1 == v)      # 輸出為True,表明v,v1代表的是相同的Tensorflow變數。

# 將下面兩行程式碼的註釋去掉,執行程式,控制檯會輸出:
# ValueError: Variable bar/v does not exist, or was not created with tf.get_variable(). Did you mean to set reuse=None in VarScope?
#
# 將引數reuse設定為True時,tf.variable_scope將只能獲取已經建立過的變數。
# 以為名稱空間bar中還沒有建立變數v,所以程式碼會報錯。
# with tf.variable_scope("bar", reuse=True):
#     v = tf.get_variable("v", [1])

# 初始化全部全域性變數
tf.global_variables_initializer().run()

程式執行結果:

True

上面的樣例簡單的說明了tf.variable_scope函式可以控制tf.get_variable函式的語義。

1、當tf.variable_scope函式使用引數reuse=Ture生成上下文管理器時,這個上下文管理器內所有的tf.get_variable函式會直接獲取已經建立的變數,如果變數不存在,則tf.get_variable函式將報錯。

2、當tf.variable_scope函式使用引數reuse=None或者reuse=False生成上下文管理器時,這個上下文管理器內所有的tf.get_variable函式會建立新的變數,如果同名的變數已經存在,則tf.get_variable函式將報錯。

3、reuse引數預設為None。

Tensorflow中tf.variable_scope函式是可以巢狀的。下面是一個簡單的例子,說明了當tf.variable_scope函式巢狀時,reuse的函式取值是如何確定的。

import tensorflow as tf
import cv2

# Tensorflow預設會話
sess = tf.InteractiveSession()

# 可以通過tf.get_variable_scope().reuse函式來獲取當前上下文管理器中的reuse引數的取值。
with tf.variable_scope("root"):
    # 下面的print語句將輸出:0 False,即最外層的reuse是False。
    print("0", tf.get_variable_scope().reuse)

    # 新建一個上下文管理器,名稱空間為foo,並指定reuse引數為True。
    with tf.variable_scope("foo", reuse=True):

        # 下面的print語句將輸出:1 True。
        print("1", tf.get_variable_scope().reuse)

        # 新建一個上下文管理器bar,名稱空間為bar。
        # 這裡未指定reuse引數,這是reuse會和外面一層保持一致。
        with tf.variable_scope("bar"):

            # 下面的print語句將輸出:2 True。
            print("2", tf.get_variable_scope().reuse)

    # 退出reuse設定為True的上下文環境之後,reuse的取值又回到了False。
    # 下面的print語句將輸出:3 False。
    print("3", tf.get_variable_scope().reuse)

# 初始化全部全域性變數
tf.global_variables_initializer().run()

程式執行結果如下:

0 False
1 True
2 True
3 False

tf.variable_scope函式生成的上下文管理器也會建立一個Tensorflow中的名稱空間,在名稱空間內建立的變數名稱都會帶上這個名稱空間名作為字首。所以,tf.variable_scope函式除了可以控制tf.get_variable執行的功能之外,這個函式還提供了一種管理變數名稱空間的方式。

下面的程式碼顯示瞭如何通過tf.variable_scope來管理變數的名稱。

import tensorflow as tf
import cv2

# Tensorflow預設會話
sess = tf.InteractiveSession()

# 無名稱空間
v1 = tf.get_variable("v", [1])

# 下面的print將輸出:v1 v:0.
# "v"是變數的名稱,“:0”表示這個變數是生成變數這個運算的第一個結果。
print("v1", v1.name)

with tf.variable_scope("foo"):
    v2 = tf.get_variable("v", [1])

    # 下面的print將輸出:v2 foo/v:0。
    # 在tf.variable_scope中建立的變數,名稱前面會加入名稱空間的名稱,並通過/來分分隔名稱空間的名稱和變數的名稱。
    print("v2", v2.name)

with tf.variable_scope("foo"):
    with tf.variable_scope("bar"):
        v3 = tf.get_variable("v", [1])

        # 下面的print將輸出:v3 foo/bar/v:0。
        # 名稱空間可以巢狀,同時變數的名稱也會加入所有名稱空間的名稱作為字首。
        print("v3", v3.name)

    v4 = tf.get_variable("v1", [1])

    # 下面的print將輸出:v4 foo/v1:0。
    # 當名稱空間退出後,變數名稱也就不會再被加入其字首。
    print("v4", v4.name)

# 建立一個名稱為空的名稱空間,並設定reuse=True。
with tf.variable_scope("", reuse=True):

    # 可以直接通過帶有名稱空間名稱的變數名來獲取其他名稱空間下的變數。
    # 比如這裡通過指定名稱foo/bar/v來獲取在名稱空間foo/bar中建立的變數。
    v5 = tf.get_variable("foo/bar/v", [1])

    # 下面的print將輸出:v5 == v3 True
    print("v5 == v3", v5 == v3)
    v6 = tf.get_variable("foo/v1", [1])

    # 下面的print將輸出:v6 == v4 True
    print("v6 == v4", v6 == v4)

# 初始化全部全域性變數
tf.global_variables_initializer().run()

程式執行結果如下:

v1 v:0
v2 foo/v:0
v3 foo/bar/v:0
v4 foo/v1:0
v5 == v3 True
v6 == v4 True

通過使用tf.get_variable和tf.variable_scope來管理變數,尤其是當神經網路結構更加複雜,引數更多時,會使得程式的可讀性大大調高。