声明:本篇文章参考《Tensorflow实战Google深度学习框架》一书
Tensorflow提供了通过变量名称来创建或者获取一个变量的机制。通过这个机制,在不同的函数中可以直接通过变量的名字来使用变量,而不需要将变量通过参数的形式到处传递。
Tensorflow中通过便令名称获取变量的机制主要是通过tf.get_variable和tf.variable_scope函数实现的。
除了tf.Variable函数,Tensorflow还提供了tf.get_variable函数串讲或者获取变量。当tf.get_variable用于创建变量时,它和tf.Variable的功能是基本等价的。
以下是通过这两个函数创建同一个变量的样例:
v = tf.get_variable("v", shape=[1], initializer=tf.constant_initializer(1.0))
v = tf.Variable(tf.constant(1.0, dtype=tf.float32, shape=[1], name="v"))
从上面的代码中可以看出,通过tf.get_variable和tf.Variable函数创建变量的过程基本是相同的。tf.get_variable函数调用时提供的维度(shape)信息以及初始化方法(initializer)的参数和tf.Variable函数调用的提供的初始化过程中的参数也类似。
Tensorflow中提供的initializer函数和Tensorflow中的随机数以及常量生成函数大部分是一一对应的。
比如,在上述代码中使用的常数初始化函数tf.constant_initializer和常数生成函数tf.constant功能上是一致的。
下面是常用的变量初始化函数
初始化函数 | 功能 | 主要参数 |
---|---|---|
tf.constant_initializer | 将变量初始化为给定常量 | 常量的取值 |
tf.random_normal_initializer | 将变量初始化为满足正态分布的随机值 | 正态分布的均值和标准差 |
tf.truncated_normal_initializer | 将变量初始化为满足正态分布的随机值,但如果随机出来的值偏离平均值超过2个标准差,那么这个数将会被重新随机 | 正态分布的均值和标准差 |
tf.random_uniform_initializer | 将变量初始化为满足平均分布的随机值 | 最大,最小值 |
tf.uniform_unit_scaling_initializer | 将变量初始化为满足平均分布但不影响输出数量级的随机值 | factor(产生随机值是乘以的系数) |
tf.zeros_initializer | 将变量设置为全0 | 变量维度 |
tf.ones_initializer | 将变量设置为全1 | 变量维度 |
tf.get_variable和tf.Variable两个函数最大的区别在于指定变量名称的参数。对于tf.Variable函数,变量名称是一个可选的参数,通过name = “v” 的形式给出。但是对于tf.get_variable函数,变量名称是一个必填的参数。tf.get_variable会根据这个名字去创建或者获取变量。
在上面的样例代码中,tf.get_variable首先会去试图创建一个名字为v的变量,如果创建失败(比如已经有了同名的参数),那么这个程序就会报错。这是为了避免无意识的变量复用造成的错误。
比如在定义神经网络参数时,第一层网络的权重已经叫weights了,那么在创建第二层神经网络时,如果参数名称仍然叫weights,就会触发变量重用的错误。
如果需要通过tf.get_variable获取一个已经创建的变量,需要通过tf.variable_scope函数来生成一个上下文管理器,并且明确指定在这个上下文管理器中,tf.get_variable将直接获取已经生成的变量。
下面给出一段代码说明如何通过tf.variable_scope函数控制tf.get_variable函数获取已经创建过的变量。
import tensorflow as tf
import cv2
# Tensorflow默认会话
sess = tf.InteractiveSession()
# 在命名空间foo内创建名字为v的变量。
with tf.variable_scope("foo"):
v = tf.get_variable("v", [1], initializer=tf.constant_initializer(1.0))
# 将下面的两行代码注释去掉,运行程序,控制台会输出:
# ValueError: Variable foo/v already exists, disallowed. Did you mean to set reuse=True in VarScope? Originally defined at:
# ……
# v = tf.get_variable("v", [1], initializer=tf.constant_initializer(1.0))
#
# 因为在命名空间foo中已经存在名字为v变量,所以下面两行代码会报错。
# with tf.variable_scope("foo"):
# v = tf.get_variable("v", [1])
# 在生成上下文管理器时,将参数reuse设置为True,这样tf.get_variable函数将直接获取已经声明的变量
with tf.variable_scope("foo", reuse=True):
v1 = tf.get_variable("v", [1])
print(v1 == v) # 输出为True,表明v,v1代表的是相同的Tensorflow变量。
# 将下面两行代码的注释去掉,运行程序,控制台会输出:
# ValueError: Variable bar/v does not exist, or was not created with tf.get_variable(). Did you mean to set reuse=None in VarScope?
#
# 将参数reuse设置为True时,tf.variable_scope将只能获取已经创建过的变量。
# 以为命名空间bar中还没有创建变量v,所以代码会报错。
# with tf.variable_scope("bar", reuse=True):
# v = tf.get_variable("v", [1])
# 初始化全部全局变量
tf.global_variables_initializer().run()
程序运行结果:
True
上面的样例简单的说明了tf.variable_scope函数可以控制tf.get_variable函数的语义。
1、当tf.variable_scope函数使用参数reuse=Ture生成上下文管理器时,这个上下文管理器内所有的tf.get_variable函数会直接获取已经创建的变量,如果变量不存在,则tf.get_variable函数将报错。
2、当tf.variable_scope函数使用参数reuse=None或者reuse=False生成上下文管理器时,这个上下文管理器内所有的tf.get_variable函数会创建新的变量,如果同名的变量已经存在,则tf.get_variable函数将报错。
3、reuse参数默认为None。
Tensorflow中tf.variable_scope函数是可以嵌套的。下面是一个简单的例子,说明了当tf.variable_scope函数嵌套时,reuse的函数取值是如何确定的。
import tensorflow as tf
import cv2
# Tensorflow默认会话
sess = tf.InteractiveSession()
# 可以通过tf.get_variable_scope().reuse函数来获取当前上下文管理器中的reuse参数的取值。
with tf.variable_scope("root"):
# 下面的print语句将输出:0 False,即最外层的reuse是False。
print("0", tf.get_variable_scope().reuse)
# 新建一个上下文管理器,命名空间为foo,并指定reuse参数为True。
with tf.variable_scope("foo", reuse=True):
# 下面的print语句将输出:1 True。
print("1", tf.get_variable_scope().reuse)
# 新建一个上下文管理器bar,命名空间为bar。
# 这里未指定reuse参数,这是reuse会和外面一层保持一致。
with tf.variable_scope("bar"):
# 下面的print语句将输出:2 True。
print("2", tf.get_variable_scope().reuse)
# 退出reuse设置为True的上下文环境之后,reuse的取值又回到了False。
# 下面的print语句将输出:3 False。
print("3", tf.get_variable_scope().reuse)
# 初始化全部全局变量
tf.global_variables_initializer().run()
程序运行结果如下:
0 False
1 True
2 True
3 False
tf.variable_scope函数生成的上下文管理器也会创建一个Tensorflow中的命名空间,在命名空间内创建的变量名称都会带上这个命名空间名作为前缀。所以,tf.variable_scope函数除了可以控制tf.get_variable执行的功能之外,这个函数还提供了一种管理变量命名空间的方式。
下面的代码显示了如何通过tf.variable_scope来管理变量的名称。
import tensorflow as tf
import cv2
# Tensorflow默认会话
sess = tf.InteractiveSession()
# 无命名空间
v1 = tf.get_variable("v", [1])
# 下面的print将输出:v1 v:0.
# "v"是变量的名称,“:0”表示这个变量是生成变量这个运算的第一个结果。
print("v1", v1.name)
with tf.variable_scope("foo"):
v2 = tf.get_variable("v", [1])
# 下面的print将输出:v2 foo/v:0。
# 在tf.variable_scope中创建的变量,名称前面会加入命名空间的名称,并通过/来分分隔命名空间的名称和变量的名称。
print("v2", v2.name)
with tf.variable_scope("foo"):
with tf.variable_scope("bar"):
v3 = tf.get_variable("v", [1])
# 下面的print将输出:v3 foo/bar/v:0。
# 命名空间可以嵌套,同时变量的名称也会加入所有命名空间的名称作为前缀。
print("v3", v3.name)
v4 = tf.get_variable("v1", [1])
# 下面的print将输出:v4 foo/v1:0。
# 当命名空间退出后,变量名称也就不会再被加入其前缀。
print("v4", v4.name)
# 创建一个名称为空的命名空间,并设置reuse=True。
with tf.variable_scope("", reuse=True):
# 可以直接通过带有命名空间名称的变量名来获取其他命名空间下的变量。
# 比如这里通过指定名称foo/bar/v来获取在命名空间foo/bar中创建的变量。
v5 = tf.get_variable("foo/bar/v", [1])
# 下面的print将输出:v5 == v3 True
print("v5 == v3", v5 == v3)
v6 = tf.get_variable("foo/v1", [1])
# 下面的print将输出:v6 == v4 True
print("v6 == v4", v6 == v4)
# 初始化全部全局变量
tf.global_variables_initializer().run()
程序运行结果如下:
v1 v:0
v2 foo/v:0
v3 foo/bar/v:0
v4 foo/v1:0
v5 == v3 True
v6 == v4 True
通过使用tf.get_variable和tf.variable_scope来管理变量,尤其是当神经网络结构更加复杂,参数更多时,会使得程序的可读性大大调高。