Tensorflow:variable变量和变量空间
Posted -柚子皮-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tensorflow:variable变量和变量空间相关的知识,希望对你有一定的参考价值。
Tensorflow的Graph 思想:在 TensorFlow 中,我们定义一个变量,相当于往 Graph 中添加了一个节点。和普通的 python 函数不一样,在一般的函数中,我们对输入进行处理,然后返回一个结果,而函数里边定义的一些局部变量我们就不管了。但是在 TensorFlow 中,我们在函数里边创建了一个变量,就是往 Graph 中添加了一个节点。出了这个函数后,这个节点还是存在于 Graph 中的。
variable变量和变量空间
tf.Variable
无scope情况下tf.Variable永远会去创建新变量,且会自动处理name冲突问题,会自动给name+1。
怎么用怎么初始化参考[2.5 tensorflow2.3--变量Variable声明和初始化]
name_scope
为了更好地管理变量的命名空间而提出的。比如在 tensorboard 中,因为引入了 name_scope, 我们的 Graph 看起来才井然有序。tf.name_scope会为tf.Variable,tensor等增加前缀,方便管理变量,并对tf.get_variable无效。即tf.name_scope() 并不会对 tf.get_variable() 创建的变量有任何影响。 (参考示例)
Note: 注意,name_scope默认不复用,本身调用第二次也会自动name+1的,此时可以加后缀/避免。为什么name_scope的名字不能重复呢?我的理解是:对于ops来说不涉及需不需要reuse的问题,所以系统自动加上"_1"的后缀。而对于variable来说,有可能会需要同一空间中该变量的值,所以名字需要一致。[Tensorflow中使用tf.variable_scope()而scope名字自动加"_1"]
后缀自动加1的示例[Tensorflow中的name_scope和variable_scope]
variable_scope
大部分情况下,跟 tf.get_variable() 配合使用,实现变量共享的功能。with tf.variable_scope('scopename', reuse=True): func() #函数调用,则函数func内部定义的变量空间名在前面也会加上scopename的空间。
partitioner 参数:tf.variable_scope 有一个 partitioner 参数,如 documentation 中所述。
它用于分布式培训。巨大的 tensorflow 变量可以在多台机器上分片(参见 this discussion)。 Partitioner 是一种机制,通过它 tensorflow 分发和组装张量,以便程序的其余部分不知道这些实现细节并以通常的方式使用张量。
partitioner = tf.min_max_variable_partitioner(max_partitions=self.num_ps,
axis=0, min_slice_size=1 << 15)
with tf.variable_scope(self.model_name, partitioner=partitioner, reuse=tf.AUTO_REUSE):
[python - Tensorflow variable_scope 中的分区参数是做什么用的?]
tf.get_variable()
tf.get_variable()方法是TensorFlow提供的比tf.Variable()稍微高级的创建/获取变量的方法,它的工作方式根据当前的变量域(Variable Scope)的reuse属性变化而变化,我们可以通过tf.get_variable_scope().reuse来查看这个属性,它默认是False。tf.get_variable() 的机制:如果指定的变量名已经存在(即先前已经用同一个变量名通过 get_variable() 函数实例化了变量),那么 get_variable()只会返回之前的变量,否则才创造新的变量。
reuse=True is not for variable_scope names. It is for the tf.Variable inside the tensorflow variable_scope.
[What happens when setting reuse=True in tensorflow]
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:新变量或现有变量的名称。
shape:新变量或现有变量的形状。
dtype:新变量或现有变量的类型(默认为DT_FLOAT)。
initializer:如果创建了变量的初始化器。可以是初始化器对象,也可以是张量。如果它是一个张量,它的形状必须是已知的,除非validate_shape是假的。
regularizer:A(张量->张量或无)函数;将其应用于新创建的变量的结果将添加到集合tf.GraphKeys中。正则化-损耗,可用于正则化。
trainable:如果为真,也将变量添加到图形集合GraphKeys-TRAINABLE_VARIABLES。
collections:要向其中添加变量的图形集合键的列表。默认为[GraphKeys.GLOBAL_VARIABLES]
tf.variable_scope会为两者都添加前缀。但是主要是为了配合tf.get_variable进行变量重用。
默认情况(reuse=False)下变量重用关闭,tf.get_variable还是会去创建新变量,若重复则冲突报错。
若reuse=True情况下,变量一定重用,tf.get_variable一定会去获取之前已创建过的变量,若没找到则会报错。
若reuse=tf.AUTO_REUSE情况下,tf.get_variable会自动判断,变量不存在则新建,存在则获取。
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]
查看变量是否共享
方法1:将所有变量打印出来,如果同一个变量存在非正常自动给变量name后缀加_1,就应该是没有共享。
print("\\nLOG:Global Variable:")
for v in tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES):
print(''.format(v))
方法2:使用inspect_checkpoint检查保存的变量值变化。如示例。
GraphKeys
tf.GraphKeys
包含所有graph collection中的标准集合名。
tf.Graph包含两类相关信息:
1 图结构。图的节点和边缘,指明了各个指令组合在一起的方式,但不规定它们的使用方式。图结构与汇编代码类似:检查图结构可以传达一些有用的信息,但它不包含源代码传达的的所有有用上下文。
2 **图集合。**TensorFlow提供了一种通用机制,以便在tf.Graph中存储元数据集合。tf.add_to_collection函数允许您将对象列表与一个键相关联(其中tf.GraphKeys定义了部分标准键),tf.get_collection则允许您查询与键关联的所有对象。TensorFlow库的许多组成部分会使用它:例如,当您创建tf.Variable时,系统会默认将其添加到表示“全局变量(tf.global_variables)”和“可训练变量tf.trainable_variables)”的集合中。当您后续创建tf.train.Saver或tf.train.Optimizer时,这些集合中的变量将用作默认参数。
在创建图的过程中,TensorFlow的Python底层会自动用一些collection对op进行归类,方便之后的调用。这部分collection的名字被称为tf.GraphKeys,可以用来获取不同类型的op。当然,我们也可以自定义collection来收集op。
常见GraphKeys
GLOBAL_VARIABLES: 该collection默认加入所有的Variable对象,并且在分布式环境中共享。一般来说,TRAINABLE_VARIABLES包含在MODEL_VARIABLES中,MODEL_VARIABLES包含在GLOBAL_VARIABLES中。
LOCAL_VARIABLES: 与GLOBAL_VARIABLES不同的是,它只包含本机器上的Variable,即不能在分布式环境中共享。
MODEL_VARIABLES: 顾名思义,模型中的变量,在构建模型中,所有用于正向传递的Variable都将添加到这里。
TRAINALBEL_VARIABLES: 所有用于反向传递的Variable,即可训练(可以被optimizer优化,进行参数更新)的变量。
SUMMARIES: 跟Tensorboard相关,这里的Variable都由tf.summary建立并将用于可视化。
QUEUE_RUNNERS: the QueueRunner objects that are used to produce input for a computation.
MOVING_AVERAGE_VARIABLES: the subset of Variable objects that will also keep moving averages.
REGULARIZATION_LOSSES: regularization losses collected during graph construction.
[tensorflow教程——tf.GraphKeys][tensorflow--变量--汇总]
在函数体内定义tf.variable()或者tf.get_variable()变量的时候,跟其他语言不同,在tensorflow的函数体内定义的变量并不会随着函数的执行结束而消失。这是因为tensorflow设置的全局变量及局部变量与其他语言有着本质的不同,这是因为tf里面是由图定义的。
global和local variable区别:
只是逻辑上的区分!技术上是一样,都是记录在collection里面,也就是你可以自行定义你的变量是global或local,只要在collection里面声明好。如:e = tf.Variable(6, name='var_e', collections=[tf.GraphKeys.LOCAL_VARIABLES])其次,你可以自行定义自己的collection,可以不是local global trainable 或model中的任何一个。my_variable10 = tf.get_variable("var10", dtype=tf.int32, initializer=tf.constant(3), collections=["my_collection"], trainable=True)
[tensorflow中global_variables和local_variables的关系?]
对于局部变量来说,其变量也是一种普通的变量,不过其定义在tf.GraphKeys.LOCAL_VARIABLES。通常该集合用于保存程序用于初始化的默认变量列表,因此local指定的变量在默认情况下不会保存。即不会保存到checkpoint中。
定义一个局部变量:需要显示指定所在的变量集合collection
e = tf.Variable(6, name='var_e', collections=[tf.GraphKeys.LOCAL_VARIABLES])
[tensorflow中的全局变量GLOBAL_VARIABLES及局部变量LOCAL_VARIABLES]
变量初始化
示例:所有全局变量初始化
tf.global_variables_initializer().run()
sess.run(tf.global_variables_initializer())
使用dataset.make_initializable_iterator(),第一次或者每次重新初始化的时候,都要调用sess.run(iterator.initializer)
在变量域内初始化变量
每次初始化变量时都要传入一个 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.
tf.tables_initializer函数
tf.tables_initializer函数返回初始化所有表的操作。请注意,如果没有表格,则返回的操作是空操作。[TensorFlow函数:tf.tables_initializer]
什么是表:Tables in TensorFlow generally refer to data structures supporting lookup operations, i.e. map-like collections. So far, these operations have lived under tf.contrib.lookup, but it seems the TensorFlow team are moving these to TensorFlow core (probably as part of the efforts towards TensorFlow 2.0, where tf.contrib will not be included). In fact, you can see classes that are actually already in core TensorFlow:
import tensorflow as tf
from tensorflow.python.ops import lookup_ops # This is inside core TensorFlow
print(tf.contrib.lookup.HashTable is lookup_ops.HashTable)
# True
[What does tensorflow's tables_initializer do?]
初始化方法
Zeros初始化
示例:一般bias的初始化
initializer=tf.python.keras.initializers.Zeros()
Glorot初始化方法
(1)glorot_normal正态化的Glorot初始化
Glorot 正态分布初始化器,也称为 Xavier 正态分布初始化器。它从以 0 为中心,标准差为 stddev = sqrt(2 / (fan_in + fan_out)) 的截断正态分布中抽取样本, 其中 fan_in 是权值张量中的输入单位的数量, fan_out 是权值张量中的输出单位的数量。
参数seed: 一个 Python 整数。作为随机发生器的种子。
示例:initializer=tf.python.keras.initializers.glorot_normal(seed=seed)
initializer=keras.initializers.glorot_normal(seed=None)
tf.contrib.layers.python.layers.initializers.xavier_initializer(uniform=False)
(2)标准化的Glorot初始化——glorot_uniform
Glorot 均匀分布初始化器,也称为 Xavier 均匀分布初始化器。
它从 [-limit,limit] 中的均匀分布中抽取样本, 其中 limit 是 sqrt(6 / (fan_in + fan_out)), fan_in 是权值张量中的输入单位的数量, fan_out 是权值张量中的输出单位的数量。
以keras为例:keras.initializers.glorot_uniform(seed=None)
(3)Glorot初始化器的缺点
因为Xavier的推导过程是基于几个假设的,
其中一个是激活函数是线性的,这并不适用于ReLU,sigmoid等非线性激活函数;
另一个是激活值关于0对称,这个不适用于sigmoid函数和ReLU函数它们不是关于0对称的。
Glorot条件:优秀的初始化应该保证以下两个条件:
(1)各个层的激活值h(输出值)的方差要保持一致,即
(2)各个层对状态Z的梯度的方差要保持一致,即
lecun初始化
出自大神Lecun之手。
(1)标准化化的kaiming初始化——lecun_uniform
LeCun 均匀初始化器。
它从 [-limit,limit] 中的均匀分布中抽取样本, 其中 limit 是 sqrt(3 / fan_in), fan_in 是权值张量中的输入单位的数量。
keras.initializers.lecun_uniform(seed=None)
(2)正态化的kaiming初始化——lecun_normal
LeCun 正态分布初始化器。
它从以 0 为中心,标准差为 stddev = sqrt(1 / fan_in) 的截断正态分布中抽取样本, 其中 fan_in是权值张量中的输入单位的数量。
keras.initializers.lecun_normal(seed=None)
[tf.keras.initializers]
[keras官网初始化器的用法]
不同初始化的优缺点[一文详解深度学习参数初始化(weights initializer)策略]
tensorflow为什么变量一定要初始化
tensorflow实际是以graph图表结构的形式运行的,在执行sess.run(传入需要取值的节点)时才去计算该图表某个节点的值,在此之前的操作都是为了构建此graph的结构并没有真正的赋于实际的值。执行variable(1)时也就是只是定义结构(类型为变量,初始值为1)。只有执行变量初始化方法时才赋予其定义的值。
tensorflow获取变量维度信息
获取变量维度是一个使用频繁的操作,在tensorflow中获取变量维度主要用到的操作有以下三种:
Tensor.shape
Tensor.get_shape()
tf.shape(input,name=None,out_type=tf.int32)
对上面三种操作做一下简单分析:(这三种操作先记作A、B、C)
A 和 B 基本一样,只不过前者是Tensor的属性变量,后者是Tensor的函数。
A 和 B 均返回TensorShape类型,而 C 返回一个1D的out_type类型的Tensor。
A 和 B 可以在任意位置使用,而 C 必须在Session中使用。
A 和 B 获取的是静态shape,可以返回不完整的shape; C 获取的是动态的shape,必须是完整的shape。
示例
示例:变量共享范式
with tf.variable_scope('embedding', reuse=tf.AUTO_REUSE):
variable = tf.get_variable('embedding_matrix', initializer=variable, dtype=tf.float32, trainable=True)
示例2:变量共享
在指定的变量域中调用:
# 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和variable_scope进行变量共享时不生效
class AtomicModel(Layer):
def build_model(self):
with tf.variable_scope(self.model_name, reuse=tf.AUTO_REUSE):
self.kernels = [self.add_weight(name='kernel' + str(i),
shape=(hidden_units[i], hidden_units[i + 1]),
initializer=glorot_normal(seed=self.seed),
regularizer=l2(self.l2_reg), trainable=self.is_training,
getter=tf.get_variable
)
for i in range(layers_cnt)]
self.bias = [self.add_weight(name='bias' + str(i), shape=(hidden_units[i + 1],),
initializer=Zeros(), trainable=self.is_training,
getter=tf.get_variable)
for i in range(layers_cnt)]
if self.use_bn:
self.bn_layers = [
tf.layers.BatchNormalization(name='bn' + str(i), trainable=self.is_training, _reuse=tf.AUTO_REUSE)
for i in range(layers_cnt)]
def call(self, input_tensor, training=None, **kwargs):
fc = input_tensor[0]
# fc = tf.Print(fc, ['userfeatures: ', fc])
for i in range(len(self.hidden_units)):
fc = tf.nn.bias_add(tf.tensordot(fc, self.kernels[i], axes=(-1, 0)), self.bias[i])
if self.use_bn:
fc = self.bn_layers[i](fc, training=training)
fc = self.activation_layers[i](fc)
if self.dropout_rate:
fc = self.dropout_layers[i](fc, training=training)
model_train = AtomicModel()
model_test = AtomicModel()
在variable_scope里共享变量时,如果使用keras的Layer.add_weight进行层声明时,如果同时声明两次(比如搞了两个模型model_train,model_test都是实例化此类,都进行了一次层声明),这时如果不加参数getter=tf.get_variable是不能进行变量共享的。应该是里面的变量是默认不是通过get_variable实现的(keras的变量共享哲学?keras可能更鼓励使用一个模型声明,直接在模型里面做train和test,call调用时总是使用build里相同的层,即共享了)。另外tf.layers.BatchNormalization没有参数_reuse=tf.AUTO_REUSE时也不能进行参数共享,应该是同样道理。[_reuse参数在这里tensorflow/normalization.py at v1.15.0 · tensorflow/tensorflow · GitHub]
如果不想用keras在build声明再在call里面调用,可以直接在call里面使用layers.fully_connected和tf.layers.batch_normalization,这时的variable_scope不会不共享。
Note: ~python3.6/site-packages/tensorflow/python/keras/engine/base_layer.py>Layer.add_weight
还有个例外,那就是除了variable其他的tensor,variable_scope会给其他类型tensor默认也添加一个name_scope,而由上文可知name_scope是默认不复用的。(一般不把非变量写进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=...)也会报错。
tf.get_variable_scope().reuse_variables() # set reuse to True
entity = tf.get_variable(name='entity')
tf.get_variable使用示例3:使用tf.AUTO_REUSE自动判断
解决示例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])(显示的是每次restore时variable的第一个word的embed值)每两次输出是一样的:
[[0.197622746 1.53550613 -0.476417035...]...]
[[0.197622746 1.53550613 -0.476417035...]...]
[[0.198618323 1.53450835 -0.477412552...]...]
[[0.198618323 1.53450835 -0.477412552...]...]
...
2 vocab中的字符需要按频次c降序输出,这样才能很好地在调试中variable = tf.Print(variable, [variable])中很好地观察到高频出现的字符的变化(否则低频可能会让你以为数据每次读取都一样没变)
同时使用
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:variable变量和变量空间的主要内容,如果未能解决你的问题,请参考以下文章
如何在 TensorFlow 中使用 tf.get_variable 和 numpy 值初始化变量?