tensorflow get_variabe()和scope
1. tf.Variable()和tf.get_variable()的区别
TensorFlow的文档里推荐尽量使用tf.get_variable()。tf.Variable()必须提供一个初始值,这一般通过numpy(或者Python)的随机函数来生成,初始值在传入的时候已经确定(但还没有真正初始化变量,还需要用session.run来初始化变量)。而tf.get_variable()一般通过initializer来进行初始化,这是一个函数,它可以更加灵活的根据网络的结构来初始化。比如我们调用tf.get_variable()时不提供initializer,那么默认会使用tf.glorot_uniform_initializer,它会根据输入神经元的个数和输出神经元的个数来进行合适的初始化,从而使得模型更加容易收敛。
另外一个重要的区别就是tf.Variable()总是会(成功的)创建一个变量,如果我们提供的名字重复,那么它会自动的在后面加上下划线和一个数字,比如:
from __future__ import print_function from __future__ import division import tensorflow as tf x1 = tf.Variable(1, name="x") x2 = tf.Variable(2, name="x") print(x1) print(x2) # tf.Variable会自动生成名字 x3 = tf.Variable(3) print(x3) 输出的结果为
<tf.Variable 'x_2:0' shape=() dtype=int32_ref> <tf.Variable 'x_3:0' shape=() dtype=int32_ref> <tf.Variable 'Variable:0' shape=() dtype=int32_ref> 而tf.get_variable()一定需要提供变量名(tf.Variable()可以不提供name,系统会自动生成),而且它检测变量是否存在,默认情况下已经存在会抛出异常。因此tf.get_variable()强制我们为不同的变量提供不同的名字,从而让Graph更加清晰。除此之外,tf.get_variable()可以让我们更加容易的复用变量。
如果一个变量由tf.Variable()得到,那么共享(复用)它的唯一办法就是把这个变量传给需要用到的地方。这种方法看起来很简单自然,但是在实际的应用中却很麻烦。原因之一就是tf.layers里的layer(包括我们自己封装的类似的类)隐藏了很多细节,它创建的变量都是对象的成员变量,使用它的人甚至不知道它创建了哪些变量。比如tf.layers.Dense创建了一个矩阵kernle和一个bias(可选),假设我们想让两个tf.layers.Dense的参数实现共享,那么tf.layers.Dense必须把所有的参数都对外暴露,而且我们在构造第二个tf.layers.Dense时需要传入。这就要求使用tf.layers.Dense的人了解代码的细节,这就破坏了封装的要求。比如哪天tf.layers.Dense的实现发生了改变,我们不用两个参数kernel和bias,而是把这两个参数合并为一个大的矩阵,那么所有用到它的地方都需要修改,这就非常麻烦。
因此TensorFlow通过get_variable()提供了另外一种通过名字来共享变量的方式,它需要和variable_scope配合。我们可以通过tf.variable_scope构造一个variable scope,然后通过variable scope来共享变量:
import tensorflow as tf with tf.variable_scope("myscope") as scope: x = tf.get_variable(name="x", dtype=tf.int32, initializer=tf.constant([1, 2, 3])) print(x) with tf.variable_scope("myscope", reuse=True) as scope: # 注意要加上dtype=tf.int32 x = tf.get_variable(name="x", dtype=tf.int32) # 我们可以不提供initializer # x = tf.get_variable(name="x", dtype=tf.int32) print(x) 代码的输出是:
<tf.Variable 'myscope/x:0' shape=(3,) dtype=int32_ref> <tf.Variable 'myscope/x:0' shape=(3,) dtype=int32_ref> 也就是说第一次创建的时候with tf.variable_scope(“myscope”) as scope:,参数中不需要加reuse = True,而在第二次使用的时候,需要加上,表示复用该变量(注意,数据类型必须指定,否则可能会报错)。
我们首先在名字为myscope的variable scope里创建变量x,打印它的名字发现Tensorflow自动在名字x前加上了scope的名字和/作为前缀。接着我们又在一个新的名字叫myscope的scope里创建变量x,需要注意的是这次调用tf.variable_scope是我们传入了参数reuse=True,如果不传这个参数,那么执行第二次get_variable会抛出异常,因为默认情况下变量是不共享的。另外一个需要注意的地方是我们在第二次调用tf.get_variable时传入了参数dtype=tf.int32,如果不传会怎么样呢?我们修改后运行一下会得到如下异常:
ValueError: Trying to share variable myscope/x, but specified dtype float32 and found dtype int32_ref. 为什么这样呢?因为我们第一次创建变量时传入了初始值[1,2,3],TensorFlow会推测我们的变量的dtype是tf.int32,第二次是重用变量,它会忽略我们传入的initializer,这个时候它会任务dtype是默认的tf.float32,而之前我们创建的变量是tf.int32,这就发生运行时异常了。
除了在tf.variable_scope传入reuse=True,我们还可以用下面的方式来更加细粒度的控制变量共享:
with tf.variable_scope("myscope") as scope: x = tf.get_variable(name="x", initializer=tf.constant([1, 2, 3])) y = tf.get_variable(name="y1", initializer=tf.constant(1)) print(x) print(y) with tf.variable_scope("myscope") as scope: # y不共享 y = tf.get_variable(name="y2", initializer=tf.constant(2)) scope.reuse_variables() x = tf.get_variable(name="x", dtype=tf.int32) print(x) print(y) 它的输出是:
<tf.Variable 'myscope/x:0' shape=(3,) dtype=int32_ref> <tf.Variable 'myscope/y1:0' shape=() dtype=int32_ref> <tf.Variable 'myscope/x:0' shape=(3,) dtype=int32_ref> <tf.Variable 'myscope/y2:0' shape=() dtype=int32_ref> 注意scope.reuse_variables()一旦调用后,这个scope里的变量都是共享的,TensorFlow没有一个函数能够把它改成不共享的。因此我们首先需要把不共享的变量创建好,然后调用scope.reuse_variables(),接着再使用共享的变量。
另外需要注意的是:虽然我们示例代码的两个with tf.variable_scope(“myscope”) as scope隔得很近,但是在实际代码中这两行代码可以隔得很远,甚至不在一个文件里,因此这种方式的共享变量会很方便。
tf.layers里的layer都是通过这种方式来共享变量的,比如我们想共享两个Dense:
x1 = tf.placeholder(dtype=tf.float32, shape=[None, 3], name="x1") x2 = tf.placeholder(dtype=tf.float32, shape=[None, 3], name="x2") with tf.variable_scope("myscope") as scope: l1 = tf.layers.Dense(units=2) h11 = l1(x1) with tf.variable_scope("myscope", reuse=True) as scope: l2 = tf.layers.Dense(units=2) h12 = l2(x2) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) print(sess.run([h11, h12], feed_dict={x1: [[1, 2, 3]], x2: [[2, 4, 6]]})) 2. name scope和variable scope
除了variable scope,TensorFlow还有一个name scope。variable scope的目的是为了共享变量,而name scope的目的是为了便于组织变量的namespace。下面我们通过一个例子来比较这两个scope。
with tf.name_scope("my_scope"): v1 = tf.get_variable("var1", [1], dtype=tf.float32) v2 = tf.Variable(1, name="var2", dtype=tf.float32) a = tf.add(v1, v2) print(v1.name) # var1:0 print(v2.name) # my_scope/var2:0 print(a.name) # my_scope/Add:0 with tf.variable_scope("my_scope"): v1 = tf.get_variable("var1", [1], dtype=tf.float32) v2 = tf.Variable(1, name="var2", dtype=tf.float32) a = tf.add(v1, v2) print(v1.name) # my_scope/var1:0 print(v2.name) # my_scope_1/var2:0 print(a.name) # my_scope_1/Add:0 第一个scope是name_scope,在这里面通过tf.Variable()或者其它函数产生的变量的名字都会加上scope的名字为前缀,但是tf.get_variable()创建的变量会忽略name scope。因此v1的名字没有my_scope作为前缀。
第二个scope是variable_scope,所有的变量都会加上scope的名字。值得注意的是:name scope和variable scope可以同名。对于同名的scope,tf.Variable()或者其它函数产生的变量会自动给第二个scope加上数字的后缀以避免重名。但是对于tf.get_variable(),加的一定是scope的名字(而不会加后缀),如果有重名的而又不是reuse=True,那么就会抛异常。上面的例子中两个get_variable都是使用名字”var1”,但是第一个在name scope里,所以它的名字叫”var1:0”,而第二个在variable scope里,所以名字叫”my_scope/var1:0”。它们的名字其实是不同的。
总结一下:tf.get_varialbe()只会使用variable_scope而且变量名一定是scope名/变量名。而tf.Variable()会同时使用name_scope和variable_scope,并且在重名的时候通过后缀避免重名。而如果两个scope重名的时候(不管是name scope和variable scope重名还是两个name scope重名),tf.Variable()发现重名变量时会给scope name加后缀(而不是给变量名加后缀)。这一点也可以从下面这个例子验证:
with tf.name_scope("my_scope"): v2 = tf.Variable(1, name="var2", dtype=tf.float32) print(v2.name) with tf.name_scope("my_scope"): v2 = tf.Variable(1, name="var2", dtype=tf.float32) print(v2.name) 它的运行结果是:
my_scope/var2:0 my_scope_1/var2:0 3. 简单线性回归
3.1 导库
import tensorflow as tf import numpy as np import matplotlib.pyplot as plt 3.2 生成数据
# 生成200个随机点,并改变其形状为200*1 x_data = np.linspace(-0.5, 0.5, 200)[:,np.newaxis] noise = np.random.normal(0,0.02,x_data.shape) y_data = np.square(x_data) + noise #查看一下数据形状 print(x_data.shape) type(x_data) print(noise.shape) 3.3 正向传播
x = tf.placeholder(tf.float32, [None, 1]) y = tf.placeholder(tf.float32, [None, 1]) with tf.name_scope("dnn"): w1 = tf.get_variable(name = 'w1', shape = [1, 10], initializer=tf.contrib.layers.xavier_initializer()) b1 = tf.get_variable(name = 'b1', shape = [10], initializer=tf.contrib.layers.xavier_initializer()) h1 = tf.nn.bias_add(tf.matmul(x, w1), b1) output1 = tf.nn.tanh(h1) w2 = tf.get_variable(name = 'w2', shape = [10, 1], initializer=tf.contrib.layers.xavier_initializer()) b2 = tf.get_variable(name = 'b2', shape = [1], initializer=tf.contrib.layers.xavier_initializer()) h2 = tf.nn.bias_add(tf.matmul(output1, w2), b2) prediction = tf.nn.tanh(h2) 3.4 训练网络
loss = tf.reduce_mean(tf.square(y - prediction)) # 梯度下降法 train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss) with tf.Session() as sess: # 变量的初始化 sess.run(tf.global_variables_initializer()) for _ in range(20000): sess.run(train_step, feed_dict={x:x_data, y:y_data}) # 获得预测值 prediction_value = sess.run(prediction, feed_dict={x:x_data}) plt.figure() plt.scatter(x_data, y_data) plt.plot(x_data, prediction_value,'r-', lw=5) plt.show()