name_scope與variable_scope 詳解

name_scope 與 variable_scope詳解


[參考文獻]:

1.scope 命名方法

2.name&variable scope

3.tf.variable_scope和tf.name_scope的用法


1.scope是幹什麼的

顧名思義“scope”的意思是“範圍”,那麼name_scope和variable_scope就是針對name所做的範圍定義。典型的 TensorFlow 可以有數以千計的節點,在構建各op的過程中,命名要做到不重複,那麼在編寫程序的時候就要特別注意,例如“x”、“y”、”W”、”B”甚至“weight”等經常使用的變量/常量命名就很可能會重複,否則就要增加一個字符串來表示不同的op所屬,例如在x前加train成爲train_x,在x前加scan成爲scan_x等等,一方面表明此變量、常量所屬“範圍”的標識,這個name_scope和variable_scope就是幹這個事情的,另一方面是從name上保證此變量命名的唯一性。有了這個scope的存在,其中描述的變量和常量就會在機器當中自動給添加前綴描述,作爲對各個變量、常量的區分。在這裏的編程和以前使用C、C++不同之處就是,C/C++當中的變量名實際相當於我們這裏的XXX.name()而不是XXX,計算機區分的是XXX.name()。簡單說,有了scope,那麼在tensorflow當中就是用op/tensor名來劃定範圍,實現對變量或者常量名的唯一性定義,避免衝突。

2.scope是怎麼幹的

接下來我們要分析的問題是:究竟name_scope()和variable_scope()對可以產生變量的tf.get_variable()和tf.Variable()會有什麼不同?爲了一探究竟,接下來做一些測試:

2.1 tf.name_scope對tf.get_variable()的影響

import tensorflow as tf

with tf.name_scope("a_name_scope") as myscope:
    initializer = tf.constant_initializer(value=1)
    var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # var1:0
    print(sess.run(var1))   # [ 1.]
var1:0
[ 1.]

[結論]:

name_scope()沒有對 tf.get_variable()產生的變量增加“範圍”。於是,如果再有相同的變量名生成,即便name_scope使用了新的名字“a_name_scope_new”,仍舊會破壞“唯一性”,從而導致出錯“該變量已經定義過了!”,試驗一下:

with tf.name_scope("a_name_scope_new") as myscope:
    initializer = tf.constant_initializer(value=1)
    var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
---------------------------------------------------------------------------

ValueError: Variable var1 already exists, disallowed. Did you mean to set reuse=True in VarScope? 

【結論】:

果然出錯了!“Variable var1 already exists, disallowed.”,說明name_scope沒有對get_variable()增加“範圍”。那我現在還想生成一個新的變量var2而且要讓其內容與var1相同怎麼辦?

with tf.name_scope("a_name_scope") as myscope:
    #initializer = tf.constant_initializer(value=1)
    var2 = tf.get_variable(name='var2', dtype=tf.float32, initializer=var1)
    #var2=var1
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # var1:0
    print(sess.run(var1))   # [ 1.]
    print(var2.name)        # var2:0
    print(sess.run(var2))   # [ 1.]
var1:0
[ 1.]
var2:0
[ 1.]

【結論】:

看結果,的確生成了新的變量var2。如果我不想生成新的變量var2,而只想做個變量的名稱更改怎麼辦?注意,這裏就和C、C++有不同了。
with tf.name_scope("a_name_scope") as myscope:
    var3=var1
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # var1:0
    print(sess.run(var1))   # [ 1.]
    print(var3.name)        # var1:0
    print(sess.run(var3))   # [ 1.]
var1:0
[ 1.]
var1:0
[ 1.]

【結論】:

雖然有一個變量var3,但是其name和值都是與var1相同的,計算機認的是name,認爲這兩個是完全一樣的,並不是新建,只是引用,我們可以理解爲是C語言中的指針,指針的名字可以千奇百怪,但是其指向的地址內容纔是其真正的歸屬。所以,這個變量var3你可以隨時設置,不會引起重複性、唯一性的錯誤——“Variable var1 already exists, disallowed.”。

變量的名稱只是指針名,變量的name是地址

with tf.name_scope("a_name_scope") as myscope:
    var3=var1

【結論】:

的確沒有引起錯誤提示。

2.2 variable_scope對get_variable的影響

那麼接着分析,既然name_scope對get_variable沒有增加“範圍”,那variable_scope呢?
with tf.variable_scope("a_variable_scope") as myscope:
    initializer = tf.constant_initializer(value=1)
    var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # a_variable_scope/var1:0
    print(sess.run(var1))   # [ 1.]
a_variable_scope/var1:0
[ 1.]

【結論】:variable_scope對get_variable有命名的影響

看到var1指向的內容被新建了,其name變成了“a_variable_scope/var1:0”,也就是說對計算機而言這已經是一個新的內容了,需要一個新的內存來存儲,這個和之前的var1.name = var1:0已經不是一個東西了.同時,我們想要找到之前的var1的name已經是不能夠了,這個完全就是C/C++指針的意思,指針指向了一個新的內存,如果不使用一個新的指針指向舊的地址,那麼就會導致舊地址的內容無法檢索了。驗證一下:
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # a_variable_scope/var1:0
    print(sess.run(var1))   # [ 1.]
    print(var3.name)        # var1:0
    print(sess.run(var3))   # [ 1.]
a_variable_scope/var1:0
[ 1.]
var1:0
[ 1.]

【結論】: 變量名就是指針,變量的name屬性是地址

var3是前面對舊的var1的內容的指針,雖然在前面var1指向了新建的內容(a_variable_scope/var1:0),但是原有內容的指針給了var3,所以現在仍舊可以通過指針找到舊地址內容。前面看到了,使用variable_scope能夠產生新的變量,即增加了“範圍”標識的變量,那麼這個能否再次執行產生新的變量呢?也就是說,新產生的name爲(a_variable_scope/var1:0)的地址能否再次被新建?
with tf.variable_scope("a_variable_scope") as myscope:
    initializer = tf.constant_initializer(value=1)
    var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)
ValueError: Variable a_variable_scope/var1 already exists, disallowed. Did you mean to set reuse=True in VarScope? 

【結論】:get_variable取值的唯一性仍舊需要保證

可見雖然variable_scope可以增加name“範圍”,但是get_variable取值的唯一性仍舊需要維護,否則就會出錯,你是不可以任性新建的。那我現在就是想重用此變量,我就是想更改一個變量名而已,行嗎?當然行,之前我們就採用了直接變量名賦值就行了“var3=var1”,這樣變量名改爲了var3,但是其name仍舊是var1的name,從c理解就是,指針隨便建,地址唯一不變,街道名你隨便改,可是街道你不能隨便拆改。那還有一種材料中介紹的方法,就是使用reuse_variables(),但是一旦重用的對象本身不存在就會報錯。這裏說的對象不是“指針”,而是指針地址,而且是帶有variable_scope指定的“範圍”的指針地址。
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # a_variable_scope/var1:0
    print(sess.run(var1))   # [ 1.]
    print(var2.name)        # var2:0
    print(sess.run(var2))   # [ 1.]
    print(var3.name)        # var1:0
    print(sess.run(var3))   # [ 1.]
a_variable_scope/var1:0
[ 1.]
var2:0
[ 1.]
var1:0
[ 1.]

【分析】:

看看上面三個變量,如果在 tf.variable_scope(“a_variable_scope”)下則會增加“範圍”——(a_variable_scope),顯然,var1是已存在的對象,而var2雖然也是有已有的地址,但是如果在 tf.variable_scope當中使用get_variable來獲取則會新建一個name爲:(a_variable_scope/var2:0),所以var2不能當做是已有地址的變量來被重用,否則會出錯。var1是可以被重用的,因爲他的確是一個已有的範圍內的地址。

with tf.variable_scope("a_variable_scope") as myscope:
    initializer = tf.constant_initializer(value=1)
    myscope.reuse_variables()
    var2 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)    
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # a_variable_scope/var1:0
    print(sess.run(var1))   # [ 1.]
    print(var2.name)        # a_variable_scope/var1:0
    print(sess.run(var2))   # [ 1.]
a_variable_scope/var1:0
[ 1.]
a_variable_scope/var1:0
[ 1.]

**【結論】:**var1被var2重用了

with tf.variable_scope("a_variable_scope") as myscope:
    initializer = tf.constant_initializer(value=1)
    myscope.reuse_variables()
    var2 = tf.get_variable(name='var2', shape=[1], dtype=tf.float32, initializer=initializer)    
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # a_variable_scope/var1:0
    print(sess.run(var1))   # [ 1.]
    print(var2.name)        # a_variable_scope/var1:0
    print(sess.run(var2))   # [ 1.]
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

ValueError: Variable a_variable_scope/var2 does not exist, or was not created with tf.get_variable(). Did you mean to set reuse=None in VarScope?

【結論】:

雖然var2經過重用了,但是其name是var1的,也就是(a_variable_scope/var1:0),而不是(a_variable_scope/var2:0),所以一旦重用則會因爲沒有這個地址而報錯!同時,重用操作“myscope.reuse_variables()”在with範圍內都是生效的,所以導致如果想如之前一般新建一個變量都會報錯,如下:
with tf.variable_scope("a_variable_scope") as myscope:
    initializer = tf.constant_initializer(value=1)
    myscope.reuse_variables()
    var2 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)    
    var7= tf.get_variable(name='var7', shape=[1], dtype=tf.float32, initializer=initializer)    
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # a_variable_scope/var1:0
    print(sess.run(var1))   # [ 1.]
    print(var2.name)        # a_variable_scope/var1:0
    print(sess.run(var2))   # [ 1.]
    print(var7.name)        # a_variable_scope/var1:0
    print(sess.run(var7))   # [ 1.]
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

ValueError: Variable a_variable_scope/var7 does not exist, or was not created with tf.get_variable(). Did you mean to set reuse=None in VarScope?

【結論】:

在上面var1被var2重用,這個是正確的,但是緊接着想重新新建一個就會立馬報錯的,說明這個重用操作“myscope.reuse_variables()”在with範圍內都是生效的。如果name_scope下使用reuse_variables()則會報錯。
with tf.name_scope("a_name_scope") as myscope:
    initializer = tf.constant_initializer(value=1)
    myscope.reuse_variables()
    var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)



with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # var1:0
    print(sess.run(var1))   # [ 1.]
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)
AttributeError: 'str' object has no attribute 'reuse_variables'

2.3 name_scope()對variable的影響

with tf.name_scope("a_name_scope") as myscope:    
    var2 = tf.Variable(name='var2', initial_value=[2], dtype=tf.float32)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # var1:0
    print(sess.run(var1))   # [ 1.]
    print(var2.name)        # a_name_scope/var2:0
    print(sess.run(var2))   # [ 2.]
a_variable_scope/var1:0
[ 1.]
a_name_scope_5/var2:0
[ 2.]

【結論】:

很棒!name_scope對variable有效,新增了“範圍”名。那這個範圍名是否與get_variable一樣有唯一性?
with tf.name_scope("a_name_scope") as myscope:    
    var2 = tf.Variable(name='var2', initial_value=[2], dtype=tf.float32)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # var1:0
    print(sess.run(var1))   # [ 1.]
    print(var2.name)        # a_name_scope/var2:0
    print(sess.run(var2))   # [ 2.]
a_variable_scope/var1:0
[ 1.]
a_name_scope_6/var2:0
[ 2.]

【結論】:

從上面結果看,唯一性仍舊保證了,原因是自動的將var2.name從(a_name_scope_1/var2:0)變爲了(a_name_scope_2/var2:0).這裏使用了“變爲”這個詞實際是錯誤的,因爲根本不是變爲,而是新建了一個name 爲(a_name_scope_2/var2:0)的變量,這個變量和前面的那個變量的名都爲var2但是name屬性卻是不同的。

2.4 variable_scope對variable的影響

with tf.variable_scope("my_variable_scope") as myscope:    
    var2 = tf.Variable(name='var2', initial_value=[initializer.value], dtype=tf.float32)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # var1:0
    print(sess.run(var1))   # [ 1.]
    print(var2.name)        # a_name_scope/var2:0
    print(sess.run(var2))   # [ 2.]
a_variable_scope/var1:0
[ 1.]
my_variable_scope/var2:0
[ 1.]

【結論】:

variable_scope對variable有效,而且從下面的運行結果看,唯一性可以得到保證,新建了一個新的變量,這點和name_scope一致。
with tf.variable_scope("my_variable_scope") as myscope:    
    var2 = tf.Variable(name='var2', initial_value=[initializer.value], dtype=tf.float32)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(var1.name)        # var1:0
    print(sess.run(var1))   # [ 1.]
    print(var2.name)        # a_name_scope/var2:0
    print(sess.run(var2))   # [ 2.]
a_variable_scope/var1:0
[ 1.]
my_variable_scope_1/var2:0
[ 1.]

總結:

[1]. name_scope 對 get_variable新建變量的name屬性無影響;對variable新建變量的name屬性增加了“範圍”標識。

[2]. variable_scope對get_variable新建變量的name屬性和variable新建變量的name屬性都增加了“範圍”標識。

[3]. get_variable新建變量如果遇見重複的name則會因爲重複而報錯。

[4]. variable新建的變量如果遇見重複的name則會自動修改前綴,以避免重複出現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章