TensorFlow中所有的计算都会被转换为计算图上的节点。如果说TensorFlow的Tensor是计算图的数据结构,那么Flow则体现了它的计算模型,Tensor是张量的含义。
TensorFlow程序一般分为两个阶段,第一阶段定义计算图中所有的计算。第二阶段为执行计算。
在编写程序过程中,TensorFlow会自动将定义的计算转化为计算图上的节点,在TensorFlow中,系统会自动维护一个默认的计算图,通过tf.get_default_graph函数可以获取当前默认的计算图。
import tensorflow as tf
a = tf.constant([1.0,2.0],name='a')
b = tf.constant([2.0,3.0],name='b')
result = a + b
#通过a.graph可以查看张量所属的计算图,因为没有特意指定,所以这个计算图应该是默认的计算图
print(a.graph is tf.get_default_graph())
>>>True
除了使用默认的计算图,TensorFlow支持通过tf.Graph函数生成新的计算图。使用tf.Graph.as_default()方法将一个计算图设置为默认计算图,同时返回一个上下文管理器。这里可以配合with语句是保证操作的资源可以正确的打开和释放。不同的计算图上的张量和运算不会共享。。下面代码示意在不同计算图上定义和使用变量:
import tensorflow as tf
g1 = tf.Graph()
with g1.as_default():
#在g1中定义v
v = tf.get_variable("v",shape=[1],initializer=tf.zeros_initializer())
g2 = tf.Graph()
with g2.as_default():
#在g2中定义v
v = tf.get_variable("v",shape=[1],initializer=tf.ones_initializer())
with tf.Session(graph=g1) as sess:
tf.global_variables_initializer().run()
with tf.variable_scope("",reuse=True):
#在g1中,变量v取值应该为0,下面输出应该为[0.]
print(sess.run(tf.get_variable('v')))
with tf.Session(graph=g2) as sess:
tf.global_variables_initializer().run()
with tf.variable_scope("",reuse=True):
#在g2中,变量v取值应该为1,下面输出应该为[1.]
print(sess.run(tf.get_variable('v')))
[ 0.] #g1初始化为0
[ 1.] #g2初始化为1
TensorFlow还提供了管理Tensor和计算的机制,计算图可以通过tf.Graph.device函数来指定运行计算的设备。下面程序将加法计算放在GPU上执行。
g = tf.Graph()
with g.device('/gpu:0'):
result = a + b
TensorFlow可以通过集合(collection)来管理不同类别的资源。例如使用tf.add_to_collection函数可以将资源加入一个或多个集合。使用tf.get_collection获取一个集合里面的所有资源。这些资源可以是张量/变量或者运行Tensorflow程序所需要的资源。(在神经网络的训练中会大量使用集合管理技术)
集合名称 | 集合内容 | 使用场景 |
---|---|---|
tf.GraphKeys.GLOBAL_VARIABLES | 所有变量 | 持久化Tensorflow模型 |
tf.GraphKeys.TRAINABLE_VARIABLES | 可学习的变量(神经网络的参数) | 模型训练/生成模型可视化内容 |
tf.GraphKeys.SUMMARIES | 日志生成相关的张量 | Tensorflow计算可视化 |
tf.GraphKeys.QUEUE_RUNNERS | 处理输入的QueueRunner | 输入处理 |
tf.GraphKeys.MOVING_AVERAGE_VARIABLES | 所有计算了滑动平均值的变量 | 计算变量的滑动平均值 |
Tensor是TensorFlow管理数据的形式,从功能的角度上来看,Tensor可以简单的理解为多维数数组,其中零阶Tensor表示为标量(Scalar),即一个数。但Tensor在TensorFlow中实现并不是直接采用数组的形式,而是对TensorFlow中运算结果的引用。Tensor保存是对如何得到数字的计算过程.
Tensor的用途分为两类:一是对中间计算结果的引用,这样方便获取中间计算结果同时提高了代码的阅读性。二是可以用来获得计算结果,这需要配合session.
import tensorflow as tf
a = tf.constant([1.0,2.0],name='a')
b = tf.constant([2.0,3.0],name='b')
result = tf.add(a,b,name='add')
print(result)
#输出
Tensor("add:0", shape=(2,), dtype=float32)
#这是一个张量结构,需要配合session使用才能计算出结果
以上述程序为例,一个Tensor的结构为
Tensor("add:0", shape=(2,), dtype=float32)
这其中主要包含了三个属性:name/shape/type(标识/维度/类型).
name是一个Tensor的唯一标识符,同时name也给出了该Tensor是如何计算出来的。计算图上的node和计算是相对应的。
计算的结果保存在Tensor中,Tensor的name属性可以通过”node:src_output”形式给出。
例如上述程序的
Tensor("add:0", shape=(2,), dtype=float32)
#name为"add:0"即 表示node的ops为add 且是第0个输出.
shape属性描述了一个Tensor的维度信息,维度是Tensor一个极其重要的属性,后面学习过程会有大量操作维度的计算。
在程序中:
Tensor("add:0", shape=(2,), dtype=float32)
#"shape=(2,)"说明result是一个一维向量,向量的长度为2.
每一个Tensor都有一个唯一的类型,TensorFlow会对所有参与计算的Tensor进行类型检查,当发现类型不匹配时会报错。
例如:
import tensorflow as tf
a = tf.constant([1,2],name='a') #检测为int32
b = tf.constant([2.0,3.0],name='b') #检测为float32
result = a + b
# result = a + b
# ValueError: Tensor conversion requested dtype int32 for Tensor with dtype float32: 'Tensor("b:0", shape=(2,), dtype=float32)'
这里对常量a默认为int32类型,在与float32类型的b相加会出现类型不匹配,故报错.
我们可以指定constant的类型,例如将a改为float32类型,例如下面程序就不会报错了
import tensorflow as tf
a = tf.constant([1,2],name='a',dtype=tf.float32) #dtype='float32'也可以,为了兼容性,最好还是tf.float32
b = tf.constant([2.0,3.0],name='b')
result = a + b
会话(Session)拥有并管理TensorFlow运行时的所有资源,同时每个会话有自己的资源,例如 tf.Variable, tf.QueueBase, and tf.ReaderBase.当这些资源使用完毕后,及时的释放这些资源是很重要的,此时可以使用Session.close释放会话资源.,当所有计算完成后需要关闭会话来帮助系统回收资源,避免资源泄漏等问题。
TensorFlow使用会话模式一般分为两种,明确调用会话生成函数和通过上下文管理器管理会话.
使用这种模式,当所有计算完成后,需要使用session.close函数关闭会话释放资源,当程序出现异常,会话得不到正常关闭。使用示例如下:
#创建一个会话
sess = tf.Session()
#使用这个会话可以得到张量的结果,例如sess.run(result)
sess.run(...)
#关闭会话
sess.close()
在Python中,我们常使用上下文管理器来操作文件,例如
with open('...') as fp:
这样做的好处,是利用上下文管理器来帮助我们简化操作,保证资源的有效利用和释放.同样的我们也可以使用with来操作Session.
#创建一个会话,通过上下文管理器管理会话
with tf.Session() as sess:
#do what you want
sess.run(...)
#用完了就不用管了
TensorFlow在管理计算图时会自动生成一个默认的计算图,会话也有类似的机制,但需要手动指定。当默认的会话被指定之后可以通过tf.Tensor.eval函数来计算一个张量的取值.例如
sess = tf.Session()
with sess.as_default():
print(result.eval()) #计算张量的结果
或者代码这样写
sess = tf.Session()
#下面两个功能一样
print(sess.run(result))
print(result.eval(session=sess))
在交互式环境中,通过设置默认会话的方式获取张量的结果更加容易.TensorFlow提供了一种在交互式环境下直接构造默认会话的函数,即tf.InteractiveSession,用法如下
sess = tf.InteractiveSession()
print(result.eval()) #通过tf.InteractiveSession可以省去注册默认会话的操作
sess.close()
在TensorFlow中变量(tf.Variable)的作用可用保存和模型中参数,创建Variable需要传入一个初始化值,TensorFlow中变量初始值可以设置为随机数、常数或者是通过其他变量初始值计算得到。初始化时这需要指定Variable的type和shape(初始化过后Tensor的type和shape不可变,Value可以通过assign函数改变).如果需要动态的改变Variable的shape,在声明时指定validate_shape=False.)
TensorFlow的Variable可以通过随机函数初始化,下面是TensorFlow中常用的随机函数:
函数名称 | 随机数分布 | 主要参数 |
---|---|---|
tf.random_normal | 正态分布 | 平均值/标准差/取值类型 |
tf.truncated_normal | 正态分布,但如果随机出来的值偏离平均值超过2个标准差,这个数会被重新随机 | 平均值/标准差/取值类型 |
tf.random_uniform | 平均分布 | 最小/最大取值/取值类型 |
tf.random_gamma | Gamma分布 | 形状参数alpha/尺度参数beta/取值类型 |
TensorFlow也支持通过常数来初始化一个变量。下表是TensorFlow中常用的常量声明方法.
函数名称 | 功能 | 样例 |
---|---|---|
tf.zeros | 全0数组 | tf.zeros([2,3],int32)->[[0,0,0],[0,0,0]] |
tf.ones | 全1数组 | tf.zeros([2,3],int32)->[[1,1,1],[1,1,1]] |
tf.fill | 产生一个全部为给定数字的数组 | tf.zeros([2,3],9)->[[9,9,9],[9,9,9]] |
tf.constant | 产生一个给定值常量 | tf.constant([1,2,3])->[1,2,3] |
TensorFlow提供了通过变量名称来创建或者获取一个变量的机制,通过这个机制,在不同的函数中可以直接通过变量的名字来使用变量,而不需要将变量通过参数的形式传递。
TensorFlow通过变量名称获取变量的机制主要通过tf.get_variable和tf.variable_scope函数实现.
创建Variable可通过Variable()也可以使用get_variable()。
以下代码是通过两个函数创建同一个变量的实例:
v =tf.get_variable("v",shape=[1],initializer=tf.constant_initializer(1.0))
v = tf.Variable(tf.constant(1.0,shape=[1],name='v')
在这里,我们结合神经网络的功能进一步的介绍如何通过TensorFlow来实现神经网络。首先我们使用TensorFlow游乐场(TensorFlow工具)简单了解实现神经网络的功能和计算流程。再使用TensorFlow实现神经网络的FP(前向传播)和BP(反向传播)算法.
TensorFlow游乐场(http://playground.tensorflow.org)是一个Web应用,可以训练简单的神经网络并实现可视化训练过程的工具.
使用神经网络解决分类问题主要分为一下4个步骤:
不同的神经网络结构前向传播的方式也不一样,这里介绍最简单的全连接网络结构的前向传播算法,之所以称为全连接神经网络是因为相邻两层之间任意两个节点都有链接.
其中第一部分是神经网络的输入:从实体中提取的特征向量。图示为x1和x2
第二部分是神经网络的连接结构:节点a11/a12/a13和连接权值W矩阵,其中w的上标表明了神经网络的层数,下标表明了连接节点编号,比如w11即表示连接x1到a11的权值.(连接元素的具体位置取决与上标)
整个神经网络前向传播的过程
前向传播算法可以表示为矩阵乘法,将输入x1,x2,组织成一个1x2的矩阵X=[x1,x2],而W组织成一个2x3的矩阵(矩阵行数为输入个数,矩阵列数为当前层节点个数):
这样前向算法用矩阵方式表达出来了,在TensorFlow中矩阵乘法是很容易实现的。
a = tf.matmul(x,w1)
y = tf.matmul(a,w2) #matmul实现矩阵乘法
代码如下
import tensorflow as tf
# 声明W1,W2两个变量,seed参数设定随机种子,保证每次运行结果一致
w1 = tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2 = tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))
# x 设置为一个占位符
x = tf.placeholder(tf.float32,shape=(None,2),name="input")
# 矩阵乘法
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
# 创建一个会话
sess = tf.Session()
# 初始化W1,W2
sess.run(w1.initializer)
sess.run(w2.initializer)
# 使用sess计算前向传播输出值
print(sess.run(y,feed_dict={x:[[0.7,0.9]]}))
sess.close()
输出为:
需要注意的地方有:
在神经网络中,常用的方法是BP算法,下图是BP算法执行的流程图
BP算法是一个迭代的过程,再每次迭代过程开始,取一小部分训练数据叫做一个batch.依据前向传播的输出值与标签值的差值做BP优化。
这里需要注意,上一节代码我们声明输入用的是x=tf.constant([[0.7,0.9]]),一般神经网络训练过程会需要多次迭代,每次迭代中选取的数据不能靠变量来表示,这里TensorFlow提供了placeholder机制用于输入数据,placeholder相当于定义一个位置,这个位置中的数据在程序运行时再指定。placeholder定义时,这个位置的数据类型需要指定而且不能改变。
import tensorflow as tf
#声明w1,w2两个变量,这里还通过seed参数设定随机种子,保证每次运行结果一致
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))
#暂时将输入特征向量设置为一个变量
x = tf.placeholder(tf.float32, shape=(1, 2), name='input')
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
sess = tf.Session()
sess.run(tf.initialize_all_variables())
print(sess.run(y, feed_dict={x: [[0.7, 0.9]]}))
sess.close()
输出:
[[ 3.95757794]]
可以改变输入矩阵,得到n个样例的前向传播结果.例如:将输入改为3组数据
import tensorflow as tf
#声明w1,w2两个变量,这里还通过seed参数设定随机种子,保证每次运行结果一致
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))
#暂时将输入特征向量设置为一个变量
x = tf.placeholder(tf.float32, shape=(3, 2), name='input')
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
sess = tf.Session()
sess.run(tf.initialize_all_variables())
print(sess.run(y, feed_dict={x: [[0.7, 0.9], [0.1, 0.4], [0.5, 0.8]]}))
sess.close()
输出:
[[ 3.95757794]
[ 1.15376532]
[ 3.16749239]]
再得到batch的前向传播结果后,需要定义损失函数刻画输出与标签值的差距,再通过BP调整网络参数。
#定义损失函数
cross_entropy = -tf.reduce_mean(y_*tf.log(tf.clip_by_value(y,1e-10,1.0)))
#定义学习率
learning_rate = 0.001
#定义BP算法优化神经网络参数
train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)
cross_entropy定义了输出值和标签值的交叉熵,这是分类问题的一个常用的损失函数.
train_step定义了BP算法的优化方法,目前TensorFlow支持7种不同的优化器,常用的三种:tf.train.GradientDescentOptimizer、tf.train.AdamOptimizer和tf.train.MomentumOptimizer。再定义BP算法后,通过运行sess.run(train_step)可以对所有的GraphKeys.TRAINABLE_VARIABLES集合中的变量进行优化.
训练数据网络过程可以分为3个步骤:
import tensorflow as tf
from numpy.random import RandomState
# 定义训练数据batch 的大小
batch_size = 8
# 定义神经网络参数
W1 = tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
W2 = tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))
x = tf.placeholder(tf.float32,shape=(None,2),name='x-input')
y_ = tf.placeholder(tf.float32,shape=(None,1),name='y-output')
# 定义神经网络前向传播
a = tf.matmul(x,W1)
y = tf.matmul(a,W2)
# 定义损失函数和反向传播
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y,1e-10,1.0)))
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
# 通过随机数生成一个模拟数据集
rdm = RandomState(1)
dataset_size = 128
X= rdm.rand(dataset_size,2)
# 定义规则来给出样本标签,在这里所有x1+x2<1的样例都被认为是正样本, 0代表负样本 1代表正样本
Y = [[int(X1+X2<1)] for (X1,X2) in X]
#创建会话程序
with tf.Session() as sess:
sess.run(tf.initialize_all_variables()) # 初始化所有变量
print(sess.run(W1))
print(sess.run(W2))
SETPS = 5000
for i in range(SETPS):
# 每次选取batch_size个样本训练
start = (i*batch_size) % dataset_size
end = min(start+batch_size,dataset_size)
sess.run(train_step,feed_dict={x:X[start:end],y_:Y[start:end]})
if i % 1000 == 0:
# 每隔一段时间计算在所有数据上的交叉熵并输出
total_cross_entropy = sess.run(cross_entropy,feed_dict={x:X,y_:Y})
print("After %d training step,cross entropy on all data is %g" % (i,total_cross_entropy))
print(sess.run(W1))
print(sess.run(W2))
输出: