共享變量 tensorflow解讀

共享變量 - TensorFlow 官方文檔中文版 - 極客學院Wiki

你可以在怎么使用變量中所描述的方式來創建,初始化,保存及加載單一的變量.但是當創建復雜的模塊時,通常你需要共享大量變量集并且如果你還想在同一個地方初始化這所有的變量,我們又該怎么做呢.本教程就是演示如何使用tf.variable_scope()tf.get_variable()兩個方法來實現這一點.

問題

假設你為圖片過濾器創建了一個簡單的模塊,和我們的卷積神經網絡教程模塊相似,但是這里包括兩個卷積(為了簡化實例這里只有兩個).如果你僅使用tf.Variable變量,那么你的模塊就如怎么使用變量里面所解釋的是一樣的模塊.

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)

你很容易想到,模塊集很快就比一個模塊變得更為復雜,僅在這里我們就有了四個不同的變量:conv1_weights,conv1_biases, conv2_weights, 和conv2_biases. 當我們想重用這個模塊時問題還在增多.假設你想把你的圖片過濾器運用到兩張不同的圖片, image1image2.你想通過擁有同一個參數的同一個過濾器來過濾兩張圖片,你可以調用my_image_filter()兩次,但是這會產生兩組變量.

# First call creates one set of variables.
result1 = my_image_filter(image1)
# Another set is created in the second call.
result2 = my_image_filter(image2)

通常共享變量的方法就是在單獨的代碼塊中來創建他們并且通過使用他們的函數.如使用字典的例子:

variables_dict = {
    "conv1_weights": tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv1_weights")
    "conv1_biases": tf.Variable(tf.zeros([32]), name="conv1_biases")
    ... etc. ...
}

def my_image_filter(input_images, variables_dict):
    conv1 = tf.nn.conv2d(input_images, variables_dict["conv1_weights"],
        strides=[1, 1, 1, 1], padding='SAME')
    relu1 = tf.nn.relu(conv1 + variables_dict["conv1_biases"])

    conv2 = tf.nn.conv2d(relu1, variables_dict["conv2_weights"],
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv2 + variables_dict["conv2_biases"])

# The 2 calls to my_image_filter() now use the same variables
result1 = my_image_filter(image1, variables_dict)
result2 = my_image_filter(image2, variables_dict)

雖然使用上面的方式創建變量是很方便的,但是在這個模塊代碼之外卻破壞了其封裝性:

  • 在構建試圖的代碼中標明變量的名字,類型,形狀來創建.
  • 當代碼改變了,調用的地方也許就會產生或多或少或不同類型的變量.

解決此類問題的方法之一就是使用類來創建模塊,在需要的地方使用類來小心地管理他們需要的變量. 一個更高明的做法,不用調用類,而是利用TensorFlow 提供了變量作用域 機制,當構建一個視圖時,很容易就可以共享命名過的變量.

變量作用域實例

變量作用域機制在TensorFlow中主要由兩部分組成:

  • tf.get_variable(<name>, <shape>, <initializer>): 通過所給的名字創建或是返回一個變量.
  • tf.variable_scope(<scope_name>): 通過 tf.get_variable()為變量名指定命名空間.

方法 tf.get_variable() 用來獲取或創建一個變量,而不是直接調用tf.Variable.它采用的不是像`tf.Variable這樣直接獲取值來初始化的方法.一個初始化就是一個方法,創建其形狀并且為這個形狀提供一個張量.這里有一些在TensorFlow中使用的初始化變量:

  • tf.constant_initializer(value) 初始化一切所提供的值,
  • tf.random_uniform_initializer(a, b)從a到b均勻初始化,
  • tf.random_normal_initializer(mean, stddev) 用所給平均值和標準差初始化均勻分布.

為了了解tf.get_variable()怎么解決前面所討論的問題,讓我們在單獨的方法里面創建一個卷積來重構一下代碼,命名為conv_relu

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_intializer(0.0))
    conv = tf.nn.conv2d(input, weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv + biases)

這個方法中用了"weights""biases"兩個簡稱.而我們更偏向于用conv1conv2這兩個變量的寫法,但是不同的變量需要不同的名字.這就是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])

現在,讓我們看看當我們調用 my_image_filter() 兩次時究竟會發生了什么.

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

就像你看見的一樣,tf.get_variable()會檢測已經存在的變量是否已經共享.如果你想共享他們,你需要像下面使用的一樣,通過reuse_variables()這個方法來指定.

with tf.variable_scope("image_filters") as scope:
    result1 = my_image_filter(image1)
    scope.reuse_variables()
    result2 = my_image_filter(image2)

用這種方式來共享變量是非常好的,輕量級而且安全.

變量作用域是怎么工作的?

理解 tf.get_variable()

為了理解變量作用域,首先完全理解tf.get_variable()是怎么工作的是很有必要的. 通常我們就是這樣調用tf.get_variable 的.

v = tf.get_variable(name, shape, dtype, initializer)

此調用做了有關作用域的兩件事中的其中之一,方法調入.總的有兩種情況.

  • 情況1:當tf.get_variable_scope().reuse == False時,作用域就是為創建新變量所設置的.

這種情況下,v將通過tf.Variable所提供的形狀和數據類型來重新創建.創建變量的全稱將會由當前變量作用域名+所提供的名字所組成,并且還會檢查來確保沒有任何變量使用這個全稱.如果這個全稱已經有一個變量使用了,那么方法將會拋出ValueError錯誤.如果一個變量被創建,他將會用initializer(shape)進行初始化.比如:

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])
assert v.name == "foo/v:0"
  • 情況1:當tf.get_variable_scope().reuse == True時,作用域是為重用變量所設置

這種情況下,調用就會搜索一個已經存在的變量,他的全稱和當前變量的作用域名+所提供的名字是否相等.如果不存在相應的變量,就會拋出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 == v

tf.variable_scope() 基礎

知道tf.get_variable()是怎么工作的,使得理解變量作用域變得很容易.變量作用域的主方法帶有一個名稱,它將會作為前綴用于變量名,并且帶有一個重用標簽來區分以上的兩種情況.嵌套的作用域附加名字所用的規則和文件目錄的規則很類似:

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

當前變量作用域可以用tf.get_variable_scope()進行檢索并且reuse 標簽可以通過調用tf.get_variable_scope().reuse_variables()設置為True .

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 == v

注意你不能設置reuse標簽為False.其中的原因就是允許改寫創建模塊的方法.想一下你前面寫得方法my_image_filter(inputs).有人在變量作用域內調用reuse=True 是希望所有內部變量都被重用.如果允許在方法體內強制執行reuse=False,將會打破內部結構并且用這種方法使得很難再共享參數.

即使你不能直接設置 reuseFalse ,但是你可以輸入一個重用變量作用域,然后就釋放掉,就成為非重用的變量.當打開一個變量作用域時,使用reuse=True 作為參數是可以的.但也要注意,同一個原因,reuse 參數是不可繼承.所以當你打開一個重用變量作用域,那么所有的子作用域也將會被重用.

with tf.variable_scope("root"):
    # At start, the scope is not reusing.
    assert tf.get_variable_scope().reuse == False
    with tf.variable_scope("foo"):
        # Opened a sub-scope, still not reusing.
        assert tf.get_variable_scope().reuse == False
    with tf.variable_scope("foo", reuse=True):
        # Explicitly opened a reusing scope.
        assert tf.get_variable_scope().reuse == True
        with tf.variable_scope("bar"):
            # Now sub-scope inherits the reuse flag.
            assert tf.get_variable_scope().reuse == True
    # Exited the reusing scope, back to a non-reusing one.
    assert tf.get_variable_scope().reuse == False

獲取變量作用域

在上面的所有例子中,我們共享參數只因為他們的名字是一致的,那是因為我們開啟一個變量作用域重用時剛好用了同一個字符串.在更復雜的情況,他可以通過變量作用域對象來使用,而不是通過依賴于右邊的名字來使用.為此,變量作用域可以被獲取并使用,而不是僅作為當開啟一個新的變量作用域的名字.

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

當開啟一個變量作用域,使用一個預先已經存在的作用域時,我們會跳過當前變量作用域的前綴而直接成為一個完全不同的作用域.這就是我們做得完全獨立的地方.

with tf.variable_scope("foo") as foo_scope:
    assert foo_scope.name == "foo"
with tf.variable_scope("bar")
    with tf.variable_scope("baz") as other_scope:
        assert other_scope.name == "bar/baz"
        with tf.variable_scope(foo_scope) as foo_scope2:
            assert foo_scope2.name == "foo"  # Not changed.

變量作用域中的初始化器

使用tf.get_variable()允許你重寫方法來創建或者重用變量,并且可以被外部透明調用.但是如果我們想改變創建變量的初始化器那要怎么做呢?是否我們需要為所有的創建變量方法傳遞一個額外的參數呢?那在大多數情況下,當我們想在一個地方并且為所有的方法的所有的變量設置一個默認初始化器,那又改怎么做呢?為了解決這些問題,變量作用域可以攜帶一個默認的初始化器.他可以被子作用域繼承并傳遞給tf.get_variable() 調用.但是如果其他初始化器被明確地指定,那么他將會被重寫.

with tf.variable_scope("foo", initializer=tf.constant_initializer(0.4)):
    v = tf.get_variable("v", [1])
    assert v.eval() == 0.4  # Default initializer as set above.
    w = tf.get_variable("w", [1], initializer=tf.constant_initializer(0.3)):
    assert w.eval() == 0.3  # Specific initializer overrides the default.
    with tf.variable_scope("bar"):
        v = tf.get_variable("v", [1])
        assert v.eval() == 0.4  # Inherited default initializer.
    with tf.variable_scope("baz", initializer=tf.constant_initializer(0.2)):
        v = tf.get_variable("v", [1])
        assert v.eval() == 0.2  # Changed default initializer.

tf.variable_scope()中ops的名稱

我們討論 tf.variable_scope 怎么處理變量的名字.但是又是如何在作用域中影響到 其他ops的名字的呢?ops在一個變量作用域的內部創建,那么他應該是共享他的名字,這是很自然的想法.出于這樣的原因,當我們用with tf.variable_scope("name")時,這就間接地開啟了一個tf.name_scope("name").比如:

with tf.variable_scope("foo"):
    x = 1.0 + tf.get_variable("v", [1])
assert x.op.name == "foo/add"

名稱作用域可以被開啟并添加到一個變量作用域中,然后他們只會影響到ops的名稱,而不會影響到變量.

with tf.variable_scope("foo"):
    with tf.name_scope("bar"):
        v = tf.get_variable("v", [1])
        x = 1.0 + v
assert v.name == "foo/v:0"
assert x.op.name == "foo/bar/add"

當用一個引用對象而不是一個字符串去開啟一個變量作用域時,我們就不會為ops改變當前的名稱作用域.

使用實例

這里有一些指向怎么使用變量作用域的文件.特別是,他被大量用于 時間遞歸神經網絡sequence-to-sequence模型,

File What's in it?
models/image/cifar10.py 圖像中檢測對象的模型.
models/rnn/rnn_cell.py 時間遞歸神經網絡的元方法集.
models/rnn/seq2seq.py 為創建sequence-to-sequence模型的方法集.

原文:Sharing Variables 翻譯:nb312校對:Wiki

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容