Tensorflow:名字/變量空間和變量共享

        name_scope: 爲了更好地管理變量的命名空間而提出的。比如在 tensorboard 中,因爲引入了 name_scope, 我們的 Graph 看起來才井然有序。
        variable_scope: 大部分情況下,跟 tf.get_variable() 配合使用,實現變量共享的功能。with tf.variable_scope('scopename', reuse=True): func() #函數調用,則函數func內部定義的變量空間名 在前面也會加上scopename的空間。

Note: 要確立一種 Graph 的思想。在 TensorFlow 中,我們定義一個變量,相當於往 Graph 中添加了一個節點。和普通的 python 函數不一樣,在一般的函數中,我們對輸入進行處理,然後返回一個結果,而函數裏邊定義的一些局部變量我們就不管了。但是在 TensorFlow 中,我們在函數裏邊創建了一個變量,就是往 Graph 中添加了一個節點。出了這個函數後,這個節點還是存在於 Graph 中的。

        tf.get_variable():tf.get_variable()方法是TensorFlow提供的比tf.Variable()稍微高級的創建/獲取變量的方法,它的工作方式根據當前的變量域(Variable Scope)的reuse屬性變化而變化,我們可以通過tf.get_variable_scope().reuse來查看這個屬性,它默認是False。tf.get_variable() 的機制跟 tf.Variable() 有很大不同,如果指定的變量名已經存在(即先前已經用同一個變量名通過 get_variable() 函數實例化了變量),那麼 get_variable()只會返回之前的變量,否則才創造新的變量。

tf.get_variable(
    name,
    shape=None,
    dtype=None,
    initializer=None,
    regularizer=None,
    trainable=None,
    collections=None,
    caching_device=None,
    partitioner=None,
    validate_shape=True,
    use_resource=None,
    custom_getter=None,
    constraint=None,
    synchronization=tf.VariableSynchronization.AUTO,
    aggregation=tf.VariableAggregation.NONE
)

name_scope和varialbe_scope區別示例

with tf.name_scope('nsc1'):
    v1 = tf.Variable([1], name='v1')
    with tf.variable_scope('vsc1'):
        v2 = tf.Variable([1], name='v2')
        v3 = tf.get_variable(name='v3', shape=[])
print 'v1.name: ', v1.name
print 'v2.name: ', v2.name
print 'v3.name: ', v3.name

v1.name:  nsc1/v1:0
v2.name:  nsc1/vsc1/v2:0
v3.name:  vsc1/v3:0

        tf.name_scope() 並不會對 tf.get_variable() 創建的變量有任何影響。 tf.name_scope() 主要是用來管理命名空間的,這樣子讓我們的整個模型更加有條理。

        而 tf.variable_scope() 的作用是爲了實現變量共享,它和 tf.get_variable() 來完成變量共享的功能。

[TensorFlow入門(七) 充分理解 name / variable_scope]

在變量域內初始化變量

每次初始化變量時都要傳入一個 initializer,這實在是麻煩,而如果使用變量域的話,就可以批量初始化參數了:

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.

[TensorFlow學習筆記:共享變量]

-柚子皮-

 

tf.get_variable使用示例1:變量共享

在指定的變量域中調用:
# create var
with tf.variable_scope('embedding'):
    entity = tf.get_variable(name='entity', initializer=...)
# reuse var
with tf.variable_scope('embedding', reuse=True):
    entity = tf.get_variable(name='entity')

得到的變量entity的名字是embedding/entity。

更多變量共享在cnn、rnn中的使用參考[TensorFlow入門(七) 充分理解 name / variable_scope]

tf.get_variable使用示例2

1 tf.get_variable_scope().reuse == False
        此時調用tf.get_variable(name, shape, dtype, initializer),TensorFlow 認爲你是要初始化一個新的變量,這個變量的名字爲name,維度是shape,數據類型是dtype,初始化方法是指定的initializer。如果名字爲name的變量已經存在的話,會導致ValueError。當然直接 tf.get_variable(name='entity')而沒有initializer 也會報錯:The initializer passed is not valid. It should be a callable with no arguments and the shape should not be provided or an instance of `tf.keras.initializers.*' and `shape` should be fully defined.。
一個例子如下:

# create var
entity = tf.get_variable(name='entity', initializer=...)

2 tf.get_variable_scope().reuse == True
        此時調用tf.get_variable(name),TensorFlow 認爲你是要到程序裏面尋找變量名爲 scope name + name 的變量。如果這個變量不存在,會導致ValueError。當然如果直接 tf.get_variable(name='entity', initializer=...)也會報錯。
一個例子如下:

# reuse var
tf.get_variable_scope().reuse_variables()  # set reuse to True
entity = tf.get_variable(name='entity')

[TensorFlow的變量共享]

tf.get_variable使用示例3

        解決示例2的痛點可以直接使用tf.AUTO_REUSE自動判斷是不是第一次get這個變量,是則創建,否則get。
seq_a_embed = self.seq_embedding(seq_a, is_training)
seq_b_embed = self.seq_embedding(seq_b, is_training)

def seq_embedding(self, seq, is_training):
    ''' Word Embeddings '''
    with tf.variable_scope('embedding', reuse=tf.AUTO_REUSE):
        word_ids = self.vocab_words.lookup(seq)
        bert = np.load(self.params['embeddings'])['embeddings']  # np.array  # todo 移到外面
        variable = np.vstack([bert, [[0.] * self.params['dim']]])  # add oov
        variable = tf.cast(variable, dtype=tf.float32)
        variable = tf.get_variable('embedding_matrix', initializer=variable, dtype=tf.float32, trainable=True)
        variable = tf.Print(variable, [variable])
        # shape=(vocab, 768)
        embeddings = tf.nn.embedding_lookup(variable, word_ids)
        # shape = (batch, lstm*2, emb) = (32, 200, 768)
        embeddings = tf.keras.layers.Dropout(rate=self.params['dropout'])(embeddings, training=is_training)
        # shape=(?, ?, 768)=(batch_size, max_seq_len_in_batch, word_emb_size)
        # embeddings = tf.layers.batch_normalization(embeddings.transpose(1, 2)).transpose(1, 2)
    return embeddings

Note: 
1 每次checkpoint保存後再訓練Restoring parameters from results/model/model.ckpt時,tf.get_variable('embedding_matrix', initializer=..)會直接找到之前訓練好的變量,而不會再重新load一次預訓練的embedding而重新初始化embedding_matrix。
而reuse=tf.AUTO_REUSE在這裏的作用是兩次函數調用時,都使用同一個embedding_matrix來查找embeddings,每次batch訓練完成也是使用同一個更新後的embedding_matrix。所以variable = tf.Print(variable, [variable])每兩次輸出是一樣的:
[[0.197622746 1.53550613 -0.476417035...]...]
[[0.197622746 1.53550613 -0.476417035...]...]
[[0.198618323 1.53450835 -0.477412552...]...]
[[0.198618323 1.53450835 -0.477412552...]...]
[[0.199071899 1.53360093 -0.477980673...]...]
[[0.199071899 1.53360093 -0.477980673...]...]
...
2 vocab中的字符需要按頻次c降序輸出,這樣才能很好地在調試中variable = tf.Print(variable, [variable])中很好地觀察到高頻出現的字符的變化(否則低頻可能會讓你以爲數據每次讀取都一樣沒變)
variable = tf.Print(variable, [variable])可以在日誌中找到每次restore時variable的第一個word的embed值。
同時使用
from tensorflow.python.tools import inspect_checkpoint as ickpt
filename = '.../results/model/model.ckpt-315'
tensor_name = 'embedding/embedding_matrix'
ickpt.print_tensors_in_checkpoint_file(filename, tensor_name=tensor_name, all_tensors=False)
可以看到每次save的ckpt的embedding_matrix前幾個和後幾個的值,對比一下(除了第0個ckpt-0)第一個word大致是一樣的:
ckpt-0
[[0.197622746 1.53550613 -0.476417035...]...]
[[ 0.19762275  1.5355061  -0.47641703 ... -0.5410988   0.5473113
ckpt-168
[[0.204656258 1.54020405 -0.477185875...]...]
[[0.20465626  1.540204   -0.47718588 ... -0.5385146   0.5552384
...
ckpt-315
[[ 0.20471798  1.5401478  -0.47707108 ... -0.53842896  0.5551153
[[0.204717979 1.54014778 -0.477071077...]...]

[tensorflow: 對variable_scope進行reuse的兩種方法]

tf.get_variable使用示例4(錯誤示例)

with tf.variable_scope('embedding', reuse=tf.AUTO_REUSE):
    word_ids = self.vocab_words.lookup(seq)
    try:
        variable = tf.get_variable('embedding_matrix')
        print('get seq_embedding for the second time')
    except Exception as e:
        print(e)
        bert = np.load(self.params['embeddings'])['embeddings']
        variable = np.vstack([bert, [[0.] * self.params['dim']]])  # add oov
        variable = tf.cast(variable, dtype=tf.float32)
        variable = tf.get_variable('embedding_matrix', initializer=variable, dtype=tf.float32, trainable=True)

variable = tf.Print(variable, [variable])

       每次restore時不能直接使用tf.get_variable('embedding_matrix')得到variable,會出錯輸出e爲The initializer passed is not valid...,但是在每次restore之間的訓練是可以直接使用tf.get_variable('embedding_matrix')的(這可能是因爲在restore之前tf總會先進行init再進行restore賦值(直接取上次checkpoint中的值覆蓋init值)[參考Tensorflow:模型保存和服務note 2,3])。所以沒必要使用try except。

from: -柚子皮-

ref: 

 

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