上一篇文章学习数据流图的基础知识时,使用简单的标量值时很好的选择。我们掌握好了数据流图之后,下面不妨熟悉一下张量的概念。
所谓张量,就是n维矩阵的抽象。因此一维的张量等价于一维的矩阵,二维的张量等价于二维矩阵。如果用张量来描述上一篇文章中的数据流图(下图一),可以用下面图二的方式实现。
现在不再使用两个独立的输入节点了,而是换成了一个可接收向量(或者一阶张量)的节点。与标量的版本相比,这个新的数据流图有如下优点:
输入节点只有一个,简化了输入。
节点b和c都只需要依赖一个节点a即可,减少了依赖节点数量。
最重要的一点,这个数据流图可以接收任意长度的向量,灵活性大大增强。
数据流图可以这样实现:
import tensorflow as tf
a=tf.constant([5,3])
b=tf.reduce_prod(a)
c=tf.reduce_sum(a)
d=tf.add(c,d)
这里reduce_prod定义为先接收入参的所有分量,然后分别将他们相乘。reduce_sum定义为将分量相加。
tensorflow中,所有节点之间传递的数据都是tensor对象。具体的tensor对象包括以下几类:
1).python原生类型
tensorflow可接收python数值、布尔值、字符串或者由它们构成的列表。下面给出tensorflow中可用数据类型的完整清单。
利用python类型制定tensor对象既容易又快捷,但有缺陷。例如python的整数只有一个类型,但tensor的却有8位,16位,32位和64位之分。因此更常见的作法是借助numpy数组手工定义tensor对象。
2)numpy数组
tensorflow的数据类型是基于numpy的数据类型的。实际上np.int32==tf.int32的结果为TRUE。任何numpy数组都可以传递给tensorflow op,而且可以用最小代价轻易指定所需要的数据类型。
对于数值型和布尔型,tensorflow和numpy的定义是完全一致的,然而numpy中并没有与tf.string精确对应的类型,我们只能按照numpy的方式,先把字符串转化为字符串数组,然后再通过numpy传递给tensorflow。也就是说,每用到tf.string类型。示例代码如下:
import numpy as np
t_0 = np.array(50,dtype=np.int32) #元素类型为32位的0阶张量
t_1=np.array([b"apple",b"peach",b"grape"]) #元素位字节字符串的一阶张量
另外,请不要尝试用tf.int32去初始化一个numpy数组。
tensorflow代码中经常遇到形状(shape)。这里形状是tensorflow的专用术语。它同时刻画了张量的阶数以及每一维的长度。举例如下:
s0_list = [ ] #指定0阶张量的形状
s0_tuple = ( ) #指定0阶张量的形状
s_1=[3] #指定了一个长度位3的向量的形状,例如[1,2,3]
s_2=(3,2) #指定了一个3*2矩阵的形状。
除了能将张量的每一维指定为固定长度,也可以将none作为某一维的值,使该张量具有可变长度。单独一个none表示一个任意形状的张量。
s1_flex=[None] #表示一阶张量,但长度任意
s_any=None #表示任意形状
s2_flxe = (None,3) #行数任意,列数为3
shape = tf.shape(d) #获取节点d输出的tensor的shape
下面介绍以下tensorflow的graph对象。这里可以学习如何创建更多的数据流图,以及如何让多个流图协同工作。
为了方便起见,当tensorflow库被加载时,它会自动创建一个Graph对象,并将其作为默认的数据流图。因此,如果我们不另外创建新的Graph,所有的tensorflow对象和OP都会放在这个自动创建的Graph对象中。大多数情况下,只使用默认数据流图就可以了。
当我们需要需要定义多个互相不存在依赖关系的模型时,创建多个Graph对象就很有用。此时最佳实践不是使用默认的数据流图,而是给每个数据流图创建一个Graph对象。
方法一:创建新的数据流图,弃用缺省的数据流图
g1=tf.Graph()
g2=tf.Graph()
with g1.as_default():
#定义g1数据流图
with g2.as_default():
#定义g2的数据流图
方法二:找到缺省数据流图的句柄,把它当作新的数据流图来使用。
g1=tf.get_default_graph()
g2=tf.Graph()
with g1.as_default():
#定义g1数据流图
with g2.as_default():
#定义g2的数据流图
tensorflow的Session类负责数据流图的执行,构造方法tf.Session()可接收3个参数:
target指定了所要使用的执行引擎,一般默认为空。在分布式设置中需要用到
graph指定了将要在Session中加载的Graph对象,默认为None,即使用缺省的Graph. 使用多个数据流图时,需要显示传入你希望运行的Graph对象。
config参数允许用户指定配置Session对象所需要的选项,如限制CPU或GPU的使用数目等。
session.run()方法一个参数fetches,以及其他三个可选参数:feed_dict,options和run_metadata。后面两个参数用到的较少,暂不做深入讨论。
fetches参数接收任意的数据流图元素(op,or tensor对象),如果指定为op,输出为None;如果指定为tensor对象,输出将为一个Numpy数组。另外,我们还可以同时指定多个数据流图元素。
sess.run([a,b]) #返回节点a和b的输出值。
feed_dict参数用于覆盖数据流图中的tensor对象值,它需要python字典对象作为输入。字典中的键位只想应当被覆盖的tensor对象,而字典中的值的类型必须与该tensor对象相同。
a=tf.add(2,5)
b=tf.mul(a,3)
sess=tf.Session()
rep_dict={a:15} #定义一个字典,将来a的值会被替换位15
sess.run(b,feed_dict=rep_dict) #最后返回值是15,而不是21.
利用session的as_default方法可以将session对象作为上下文管理器,session对象可被某些函数自动使用。例如Operation.run()和Tensor.eval()会将他们字节传入Session.run函数,而不需要显式调用session.run函数。
with sess.as_default():
a.eval() #等价于sess.run(a)
占位符与变量
之前,我们的例子都是使用确定的输入来完成一次运算。大多数情况,我们需要用数据流图定义好计算过程,然后输入用户的输入和输出来训练数据流图。因此我们需要输入节点只定义一个形状,而不是具体的值。这就需要用到占位符。
占位符的行为与tensor对象一致,但不需要指定具体的值,它的作用是为运算时即将到来的某个tensor对象预留为止。
a=tf.placeholder(tf.int32,shape=[2]) #占位符定义好类型和形状,这里类型是必须的,形状是可选参数。
为了给占位符传一个实际的值,需要使用session中的feed_dict参数。
input_dict={a:np.arrya([5,3],dtype=np.int32)}
sess.run(d,feed_dict=input_dict)
使用占位符,我们可以每次session.run中指定一个输入,但是如果我们需要在多次session.run中不停的调整权值,显然占位符不能胜任,此时就需要使用tf.Variable了。
my_var=tf.Variable(3) #初值为3的variable
Variable的初值通常是全0,全1,或者随机数。未达到这个目的,tensorflow提供了大量辅助op,tf.zeros,tf.ones,tf.random_normal,tf.random_uniform等等。
normal = tf.random_normal([3,3,3],mean=0.0,stddev=2.0) #均值为0,标准差为2.
variable对象与大多数其他tensorflow对象在Graph中存在的方式类似。但他们的状态实际上是由session对象管理的。因此为使用Variable对象,需要采取一些额外的步骤--必须在一个session对象内对variable对象进行初始化。variable对象的初始化通常是通过tf.initialize_all_variable()操作传给session.run完成的。
如果只需要对一个variabel对象初始化,可以使用tf.initialize_variables()操作。
要修改variable对象的值,可使用variable.assign方法。
my_var2=myvar.assign(myvar*2) #先把myval*2传给myval,再把myval的值传给myval2.
variable.assign_add()和variable.assign_sub是变量的自增和自减方法。
trainable参数缺省为true,当它为false时,表示这个变量不需要被优化类修改。