从机器学习到无人驾驶
上QQ阅读APP看书,第一时间看更新

2.5 TensorFlow核心API

TensorFlow中的API主要是围绕利用TensorFlow框架进行机器学习这一核心目标构建的,包括模型构建、学习过程控制、数据输入输出、数据预处理等功能,本节的主要目的是进一步展示以Keras为主的TensorFlow高阶API的主要功能和能力,另一方面也会介绍TensorFlow低阶API中的张量等核心概念。讲解低阶API主要基于两点考虑:一方面高阶API将一些参数和过程封装于框架本身,利用标准或者平均最优进行了固定,这样做虽然能够满足基本的业务需要,但是在实际业务场景中有可能多有掣肘;另一方面,前面介绍过Keras是自1.4版本才正式内置于TensorFlow中的,很多早期资料并没有使用Keras API,而是使用了TensorFlow的低阶API,因此掌握低阶API也是非常重要的。同时,本书会在后续例子中使用低阶API,甚至使用NumPy API完成一些例子,以使读者能够全面掌握Python机器学习开发技术。

2.5.1 TensorFlow低级API

1. 张量

张量(Tensor)是TensorFlow中的核心数据表示方案,任何模型的构建起点都会构建模型的输入输出张量。一个张量由一组形成阵列(任意维数)的原始值组成。张量的阶是它的维数,而它的形式是一个整数元组,指定了阵列每个维度的长度。比如[[[1., 2., 3.]], [[7., 8., 9.]]]是一个3阶张量,张量的形式可以用[2, 1, 3]数组来表示。

2. 图和会话

利用TensorFlow低阶API进行机器学习实践可以分为两部分:第一部分是机器学习模型的设计,这一部分使用的技术就是图(Graph),又称计算图;第二部分是运行设计模型,得到模型的最优解,这一部分使用的技术是会话(Session)。打个比方,比如我们要确定一条直线,第一步是写出直线方程:Y=kX+b,第二步是利用(X,Y)数据集求出k、b的值。TensorFlow框架中的这种设计保持了和机器学习数学过程的一致性,使开发者更加容易理解并实践。

仔细分析计算图,我们可以认为计算图是排列成一个图的一系列TensorFlow指令。计算图中含有两种类型的对象:一种是操作(Operator),表示图的节点,描述了消耗和生成张量的计算;第二种是张量(Tensor),表示图的边缘节点,代表将流经图的值。简单来说,操作是过程中的节点,张量是输入输出节点。

要评估张量,需要实例化一个tf.Session对象(非正式名称为会话)。会话会封装TensorFlow运行时的状态,并运行TensorFlow操作。如果说tf.Graph像一个.py文件,那么tf.Session就像一个Python可执行对象。

3. 层

构建模型的时候要决定算法的运行方法,在TensorFlow模型构建的过程中,你会有一种直观感受,这个过程非常像我们小时候堆叠积木,堆叠的原材料在TensorFlow中称为层(Layer),不同层的串联结构是当前机器学习中最常用的,同时也是最有效的架构方式,我们将在高阶API中使用适当的方式来描述这个过程,也就是在Hello TensorFlow示例中使用的Sequential。层将变量和作用于它们的操作打包在一起,例如,密集连接层会对每个输出对应的所有输入执行加权和,并应用激活函数(可选)。连接权重和偏差由层对象管理。在一个完整的机器学习层模型的设计使用全流程中,一般会包含层创建、层初始化和层执行3个核心阶段。

(1)创建层

下面的代码会创建一个Dense层,该层会接收一批输入矢量,并为每个矢量生成一个输出值。要将层应用于输入值,请将该层当作函数来调用,例如:

   x = tf.placeholder(tf.float32, shape=[None, 3])
   linear_model = tf.layers.Dense(units=1)
   y = linear_model(x)

层会检查输入数据,以确定其内部变量的大小。因此,我们必须在这里设置x占位符的形式,以便层构建正确大小的权重矩阵。

我们现在已经定义了输出值y的计算,在运行计算之前,还需要处理一个细节,即初始化层。

(2)初始化层

层包含的变量必须先初始化,然后才能使用。尽管可以单独初始化各个变量,但也可以轻松地初始化一个TensorFlow图中的所有变量:

   init = tf.global_variables_initializer()
   sess.run(init)

调用tf.global_variables_initializer仅会创建并返回TensorFlow操作的句柄。当我们使用tf.Session.run运行该操作时,该操作将初始化所有全局变量。需要特别注意的一点是,global_variables_initializer仅会初始化创建初始化程序时图中就存在的变量。因此,应该在构建图表的最后一步添加初始化程序。

(3)执行层

我们现在已经完成了层的初始化,可以像处理任何其他张量一样评估linear_model的输出张量。例如下面的代码:

   print(sess.run(y, {x: [[1, 2, 3],[4, 5, 6]]}))

会生成一个两个元素的输出向量:

   [[-3.41378999]
    [-9.14999008]]
4. 可视化学习过程

虽然机器学习的TensorFlow的Python代码看上去相当不错,但是我们依然有让过程和数据更加直观的冲动,因为大脑对于形象理解本身就是过分溺爱的,更别说有了可视化过程,我们可以在很短的时间内把自己的想法解释清楚。当然,谷歌也是深谙此道,因此很早版本的TensorFlow就内置了一个学习过程可视化的框架TensorBoard。

首先将计算图保存为TensorBoard摘要文件,具体操作如下:

   writer = tf.summary.FileWriter('.')
   writer.add_graph(tf.get_default_graph())

这将在当前目录中生成一个events文件,名称格式如下:

   events.out.tfevents.{timestamp}.{hostname}

现在,在新的终端中使用以下Shell命令启动TensorBoard:

   tensorboard --logdir .

接下来,在浏览器中打开TensorBoard的图页面,应该会看到与图2.18类似的图。

图2.18 TensorBoard张量相加的计算图

此外,我们还能够通过TensorBoard将记录的运算中间数据可视化,通过图表的形式更加容易地对数据趋势做出准确判断。具体事例会在下面的综合实例中进行展示。

5. 低级API综合实例
   import tensorflow as tf

   a = tf.constant(3.1, name='a')
   x = tf.placeholder(tf.float32, name='x')
   w = tf.Variable(2.0, name='w')
   result = tf.add(a, w*x)
   tf.summary.scalar('result', result)

   sess = tf.Session()
   merged = tf.summary.merge_all()
   init = tf.global_variables_initializer()
   graph = sess.graph
   writer = tf.summary.FileWriter('./log', graph)

   sess.run(init)
   for i in range(100):
      summary_, result_ = sess.run([merged, result], feed_dict={x:float(i)})
      writer.add_summary(summary_, i)

   sess.close()

这个例子中首先构建了一个ax+b的表达式,然后通过TensorFlow会话进行了计算,实例中通过TensorFlow的summary模块进行可视化跟踪。程序运行完成后,会在当前目录建立一个名称为log的文件夹,在该文件夹下会存储本次运算的日志文件,日志文件名称包含运行时间戳和运行机器名称。通过tensorboard –logdir命令在命令行中启动TensorBoard,启动完成之后打开浏览器(最好是Chrome,其他浏览器特别是IE浏览器会有很多显示问题),在地址栏输入http://localhost:6006就能够看到代码中存储的计算图结构和result张量变化的情况,具体如图2.19和图2.20所示。

图2.19 利用TensorBoard展示的计算图模型

图2.20 利用TensorBoard展示的result张量的变化情况

下面我们来分析一下低阶API实例中的关键代码。按照我们对于机器学习的理解,代码实际上分为模型构建和模型运行两个阶段,在模型构建阶段应该还可以分为运行准备和实际运行两个阶段。模型构建阶段的代码如下:

   a = tf.constant(3.1, name='a')
   x = tf.placeholder(tf.float32, name='x')
   w = tf.Variable(2.0, name='w')
   result = tf.add(a, w*x)

在这里使用TensorFlow的常量、变量和占位符,通过add函数和乘法运算符完成了模型的最终构建。接下来是运行准备阶段,这个阶段的主要工作就是实例化会话对象,并且完成张量的初始化。其代码如下:

   sess = tf.Session()
   init = tf.global_variables_initializer()

最后,Session(会话)的run方法调用就是模型的实际运行,在这里要特别注意的是,我们是通过feed_dict这种类字典的简单数据集调用方式完成训练数据的输入的:

   sess.run(init)
   for i in range(100):
   summary_, result_ = sess.run([merged, result], feed_dict={x:float(i)})
   sess.close()

如果仔细对照代码,就会发现有一些代码我们跳过了,这些代码就是使用Summary进行可视化log数据记录的过程,包括可视化log数据记录的定义:

   tf.summary.scalar('result', result)

可视化log数据记录的整合初始化:

   merged = tf.summary.merge_all()

计算图的存储:

   graph = sess.graph
   writer = tf.summary.FileWriter('./log', graph)

可视化log数据记录的整合初始化的存储:

   writer.add_summary(summary_, i)

2.5.2 TensorFlow高级API

Keras的核心数据结构是Model,是一种组织网络层的方式。最简单的模型是Sequential顺序模型,它由多个网络层线性堆叠。对于更复杂的结构,应该使用Keras函数式API,它允许构建任意的神经网络图。

Keras有以下三个优点:

第一,用户友好。Keras是为人类而不是为机器设计的API。它把用户体验放在首要和中心位置。Keras遵循减少认知困难的最佳实践:它提供一致且简单的API,将常见用例所需的用户操作数量降至最低,并且在用户错误时提供清晰和可操作的反馈。

第二,模块化能力强。模型被理解为由独立的、完全可配置的模块构成的序列或图。这些模块可以尽可能少地限制组装在一起。特别是神经网络层、损失函数、优化器、初始化方法、激活函数、正则化方法,它们都是可以结合起来构建新模型的模块。

第三,易扩展性好。新的模块是很容易添加的(作为新的类和函数),现有的模块已经提供了充足的示例。由于能够轻松地创建可以提高表现力的新模块,因此Keras更加适合高级研究。

下面将展示使用tf.keras模块的一些配置和技巧。

1. 导入tf.keras

tf.keras是TensorFlow对Keras API规范的实现。这是一个用于构建和训练模型的高阶API,包含对TensorFlow特定功能(例如Eager Execution、tf.data管道和Estimator)的顶级支持。tf.keras使TensorFlow更易于使用,并且不会牺牲灵活性和性能。

导入tf.keras以设置TensorFlow程序:

   import tensorflow as tf
   from tensorflow.keras import layers

   print(tf.VERSION)
   print(tf.keras.__version__)

在终端打印结果:

   1.11.0
   2.1.6-tf

tf.keras可以运行任何与Keras兼容的代码,但请注意:

  • 最新版TensorFlow中的tf.keras版本可能与PyPI中的最新Keras版本不同。请查看tf.keras.version。
  • 保存模型的权重时,tf.keras默认采用检查点格式。请传递save_format='h5'以使用HDF5。
2. 构建简单的模型

在Keras中,你可以通过组合层来构建模型。模型(通常)是由层构成的图,最常见的模型类型是层的堆叠:tf.keras.Sequential模型。

例如,构建一个简单的全连接网络(多层感知器),可运行以下代码:

   model = tf.keras.Sequential()
   # Adds a densely-connected layer with 64 units to the model:
   model.add(layers.Dense(64, activation='relu'))
   # Add another:
   model.add(layers.Dense(64, activation='relu'))
   # Add a softmax layer with 10 output units:
   model.add(layers.Dense(10, activation='softmax'))
3. 配置层

我们可以使用很多tf.keras.layers,它们具有一些相同的构造函数参数。

  • activation:设置层的激活函数。此参数由内置函数的名称指定,或指定为可调用对象。默认情况下,系统不会应用任何激活函数。
  • kernel_initializer和bias_initializer:创建层权重(核和偏差)的初始化方案。此参数是一个名称或可调用对象,默认为Glorot uniform初始化器。
  • kernel_regularizer和bias_regularizer:应用层权重(核和偏差)的正则化方案,例如L1或L2正则化。默认情况下,系统不会应用正则化函数。

以下代码使用构造函数参数实例化tf.keras.layers. Dense层:

   # Create a sigmoid layer:
   layers.Dense(64, activation='sigmoid')
   # Or:
   layers.Dense(64, activation=tf.sigmoid)

   # A linear layer with L1 regularization of factor 0.01 applied to the kernel
matrix:
   layers.Dense(64, kernel_regularizer=tf.keras.regularizers.l1(0.01))

   # A linear layer with L2 regularization of factor 0.01 applied to the bias vector:
   layers.Dense(64, bias_regularizer=tf.keras.regularizers.l2(0.01))

   # A linear layer with a kernel initialized to a random orthogonal matrix:
   layers.Dense(64, kernel_initializer='orthogonal')

   # A linear layer with a bias vector initialized to 2.0s:
   layers.Dense(64, bias_initializer=tf.keras.initializers.constant(2.0))
4. 训练和评估

构建好模型后,可通过调用compile方法配置该模型的学习流程,代码如下:

   model = tf.keras.Sequential([
   # Adds a densely-connected layer with 64 units to the model:
   layers.Dense(64, activation='relu'),
   # Add another:
   layers.Dense(64, activation='relu'),
   # Add a softmax layer with 10 output units:
   layers.Dense(10, activation='softmax')])

   model.compile(optimizer=tf.train.AdamOptimizer(0.001),
               loss='categorical_crossentropy',
               metrics=['accuracy'])

tf.keras.Model.compile采用了以下3个重要参数。

  • optimizer:此对象会指定训练过程。从tf.train模块向其传递优化器实例,例如tf.train.AdamOptimizer、tf.train.RMSPropOptimizer或tf.train.GradientDescentOptimizer。
  • loss:要在优化期间最小化的函数。常见选择包括均方误差(Mean Squared Error, MSE)、categorical_crossentropy和binary_crossentropy。损失函数由名称或通过从tf.keras.losses模块传递可调用对象来指定。
  • metrics:用于监控训练。它是tf.keras.metrics模块中的字符串名称或可调用对象。

以下代码展示了配置模型以进行训练的两个示例:

   # Configure a model for mean-squared error regression.
   model.compile(optimizer=tf.train.AdamOptimizer(0.01),
               loss='mse',     # mean squared error
               metrics=['mae'])  # mean absolute error

   # Configure a model for categorical classification.
   model.compile(optimizer=tf.train.RMSPropOptimizer(0.01),
               loss=tf.keras.losses.categorical_crossentropy,
               metrics=[tf.keras.metrics.categorical_accuracy])