博客TensorFlow基础知识:计算图中的Op,边,和张量中有一句话,对于我来说是醍醐灌顶,一下子明白了TensorFlow的计算模式
计算图的定义和图的运算是分开的.tensorflow是一个符号主义的库.编程模式分为两类,命令式(imperative style)和符号式(symbolic style).命令式的程序很容易理解和调试,它按照原有的逻辑运行.符号式则相反,在现有的深度学习框架中,torch是命令式的,Caffe,Mxnet是两种模式的混合,tensorflow完全采用符号式.符号式编程,一般先定义各种变量,然后建立一个计算图(数据流图),计算图指定了各个变量之间的计算关系,此时对计算图进行编译,没有任何输出,计算图还是一个空壳,只有把需要运算的输入放入后,才会在模型中形成数据流.形成输出.
就是说TensorFlow是一个符号式编程模型,图上最关键的两个东西,一个数据相关的概念Tensor(张量),指的是对数据的一种引用,另一个是图上的概念Operation(操作),也就是计算图中的节点,在写TensorFlow程序的时候,我们就是在Graph上布置节点。
如下代码可以分为几个步骤
- 刚开始的时候,想象现在graph上一个op都没有;
- 然后随着代码从上往下一直到
with tf.Session() as sess:
这行,我在图上一共放了6个op,不同的op的之间是有联系的,比如matmul
这个op需要“吃掉”一个x
和一个weights
,然后“吐出”一个东西叫mat_res
,这个mat_res
又会被喂给tf.add
这个op。这里一个隐含的信息是:op是从上到下依次被定义的(通俗点说,就是依次将各个op扔到了graph上) - 但是当打印mat_res的时候,返回的结果告诉我们,mat_res是一个Tensor,也就是说,个人理解,从数据的角度看,mat_res这个Tensor代表着matmul这个op的输出。
import tensorflow as tf
x = [[1.,2.],[-1.,3.]]
y = [1,0]
weights = tf.Variable([[1,-1],[-1,-1]],name='weights',dtype=tf.float32) #虽然是定义了一个Variable类,但是也相当于在设置了一个op
bias = tf.get_variable('bias',shape=[2],initializer=tf.zeros_initializer) #get_variable也是一个op
mat_res = tf.matmul(x,weights) #定义矩阵乘法计算,也是一个op,目前图上有3个op
print(mat_res) #Tensor("MatMul:0", shape=(2, 2), dtype=float32)
add_res = tf.add(mat_res,bias) #定义矩阵相加计算,也是一个op,目前图上有4个op
y_ = tf.nn.sigmoid(add_res) #定义sigmoid运算,也是一个op,目前图上有5个op
loss = tf.losses.sparse_softmax_cross_entropy(labels=y,logits=y_) #定义sigmoid运算,也是一个op,目前图上有6个op
# 以上就是在图上放op,一共6个op
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(weights))
print(sess.run([loss]))
再看下面的代码
我定义了两个cross_entropy,获得losses1的时候,此时图上只有一个cross_entropy1;在获得losses2 的时候,此时图上有cross_entropy1和cross_entropy2;说明通过get_total_losses
获取的损失,是此时的graph上的所有损失
def test_loss():
cross_entropy1 = tf.losses.sparse_softmax_cross_entropy(labels = [1,0,1],logits=[[1/2,1/2],[1/2,1/2],[0.9,1/3]])
losses1 = tf.losses.get_losses()
#[<tf.Tensor 'sparse_softmax_cross_entropy_loss/value:0' shape=() dtype=float32>]
total_loss1 = tf.losses.get_total_loss()
#Tensor("total_loss:0", shape=(), dtype=float32)
cross_entropy2 = tf.losses.sparse_softmax_cross_entropy(labels = [1,0,1],logits=[[1/2,1/2],[1/2,1/2],[0.9,1/3]])
losses2 = tf.losses.get_losses()
#[<tf.Tensor 'sparse_softmax_cross_entropy_loss/value:0' shape=() dtype=float32>, <tf.Tensor 'sparse_softmax_cross_entropy_loss_1/value:0' shape=() dtype=float32>]
total_loss2=tf.losses.get_total_loss()
#Tensor("total_loss_1:0", shape=(), dtype=float32)
with tf.Session() as sess:
print(losses1)
print(total_loss1)
print(losses2)
print(total_loss2)
print(sess.run(cross_entropy1))
print(sess.run(cross_entropy2))
进一步理解Graph和op,下面的代码里我定义了好多好多op
- 先看两个
add_res
,代码里我定义了两个add_res
,他们都叫add_res
,但是打印出来的结果告诉我,第一个add_res
是一个名字叫做Add:0
的张量,这时候graph上只有一个tf.add
的op,所以这个Add:0
就代表着这个op的输出;而第二个add_res
是一个名字叫做Add_1:0
的张量,可以看到它的名字后面多了一个_1
,说明它和第一个add_res
不是一个东西,它是由于我创建了第二个tf.add
,因此现在有两个相加的op,它作为第二个相加的op的输出。 - 要理解图上op构建的顺序,就是按照代码执行的顺序从前往后来的,举个例子,当我获得
loss1
的时候,当前的图上又会被创建一个op,这个op就是通过sparse_softmax_cross_entropy
来创建的,它的输出就是loss1
;而get_total_loss
又会创建一个op,这个op的作用是,获取当前图内全部的损失函数的和,而目前的损失只有一个loss1
,所以get_total_loss
这个op的输出的tensor的值等于loss1
的值,但他们也仅仅是值相等,get_total_loss
这个op的输出的tensor不等于loss1
,他们仍然是两个tensor,你可以打印一下,他们的名字不同。(可以理解为定义了一个op,这个op的作用就是读取并输出loss1
的结果) -
val_loss
的生成是因为我又定义了一个sparse_softmax_cross_entropy
这个op,val_loss
作为这个op的输出,然后我再次get_total_loss
获取全部的loss的和,叫做all_loss2
。那么问题来了,现在我有两个loss了,all_loss1
的值等于loss1
的值,还是loss1
的值和val_loss
的值的和?all_loss2
又是什么?答案是,all_loss1
的值还等于loss1
的值,all_loss2
的值等于loss1
的值和val_loss
的值的和。这是因为all_loss1
是第一个get_total_loss
这个op的输出,这个op在被丢到graph上的时候,此时graph上只有一个loss相关的op,因此他只能获取了一个loss,还记得最开始说的么,这个get_total_loss
的op会以这唯一的一个loss为输入,然后输出一个tensor,也就是all_lose1
。然后后来又定义了一个loss,图上现在有两个代表loss的op了,那么第二个all_loss2
在定义的时候,相当于去找这两个loss的op的输出,然后求和,作为这第二个的get_total_loss
的输出。
想这个问题的时候,请想象一个图,然后在上面扔op
tf.reset_default_graph()
x = [[1.,2.],[-1.,3.]]
y = [1,0]
weights = tf.Variable([[1,-1],[-1,-1]],name='weights',dtype=tf.float32) #虽然是定义了一个Variable类,但是也相当于在设置了一个op
bias = tf.get_variable('bias',shape=[2],initializer=tf.zeros_initializer) #get_variable也是一个op
mat_res = tf.matmul(x,weights) #定义矩阵乘法计算,也是一个op,目前图上有3个op
add_res = tf.add(mat_res,bias) #定义矩阵相加计算,也是一个op,目前图上有4个op
print(add_res) #Tensor("Add:0", shape=(2, 2), dtype=float32)
y_ = tf.nn.sigmoid(add_res) #定义sigmoid运算,也是一个op,目前图上有5个op
loss1 = tf.losses.sparse_softmax_cross_entropy(labels=y,logits=y_) #定义sigmoid运算,也是一个op,目前图上有6个op
all_loss1 = tf.losses.get_total_loss() #获取全部损失,也是一个op,目前图上有7个op
print(all_loss1) #Tensor("total_loss:0", shape=(), dtype=float32)
val_x = [[4.,-2.]]
val_y = [0]
mat_res = tf.matmul(val_x,weights) #定义矩阵乘法计算,也是一个op,目前图上有8个op
add_res = tf.add(mat_res,bias) #定义矩阵相加计算,也是一个op,目前图上有9个op
print(add_res) #Tensor("Add_1:0", shape=(1, 2), dtype=float32)
y_ = tf.nn.sigmoid(add_res) #定义sigmoid运算,也是一个op,目前图上有10个op
val_loss = tf.losses.sparse_softmax_cross_entropy(labels=val_y,logits=y_) #定义sigmoid运算,也是一个op,目前图上有11个op
all_loss2 = tf.losses.get_total_loss() #第二次获取全部损失,也是一个op,目前图上有12个op
print(all_loss2) #Tensor("total_loss_1:0", shape=(), dtype=float32)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run([loss1,all_loss1,val_loss,all_loss2]))
补充:张量、op、variable的关系
看下面的代码,通过前三个打印可以看出,lr
和assign
都是Tensor
,前面说过,Tensor
是对数据的引用,那是对什么数据的引用呢?
以lr
为例,他是对exponential_decay_learning_rate:0
的引用,exponential_decay_learning_rate
就是一个op,也就是一个操作,那么lr
实际上指的是,exponential_decay_learning_rate
这个op的第0个输出
而从print(tf.global_variables())
可以看出,lr
并不是一个variable
,他只是某个操作的输出。
同理看assign
,他也是一个Tensor
下面的print(sess.run(assign))
执行,做的实际上就是,执行Assign
这个op,然后获取它的第0个输出。
def test_lr():
global_step = tf.train.get_or_create_global_step()
assign = tf.assign(global_step, 10)
lr = tf.train.exponential_decay(0.001, global_step, 500, 0.98, staircase=False,
name='exponential_decay_learning_rate')
saver = tf.train.Saver()
print(lr) # Tensor("exponential_decay_learning_rate:0", shape=(), dtype=float32)
print(assign) # Tensor("Assign:0", shape=(), dtype=int64_ref)
print(tf.global_variables()) # [<tf.Variable 'global_step:0' shape=() dtype=int64_ref>]
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(assign)) # 10
print(sess.run(global_step)) # 10
print(sess.run(lr)) # 0.0009995961
saver.save(sess, 'test/test', global_step=global_step)
如果我们把刚刚存好的ckpt重新读取进来,会发现global_step被设置为10了,这没问题,但lr是0.004997,这是因为lr并不是一个variable,存储的时候只会存储variable,并不会把lr存进去,他只是个tensor。就像刚刚说的,run一个tensor的时候,做的就是执行op然后返回值,执行的时候用到了global_step,然后根据此时的参数计算lr返回(0.005)。
(另,其实tensor与variable的区分并不明确,简单理解的话,variable就是变量,比如系数啊bias之类的,tensor就是某些op的输出)
def load_lr():
global_step = tf.train.get_or_create_global_step()
lr = tf.train.exponential_decay(0.005, global_step, 500, 0.98, staircase=False,
name='exponential_decay_learning_rate')
saver = tf.train.Saver()
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
saver.restore(sess, 'test/test-10')
print(sess.run(global_step)) # 10
print(sess.run(lr)) # 0.00499798
总结
所以看到这里可以再深入想想,执行sess.run()的时候,tensorflow到底帮我们干了什么?对,它帮我们做的事情就是让tensor去flow,就比如我在执行sess.run(lr)
的时候,我就是想获取lr
这个tensor的值,那怎么才能获取他的值呢,tensorflow做了下面几件事,让global_step作为输入,喂给exponential_decay
这个op,然后让这个op去执行和计算,并且将真实的值返回出来。