四. Tensorflow的编程接口
在讨论了Tensorflow计算模型之后,我们现在聚焦到更为实际的编程接口。我们先介绍可用的编程语言接口,再使用一个例子讲解Python API的使用。最后,我们概括Tensorflow API的格局和怎么能够快速创建算法原型。
A. 接口
Tensorflow拥有C++和Python两种编程接口,允许用户调用后端功能。Python API提供了丰富和完整的创建和运行计算图的编程接口,而C++的接口相对有限和与后端功能实现耦合度太高,仅仅允许去执行计算图和序列化图到谷歌协议缓冲格式。对于创建图的C++接口在文章发表的时候还很不完善。
值得注意的是,Tensorflow的API和NumPy有很好的集成。因此,我们可以看到Tensorflow的数据类型tensor和NumPy的ndarrays在很多应用场合都是可以互换的。
B. 示例解读
接下来的几个章节,我们将一步一步的解读一个Tensorflow的真实示例。我们将训练一个带有一个输入和一个输出的简单的多层感知器(MLP),它将用于识别MNIST数据集中的手写字符。在这个数据集中,样本时28x28像素的手写字符从0-9。我们将这些字符转换成784灰度像素扁平的向量。对于每个样本的标签是相应字符的数字。
我们从加载输入数据开始解读。这里数据已经被处理好成为我们需要的格式,只需要调用read函数加载数据到内存。进一步来看,我们设置one_hot=True来指定是否使用一个10维向量(d1,。。。,d10)的转置来表征某一个字符,即所有维度数字是0,只有表征该字符的位置是1。
importtensorflow as tf
# Download and extract the MNIST data set.
# Retrieve the labels as one-hot-encoded vectors.
mnist = mnist_data.read("/tmp/mnist", one_hot=True)
接下来,我们通过调用tf.Graph创建一个新计算图。为了给这个图增加算子,我们必须把这个图注册为缺省图。在TensorflowAPI和库设计里面,新的算子总是挂在缺省图上。相应代码如下:
# Create a new graph
graph = tf.Graph()
# Register the graph as the default one to add nodes
with graph.as_default():
# Add operations ...
我们现在准备通过增加算子来生成计算图。让我们先增加两个占位节点examples和labels。占位者是一种特殊变量,在图运算的时候必须被确切的张量赋值。也就是说,上面创建的占位者必须在Session.run()被调用的时候,被feed_dict参数传进来的张量所取代。对于每一个这个的占位者,我们定义它的形状和数据类型。Tensorflow在这里可以使用None来描述占位者形状的第一个维度。这就是为未来给此占位者赋值一个在此维度大小可变的张量。对于examples的列大小,我们指定每一幅图的特征数,即28 x 28 = 784个像素。labels占位者应该有10列,代表着我们在上面定义的10维字符分类向量。
# Using a 32-bit floating-point data type tf.float32
examples = tf.placeholder(tf.float32, [None, 784])
labels = tf.placeholder(tf.float32, [None, 10])
给定一个example矩阵X属于集合R是nx784,意味着包含n个图像,学习算法将使用仿射变换X.W+b,这里W是一个784x10的权重矩阵,b是10维偏置向量。这个变换的结果产生Y是nx10的矩阵,包含我们模型对于每一个样本图像识别的结果。这些结果是一些任意数值而不是概率分布。为了转换它们到一个有效概率分布,在给定似然Pr[x=i],即第x样本图像是数字i的概率,我们使用softmax函数,公式和相应代码如下:
# Draw random weights for symmetry breaking
weights = tf.Variable(tf.random_uniform([784, 10]))
# Slightly positive initial bias
bias = tf.Variable(tf.constant(0.1, shape=[10]))
# tf.matmul performs the matrix multiplication XW
# Note how the + operator is overloaded for tensors
logits = tf.matmul(examples, weights) + bias
# Applies the operation element-wise on tensors
estimates = tf.nn.softmax(logits)
接下来,我们计算我们的目标函数,产生模型在当前权重W和偏差b下的成本或者损失。公式H(L,Y)i=−jLi,j·log(Yi,j)计算我们预测的结果和训练样本实际结果之间的交叉熵。更精确而言,我们考虑的是所有训练样本的交叉熵的均值作为成本或者损失。
# Computes the cross-entropy and sums the rows
cross_entropy = -tf.reduce_sum(labels*tf.log(estimates), [1])
loss = tf.reduce_mean(cross_entropy)
现在,我们有了目标函数,就可以进行随机梯度下降计算去更新我们模型的权重矩阵。为此,Tensorflow提供了GradientDescentOptimizer这个类实现这一过程。该类使用算法的学习速度来初始化,同时提供算子minimize来处理我们的成本或损失张量。这就是我们在Session环境里训练模型需要反复迭代的算子。
# We choose a learning rate of 0.5
gdo = tf.train.GradientDescentOptimizer(0.5)
optimizer = gdo.minimize(loss)
最后,我们就可以实际训练模型了。在Tensorflow里,我们需要进入一个会话环境,使用tf.Session来管理会话。通过Session,我们训练我们的模型,执行计算图上的算子。我们有几种调用方法,最常见的方式是调用Session.run(),然后传递一组张量给它。或者,我们也可以直接在张量上调用eval()和算子上run()。在评估算子之前,我们必须确保我们图变量被初始化了。理论上,我们可以通过调用Variable.initializer算子来初始化每一个变量。然而,最常见的办法是调用tf.initialize_all_variables()方法去一次性初始化所有变量。然后我们就可以迭代几次随机梯度下降,每一次我们选取一些样本图像和标签,传递给模型去计算成本或损失。最后,我们的成本或损失会越来越小(我们希望如此):
with tf.Session(graph=graph) as session:
# Execute the operation directly
tf.initialize_all_variables().run()forstepinrange(1000):
# Fetch next 100 examples and labels
x, y = mnist.train.next_batch(100)
# Ignore the result of the optimizer (None)
_, loss_value = session.run(
[optimizer, loss],
feed_dict={examples: x, labels: y})
print(’Loss at step {0}: {1}’.format(step, loss_value))
这个例子完整的代码参见附录。
C. 抽象与封装
你可以已经注意到构建模型和训练的过程是需要花费大量的开销在创建一个非常简单的两层网络。通过深度学习的名称“深度”二字,就隐含了,你需要使用携带大量隐藏层的深度神经网络。因此,每一次构建模型都需要花这么多时间构建权重矩阵,偏置矩阵,计算矩阵乘法,加法,和应用非线性激活函数的神经网络是效率低下的,因此,我们需要抽象和封装这一过程。幸好,我们可以使用一些开源的库来完成这一过程,比如PrettyTensor,TFLearn和Keras。下面我们会分别论述PrettyTensor和TFLearn。
1)PrettyTensor:这是谷歌开发的高级编程接口,主要的是通过Builder模式来调用Tensorflow的API。它允许用户把Tensorflow的算子和张量封装进干净的版本,然后迅速将任意层串接起来。比如,它可以用一行代码完成一个输入张量传输到一个全连接(稠密)神经网络层。下面的样例代码显示了从创建一个占位者到初始化,到创建三层网络到最后输出softmax的一个分布。
examples = tf.placeholder([None, 784], tf.float32)
softmax = (prettytensor.wrap(examples)
.fully_connected(256, tf.nn.relu)
.fully_connected(128, tf.sigmoid)
.fully_connected(64, tf.tanh)
.softmax(10))
2)TFLearn:这是另一个基于Tensorflow本体的封装库,它有高度封装的神经网络层和层连接API。而且,它比PrettyTensor一定要通过Session建立训练和评估模型更进一步,它可以直接添加训练样本和标签来训练模型。在TFLearn里面,有创建完整神经层次的函数,有返回原始Tensorflow对象的函数,有混合Tensorflow本体代码调用的编程。比如,我们可以取代Tensorflow的输出层,自己完整构建它,但是依然保持其它部分不发生改变。下面10行代码完成了附录65行代码才能完成的功能。
importtflearn
importtflearn.datasets.mnist as mnist
X, Y, validX, validY = mnist.load_data(one_hot=True)
# Building our neural network
input_layer = tflearn.input_data(shape=[None, 784])
output_layer = tflearn.fully_connected(input_layer,
10, activation=’softmax’)
# Optimization
sgd = tflearn.SGD(learning_rate=0.5)
net = tflearn.regression(output_layer,
optimizer=sgd)
# Training
model = tflearn.DNN(net)
model.fit(X, Y, validation_set=(validX, validY))