Tensorflow学习笔记(2): 变量及共享变量

本文主要根据TensorFlow变量进行的翻译整理。

TensorFlow中变量主要用来表示机器学习模型中的参数,变量通过 tf.Variable 类进行操作。tf.Variable 表示张量,通过运行 op 可以改变它的值。与 tf.Tensor 对象不同,tf.Variable 存在于单个 session.run 调用的上下文之外。

在内部,tf.Variable 存储持久张量。具体 op 允许您读取和修改此张量的值。这些修改在多个 tf.Session 之间是可见的,因此对于一个 tf.Variable,多个工作器可以看到相同的值。

1. tf.Variable 创建变量

tf.Variable的初始化函数如下所示

__init__(
    initial_value=None,
    trainable=True,
    collections=None,
    validate_shape=True,
    caching_device=None,
    name=None,
    variable_def=None,
    dtype=None,
    expected_shape=None,
    import_scope=None,
    constraint=None
)

其中参数

  1. initial_value 表示初始化值,用Tensor表示
  2. trainable 表示变量是否被训练,如果被训练,将加入到tf.GraphKeys.TRAINABLE_VARIABLES集合中,TensorFlow将计算其梯度的变量
  3. collections 表示一个graph collections keys的集合,这个创建的变量将被添加到这些集合中,默认集合是[GraphKeys.GLOBAL_VARIABLES].
  4. name: 变量的命名,默认是’Variable’
  5. dtype 表示类型

例如我们创建一个变量,并且查看其name和shape

import tensorflow as tf
w1 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights")
b1 = tf.Variable(tf.zeros([200]),name="biases")

w2 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights") # 名称相同
b2 = tf.Variable(tf.zeros([200]),name="biases") # 名称相同

print w1.name, w1.shape
print b1.name, b1.shape
print w2.name, w2.shape
print b2.name, b2.shape

************************************输出**************************************
weights:0 (784, 200)
biases:0 (200,)
weights_1:0 (784, 200)
biases_1:0 (200,)

可以看到在命名的时候,如果指定的name重复,那么w2就会被命名为”name_1:0″ 这样累加下去。

2. 变量集合 collections

默认情况下,每个tf.Variable都放置在以下两个集合中:*tf.GraphKeys.GLOBAL_VARIABLES– 可以在多个设备共享的变量,*tf.GraphKeys.TRAINABLE_VARIABLES– TensorFlow 将计算其梯度的变量。

2.1 查看集合变量列表

要查看放置在某个集合中的所有变量的列表,可以采用如下方式

import tensorflow as tf
w1 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights")
b1 = tf.Variable(tf.zeros([200]),name="biases")

w2 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights") # 名称相同
b2 = tf.Variable(tf.zeros([200]),name="biases") # 名称相同

print tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)

************************************输出**************************************
[<tf.Variable 'weights:0' shape=(784, 200) dtype=float32_ref>, <tf.Variable 'biases:0' shape=(200,) dtype=float32_ref>, <tf.Variable 'weights_1:0' shape=(784, 200) dtype=float32_ref>, <tf.Variable 'biases_1:0' shape=(200,) dtype=float32_ref>]

可以看到输出结果是所有变量的列表

2.2 创建变量集合

如果您不希望变量被训练,可以将其添加到 tf.GraphKeys.LOCAL_VARIABLES 集合中。例如,以下代码段展示了如何将名为 my_local 的变量添加到此集合中:

my_local = tf.get_variable("my_local", shape=(),
collections=[tf.GraphKeys.LOCAL_VARIABLES])

或者,您可以指定 trainable=Falsetf.get_variable 的参数:

my_non_trainable = tf.get_variable("my_non_trainable",
                                   shape=(),
                                   trainable=False)

我们测试效果如下所示,可以看到b2的trainable=False,那么输出collection没有b2

import tensorflow as tf
w1 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights")
b1 = tf.Variable(tf.zeros([200]),name="biases")

w2 = tf.Variable(tf.random_normal([784,200], stddev = 0.35), name="weights") # 名称相同
b2 = tf.Variable(tf.zeros([200]),name="biases", trainable=False) # 名称相同

print tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)

************************************输出**************************************
[<tf.Variable 'weights:0' shape=(784, 200) dtype=float32_ref>, <tf.Variable 'biases:0' shape=(200,) dtype=float32_ref>, <tf.Variable 'weights_1:0' shape=(784, 200) dtype=float32_ref>]

您也可以使用自己的集合。集合名称可为任何字符串,且您无需显式创建集合。创建变量(或任何其他对象)后,要将其添加到集合,请调用 tf.add_to_collection。例如,以下代码将名为 my_local 的现有变量添加到名为 my_collection_name 的集合中:

tf.add_to_collection("my_collection_name", my_local)

3. 共享变量

我们查看下面的代码,表示一个卷积神经网络,其中包括conv1_weights, conv1_biases, conv2_weights, conv2_biases四个参数,也就是4个变量

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)

假设我们利用这个函数对两张图片进行相同的操作,也就是调用两次,那么每次都会创建4个变量,假设我们在函数内对变量进行了优化求解,那么每次都会重新创建变量,这样就无法复用参数,导致训练过程无效

# 第一次执行方法创建4个变量
result1 = my_image_filter(image1)
# 第二次执行再创建4个变量
result2 = my_image_filter(image2)

ValueError: Variable weight already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope? Originally defined at:

TensowFlow通过变量范围(variable scope)和tf.get_variable方法解决了共享变量(参数)的问题。

3.1 tf.variable_scope和tf.get_variable

tf.Variable()方法每次被调用都会创建新的变量,这样就无法解决共享变量的问题,而tf.get_variable结合作用域即可表明我们是想创建新的变量,还是共享变量,变量作用域允许在调用隐式创建和使用变量的函数时控制变量重用。作用域还允许您以分层和可理解的方式命名变量。tf.get_variable()的机制跟tf.Variable()有很大不同,如果指定的变量名已经存在(即先前已经用同一个变量名通过get_variable()函数实例化了变量),那么get_variable()只会返回之前的变量,否则才创造新的变量。我们举例进行说明。

例如上面的例子中有两个卷积层,我们先来编写一个函数创建一个卷积/relu层,这个函数使命的变量名称是’weights’和’biases’

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_initializer(0.0))
    conv = tf.nn.conv2d(input, weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv + biases)

在真实模型中需要多个卷积层,我们通过变量域来区分不同层的变量,不同的变量域下的变量名车为:scope_name/variable_name, 如下所示,第一个卷积层的变量名称是’conv1/weights’, ‘conv1/biases’, 第二个卷积层的变量名称是 ‘conv2/weights’, ‘conv2/biases’。

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])

但即便这样,如果多次调用该函数,也会抛出异常,

result1 = my_image_filter(image1)
result2 = my_image_filter(image2)
# Raises ValueError(... conv1/weights already exists ...)

因为用get_variable()创建两个相同名字的变量是会报错的,默认上它只是检查变量名,防止重复,如果要变量共享,就需要指定在哪个域名内可以共享变量。

开启共享变量有两种方式

方法1

采用scope.reuse_variables()触发重用变量,如下所示

with tf.variable_scope("model") as scope:
  output1 = my_image_filter(input1)
  scope.reuse_variables()
  output2 = my_image_filter(input2)

方法2

使用reuse=True 创建具有相同名称的作用域

with tf.variable_scope("model"):
  output1 = my_image_filter(input1)
with tf.variable_scope("model", reuse=True):
  output2 = my_image_filter(input2)

3.2 理解variable_scope

理解变量域的工作机理非常重要,我们对其进行梳理,当我们调用tf.get_variable(name, shape, dtype, initializer)时,这背后到底做了什么

首先,TensorFlow 会判断是否要共享变量,也就是判断 tf.get_variable_scope().reuse 的值,如果结果为 False(即你没有在变量域内调用scope.reuse_variables()),那么 TensorFlow 认为你是要初始化一个新的变量,紧接着它会判断这个命名的变量是否存在。如果存在,会抛出 ValueError 异常,否则,就根据 initializer 初始化变量:

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])
assert v.name == "foo/v:0"

而如果 tf.get_variable_scope().reuse == True,那么 TensorFlow 会执行相反的动作,就是到程序里面寻找变量名为 scope name + name 的变量,如果变量不存在,会抛出 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 is v

变量域可以多层重叠,例如,下面的变量上有两层的变量域,那么变量名是’foo/var/v:0′

with tf.variable_scope("foo"):
    with tf.variable_scope("bar"):
        v = tf.get_variable("v", [1])
assert v.name == "foo/bar/v:0"

在同一个变量域中,如果需要调用同名变量,那么需要重用变量即可,例如v1和v两个变量时相同的,因为变量名都是’foo/v’

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 is v

总结

  1. tf.get_variable()默认上它只检查变量名,如果变量名重复,那么就会报错;tf.Variable()每次被调用都创建相应的变量,即便变量名重复,也会创建新的变量,因此无法共享变量名。
  2. 如果scope中开启共享变量,那么调用tf.get_variable()就会查找相同变量名的变量,如果有,就直接返回该变量,如果没有,就创建一个新的变量;
  3. 如果scope没有开启共享变量(默认模式),那么 调用tf.get_variable()发现已有相同变量名的变量,就会报错,如果没有,就创建一个新的变量。
  4. 要重用变量,需要在scope中开启共享变量,有两种方法,推荐第一种
    原文作者:习翔宇
    原文地址: https://zhuanlan.zhihu.com/p/37922147
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞