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() 来完成变量共享的功能。

Note: 重复使用某函数定义网络层时,如果使用with tf.name_scope(name_scope)会导致里面的变量已定义报错,所以要使用tf.variable_scope。

[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: 

 

你可能感兴趣的:(tensorflow)