TensorFlow是由谷歌公司推出的一个人工智能学习系统,也是当前最为流行的深度学习框架之一。学习Tensorflow最好的资料就是其官网上的文档(参加文献【1】)。本文是“TensorFlow简明入门宝典”系列文章的第一篇,希望可以帮助对TensorFlow感到既陌生又神秘的朋友快速上手。
对于安装配置TensorFlow请参考【Ubuntu上安装配置Tensorflow及Jupyter详解】
TensorFlow中的核心数据单位就是tensor(张量),张量在编程语言中最straightforward的解释就是多维数组。一个张量的rank就是指其维数。例如:
# a rank 0 tensor; this is a scalar with shape []
3
# a rank 1 tensor; this is a vector with shape [3], 3表示拆掉最外层的[], 剩下的元素有3个
[1., 2., 3.]
# a rank 2 tensor; a matrix with shape [2, 3]
# 2表示拆掉最外层的[],剩下的元素有2个,再拆掉一层[],每组剩下的元素都是3个
[[1., 2., 3.], [4., 5., 6.]]
# a rank 3 tensor with shape [2, 1, 3], 可以拆3次[],每次剩下的元素分别是2, 1, 3
[[[1., 2., 3.]], [[7., 8., 9.]]]
同样,在使用TensorFlow之前需要先引用它,用于线性代数计算的NumPy也常常会被用到,这里一并引入。
import tensorflow as tf
import numpy as np
一、计算图、节点、操作、常量、变量,以及占位符
TensorFlow是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。TensorFlow的核心编程可以分解为两个离散的部分:
- 构建计算图
- 执行计算图
计算图(computational graph)是一系列被安排成图(graph)中节点(node)的TensorFlow操作。每个节点都以0或多个张量作为输入,并产生一个张量作为输出。节点的一种类型就是constant。例如,下面的语句构建了一个非常简单的计算图,其中创建了两个浮点数张量node1和node2。
node1 = tf.constant(3.0, dtype=tf.float32)
node2 = tf.constant(4.0) # also tf.float32 implicitly
注意node1和node2是两个节点(张量),要取得它们的值就必须对它们进行evaluate,而要对node进行evaluate,我们就必须在一个session中执行计算图。一个session是对TensorFlow运行时的状态和控制所进行的封装。
下面的代码创建了一个Session对象,并调用它的run方法。
sess = tf.Session()
print(sess.run([node1, node2]))
执行上述代码,会得到结果如下:
[3.0, 4.0]
我还可以通过把操作(操作也是节点)组合成张量节点的方法来实现更加复杂的计算。例如,把两个constant节点相加并生成一个新图的代码如下:
node3 = tf.add(node1, node2)
print("node3:", node3)
print("sess.run(node3):", sess.run(node3))
输出的结果如下:
node3: Tensor("Add:0", shape=(), dtype=float32)
sess.run(node3): 7.0
一个计算图还可以通过接受外部输入的方法来进行参数化,这被称为placeholders(占位符),一个placeholder可以理解为一种承诺,即后面会提供数值。例如:
a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
adder_node = a + b # + provides a shortcut for tf.add(a, b)
然后就像函数调用一样,当我们evaluate这个图的时候,就要为placeholders提供输入值:
print(sess.run(adder_node, {a: 3, b: 4.5}))
print(sess.run(adder_node, {a: [1, 3], b: [2, 4]}))
输出的结果如下:
7.5
[ 3. 7.]
向图中继续加入一个新的操作会产生一个更加复杂的图:
add_and_triple = adder_node * 3.
print(sess.run(add_and_triple, {a: 3, b: 4.5}))
跟预想的一样,上述代码会输出:
22.5
既然有Constant,很自然地会设想应该也会有Variable。的确如此,Variables允许我们向图中增加可训练的参数。构建Variables一般要指定类型和初始值,例如:
W = tf.Variable([.3], dtype=tf.float32)
b = tf.Variable([-.3], dtype=tf.float32)
x = tf.placeholder(tf.float32)
linear_model = W * x + b
Variable和constant的一个区别就在于constant在你调用tf.constant时就会被初始化,而且它们的值不会再改变。相反,在你调用tf.Variable时,Variable并不会被初始化。为了初始化你程序中的Variables,你必须显式地调用下面这个特殊的语句:
init = tf.global_variables_initializer()
sess.run(init)
因为x是一个placeholder,所以我们可以(通过同时给出几个x的值)来评估前面定义的linear_model,即
print(sess.run(linear_model, {x: [1, 2, 3, 4]}))
输出的结果如下:
[ 0. 0.30000001 0.60000002 0.90000004]
二、利用TensorFlow求解线性回归
我们已经越来越接近线性回归问题的求解了。在通常的线性回归问题中,我们需要在一些形如(x,y)这样的train data训练linear_model。因此,再启用一个新的placeholder y。然后把最小二乘误差(linear_model-y)2作为损失函数(loss function),下面的代码中调用了tf.square来计算最小二乘误差:
y = tf.placeholder(tf.float32)
squared_deltas = tf.square(linear_model - y)
loss = tf.reduce_sum(squared_deltas)
fixW = tf.assign(W, [-1.])
fixb = tf.assign(b, [1.])
sess.run([fixW, fixb])
print(sess.run(loss, {x: [1, 2, 3, 4], y: [0, -1, -2, -3]}))
当我们为参数W和b分别赋值-1和1时,损失为0。输出如下:
0.0
但是机器学习的任务是在给定任意的初始W和b时,让算法自行不断调整参数,以获得使损失最小化的参数。这里最为常用的优化算法就是梯度下降。在TensorFlow中提供的API tf.train可以帮我们完成模型训练的过程。
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)
注意要把参数的初始值重新初始化到一个随机值。
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
for i in range(1000):
sess.run(train, {x: [1, 2, 3, 4], y: [0, -1, -2, -3]})
print(sess.run([W, b]))
最终输出的参数如下,可见已经非常接近标准答案了。(误差是不可避免的)
[array([-0.9999969], dtype=float32), array([ 0.99999082], dtype=float32)]
这部分的Jupyter notebook文件可以从链接【2】中获取。
三、tf.estimator
tf.estimator是一个高层的TensorFlow库,它可以简化机器学习的机制,完成包括:
执行训练循环;执行evaluation循环;管理数据集。tf.estimator还定义了很多一般模型。
下面这个例子演示了如何利用tf.estimator来简化之前的线性回归。
import tensorflow as tf
import numpy as np
# Declare list of features. We only have one numeric feature. There are many
# other types of columns that are more complicated and useful.
feature_columns = [tf.feature_column.numeric_column("x", shape=[1])]
# An estimator is the front end to invoke training (fitting) and evaluation
# (inference). There are many predefined types like linear regression,
# linear classification, and many neural network classifiers and regressors.
# The following code provides an estimator that does linear regression.
estimator = tf.estimator.LinearRegressor(feature_columns=feature_columns)
# TensorFlow provides many helper methods to read and set up data sets.
# Here we use two data sets: one for training and one for evaluation
# We have to tell the function how many batches
# of data (num_epochs) we want and how big each batch should be.
x_train = np.array([1., 2., 3., 4.])
y_train = np.array([0., -1., -2., -3.])
x_eval = np.array([2., 5., 8., 1.])
y_eval = np.array([-1.01, -4.1, -7, 0.])
input_fn = tf.estimator.inputs.numpy_input_fn(
{"x": x_train}, y_train, batch_size=4, num_epochs=None, shuffle=True)
train_input_fn = tf.estimator.inputs.numpy_input_fn(
{"x": x_train}, y_train, batch_size=4, num_epochs=1000, shuffle=False)
eval_input_fn = tf.estimator.inputs.numpy_input_fn(
{"x": x_eval}, y_eval, batch_size=4, num_epochs=1000, shuffle=False)
# We can invoke 1000 training steps by invoking the method and passing the
# training data set.
estimator.train(input_fn=input_fn, steps=1000)
# Here we evaluate how well our model did.
train_metrics = estimator.evaluate(input_fn=train_input_fn)
eval_metrics = estimator.evaluate(input_fn=eval_input_fn)
print("train metrics: %r"% train_metrics)
print("eval metrics: %r"% eval_metrics)
执行上述代码,结果如下:
train metrics: {'loss': 1.2712867e-09, 'global_step': 1000}
eval metrics: {'loss': 0.0025279333, 'global_step': 1000}
前面我们提到tf.estimator还定义了很多一般模型,例如上述代码中就使用了tf.estimator.LinearRegressor。但是,tf.estimator并没有将你锁定在预定义的模型中。假设我们想创造一个个性化的而且没有被内置于TensorFlow中的模型,我们还是可以使用它。这时就需要使用tf.estimator.Estimator,而且事实上,tf.estimator.LinearRegressor其实是tf.estimator.Estimator的一个子类。
假设现在我们要自己实现线性回归,这里我们并不打算为tf.estimator.Estimator的创建一个子类,其实只要为这个类提供一个model_fn的方法来告知tf.estimator 如何 evaluate 预测, 训练步骤以及损失即可。来看下面这个例子:
import numpy as np
import tensorflow as tf
# Declare list of features, we only have one real-valued feature
def model_fn(features, labels, mode):
# Build a linear model and predict values
W = tf.get_variable("W", [1], dtype=tf.float64)
b = tf.get_variable("b", [1], dtype=tf.float64)
y = W * features['x'] + b
# Loss sub-graph
loss = tf.reduce_sum(tf.square(y - labels))
# Training sub-graph
global_step = tf.train.get_global_step()
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = tf.group(optimizer.minimize(loss),
tf.assign_add(global_step, 1))
# EstimatorSpec connects subgraphs we built to the
# appropriate functionality.
return tf.estimator.EstimatorSpec(
mode=mode,
predictions=y,
loss=loss,
train_op=train)
estimator = tf.estimator.Estimator(model_fn=model_fn)
# define our data sets
x_train = np.array([1., 2., 3., 4.])
y_train = np.array([0., -1., -2., -3.])
x_eval = np.array([2., 5., 8., 1.])
y_eval = np.array([-1.01, -4.1, -7, 0.])
input_fn = tf.estimator.inputs.numpy_input_fn(
{"x": x_train}, y_train, batch_size=4, num_epochs=None, shuffle=True)
train_input_fn = tf.estimator.inputs.numpy_input_fn(
{"x": x_train}, y_train, batch_size=4, num_epochs=1000, shuffle=False)
eval_input_fn = tf.estimator.inputs.numpy_input_fn(
{"x": x_eval}, y_eval, batch_size=4, num_epochs=1000, shuffle=False)
# train
estimator.train(input_fn=input_fn, steps=1000)
# Here we evaluate how well our model did.
train_metrics = estimator.evaluate(input_fn=train_input_fn)
eval_metrics = estimator.evaluate(input_fn=eval_input_fn)
print("train metrics: %r"% train_metrics)
print("eval metrics: %r"% eval_metrics)
执行上述代码,结果如下:
train metrics: {'loss': 1.227995e-11, 'global_step': 1000}
eval metrics: {'loss': 0.01010036, 'global_step': 1000}
你会注意到的一点是我们编写的函数model_fn()与之前手动首先的线性回归训练模型非常类似。这里就不再多做解释了。
参考文献
【1】https://www.tensorflow.org/get_started/get_started
【2】http://pan.baidu.com/s/1c1Ofiak
(本文完)