共享變量 - 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
. 當我們想重用這個模塊時問題還在增多.假設你想把你的圖片過濾器運用到兩張不同的圖片, image1
和image2
.你想通過擁有同一個參數的同一個過濾器來過濾兩張圖片,你可以調用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"
兩個簡稱.而我們更偏向于用conv1
和 conv2
這兩個變量的寫法,但是不同的變量需要不同的名字.這就是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
,將會打破內部結構并且用這種方法使得很難再共享參數.
即使你不能直接設置 reuse
為 False
,但是你可以輸入一個重用變量作用域,然后就釋放掉,就成為非重用的變量.當打開一個變量作用域時,使用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