tensorflow 1.15 中有2种方法可以定义变量
tf.Variable()
tf.get_variable()
具体用法不在赘述。他们的主要区别在于对于vriable重名的处理,
如果tf.Variable() 遇到重名变量,则会在原来的变量名后面加上_1,_2......而get_vairable如果定义到重名变量,则会报错。
无论是在后面加上后缀还是报错,我们都可以看出来,tensorflow 1.15 绝不允许出现重名变量,这是为什么呢?
这是因为在tensorflow1.15 的执行和普通python程序的执行方式不一样。python程序执行的单位是代码,python会依次执行每一行代码。tensorflow的执行单位是节点,tensorflow 利用python首先打造出自己的计算图,计算图由节点和节点之间的连线组成。每个节点是一个运算或者一个操作。我们定义的一个tensor ,例如tf.constant([1,1,1,1]) ,tf.ones_like(tensor) ;定义一个变量 aaa = tf.variable(tf.contant([1,1,1,1])); 或者tensor的相加相乘等运算,都是计算图中的一个节点。节点之间的线表示数据的依赖,表示有数据从一个节点流向下一个节点。
tensorflow的执行单位不是一行代码,而是计算图中的节点。我们可以创造一个会话session,来执行任意一个节点。如果节点aaa是我们要执行的节点,并且aaa并不依赖bbb节点,那么bbb节点就不会执行,即使bbb节点会修改a的值。例如
aaa = tf.variable(tf.contant([1,1,1,1]))
assign_opt = tf.assign(aaa, tf.contant([2,2,2,2]))
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(aaa))
结果:
[1,1,1,1]
上面的assign_opt节点会接收aaa的tensor值,然后修改aaa的值。但是aaa 并不依赖assign_opt,所以执行aaa节点的时候,assign_opt根本不会执行。
通过上面的例子我们可以看出来,tensorflow的更像是在python的基础上,建立了一套自己的系统,这套系统最最重要的概念就是节点(运算)。
明白这一点就可以考虑,为什么tensorflow 1.15 绝不允许出现重名变量,因为每次创建一个新的变量都是创建一个节点,而节点是tensorflow中的基本操作单位,如果出现重名的节点,就会导致数据依赖的混乱。
如果我们再进一步思考,就会发现,我们经常在各种地方创造variable,
例如我们创造一个全部连接层:
def weights_and_biases(layer1_units, layer2_units, l2=True, zero_init=False):
init = tf.truncated_normal([layer1_units, layer2_units], stddev=1.0 / math.sqrt(float(layer1_units)),
dtype=tf.float32, seed=100007)
if zero_init:
init = tf.zeros([layer1_units, layer2_units])
if l2:
weights = tf.get_variable(name='weights', initializer=init, regularizer=tf.nn.l2_loss)
else:
weights = tf.get_variable(name='weights', initializer=init)
# self.variable_summaries(weights)
biases = tf.get_variable(name='biases', shape=[layer2_units], initializer=tf.zeros_initializer,
dtype=tf.float32)
return weights, biases
def full_connect_layer(inputs, layer2_units):
layer1_units = inputs.get_shape().as_list()[-1]
weights, biases = weights_and_biases(layer1_units, layer2_units)
return tf.matmul(inputs, weights) + biases
然后我们会在所有需要全连接的地方不厌其烦的调用。这个全部连接层只是返回了一个全连接的运算结果,我们创建的变量全部都没有返回,但是我们仍然可以训练他们。所以说,使用tensorflow创建节点并不依赖python的基础语法,无论是在子函数中,还是在main 中,只要我们创建了节点,这个节点就会被添加到计算图中。
variable_scope
同样的是刚才的全连接层,如果我们有2个全连接层,分别是512×256,256×128。那么我们创建第一个全连接层的时候创了一个名为weight的变量,在创建第二个全连接层的时候会再次创建一个名为weight的变量。两次的变量都是通过get_variable创建,这就会导致变量重名报错。
这时候就需要variable_scope。
如果我们通过variable_scope 创建variable,就可以把variable_scope的名字嵌入到variable的前面:
with tf.variable_scope("scope1"):
ccc = tf.get_variable("var1",[5, 5],initializer=tf.random_normal_initializer())
with tf.variable_scope("scope2"):
ddd = tf.get_variable("var1",[5, 5],initializer=tf.random_normal_initializer())
结果:
ccc:
ddd:
回到刚才的问题,只要在创建每个全连接层的之前,定义一个variable_scope,就可以规避刚才的问题
当然variable_scope 是可以重用的:
with tf.variable_scope("scope1"):
ccc = tf.get_variable("var1",[5, 5],initializer=tf.random_normal_initializer())
with tf.variable_scope("scope2"):
ddd = tf.get_variable("var1",[5, 5],initializer=tf.random_normal_initializer())
with tf.variable_scope("scope1"):
eee = tf.get_variable("var1",[5, 5],initializer=tf.random_normal_initializer())
结果:ccc 和 eee都在scope1中定义了var1 变量,所以会报错
共享变量:
那么如果已经定义了一个variable,后来我们想要在用一次,该怎么办呢?比如说,我们现在要给一个人李明介绍对象,李明的年龄是一个sparse 特征,我们给年龄定义了一个embedding矩阵,是一个名为embedding_age的variable
with variable_scope("age):
initializer = tf.truncated_normal_initializer(stddev=0)
embedding_age = tf.get_variable("embedding_age",[100,8],initializer=initializer)
现在有5个候选女生,我们要预测李明可能会对哪个女生最喜欢,女生的年龄也是一个sparse特征,我们自然希望女生的年龄embedding矩阵和李明的是一样的。这时候,我们可以通过共享变量来实现,当我们需要重复使用一个已经定义的变量的时候,设置一下reuse 参数即可。刚才的例子
with tf.variable_scope("age"):
embedding_age1 = tf.get_variable("embedding_age",initializer=[2.,2.])
with tf.variable_scope("age",reuse=tf.AUTO_REUSE):
embedding_age2 = tf.get_variable("embedding_age",initializer=[1.,1.])
print(id(embedding_age1))
print(id(embedding_age2))
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(embedding_age1))
print(sess.run(embedding_age2))
结果:
2270755385920
2270755385920
[2. 2.]
[2. 2.]
从这段代码我们有2个结论,1:共享变量是参数引用,最终都指向同一个内存
2:共享变量只有第一次定义变量的初始化会生效,后面的初始化不会生效。
通过名称获取变量
有时候变量的创建时隐式的,例如调用各种直接创造一层的layers.dense 等等。如果我们想要看这些隐式变量,有时候需要从名字来获取变量。
第一种方法:
import tensorflow as tf
x = tf.Variable(1,name='x')
y = tf.get_variable(name='y',shape=[1,2])
for var in tf.global_variables():
if var.name == 'x:0':
print(var)
用这种方法获取的就是variable本身,var和x 以及 y的内存相同
第二种方法:
import tensorflow as tf
x = tf.Variable(1,name='x')
y = tf.get_variable(name='y',shape=[1,2])
graph = tf.get_default_graph()
x1 = graph.get_tensor_by_name("x:0")
y1 = graph.get_tensor_by_name("y:0")
这种方法得到的是一个tensor,也就是说取到了variable的值,但是如果variable迭代了,得到的值就不对了。
第三种方法:如果只是想看看变量的值,那么可以用第三种方法:
sess.run("var:0") var:0 是变量名