3.1 TensorFlow 计算模型——计算图
TensorFlow 是一个通过计算图的形式来表述计算的编程系统。TensorFlow 中的每一个计算都是计算图上的一个节点,而节点之间的边描述了计算之间的依赖关系。
TensorFlow 的程序一般可以分为两个阶段。在第一个阶段需要定义计算图中所有的计算。第二个阶段为执行计算。以下代码给出了计算定义阶段的样例:
import tensorflow as tf
a = tf.constant([1.0, 2.0], name="a")
b = tf.constant([2.0, 3.0], name="b")
在 TensorFlow 程序中,系统会自动维护一个默认的计算图,通过tf.get_default_graph
函数可以获取当前默认的计算图。以下代码示意了如何获取默认计算图以及如何查看一个运算所属的计算图:
print(a.graph is tf.get_default_graph())
除了使用默认的计算图,TensorFlow 支持通过
tf.Graph
函数来生成新的计算图
import tensorflow as tf
g1 = tf.Graph()
with g1.as_default():
v = tf.get_variable("v", initializer = tf.zeros_initializer()(shape = [1]))
g2 = tf.Graph()
with g2.as_default():
v = tf.get_variable("v", initializer = tf.ones_initializer()(shape = [1]))
with tf.Session(graph = g1) as sess:
tf.global_variables_initializer().run()
with tf.variable_scope("", reuse = True):
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):
print(sess.run(tf.get_variable("v")))
v的取值分别为0和1
以上代码产生了两个计算图,每个计算图中定义了一个名字为“v”的变量。在计算图 g1 和 g2 中,分别将 v 初始化为 0 和 1。可以看到在运行不同的计算图时,变量 v 的值也不一样。
TensorFlow 中的计算图不仅可以用来隔离张量和计算,还提供了管理张量和计算的机制。计算图可以通过
tf.Graph.device
函数来指定运行计算的设备。这为 TensorFlow 使用 GPU 提供了机制。以下程序可以将加法计算跑在 GPU 上:
import tensorflow as tf
a = tf.constant([1.0, 2.0], name="a")
b = tf.constant([2.0, 3.0], name="b")
g = tf.Graph()
with g.device('/gpu:0'):
result = a + b
sess = tf.Session()
print(sess.run(result))
注意到警告信息
Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2
,加入以下代码忽略此警告:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
3.2 TensorFlow 数据模型——张量
在 TensorFlow 中,所有的数据都通过张量的形式来表示。从功能的角度上看,张量可以简单理解为多维数组。其中零阶张量表示标量(scalar),也就是一个数;第一阶张量为向量(vector),也就是一个一维数组;第 n 阶张量可以理解为一个 n 维数组。但张量在 TensorFlow 中的实现并不是直接采用数组的形式,它只是对 TensorFlow 中运算结果的引用。在张量中并没有真正保存数字。它保存的是如何得到这些数字的计算过程。
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
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)
TensorFlow 计算的结果不是一个具体的数字,而是一个张量的结构。一个张量中主要保存了三个属性:名字(name)、维度(shape)和类型(type)。
张量的命名通过 node:src_output 的形式来给出。其中 node 为节点的名称,src_output 表示当前张量来自节点的第几个输出。比如上图中的
add:0
就说明 result 这个张量是计算节点 add 输出的第一个结果。张量的维度描述了一个张量的维度信息,比如上图中
shape=(2,)
说明了张量 result 是一个长度为2的一维数组。张量的第三个属性是类型,每一个张量有唯一的类型。TensorFlow 会对参与运算的所有张量进行类型的检查,当发现类型不匹配时会报错。
报错:类型不匹配
通过指定类型避免出错:
a = tf.constant([1, 2], name="a", dtype=tf.float32)
TensorFlow 支持 14 种不同的类型,主要包括了实数(tf.float32、tf.float64)、整数(tf.int8、tf.int16、tf.int32、tf.int64、tf.uint8)、布尔型(tf.bool)和复数(tf.complex128)。
张量的第一类用途是对中间计算结果的引用。当一个计算包含很多中间结果时,使用张量可以大大提高代码的可读性。
张量的第二类用途是当计算图构造完成之后,张量可以通过会话来获得计算结果,也就是得到真实的数字。
3.3 TensorFlow 运行模型——会话
会话(session)拥有并管理 TensorFlow 程序运行时的所有资源。所有计算完成后需要关闭会话来帮助系统回收资源,否则可能出现资源泄漏。TensorFlow 中使用会话的模式一般有两种,第一种模式需要明确调用会话生成函数和关闭会话函数,这种模式的代码流程如下:
# 创建一个会话
sess = tf.Session()
# 使用该会话来得到运算结果
sess.run(...)
# 关闭会话,释放资源
sess.close()
当程序因为异常而退出时,关闭会话的函数可能不会被执行从而导致资源泄漏。为了解决异常退出时资源释放的问题,TensorFlow 可以通过 Python 的上下文管理器来使用会话:
with tf.Session() as sess:
# 使用该会话来得到运算结果
sess.run(...)
# 当上下文退出时会话关闭和资源释放自动完成
通过 Python 上下文管理机制,只要将所有的计算放在with
的内部就可以解决因为异常退出时资源释放的问题和忘记调用 Session.close()
函数而产生的资源泄漏。
TensorFlow 不会自动生成默认的会话,而是需要手动指定。当默认的会话被指定之后可用通过tf.Tensor.eval
函数来计算一个张量的取值,以下代码展示了通过设定默认会话计算张量的取值:
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
a = tf.constant([1.0, 2.0], name="a")
b = tf.constant([2.0, 3.0], name="b")
result = a + b
sess = tf.Session()
with sess.as_default():
print(result.eval())
以下代码也可以完成相同的功能
print(sess.run(result))
print(result.eval(session=sess))
在交互式环境下,通过设置默认会话的方式来获取张量的取值更加方便。所以 TensorFlow 提供了在交互式环境下直接构建默认会话的函数
tf.InteractiveSession()
,该函数会自动将生产的会话注册为默认会话。
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
a = tf.constant([1.0, 2.0], name="a")
b = tf.constant([2.0, 3.0], name="b")
result = a + b
sess = tf.InteractiveSession()
print(result.eval())
sess.close()
3.4 TensorFlow 实现神经网络
在 TensorFlow 中,变量(tf.Variable)的作用就是保存和更新神经网络中的参数。一般使用随机数给 TensorFlow 中的变量初始化,以下示例代码给出了声明一个 2×3 的矩阵变量的方法:
weights = tf.Variable(tf.random_normal([2, 3], stddev=2))
该矩阵中的元素是均值为 0,标准差为 2 的随机数。tf.random_normal
函数通过参数 mean
来指定平均值,默认为 0。通过满足正态分布的随机数来初始化神经网络中的参数是非常常用的方法。
TensorFlow 随机数生成函数
函数名称 | 随机数分布 | 主要参数 |
---|---|---|
tf.random_normal | 正态分布 | 平均值、标准差、取值类型 |
tf.truncated_normal | 正态分布,如果随机值偏离平均值超过2个标准差,将重新随机 | 平均值、标准差、取值类型 |
tf.random_uniform | 均匀分布 | 最小、最大取值,取值类型 |
tf.random_gamma | Gamma分布 | 形状参数alpha、尺度参数beta、取值类型 |
TensorFlow 常数生成函数
函数名称 | 功能 | 样例 |
---|---|---|
tf.zeros | 产生全0的数组 | tf.zeros([2, 3], tf.int32) |
tf.ones | 产生全1的数组 | tf.ones([2, 3], tf.int32) |
tf.fill | 产生一个全部为给定数字的数组 | tf.fill([2, 3], 9) |
tf.constant | 产生一个给定值的常量 | tf.constant([1, 2, 3]) |
在神经网络中,偏置项(bias)通常会使用常数来设置初始值。以下代码生成了一个初始值全部为 0 且长度为 3 的变量:
biases = tf.Variable(tf.zeros([3]))
除了使用随机数或者常数, TensorFlow 也支持通过其他变量的初始值来初始化新的变量:
w2 = tf.Variable(weights.initialized_value())
w3 = tf.Variable(weights.initialized_value() * 2.0)
以下样例演示了如何通过变量实现神经网络的参数并实现前向传播的过程:
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# 声明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.constant([[0.7, 0.9]])
# 前向传播算法
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
sess = tf.Session()
# 初始化w1、w2
sess.run(w1.initializer)
sess.run(w2.initializer)
print(sess.run(y))
sess.close()
虽然直接调用每个变量的初始化过程是可行的,但当变量数目增多或者变量之间存在依赖关系时,单个调用方案比较麻烦。TensorFlow 提供了
tf.global_variables_initializer()
函数实现初始化所有变量的过程。
init_op = tf.global_variables_initializer()
sess.run(init_op)
所有的变量都会被自动加入到 GraphKeys.VARIABLES
集合中。通过 tf.global_variables
函数可以拿到当前计算图上的所有变量,有助于持久化整个计算图的运行状态。当构建机器学习模型时,可以通过变量声明函数中的 trainable
参数来区分需要优化的参数(比如神经网络中的参数)和其他参数(比如迭代的轮数)。如果声明变量时参数 trainable
为 True,那么这个变量将会被加入到 GraphKeys.TRAINABLE_VARIABLES
集合。可以通过 tf.trainable_variables()
函数得到所有需要优化的参数。TensorFlow 中提供的神经网络优化算法会将GraphKeys.TRAINABLE_VARIABLES
集合中的变量作为默认的优化对象。
在神经网络优化算法中,最常用的方法是反向传播算法(backpropagation)。反向传播算法实现了一个迭代的过程。在每次迭代的开始,首先需要选取一小部分训练数据,这一小部分数据叫作一个 batch。该 batch 的样例通过前向传播算法得到神经网络模型的预测结果,计算预测结果与正确答案的差距。基于预测值与真实值之间的差距,反向传播算法会相应更新神经网络参数的取值,使得在这个 batch 上神经网络预测的结果与真实答案更接近。
通过 TensorFlow 实现反向传播算法的第一步是使用 TensorFlow 表达一个 batch 的数据:
x = tf.constant([[0.7, 0.9]])
如果每轮迭代选取的数据都通过常量来表示,那么计算图将会非常大,导致利用率很低。TensorFlow 提供了 placeholder
机制用于提供输入数据,以下代码给出了通过 placeholder
实现前向传播算法:
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
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()
# 初始化
init_op = tf.global_variables_initializer()
sess.run(init_op)
print(sess.run(y, feed_dict={x: [[0.7, 0.9]]}))
sess.close()
计算前向传播结果时,需要提供一个
feed_dict
来指定 x 的取值。feed_dict
是一个字典(map),在字典中需要给出每个用到的 placeholder
的取值。如果将输入的 1×2 矩阵改为 n×2 的矩阵,就可以得到 n 个样例的前向传播结果,以下代码示例:
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
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()
# 初始化
init_op = tf.global_variables_initializer()
sess.run(init_op)
print(sess.run(y, feed_dict={x: [[0.7, 0.9], [0.1, 0.4], [0.5, 0.8]]}))
sess.close()
在得到一个 batch 的前向传播结果后,需要定义一个损失函数来刻画当前预测值与真实值之间的差距。然后通过反向传播算法来调整神经网络参数的取值使得差距可以被缩小。
以下是一个完整的在模拟数据集上训练神经网络解决二分类问题的样例程序:
import tensorflow as tf
from numpy.random import RandomState
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# 定义训练数据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))
# 在shape的一个维度上使用None可以方便使用不同的batch大小
x = tf.placeholder(tf.float32, shape=(None, 2), name='x-input')
y_ = tf.placeholder(tf.float32, shape=(None, 1), name='y-input')
# 定义神经网络前向传播的过程
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
# 定义损失函数和反向传播算法
y = tf.sigmoid(y)
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)) + (1-y_) * tf.log(tf.clip_by_value(1-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)
# 定义规则来给出样本的标签
Y = [[int(x1+x2 < 1)] for (x1, x2) in X]
# 创建会话
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
print(sess.run(w1))
print(sess.run(w2))
# 设定训练轮数
STEPS = 5000
for i in range(STEPS):
# 每次选取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(s), cross entropy on all data is %g" % (i, total_cross_entropy))
print(sess.run(w1))
print(sess.run(w2))
网友评论