TensorFlow 机器学习秘籍第二版:1~5

2023-04-23 11:28:14 浏览数 (3)

一、TensorFlow 入门

在本章中,我们将介绍一些基本的秘籍,以便了解 TensorFlow 的工作原理以及如何访问本书的数据和其他资源。

到本章结束时,您应该了解以下内容:

  • TensorFlow 如何工作
  • 声明变量和张量
  • 使用占位符和变量
  • 使用矩阵
  • 声明操作符
  • 实现激活函数
  • 使用数据源
  • 其他资源

介绍

谷歌的 TensorFlow 引擎有一种解决问题的独特方式。这种独特的方式使我们能够非常有效地解决机器学习问题。机器学习几乎用于生活和工作的所有领域,但一些更着名的领域是计算机视觉,语音识别,语言翻译,医疗保健等等。我们将介绍了解 TensorFlow 如何运行的基本步骤,并最终在本书后面构建生产代码技术。这些基础知识对于理解本书其余部分的秘籍非常重要。

TensorFlow 如何工作

起初,TensorFlow 中的计算可能看起来不必要地复杂化。但有一个原因:由于 TensorFlow 如何处理计算,开发更复杂的算法相对容易。该秘籍将指导我们完成 TensorFlow 算法的伪代码。

准备

目前,TensorFlow 在 Linux,macOS 和 Windows 上受支持。本书的代码已经在 Linux 系统上创建和运行,但也应该在任何其他系统上运行。该书的代码可在 GitHub 以及 Packt 仓库中找到。

在本书中,我们只关注 TensorFlow 的 Python 库包装器,尽管 TensorFlow 的大多数原始核心代码都是用 C 编写的。本书将使用 Python 3.6 和 TensorFlow 1.10.0 。虽然 TensorFlow 可以在 CPU 上运行,但是如果在 GPU 上处理,大多数算法运行得更快,并且在具有 Nvidia Compute Capability v4.0 (推荐 v5.1)的显卡上支持。

TensorFlow 的流行 GPU 是 Nvidia Tesla 架构和具有至少 4 GB 视频 RAM 的 Pascal 架构。要在 GPU 上运行,您还需要下载并安装 Nvidia CUDA 工具包以及版本 5.x 。

本章中的一些秘籍将依赖于当前安装的 SciPy,NumPy 和 scikit-learn Python 包。这些随附的包也包含在 Anaconda 包中。

操作步骤

在这里,我们将介绍 TensorFlow 算法的一般流程。大多数秘籍将遵循以下大纲:

  1. 导入或生成数据集:我们所有的机器学习算法都依赖于数据集。在本书中,我们将生成数据或使用外部数据集源。有时,最好依赖生成的数据,因为我们只想知道预期的结果。大多数情况下,我们将访问给定秘籍的公共数据集。有关访问这些数据集的详细信息,请参见本章第 8 节的其他资源。
  2. 转换和正则化数据:通常,输入数据集不会出现在图片中。 TensorFlow 期望我们需要转换 TensorFlow,以便它们获得可接受的形状。数据通常不在我们的算法所期望的正确维度或类型中。在我们使用之前,我们必须转换数据。大多数算法也期望归一化数据,我们也会在这里看一下。 TensorFlow 具有内置函数,可以为您正则化数据,如下所示:
代码语言:javascript复制
import tensorflow as tf
data = tf.nn.batch_norm_with_global_normalization(...) 
  1. 将数据集划分为训练集,测试集和验证集:我们通常希望在我们训练过的不同集上测试我们的算法。此外,许多算法需要超参数调整,因此我们留出一个验证集来确定最佳的超参数集。
  2. 设置算法参数(超参数):我们的算法通常有一组参数,我们在整个过程中保持不变。例如,这可以是我们选择的迭代次数,学习率或其他固定参数。将这些一起初始化是一种良好的做法,以便读者或用户可以轻松找到它们,如下所示:
代码语言:javascript复制
learning_rate = 0.01 
batch_size = 100 
iterations = 1000
  1. 初始化变量和占位符:TensorFlow 依赖于知道它能够和不能修改的内容。 TensorFlow 将在优化期间修改/调整变量(模型权重/偏差)以最小化损失函数。为此,我们通过占位符提供数据。我们需要初始化变量和占位符的大小和类型,以便 TensorFlow 知道会发生什么。 TensorFlow 还需要知道期望的数据类型。对于本书的大部分内容,我们将使用float32。 TensorFlow 还提供float64float16。请注意,用于精度的更多字节会导致算法速度变慢,但使用较少会导致精度降低。请参阅以下代码:
代码语言:javascript复制
a_var = tf.constant(42) 
x_input = tf.placeholder(tf.float32, [None, input_size]) 
y_input = tf.placeholder(tf.float32, [None, num_classes]) 
  1. 定义模型结构:在获得数据并初始化变量和占位符之后,我们必须定义模型。这是通过构建计算图来完成的。我们将在第 2 章,TensorFlow 方法中更详细地讨论计算图 TensorFlow 秘籍中的运算中的计算图。此示例的模型将是线性模型(y = mx b):
代码语言:javascript复制
y_pred = tf.add(tf.mul(x_input, weight_matrix), b_matrix) 
  1. 声明损失函数:定义模型后,我们必须能够评估输出。这是我们宣布损失函数的地方。损失函数非常重要,因为它告诉我们预测距实际值有多远。在第 2 章,TensorFlow 方法中的实现反向传播秘籍中更详细地探讨了不同类型的损失函数。在这里,我们实现了 n 点的均方误差,即: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QFzZchmR-1681566813061)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/dedb314e-baf6-4580-83d0-9fb217fbe829.png)]
代码语言:javascript复制
loss = tf.reduce_mean(tf.square(y_actual - y_pred)) 
  1. 初始化和训练模型:现在我们已经完成了所有工作,我们需要创建图实例,通过占位符输入数据,让 TensorFlow 更改变量以更好地预测我们的训练数据。这是初始化计算图的一种方法:
代码语言:javascript复制
with tf.Session(graph=graph) as session: 
... 
session.run(...) 
... 
Note that we can also initiate our graph with:
session = tf.Session(graph=graph) 
session.run(...) 
  1. 评估模型:一旦我们构建并训练了模型,我们应该通过查看通过某些指定标准对新数据的处理程度来评估模型。我们对训练和测试装置进行评估,这些评估将使我们能够看到模型是否过拟合。我们将在后面的秘籍中解决这个问题。
  2. 调整超参数:大多数情况下,我们希望根据模型的表现返回并更改一些超参数。然后,我们使用不同的超参数重复前面的步骤,并在验证集上评估模型。
  3. 部署/预测新结果:了解如何对新数据和未见数据进行预测也很重要。一旦我们对他们进行了训练,我们就可以对所有模型进行此操作。

工作原理

在 TensorFlow 中,我们必须先设置数据,变量,占位符和模型,然后才能告诉程序训练和更改变量以改进预测。 TensorFlow 通过计算图完成此任务。这些计算图是没有递归的有向图,这允许计算并行性。为此,我们需要为 TensorFlow 创建一个最小化的损失函数。 TensorFlow 通过修改计算图中的变量来实现此目的。 TensorFlow 知道如何修改变量,因为它跟踪模型中的计算并自动计算变量梯度(如何更改每个变量)以最小化损失。因此,我们可以看到进行更改和尝试不同数据源是多么容易。

另见

有关 TensorFlow 的更多介绍和资源,请参阅官方文档和教程:

  • 一个更好的起点是 Python API 官方文档
  • 还有教程
  • TensorFlow 教程,项目,演示文稿和代码仓库的非官方集合

声明变量和张量

张量是 TensorFlow 用于在计算图上操作的主要数据结构。我们可以将这些张量声明为变量和/或将它们作为占位符提供。要做到这一点,首先,我们必须学习如何创建张量。

tensor是指广义向量或矩阵的数学术语。如果向量是一维的并且矩阵是二维的,则张量是 n 维的(其中n可以是 1,2 或甚至更大)。

准备

当我们创建一个张量并将其声明为变量时,TensorFlow 会在我们的计算图中创建几个图结构。同样重要的是要指出,仅通过创建张量,TensorFlow 不会向计算图中添加任何内容。 TensorFlow 仅在运行初始化变量的操作后执行此操作。有关更多信息,请参阅下一节有关变量和占位符的内容。

操作步骤

在这里,我们将介绍我们可以在 TensorFlow 中创建张量的主要方法:

1.固定张量:

    • 在下面的代码中,我们创建了一个零填充张量:
代码语言:javascript复制
zero_tsr = tf.zeros([row_dim, col_dim])
    • 在下面的代码中,我们创建了一个填充张量:
代码语言:javascript复制
ones_tsr = tf.ones([row_dim, col_dim]) 
    • 在下面的代码中,我们创建了一个常量填充张量:
代码语言:javascript复制
filled_tsr = tf.fill([row_dim, col_dim], 42) 
    • 在下面的代码中,我们从现有常量中创建一个张量:
代码语言:javascript复制
constant_tsr = tf.constant([1,2,3])

请注意,tf.constant()函数可用于将值广播到数组中,通过编写tf.constant(42, [row_dim, col_dim])来模仿tf.fill()的行为。

  1. 相似形状的张量:我们还可以根据其他张量的形状初始化变量,如下所示:
代码语言:javascript复制
zeros_similar = tf.zeros_like(constant_tsr) 
ones_similar = tf.ones_like(constant_tsr) 

请注意,由于这些张量依赖于先前的张量,我们必须按顺序初始化它们。尝试一次初始化所有张量将导致错误。有关变量和占位符,请参阅下一节末尾的“更多”小节。

  1. 序列张量:TensorFlow 允许我们指定包含定义间隔的张量。以下函数与 NumPy 的linspace()输出和range()输出非常相似。请参阅以下函数:
代码语言:javascript复制
linear_tsr = tf.linspace(start=0, stop=1, start=3) 

得到的张量具有[0.0,0.5,1.0]的序列。请注意,此函数包含指定的停止值。有关更多信息,请参阅以下函数:

代码语言:javascript复制
integer_seq_tsr = tf.range(start=6, limit=15, delta=3) 

结果是序列[6,9,12]。请注意,此函数不包括限制值。

  1. 随机张量:以下生成的随机数来自均匀分布:
代码语言:javascript复制
randunif_tsr = tf.random_uniform([row_dim, col_dim], minval=0, maxval=1) 

注意,这种随机均匀分布来自包含minval但不包括maxvalminval >= x < maxval)的区间。

要从正态分布中获取随机抽取的张量,可以运行以下代码:

代码语言:javascript复制
randnorm_tsr = tf.random_normal([row_dim, col_dim], mean=0.0, stddev=1.0) 

有时候我们想要生成在某些范围内保证的正常随机值。truncated_normal()函数总是在指定均值的两个标准偏差内选择正常值:

代码语言:javascript复制
runcnorm_tsr = tf.truncated_normal([row_dim, col_dim], mean=0.0, stddev=1.0) 

我们可能也对随机化数组条目感兴趣。要做到这一点,有两个函数可以帮助我们:random_shuffle()random_crop()。以下代码执行此操作:

代码语言:javascript复制
shuffled_output = tf.random_shuffle(input_tensor) 
cropped_output = tf.random_crop(input_tensor, crop_size) 

在本书的后面,我们将有兴趣随机裁剪大小(高度,宽度,3)的图像,其中有三种颜色光谱。要修复cropped_output中的大小,您必须在该大小中为其指定最大大小:

代码语言:javascript复制
cropped_image = tf.random_crop(my_image, [height/2, width/2, 3]) 

工作原理

一旦我们决定如何创建张量,我们也可以通过在Variable()函数中包含张量来创建相应的变量,如下所示(下一节将详细介绍):

代码语言:javascript复制
my_var = tf.Variable(tf.zeros([row_dim, col_dim])) 

更多

我们不仅限于内置函数:我们可以使用convert_to_tensor()函数将任何 NumPy 数组转换为 Python 列表,或将常量转换为张量。注意,如果我们希望概括函数内部的计算,该函数也接受张量作为输入。

使用占位符和变量

占位符和变量是在 TensorFlow 中使用计算图的关键工具。我们必须了解它们之间的区别以及何时最好地利用它们对我们有利。

准备

与数据最重要的区别之一是它是占位符还是变量。变量是算法的模型参数,TensorFlow 跟踪如何更改这些参数以优化算法。占位符是允许您提供特定类型和形状的数据的对象,或者取决于计算图的结果,例如计算的预期结果。

操作步骤

创建变量的主要方法是使用Variable()函数,该函数将张量作为输入并输出变量。这只是声明,我们仍然需要初始化变量。初始化是将变量与相应方法放在计算图上的内容。以下是创建和初始化变量的示例:

代码语言:javascript复制
my_var = tf.Variable(tf.zeros([2,3])) 
sess = tf.Session() 
initialize_op = tf.global_variables_initializer() 
sess.run(initialize_op) 

要在创建和初始化变量后查看计算图是什么样的,请参阅此秘籍的以下部分。占位符只是保持数据的位置以输入图。占位符从会话中的feed_dict参数获取数据。要将占位符放入图中,我们必须对占位符执行至少一个操作。在下面的代码片段中,我们初始化图,将x声明为占位符(预定义大小),并将y定义为x上的标识操作,它只返回x。然后,我们创建数据以提供给x占位符并运行身份操作。代码如下所示,结果图如下:

代码语言:javascript复制
sess = tf.Session() 
x = tf.placeholder(tf.float32, shape=[2,2]) 
y = tf.identity(x) 
x_vals = np.random.rand(2,2) 
sess.run(y, feed_dict={x: x_vals}) 
# Note that sess.run(x, feed_dict={x: x_vals}) will result in a self-referencing error. 

值得注意的是,TensorFlow 不会在馈送字典中返回自引用占位符。换句话说,在下图中运行sess.run(x, feed_dict={x: x_vals})将返回误差。

工作原理

将变量初始化为零张量的计算图如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tiYSQoax-1681566813062)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/9aed31ea-b420-4fb7-9ed3-c05a7a15d926.png)]

图 1:变量

在这里,我们只用一个变量就可以看到计算图的详细信息,并将其全部初始化为零。灰色阴影区域是对所涉及的操作和常数的非常详细的视图。细节较少的主要计算图是右上角灰色区域之外的较小图。有关创建和可视化图的更多详细信息,请参阅第 10 章的第一部分,将 TensorFlow 转换为生产。类似地,可以在下图中看到将 NumPy 数组送入占位符的计算图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RHMzUkVg-1681566813063)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/3cb49bac-be51-46c3-ac43-589c2f04b799.png)]

图 2:初始化占位符的计算图

灰色阴影区域是对所涉及的操作和常数的非常详细的视图。细节较少的主要计算图是右上角灰色区域之外的较小图。

更多

在计算图运行期间,我们必须告诉 TensorFlow 何时初始化我们创建的变量。虽然每个变量都有一个initializer方法,但最常用的方法是使用辅助函数,即global_variables_initializer()。此函数在图中创建一个初始化我们创建的所有变量的操作,如下所示:

代码语言:javascript复制
initializer_op = tf.global_variables_initializer() 

但是如果我们想根据初始化另一个变量的结果来初始化变量,我们必须按照我们想要的顺序初始化变量,如下所示:

代码语言:javascript复制
sess = tf.Session() 
first_var = tf.Variable(tf.zeros([2,3])) 
sess.run(first_var.initializer) 
second_var = tf.Variable(tf.zeros_like(first_var)) 
# 'second_var' depends on the 'first_var'
sess.run(second_var.initializer)

使用矩阵

了解 TensorFlow 如何与矩阵一起工作对于通过计算图来理解数据流非常重要。

值得强调的是矩阵在机器学习(以及一般数学)中的重要性。大多数机器学习算法在计算上表示为矩阵运算。本书未涉及矩阵属性和矩阵代数(线性代数)的数学背景,因此强烈建议读者充分了解矩阵以适应矩阵代数。

准备

许多算法依赖于矩阵运算。 TensorFlow 为我们提供了易于使用的操作来执行此类矩阵计算。对于以下所有示例,我们首先通过运行以下代码来创建图会话:

代码语言:javascript复制
import tensorflow as tf 
sess = tf.Session() 

操作步骤

我们将按如下方式处理秘籍:

  1. 创建矩阵:我们可以从 NumPy 数组或嵌套列表创建二维矩阵,正如我们在创建和使用张量秘籍中所描述的那样。我们还可以使用张量创建函数并为zeros()ones()truncated_normal()等函数指定二维形状。 TensorFlow 还允许我们使用diag()函数从一维数组或列表创建对角矩阵,如下所示:
代码语言:javascript复制
identity_matrix = tf.diag([1.0, 1.0, 1.0]) 
A = tf.truncated_normal([2, 3]) 
B = tf.fill([2,3], 5.0) 
C = tf.random_uniform([3,2]) 
D = tf.convert_to_tensor(np.array([[1., 2., 3.],[-3., -7., -1.],[0., 5., -2.]])) 
print(sess.run(identity_matrix)) 
[[ 1.  0.  0.] 
 [ 0.  1.  0.] 
 [ 0.  0.  1.]] 
print(sess.run(A)) 
[[ 0.96751703  0.11397751 -0.3438891 ] 
 [-0.10132604 -0.8432678   0.29810596]] 
print(sess.run(B)) 
[[ 5.  5.  5.] 
 [ 5.  5.  5.]] 
print(sess.run(C)) 
[[ 0.33184157  0.08907614] 
 [ 0.53189191  0.67605299] 
 [ 0.95889051 0.67061249]] 
print(sess.run(D)) 
[[ 1.  2.  3.] 
 [-3. -7. -1.] 
 [ 0.  5. -2.]] 

请注意,如果我们再次运行sess.run(C),我们将重新初始化随机变量并最终得到不同的随机值。

  1. 加法,减法和乘法:要添加,减去或相乘相同维度的矩阵,TensorFlow 使用以下函数:
代码语言:javascript复制
print(sess.run(A B)) 
[[ 4.61596632  5.39771316  4.4325695 ] 
 [ 3.26702736  5.14477345  4.98265553]] 
print(sess.run(B-B)) 
[[ 0.  0.  0.] 
 [ 0.  0.  0.]] 
Multiplication 
print(sess.run(tf.matmul(B, identity_matrix))) 
[[ 5.  5.  5.] 
 [ 5.  5.  5.]] 

值得注意的是,matmul()函数具有参数,用于指定是否在乘法之前转置参数或每个矩阵是否稀疏。

请注意,未明确定义矩阵除法。虽然许多人将矩阵划分定义为乘以逆,但与实数除法相比,它基本上是不同的。

  1. 转置:转置矩阵(翻转列和行),如下所示:
代码语言:javascript复制
print(sess.run(tf.transpose(C))) 
[[ 0.67124544  0.26766731  0.99068872] 
 [ 0.25006068  0.86560275  0.58411312]] 

同样,值得一提的是,重新初始化为我们提供了与以前不同的值。

  1. 行列式:要计算行列式,请使用以下内容:
代码语言:javascript复制
print(sess.run(tf.matrix_determinant(D))) 
-38.0 
  1. 逆:要查找方阵的逆,请参阅以下内容:
代码语言:javascript复制
print(sess.run(tf.matrix_inverse(D))) 
[[-0.5        -0.5        -0.5       ] 
 [ 0.15789474  0.05263158  0.21052632] 
 [ 0.39473684  0.13157895  0.02631579]] 

只有当矩阵是对称正定时,逆方法才基于 Cholesky 分解。如果矩阵不是对称正定,那么它基于 LU 分解。

  1. 分解:对于 Cholesky 分解,请使用以下内容:
代码语言:javascript复制
print(sess.run(tf.cholesky(identity_matrix))) 
[[ 1.  0.  1.] 
 [ 0.  1.  0.] 
 [ 0.  0.  1.]] 
  1. 特征值和特征向量:对于特征值和特征向量,请使用以下代码:
代码语言:javascript复制
print(sess.run(tf.self_adjoint_eig(D)) 
[[-10.65907521  -0.22750691   2.88658212] 
 [  0.21749542   0.63250104  -0.74339638] 
 [  0.84526515   0.2587998    0.46749277] 
 [ -0.4880805    0.73004459   0.47834331]] 

注意,self_adjoint_eig()函数输出第一行中的特征值和剩余向量中的后续向量。在数学中,这被称为矩阵的特征分解。

工作原理

TensorFlow 为我们提供了开始使用数值计算并将这些计算添加到图中的所有工具。对于简单的矩阵运算,这种表示法可能看起来很重。请记住,我们正在将这些操作添加到图中,并告诉 TensorFlow 哪些张量运行这些操作。虽然现在看起来似乎很冗长,但它有助于我们理解后面章节中的符号,这种计算方式将使我们更容易实现目标。

声明操作符

现在,我们必须了解我们可以添加到 TensorFlow 图的其他操作。

准备

除了标准算术运算之外,TensorFlow 还为我们提供了更多我们应该了解的操作以及如何在继续操作之前使用它们。同样,我们可以通过运行以下代码来创建图会话:

代码语言:javascript复制
import tensorflow as tf 
sess = tf.Session() 

操作步骤

TensorFlow 对张量有标准操作,即add()sub()mul()div()。请注意,除非另有说明,否则本节中的所有操作都将按元素评估输入:

  1. TensorFlow 提供了div()和相关函数的一些变体。
  2. 值得一提的是div()返回与输入相同的类型。这意味着如果输入是整数,它确实返回了分区的底线(类似于 Python2)。要返回 Python3 版本,它在分割之前将整数转换为浮点数并始终返回浮点数,TensorFlow 提供truediv()函数,如下所示:
代码语言:javascript复制
print(sess.run(tf.div(3, 4))) 
0 
print(sess.run(tf.truediv(3, 4))) 
0.75 
  1. 如果我们有浮点数并想要整数除法,我们可以使用floordiv()函数。请注意,这仍然会返回一个浮点数,但它会向下舍入到最接近的整数。这个函数如下:
代码语言:javascript复制
print(sess.run(tf.floordiv(3.0,4.0))) 
0.0 
  1. 另一个重要函数是mod()。此函数返回除法后的余数。它如下:
代码语言:javascript复制
print(sess.run(tf.mod(22.0, 5.0))) 
2.0 
  1. 两个张量之间的交叉积通过cross()函数实现。请记住,交叉乘积仅针对两个三维向量定义,因此它只接受两个三维张量。以下代码说明了这种用法:
代码语言:javascript复制
print(sess.run(tf.cross([1., 0., 0.], [0., 1., 0.]))) 
[ 0.  0.  1.0]
  1. 这是一个更常见的数学函数的紧凑列表。所有这些函数都以元素方式运行:

abs()

输入张量的绝对值

ceil()

输入张量的向上取整函数

cos()

输入张量的余弦函数

exp()

输入张量的基于e指数

floor()

输入张量的向下取整函数

inv()

输入张量的乘法逆(1 / x)

log()

输入张量的自然对数

maximum()

两个张量的逐元素最大值

minimum()

两个张量的逐元素最小值

neg()

输入张量的反转

pow()

第一个张量元素的第二个张量元素次幂

round()

输入张量的舍入

rsqrt()

输入张量的平方根倒数

sign()

返回 -1,0 或 1,具体取决于张量的符号

sin()

输入张量的正弦函数

sqrt()

输入张量的平方根

square()

输入张量的平方

  1. 专业数学函数:有一些特殊的数学函数可以在机器学习中使用,值得一提,TensorFlow 为它们提供了内置函数。同样,除非另有说明,否则这些函数在元素方面运行:

digamma()

Psi 函数,lgamma()函数的导数

erf()

张量的逐元素高斯误差函数

erfc()

张量的互补误差函数

igamma()

较低正则化的不完全伽玛函数

igammac()

较高正则化的不完全伽马函数

lbeta()

Beta 函数绝对值的自然对数

lgamma()

伽玛函数绝对值的自然对数

squared_difference()

两个张量之间差异的平方

工作原理

重要的是要知道我们可以使用哪些函数,以便我们可以将它们添加到我们的计算图中。我们将主要关注前面的函数。我们还可以生成许多不同的自定义函数作为前面的组合,如下所示:

代码语言:javascript复制
# Tangent function (tan(pi/4)=1) 
print(sess.run(tf.tan(3.1416/4.)))
1.0 

更多

如果我们希望向我们未在此处列出的图添加其他操作,我们必须从前面的函数创建自己的操作。以下是我们之前未使用的操作示例,我们可以将其添加到图中。我们选择使用以下代码添加自定义多项式函数3x^2 - x 10

代码语言:javascript复制
def custom_polynomial(value): 
    return tf.sub(3 * tf.square(value), value)   10
print(sess.run(custom_polynomial(11))) 
362 

实现激活函数

激活函数是神经网络近似非线性输出并适应非线性特征的关键。他们将非线性运算引入神经网络。如果我们小心选择了哪些激活函数以及放置它们的位置,它们是非常强大的操作,我们可以告诉 TensorFlow 适合和优化。

准备

当我们开始使用神经网络时,我们将定期使用激活函数,因为激活函数是任何神经网络的重要组成部分。激活函数的目标只是调整权重和偏差。在 TensorFlow 中,激活函数是作用于张量的非线性操作。它们是以与先前的数学运算类似的方式运行的函数。激活函数有很多用途,但主要的概念是它们在对输出进行归一化的同时在图中引入了非线性。使用以下命令启动 TensorFlow 图:

代码语言:javascript复制
import tensorflow as tf 
sess = tf.Session() 

操作步骤

激活函数存在于 TensorFlow 中的神经网络(nn)库中。除了使用内置激活函数外,我们还可以使用 TensorFlow 操作设计自己的函数。我们可以导入预定义的激活函数(import tensorflow.nn as nn)或显式,并在函数调用中写入nn。在这里,我们选择明确每个函数调用:

  1. 被整流的线性单元,称为 ReLU,是将非线性引入神经网络的最常见和最基本的方式。这个函数叫做max(0,x)。它是连续的,但不是平滑的。它看起来如下:
代码语言:javascript复制
print(sess.run(tf.nn.relu([-3., 3., 10.]))) 
[  0.  3.  10.] 
  1. 有时我们会想要限制前面的 ReLU 激活函数的线性增加部分。我们可以通过将max(0,x)函数嵌套到min()函数中来实现。 TensorFlow 具有的实现称为 ReLU6 函数。这被定义为min(max(0,x),6)。这是硬 sigmoid 函数的一个版本,并且计算速度更快,并且不会消失(无穷小接近零)或爆炸值。当我们在第 8 章,卷积神经网络和第 9 章,循环神经网络中讨论更深层的神经网络时,这将派上用场。它看起来如下:
代码语言:javascript复制
print(sess.run(tf.nn.relu6([-3., 3., 10.]))) 
[ 0. 3. 6.]
  1. Sigmoid 函数是最常见的连续和平滑激活函数。它也被称为逻辑函数,其形式为1 / (1 exp(-x))。 Sigmoid 函数不经常使用,因为它倾向于在训练期间将反向传播项置零。它看起来如下:
代码语言:javascript复制
print(sess.run(tf.nn.sigmoid([-1., 0., 1.]))) 
[ 0.26894143  0.5         0.7310586 ] 

我们应该知道一些激活函数不是以零为中心的,例如 sigmoid。这将要求我们在大多数计算图算法中使用之前将数据归零。

  1. 另一个平滑激活函数是超切线。超正切函数与 sigmoid 非常相似,除了它的范围在 0 和 1 之间,它的范围在 -1 和 1 之间。该函数具有双曲正弦与双曲余弦的比率的形式。写这个的另一种方法是(exp(x) - exp(-x)) / (exp(x) exp(-x))。此激活函数如下:
代码语言:javascript复制
print(sess.run(tf.nn.tanh([-1., 0., 1.]))) 
[-0.76159418  0.         0.76159418 ] 
  1. softsign函数也可用作激活函数。该函数的形式是x / (|x| 1)softsign函数应该是符号函数的连续(但不是平滑)近似。请参阅以下代码:
代码语言:javascript复制
print(sess.run(tf.nn.softsign([-1., 0., -1.]))) 
[-0.5  0.   0.5] 
  1. 另一个函数是softplus函数,是 ReLU 函数的流畅版本。该函数的形式是log(exp(x) 1)。它看起来如下:
代码语言:javascript复制
print(sess.run(tf.nn.softplus([-1., 0., -1.]))) 
[ 0.31326166  0.69314718  1.31326163] 

当输入增加时,softplus函数变为无穷大,而softsign函数变为 1.然而,当输入变小时,softplus函数接近零,softsign函数变为 -1。

  1. 指数线性单元(ELU)与 softplus 函数非常相似,只是底部渐近线为 -1 而不是 0.如果x < 0其他x,则形式为exp(x) 1。它看起来如下:
代码语言:javascript复制
print(sess.run(tf.nn.elu([-1., 0., -1.]))) 
[-0.63212055  0.          1.        ] 

工作原理

这些激活函数是我们将来可以在神经网络或其他计算图中引入非线性的方法。重要的是要注意我们的网络中我们使用激活函数的位置。如果激活函数的范围在 0 和 1(sigmoid)之间,则计算图只能输出 0 到 1 之间的值。如果激活函数在内部并隐藏在节点之间,那么我们想要知道当我们传递它们时,范围可以在我们的张量上。如果我们的张量被缩放到平均值为零,我们将希望使用一个激活函数来保持尽可能多的方差在零附近。这意味着我们想要选择激活函数,例如双曲正切(tanh)或 softsign。如果张量都被缩放为正数,那么我们理想地选择一个激活函数来保留正域中的方差。

更多

以下是两个绘图,说明了不同的激活函数。下图显示了 ReLU,ReLU6,softplus,指数 LU,sigmoid,softsign 和双曲正切函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lNR0V2s0-1681566813063)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/2a1256eb-1993-4e62-a561-48577ebcfec2.png)]

图 3:softplus,ReLU,ReLU6 和指数 LU 的激活函数

在这里,我们可以看到四个激活函数:softplus,ReLU,ReLU6 和指数 LU。这些函数在零的左边展平并线性增加到零的右边,但 ReLU6 除外,其最大值为 6:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ve7xHOtM-1681566813063)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/d2167d3b-96f9-46d1-87c4-79d0839b3745.png)]

图 4:Sigmoid,双曲正切(tanh)和 softsign 激活函数

这是 sigmoid,双曲正切(tanh)和 softsign 激活函数。这些激活函数都是平滑的,具有S n形状。请注意,这些函数有两个水平渐近线。

使用数据源

对于本书的大部分内容,我们将依赖数据集的使用来适应机器学习算法。本节介绍如何通过 TensorFlow 和 Python 访问每个数据集。

一些数据源依赖于外部网站的维护,以便您可以访问数据。如果这些网站更改或删除此数据,则可能需要更新本节中的以下某些代码。您可以在作者的 GitHub 页面上找到更新的代码。

准备

在 TensorFlow 中,我们将使用的一些数据集构建在 Python 库中,一些将需要 Python 脚本下载,一些将通过互联网手动下载。几乎所有这些数据集都需要有效的互联网连接,以便您可以检索它们。

操作步骤

  1. 鸢尾数据:该数据集可以说是机器学习中使用的最经典的数据集,也可能是所有统计数据。它是一个数据集,可以测量三种不同类型鸢尾花的萼片长度,萼片宽度,花瓣长度和花瓣宽度:山鸢尾,弗吉尼亚和杂色鸢尾。总共有 150 个测量值,这意味着每个物种有 50 个测量值。要在 Python 中加载数据集,我们将使用 scikit-learn 的数据集函数,如下所示:
代码语言:javascript复制
from sklearn import datasets 
iris = datasets.load_iris() 
print(len(iris.data)) 
150 
print(len(iris.target)) 
150 
print(iris.data[0]) # Sepal length, Sepal width, Petal length, Petal width 
[ 5.1 3.5 1.4 0.2] 
print(set(iris.target)) # I. setosa, I. virginica, I. versicolor 
{0, 1, 2} 
  1. 出生体重数据:该数据最初来自 Baystate Medical Center,Springfield,Mass 1986(1)。该数据集包含出生体重的测量以及母亲和家族病史的其他人口统计学和医学测量。有 11 个变量的 189 个观测值。以下代码显示了如何在 Python 中访问此数据:
代码语言:javascript复制
import requests
birthdata_url = 'https://github.com/nfmcclure/tensorflow_cookbook/raw/master/01_Introduction/07_Working_with_Data_Sources/birthweight_data/birthweight.dat' 
birth_file = requests.get(birthdata_url) 
birth_data = birth_file.text.split('rn') 
birth_header = birth_data[0].split('t') 
birth_data = [[float(x) for x in y.split('t') if len(x)>=1] for y in birth_data[1:] if len(y)>=1]
print(len(birth_data)) 
189 
print(len(birth_data[0])) 
9
  1. 波士顿住房数据:卡内基梅隆大学在其 StatLib 库中维护着一个数据集库。这些数据可通过加州大学欧文分校的机器学习库轻松访问。有 506 个房屋价值观察,以及各种人口统计数据和住房属性(14 个变量)。以下代码显示了如何通过 Keras 库在 Python 中访问此数据:
代码语言:javascript复制
from keras.datasets import boston_housing
(x_train, y_train), (x_test, y_test) = boston_housing.load_data()
housing_header = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV'] 
print(x_train.shape[0]) 
404 
print(x_train.shape[1]) 
13 
  1. MNIST 手写数据:MNIST(混合国家标准与技术研究院)数据集是较大的 NIST 手写数据库的子集。 MNIST 手写数据集托管在 Yann LeCun 的网站上。它是一个包含 70,000 个单元数字图像(0-9)的数据库,其中带标签的约 60,000 个用于训练集,10,000 个用于测试集。 TensorFlow 在图像识别中经常使用此数据集,TensorFlow 提供了访问此数据的内置函数。在机器学习中,提供验证数据以防止过拟合(目标泄漏)也很重要。因此,TensorFlow 将 5000 列训练图像留在验证集中。以下代码显示了如何在 Python 中访问此数据:
代码语言:javascript复制
from tensorflow.examples.tutorials.mnist import input_data 
mnist = input_data.read_data_sets("MNIST_data/"," one_hot=True) 
print(len(mnist.train.images)) 
55000 
print(len(mnist.test.images)) 
10000 
print(len(mnist.validation.images)) 
5000 
print(mnist.train.labels[1,:]) # The first label is a 3 
[ 0.  0.  0.  1.  0.  0.  0.  0.  0.  0.] 
  1. 垃圾邮件文本数据。 UCI 的机器学习数据集库还包含垃圾短信文本消息数据集。我们可以访问此.zip文件并获取垃圾邮件文本数据,如下所示:
代码语言:javascript复制
import requests 
import io 
from zipfile import ZipFile 
zip_url = 'http://archive.ics.uci.edu/ml/machine-learning-databases/00228/smsspamcollection.zip' 
r = requests.get(zip_url) 
z = ZipFile(io.BytesIO(r.content)) 
file = z.read('SMSSpamCollection') 
text_data = file.decode() 
text_data = text_data.encode('ascii',errors='ignore') 
text_data = text_data.decode().split('n') 
text_data = [x.split('t') for x in text_data if len(x)>=1] 
[text_data_target, text_data_train] = [list(x) for x in zip(*text_data)] 
print(len(text_data_train)) 
5574 
print(set(text_data_target)) 
{'ham', 'spam'} 
print(text_data_train[1]) 
Ok lar... Joking wif u oni... 
  1. 电影评论数据:来自康奈尔大学的 Bo Pang 发布了一个电影评论数据集,将评论分为好或坏(3)。您可以在以下网站上找到数据。要下载,提取和转换此数据,我们可以运行以下代码:
代码语言:javascript复制
import requests 
import io 
import tarfile 
movie_data_url = 'http://www.cs.cornell.edu/people/pabo/movie-review-data/rt-polaritydata.tar.gz' 
r = requests.get(movie_data_url) 
# Stream data into temp object 
stream_data = io.BytesIO(r.content) 
tmp = io.BytesIO() 
while True: 
    s = stream_data.read(16384) 
    if not s: 
        break 
    tmp.write(s) 
    stream_data.close() 
tmp.seek(0) 
# Extract tar file 
tar_file = tarfile.open(fileobj=tmp, mode="r:gz") 
pos = tar_file.extractfile('rt-polaritydata/rt-polarity.pos') 
neg = tar_file.extractfile('rt-polaritydata/rt-polarity.neg') 
# Save pos/neg reviews (Also deal with encoding) 
pos_data = [] 
for line in pos: 
    pos_data.append(line.decode('ISO-8859-1').encode('ascii',errors='ignore').decode()) 
neg_data = [] 
for line in neg: 
    neg_data.append(line.decode('ISO-8859-1').encode('ascii',errors='ignore').decode()) 
tar_file.close() 
print(len(pos_data)) 
5331 
print(len(neg_data)) 
5331 
# Print out first negative review 
print(neg_data[0]) 
simplistic , silly and tedious . 
  1. CIFAR-10 图像数据:加拿大高级研究院发布了一个图像集,其中包含 8000 万个带标签的彩色图像(每个图像缩放为32 x 32像素)。有 10 种不同的目标类别(飞机,汽车,鸟类等)。 CIFAR-10 是包含 60,000 张图像的子集。训练集中有 50,000 个图像,测试集中有 10,000 个。由于我们将以多种方式使用此数据集,并且因为它是我们较大的数据集之一,因此我们不会在每次需要时运行脚本。要获取此数据集,请导航至此链接并下载 CIFAR-10 数据集。我们将在相应的章节中介绍如何使用此数据集。
  2. 莎士比亚文本数据的作品:古登堡项目(5)是一个发布免费书籍电子版的项目。他们一起编辑了莎士比亚的所有作品。以下代码显示了如何通过 Python 访问此文本文件:
代码语言:javascript复制
import requests 
shakespeare_url = 'http://www.gutenberg.org/cache/epub/100/pg100.txt' 
# Get Shakespeare text 
response = requests.get(shakespeare_url) 
shakespeare_file = response.content 
# Decode binary into string 
shakespeare_text = shakespeare_file.decode('utf-8') 
# Drop first few descriptive paragraphs. 
shakespeare_text = shakespeare_text[7675:] 
print(len(shakespeare_text)) # Number of characters 
5582212
  1. 英语 - 德语句子翻译数据:Tatoeba 项目 收集多种语言的句子翻译。他们的数据已在 CC 协议下发布。根据这些数据,ManyThings.org 编译了可供下载的文本文件中的句子到句子的翻译。在这里,我们将使用英语 - 德语翻译文件,但您可以将 URL 更改为您想要使用的语言:
代码语言:javascript复制
import requests 
import io 
from zipfile import ZipFile 
sentence_url = 'http://www.manythings.org/anki/deu-eng.zip' 
r = requests.get(sentence_url) 
z = ZipFile(io.BytesIO(r.content)) 
file = z.read('deu.txt') 
# Format Data 
eng_ger_data = file.decode() 
eng_ger_data = eng_ger_data.encode('ascii',errors='ignore') 
eng_ger_data = eng_ger_data.decode().split('n') 
eng_ger_data = [x.split('t') for x in eng_ger_data if len(x)>=1] 
[english_sentence, german_sentence] = [list(x) for x in zip(*eng_ger_data)] 
print(len(english_sentence)) 
137673 
print(len(german_sentence)) 
137673 
print(eng_ger_data[10]) 
['I' won!, 'Ich habe gewonnen!'] 

工作原理

当在秘籍中使用这些数据集之一时,我们将引用您到本节并假设数据以上一节中描述的方式加载。如果需要进一步的数据转换或预处理,那么这些代码将在秘籍本身中提供。

另见

以下是我们在本书中使用的数据资源的其他参考:

  • Hosmer,D.W.,Lemeshow,S. 和 Sturdivant,R.X.,2013,应用逻辑回归:第三版
  • Lichman,M.,2013,UCI 机器学习库,Irvine,CA:加州大学信息与计算机科学学院
  • Bo Pang,Lillian Lee 和 Shivakumar Vaithyanathan,竖起大拇指?使用机器学习技术的情感分类,EMNLP 2002 年会议录
  • Krizhevsky,2009 年,从微小图像学习多层特征
  • 古腾堡项目,2016 年 4 月

其他资源

在本节中,您将找到对学习和使用 TensorFlow 有很大帮助的其他链接,文档资源和教程。

准备

在学习如何使用 TensorFlow 时,有助于知道在哪里寻求帮助或指针。本节列出了运行 TensorFlow 和解决问题的资源。

操作步骤

以下是 TensorFlow 资源列表:

  • 本书的代码可在 Packt 仓库在线获取。
  • TensorFlow Python API 官方文档位于这里。这里有 TensorFlow 中所有函数,对象和方法的文档和示例。
  • TensorFlow 的官方教程非常详尽。它们位于这里。他们开始覆盖图像识别模型,并通过 Word2Vec,RNN 模型和序列到序列模型进行工作。他们还有额外的教程来生成分形和解决 PDE 系统。请注意,他们不断向此集合添加更多教程和示例。
  • TensorFlow 的 GitHub 官方仓库可通过此链接获得。在这里,您可以查看开源代码,甚至可以根据需要分叉或克隆最新版本的代码。如果导航到 issues 目录,您还可以查看当前提交的问题。
  • Dockerhub 的此链接提供了一个由 TensorFlow 保持最新的公共 Docker 容器。
  • Stack Overflow 是社区帮助的重要来源。 TensorFlow 有一个标签。随着 TensorFlow 越来越受欢迎,这个标签似乎越来越受关注。要查看此标记上的活动,请访问此链接。
  • 虽然 TensorFlow 非常灵活且可以用于很多事情,但 TensorFlow 最常见的用途是深度学习。为了理解深度学习的基础,基础数学如何运作,以及在深度学习方面发展更多直觉,谷歌创建了一个在 Udacity 上可用的在线课程。要注册并参加视频讲座课程,请访问此链接。
  • TensorFlow 还建立了一个网站,您可以在视觉上探索训练神经网络,同时更改参数和数据集。访问此链接,探讨不同的设置如何影响神经网络的训练。
  • Geoffrey Hinton 通过 Coursera 教授一个名为神经网络的机器学习在线课程。
  • 斯坦福大学有一个在线教学大纲和详细的视觉识别卷积神经网络课程笔记。

二、TensorFlow 的方式

在本章中,我们将介绍 TensorFlow 如何运作的关键组件。然后,我们将它们组合在一起以创建一个简单的分类器并评估结果。到本章结束时,您应该了解以下内容:

  • 计算图中的操作
  • 分层嵌套操作
  • 使用多个层
  • 实现损失函数
  • 实现反向传播
  • 使用批量和随机训练
  • 把所有东西结合在一起
  • 评估模型

介绍

现在我们已经介绍了 TensorFlow 如何创建张量,并使用变量和占位符,我们将介绍如何在计算图中对这些对象进行操作。由此,我们可以设置一个简单的分类器,看看它的表现如何。

此外,请记住,本书中的当前和更新代码可以在 GitHub 上在线获取。

计算图中的操作

现在我们可以将对象放入计算图中,我们将介绍对这些对象起作用的操作。

准备

要启动图,我们加载 TensorFlow 并创建一个会话,如下所示:

代码语言:javascript复制
import tensorflow as tf 
sess = tf.Session() 

操作步骤

在这个例子中,我们将结合我们学到的东西并将列表中的每个数字提供给图中的操作并打印输出:

首先,我们宣布我们的张量和占位符。在这里,我们将创建一个 NumPy 数组来提供给我们的操作:

代码语言:javascript复制
import numpy as np 
x_vals = np.array([1., 3., 5., 7., 9.]) 
x_data = tf.placeholder(tf.float32) 
m_const = tf.constant(3.) 
my_product = tf.multiply(x_data, m_const) 
for x_val in x_vals: 
    print(sess.run(my_product, feed_dict={x_data: x_val})) 

上述代码的输出如下:

代码语言:javascript复制
3.0 
9.0 
15.0 
21.0 
27.0 

工作原理

本节中的代码在计算图上创建数据和操作。下图是计算图的样子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hfSHVMfE-1681566813064)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/c92d9c7b-a07f-48c5-9717-01ced228ffe9.png)]

图 1:x_data占位符以及乘法常数输入到乘法运算中

对嵌套操作分层

在本文中,我们将学习如何在同一计算图上放置多个操作。

准备

了解如何将操作链接在一起非常重要。这将在计算图中设置分层操作。对于演示,我们将占位符乘以两个矩阵,然后执行加法。我们将以三维 NumPy 数组的形式提供两个矩阵:

代码语言:javascript复制
import tensorflow as tf 
sess = tf.Session() 

操作步骤

同样重要的是要注意数据在通过时如何改变形状。我们将输入两个大小为3 x 5的 NumPy 数组。我们将每个矩阵乘以一个大小常数5 x 1,,这将产生一个大小为3 x 1的矩阵。然后我们将其乘以1 x 1矩阵,再次产生3 x 1矩阵。最后,我们在最后添加3 x 1矩阵,如下所示:

  1. 首先,我们创建要输入的数据和相应的占位符:
代码语言:javascript复制
my_array = np.array([[1., 3., 5., 7., 9.], 
                   [-2., 0., 2., 4., 6.], 
                   [-6., -3., 0., 3., 6.]]) 
x_vals = np.array([my_array, my_array   1]) 
x_data = tf.placeholder(tf.float32, shape=(3, 5)) 
  1. 接下来,我们创建将用于矩阵乘法和加法的常量:
代码语言:javascript复制
m1 = tf.constant([[1.], [0.], [-1.], [2.], [4.]]) 
m2 = tf.constant([[2.]]) 
a1 = tf.constant([[10.]]) 
  1. 现在,我们声明操作并将它们添加到图中:
代码语言:javascript复制
prod1 = tf.matmul(x_data, m1) 
prod2 = tf.matmul(prod1, m2) 
add1 = tf.add(prod2, a1)
  1. 最后,我们为图提供数据:
代码语言:javascript复制
for x_val in x_vals: 
    print(sess.run(add1, feed_dict={x_data: x_val})) 
[[ 102.] 
 [  66.] 
 [  58.]] 
[[ 114.] 
 [  78.] 
 [  70.]] 

工作原理

我们刚刚创建的计算图可以使用 TensorBoard 进行可视化。 TensorBoard 是 TensorFlow 的一个功能,它允许我们可视化计算图和这些图中的值。与其他机器学习框架不同,这些功能是本机提供的。要了解如何完成此操作,请参阅第 11 章中的 TensorBoard 秘籍中的可视化图,更多内容使用 TensorFlow。以下是我们的分层图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-usXCeT6J-1681566813064)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/e14ff4a8-12d8-42ba-968f-87b50c1afc6d.png)]

图 2:向上传播到图时的数据大小

更多

在通过图运行数据之前,我们必须声明数据形状并知道操作的结果形状。这并非总是如此。可能有一两个我们事先不知道的维度,或者一些可能变化的维度。为实现此目的,我们将可以改变(或未知)的维度或维度指定为值None。例如,要使先前的数据占位符具有未知数量的列,我们将编写以下行:

代码语言:javascript复制
x_data = tf.placeholder(tf.float32, shape=(3,None))

这允许我们打破矩阵乘法规则,但我们仍然必须遵守乘法常数必须具有相同行数的事实。当我们将数据输入图时,我们可以动态生成或重新整形x_data。当我们以不同批次大小的多批次提供数据时,这将在后面的章节中派上用场。

虽然使用None作为大小允许我们使用可变大小的大小,但在填充大小时始终建议尽可能明确。如果我们将大小标准化为固定大小,那么我们应该明确地将该大小写为大小。建议将None用作维度,以限制数据的批量大小(或我们一次计算的数据点数)。

使用多个层

现在我们已经介绍了多个操作,我们将介绍如何连接具有通过它们传播的数据的各个层。

准备

在本文中,我们将介绍如何最好地连接各种层,包括自定义层。我们将生成和使用的数据将代表小型随机图像。最好通过一个简单的例子来理解这种类型的操作,看看我们如何使用一些内置层来执行计算。我们将探索的第一层称为移动窗口。我们将在 2D 图像上执行小的移动窗口平均值,然后第二层将是自定义操作层。

在本节中,我们将看到计算图可能变得庞大且难以查看。为了解决这个问题,我们还将介绍命名操作和创建层范围的方法。首先,加载numpytensorflow,然后使用以下命令创建图:

代码语言:javascript复制
import tensorflow as tf 
import numpy as np 
sess = tf.Session() 

操作步骤

我们按如下方式处理秘籍:

  1. 首先,我们使用 NumPy 创建示例 2D 图像。该图像将是4 x 4像素图像。我们将在四个方面创建它;第一个和最后一个维度的大小为 1。请注意,某些 TensorFlow 图像函数将在四维图像上运行。这四个维度是图像编号,高度,宽度和通道,为了使其成为一个具有一个通道的图像,我们将两个维度设置为1,如下所示:
代码语言:javascript复制
x_shape = [1, 4, 4, 1] 
x_val = np.random.uniform(size=x_shape) 
  1. 现在,我们必须在图中创建占位符,我们可以在其中提供示例图像,如下所示:
代码语言:javascript复制
x_data = tf.placeholder(tf.float32, shape=x_shape) 
  1. 为了在我们的4 x 4图像上创建一个移动窗口平均值,我们将使用一个内置函数,它将在形状窗口2 x 2上收敛一个常量。我们将使用的函数是conv2d();此函数在图像处理和 TensorFlow 中非常常用。此函数采用窗口的分段产品和我们指定的过滤器。我们还必须在两个方向上指定移动窗口的步幅。在这里,我们将计算四个移动窗口平均值:左上角,右上角,左下角和右下角四个像素。我们通过创建2 x 2窗口并在每个方向上具有长度2的步幅来实现这一点。为取平均值,我们将2 x 2窗口用0.25的常数卷积,如下:
代码语言:javascript复制
my_filter = tf.constant(0.25, shape=[2, 2, 1, 1]) 
my_strides = [1, 2, 2, 1] 
mov_avg_layer= tf.nn.conv2d(x_data, my_filter, my_strides, 
                            padding='SAME', name='Moving_Avg_Window') 

请注意,我们还使用函数的name参数命名此层Moving_Avg_Window。 为了计算卷积层的输出大小,我们可以使用下面的公式: Output = (W - F 2P) / S 1,其中W是输入大小,F是过滤器大小,P是零填充,并且S是步幅。

  1. 现在,我们定义一个自定义层,它将在移动窗口平均值的2 x 2输出上运行。自定义函数将首先将输入乘以另一个2 x 2矩阵张量,然后为每个条目添加 1。在此之后,我们取每个元素的 sigmoid 并返回2 x 2矩阵。由于矩阵乘法仅对二维矩阵进行操作,因此我们需要删除大小为1的图像的额外维度。 TensorFlow 可以使用内置的squeeze()函数执行此操作。在这里,我们定义新层:
代码语言:javascript复制
    def custom_layer(input_matrix): 
        input_matrix_sqeezed = tf.squeeze(input_matrix) 
        A = tf.constant([[1., 2.], [-1., 3.]]) 
        b = tf.constant(1., shape=[2, 2]) 
        temp1 = tf.matmul(A, input_matrix_sqeezed) 
        temp = tf.add(temp1, b) # Ax   b 
        return tf.sigmoid(temp) 
  1. 现在,我们必须将新层放在图上。我们将使用命名范围执行此操作,以便它在计算图上可识别和可折叠/可扩展,如下所示:
代码语言:javascript复制
with tf.name_scope('Custom_Layer') as scope: 
    custom_layer1 = custom_layer(mov_avg_layer) 
  1. 现在,我们只需输入4 x 4图像来替换占位符并告诉 TensorFlow 运行图,如下所示:
代码语言:javascript复制
print(sess.run(custom_layer1, feed_dict={x_data: x_val})) 
[[ 0.91914582 0.96025133] 
 [ 0.87262219  0.9469803 ]] 

工作原理

通过命名操作和层范围,可视化绘图看起来更好。我们可以折叠和展开自定义层,因为我们在命名范围内创建了它。在下图中,请参阅左侧的折叠版本和右侧的展开版本:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9nG29oYh-1681566813064)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/16011597-1adf-4b5c-b12e-38eed5a3e596.png)]

图 3:具有两层的计算图

第一层名为Moving_Avg_Window。第二个是名为Custom_Layer的操作集合。它在左侧折叠并在右侧展开。

实现损失函数

损失函数对于机器学习算法非常重要。它们测量模型输出与目标(真值)值之间的距离。在这个秘籍中,我们在 TensorFlow 中展示了各种损失函数实现。

准备

为了优化我们的机器学习算法,我们需要评估结果。评估 TensorFlow 中的结果取决于指定损失函数。损失函数告诉 TensorFlow 预测与期望结果相比有多好或多坏。在大多数情况下,我们将有一组数据和一个目标来训练我们的算法。损失函数将目标与预测进行比较,并给出两者之间的数值距离。

对于这个秘籍,我们将介绍我们可以在 TensorFlow 中实现的主要损失函数。

要了解不同损失函数的运行方式,我们将在此秘籍中绘制它们。我们将首先启动一个计算图并加载matplotlib,一个 Python 绘图库,如下所示:

代码语言:javascript复制
import matplotlib.pyplot as plt 
import tensorflow as tf 

操作步骤

  1. 首先,我们将讨论回归的损失函数,这意味着预测连续的因变量。首先,我们将创建一个预测序列和一个作为张量的目标。我们将在 -1 和 1 之间输出 500 个值的结果。有关输出的绘图,请参阅“工作原理”部分。使用以下代码:
代码语言:javascript复制
x_vals = tf.linspace(-1., 1., 500) 
target = tf.constant(0.) 
  1. L2 范数损失也称为欧几里德损失函数。它只是到目标的距离的平方。在这里,我们将计算损失函数,就像目标为零一样。 L2 范数是一个很大的损失函数,因为它在目标附近非常弯曲,并且算法可以使用这个事实来越慢地收敛到目标,越接近零。我们可以按如下方式实现:
代码语言:javascript复制
l2_y_vals = tf.square(target - x_vals) 
l2_y_out = sess.run(l2_y_vals)

TensorFlow 具有 L2 范数的内置形式,称为nn.l2_loss()。这个函数实际上是 L2 范数的一半。换句话说,它与前一个相同,但除以 2。

  1. L1 范数损失也称为绝对损失函数。我们不是平衡差异,而是取绝对值。 L1 范数对于异常值比 L2 范数更好,因为对于较大的值,它不是那么陡峭。需要注意的一个问题是 L1 范数在目标处不平滑,这可能导致算法收敛不好。它看起来如下:
代码语言:javascript复制
l1_y_vals = tf.abs(target - x_vals) 
l1_y_out = sess.run(l1_y_vals) 
  1. 伪 Huber 损失是 Huber 损失函数的连续且平滑的近似。这种损失函数试图通过在目标附近凸起并且对于极值不太陡峭来充分利用 L1 和 L2 范数。表格取决于额外的参数delta,它决定了它的陡峭程度。我们将绘制两种形式,delta1 = 0.25delta2 = 5,以显示差异,如下所示:
代码语言:javascript复制
delta1 = tf.constant(0.25) 
phuber1_y_vals = tf.multiply(tf.square(delta1), tf.sqrt(1.    
                        tf.square((target - x_vals)/delta1)) - 1.) 
phuber1_y_out = sess.run(phuber1_y_vals) 
delta2 = tf.constant(5.) 
phuber2_y_vals = tf.multiply(tf.square(delta2), tf.sqrt(1.    
                        tf.square((target - x_vals)/delta2)) - 1.) 
phuber2_y_out = sess.run(phuber2_y_vals) 

现在,我们继续讨论分类问题的损失函数。分类损失函数用于在预测分类结果时评估损失。通常,我们的类别类型的输出是 0 到 1 之间的实数值。然后,我们选择截止值(通常选择 0.5)并且如果数字高于截止值,则将结果分类为该类别。在这里,我们考虑分类输出的各种损失函数:

  1. 首先,我们需要重新定义我们的预测(x_vals)和target。我们将保存输出并在下一节中绘制它们。使用以下内容:
代码语言:javascript复制
x_vals = tf.linspace(-3., 5., 500) 
target = tf.constant(1.) 
targets = tf.fill([500,], 1.)
  1. 铰链损失主要用于支持向量机,但也可用于神经网络。它旨在计算两个目标类 1 和 -1 之间的损失。在下面的代码中,我们使用目标值1,因此我们的预测越接近 1,损失值越低:
代码语言:javascript复制
hinge_y_vals = tf.maximum(0., 1. - tf.multiply(target, x_vals)) 
hinge_y_out = sess.run(hinge_y_vals) 
  1. 二元情形的交叉熵损失有时也称为逻辑损失函数。它是在我们预测两个 0 或 1 类时出现的。我们希望测量从实际类(0 或 1)到预测值的距离,预测值通常是介于 0 和 1 之间的实数。为了测量这个距离,我们可以使用信息论中的交叉熵公式,如下:
代码语言:javascript复制
xentropy_y_vals = - tf.multiply(target, tf.log(x_vals)) - tf.multiply((1. - target), tf.log(1. - x_vals)) 
xentropy_y_out = sess.run(xentropy_y_vals) 
  1. Sigmoid 交叉熵损失与之前的损失函数非常相似,除了我们在将它们置于交叉熵损失之前使用 sigmoid 函数转换 x 值,如下所示:
代码语言:javascript复制
xentropy_sigmoid_y_vals = tf.nn.sigmoid_cross_entropy_with_logits_v2(logits=x_vals, labels=targets) 
xentropy_sigmoid_y_out = sess.run(xentropy_sigmoid_y_vals) 
  1. 加权交叉熵损失是 Sigmoid 交叉熵损失的加权版本。我们对积极目标给予了重视。举个例子,我们将正面目标加权 0.5,如下:
代码语言:javascript复制
weight = tf.constant(0.5) 
xentropy_weighted_y_vals = tf.nn.weighted_cross_entropy_with_logits(logits=x_vals, targets=targets, pos_weight=weight) 
xentropy_weighted_y_out = sess.run(xentropy_weighted_y_vals) 
  1. Softmax 交叉熵损失在非标准化输出上运行。当只有一个目标类别而不是多个目标类别时,此函数用于测量损失。因此,函数通过 softmax 函数将输出转换为概率分布,然后根据真实概率分布计算损失函数,如下所示:
代码语言:javascript复制
unscaled_logits = tf.constant([[1., -3., 10.]]) 
target_dist = tf.constant([[0.1, 0.02, 0.88]]) 
softmax_xentropy = tf.nn.softmax_cross_entropy_with_logits_v2(logits=unscaled_logits, labels=target_dist) 
print(sess.run(softmax_xentropy)) 
[ 1.16012561] 
  1. 稀疏 softmax 交叉熵损失与前一个相同,除了目标是概率分布,它是哪个类别为真的索引。我们只传递真值的类别的索引,而不是稀疏的全零目标向量,其值为 1,如下所示:
代码语言:javascript复制
unscaled_logits = tf.constant([[1., -3., 10.]]) 
sparse_target_dist = tf.constant([2]) 
sparse_xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=unscaled_logits, labels=sparse_target_dist) 
print(sess.run(sparse_xentropy)) 
[ 0.00012564] 

工作原理

以下是如何使用matplotlib绘制回归损失函数:

代码语言:javascript复制
x_array = sess.run(x_vals) 
plt.plot(x_array, l2_y_out, 'b-', label='L2 Loss') 
plt.plot(x_array, l1_y_out, 'r--', label='L1 Loss') 
plt.plot(x_array, phuber1_y_out, 'k-.', label='P-Huber Loss (0.25)') 
plt.plot(x_array, phuber2_y_out, 'g:', label='P-Huber Loss (5.0)') 
plt.ylim(-0.2, 0.4) 
plt.legend(loc='lower right', prop={'size': 11}) 
plt.show() 

我们得到以下图作为上述代码的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ARVkIoCo-1681566813065)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/8475c505-e8c7-49ba-a1d1-b6764a1b45b0.png)]

图 4:绘制各种回归损失函数

以下是如何使用matplotlib绘制各种分类损失函数:

代码语言:javascript复制
x_array = sess.run(x_vals) 
plt.plot(x_array, hinge_y_out, 'b-''', label='Hinge Loss''') 
plt.plot(x_array, xentropy_y_out, 'r--''', label='Cross' Entropy Loss') 
plt.plot(x_array, xentropy_sigmoid_y_out, 'k-.''', label='Cross' Entropy Sigmoid Loss') 
plt.plot(x_array, xentropy_weighted_y_out, g:''', label='Weighted' Cross Enropy Loss (x0.5)') 
plt.ylim(-1.5, 3) 
plt.legend(loc='lower right''', prop={'size''': 11}) 
plt.show() 

我们从前面的代码中得到以下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KYAF6ijX-1681566813065)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/ac1fb199-5d83-474a-aad9-efeefb7e4caf.png)]Figure 5: Plots of classification loss functions

更多

这是一个总结我们描述的不同损失函数的表:

损失函数

任务

优点

缺点

L2

回归

更稳定

不太强大

L1

回归

更强大

不太稳定

伪 Huber

回归

更强大,更稳定

还有一个参数

Hinge

分类

创建 SVM 中使用的最大边距

受到异常值影响的无限损失

交叉熵

分类

更稳定

无限损失,不那么强大

剩余的分类损失函数都与交叉熵损失的类型有关。交叉熵 sigmoid 损失函数用于未缩放的对率,并且优于计算 sigmoid 然后交叉熵,因为 TensorFlow 具有更好的内置方式来处理数字边缘情况。 softmax 交叉熵和稀疏 softmax 交叉熵也是如此。

这里描述的大多数分类损失函数用于两类预测。通过对每个预测/目标上的交叉熵项求和,可以将其扩展到多个类。

评估模型时还需要考虑许多其他指标。以下列出了一些需要考虑的事项:

模型指标

描述

R 平方(确定系数)

对于线性模型,这是因变量的方差比例,由独立数据解释。对于具有大量特征的模型,请考虑使用调整后的 R 平方。

均方根误差

对于连续模型,它通过平均平方误差的平方根来测量预测与实际之间的差异。

混淆矩阵

对于分类模型,我们查看预测类别与实际类别的矩阵。一个完美的模型具有沿对角线的所有计数。

召回

对于分类模型,这是所有预测阳性的真阳性分数。

精确

对于分类模型,这是所有实际阳性的真阳性分数。

F-得分

对于分类模型,这是精度和召回的调和平均值。

实现反向传播

使用 TensorFlow 的一个好处是它可以跟踪操作并根据反向传播自动更新模型变量。在本文中,我们将介绍如何在训练机器学习模型时将此方面用于我们的优势。

准备

现在,我们将介绍如何以最小化损失函数的方式更改模型中的变量。我们已经学会了如何使用对象和操作,并创建了测量我们的预测和目标之间距离的损失函数。现在,我们只需告诉 TensorFlow 如何通过我们的计算图反向传播误差来更新变量并最小化损失函数。这是通过声明优化函数完成的。一旦我们声明了一个优化函数,TensorFlow 将通过并计算出图中所有计算的反向传播项。当我们输入数据并最小化损失函数时,TensorFlow 将相应地修改图中的变量。

对于这个秘籍,我们将做一个非常简单的回归算法。我们将从正态分布中抽取随机数,均值为 1,标准差为 0.1。然后,我们将通过一个操作来运行数字,这将是它们乘以变量A。由此,损失函数将是输出和目标之间的 L2 范数,其总是值 10。理论上,A 的最佳值将是数字 10,因为我们的数据将具有平均值 1。

第二个例子是一个非常简单的二分类算法。在这里,我们将从两个正态分布N(-1,1)N(3,1)生成 100 个数字。来自N(-1, 1)的所有数字将在目标等级 0 中,并且来自N(3, 1)的所有数字将在目标等级 1 中。用于区分这些数字的模型将是翻译的 Sigmoid 函数。换句话说,模型将是sigmoid(x A),其中A是我们将适合的变量。从理论上讲,A 将等于 -1。我们得到这个数字是因为如果m1m2是两个正常函数的平均值,那么加到它们以将它们等距离转换为零的值将是 - (m1 m2) / 2。我们将在第二个例子中看到 TensorFlow 如何达到该数字。

虽然指定良好的学习率有助于算法的收敛,但我们还必须指定一种优化。从前两个例子中,我们使用标准梯度下降。这是通过GradientDescentOptimizer() TensorFlow 函数实现的。

操作步骤

以下是回归示例的工作原理:

  1. 我们首先加载numpytensorflow数值 Python 包:
代码语言:javascript复制
import numpy as np 
import tensorflow as tf 
  1. 现在,我们启动图会话:
代码语言:javascript复制
sess = tf.Session() 
  1. 接下来,我们创建数据,占位符和A变量:
代码语言:javascript复制
x_vals = np.random.normal(1, 0.1, 100) 
y_vals = np.repeat(10., 100) 
x_data = tf.placeholder(shape=[1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[1])) 
  1. 我们将乘法运算添加到图中:
代码语言:javascript复制
my_output = tf.mul(x_data, A) 
  1. 接下来,我们在乘法输出和目标数据之间添加 L2 Loss函数:
代码语言:javascript复制
loss = tf.square(my_output - y_target)
  1. 现在,我们必须声明一种优化图中变量的方法。我们声明了一种优化算法。大多数优化算法需要知道每次迭代中的步进距离。该距离由学习率控制。如果我们的学习率太大,我们的算法可能会超过最小值,但如果我们的学习率太小,我们的算法可能需要很长时间才能收敛;这与消失和爆炸的梯度问题有关。学习率对收敛有很大影响,我们将在本节末尾讨论这个问题。虽然我们在这里使用标准梯度下降算法,但是有许多不同的优化算法可以不同地运行,并且可以根据问题做得更好或更差。有关不同优化算法的精彩概述,请参阅 Sebastian Ruder 在本文末尾的另见部分中的文章:
代码语言:javascript复制
my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.02)
train_step = my_opt.minimize(loss)
  1. 现在我们可以初始化我们的模型变量:
代码语言:javascript复制
init = tf.global_variable_initializer()
sess.run(init) 

There is a lot of theory on which learning rates are best. This is one of the harder things to figure out in machine learning algorithms. Good papers to read about how learning rates are related to specific optimization algorithms are listed in the See also section at the end of this recipe.

  1. 最后一步是循环我们的训练算法并告诉 TensorFlow 多次训练。我们将这样做 101 次,并且每 25 次迭代打印出结果。为了训练,我们将选择随机xy条目并通过图提供。 TensorFlow 将自动计算损失,并略微改变A偏差以最小化损失:
代码语言:javascript复制
for i in range(100): 
    rand_index = np.random.choice(100) 
    rand_x = [x_vals[rand_index]] 
    rand_y = [y_vals[rand_index]] 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    if (i   1) % 25 == 0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))) 
        print('Loss = '   str(sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}))) 
# Here is the output: 
Step #25 A = [ 6.23402166] 
Loss = 16.3173 
Step #50 A = [ 8.50733757] 
Loss = 3.56651 
Step #75 A = [ 9.37753201] 
Loss = 3.03149 
Step #100 A = [ 9.80041122] 
Loss = 0.0990248 

现在,我们将介绍简单分类示例的代码。如果我们先重置图,我们可以使用相同的 TensorFlow 脚本。请记住,我们将尝试找到一个最佳平移A,它将两个分布转换为原点,而 sigmoid 函数将两个分为两个不同的类:

  1. 首先,我们重置图并重新初始化图会话:
代码语言:javascript复制
from tensorflow.python.framework import ops 
ops.reset_default_graph() 
sess = tf.Session() 
  1. 接下来,我们从两个不同的正态分布N(-1, 1)N(3, 1)中提取数据。我们还将生成目标标签,数据占位符和偏差变量A
代码语言:javascript复制
x_vals = np.concatenate((np.random.normal(-1, 1, 50), np.random.normal(3, 1, 50))) 
y_vals = np.concatenate((np.repeat(0., 50), np.repeat(1., 50))) 
x_data = tf.placeholder(shape=[1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(mean=10, shape=[1])) 

我们将A初始化为大约 10 的值,远离理论值-1。我们这样做的目的是为了说明算法如何从 10 的值收敛到最佳值 -1。

  1. 接下来,我们将转换操作添加到图中。请记住,我们不必将它包装在 sigmoid 函数中,因为损失函数将为我们执行此操作:
代码语言:javascript复制
my_output = tf.add(x_data, A)
  1. 由于特定损失函数需要具有与之关联的额外维度的批量数据(添加的维度,即批次编号),因此我们将使用expand_dims()函数为输出添加额外维度。在下一节中,我们将讨论如何在训练中使用可变大小的批次。现在,我们将再次使用一个随机数据点:
代码语言:javascript复制
my_output_expanded = tf.expand_dims(my_output, 0) 
y_target_expanded = tf.expand_dims(y_target, 0) 
  1. 接下来,我们将初始化我们的一个变量A
代码语言:javascript复制
init = tf.initialize_all_variables() 
sess.run(init) 
  1. 现在,我们宣布我们的损失函数。我们将使用带有未缩放的对率的交叉熵,它使用 sigmoid 函数对它们进行转换。在名为nn.sigmoid_cross_entropy_with_logits()的神经网络包中,TensorFlow 为我们提供了这一函数。如前所述,它希望参数具有特定的维度,因此我们必须相应地使用扩展的输出和目标:
代码语言:javascript复制
xentropy = tf.nn.sigmoid_cross_entropy_with_logits( my_output_expanded, y_target_expanded) 
  1. 与回归示例一样,我们需要向图中添加优化器函数,以便 TensorFlow 知道如何更新图中的偏差变量:
代码语言:javascript复制
my_opt = tf.train.GradientDescentOptimizer(0.05) 
train_step = my_opt.minimize(xentropy) 
  1. 最后,我们循环遍历随机选择的数据点数百次并相应地更新A变量。每 200 次迭代,我们将打印出A的值和损失:
代码语言:javascript复制
for i in range(1400): 
    rand_index = np.random.choice(100) 
    rand_x = [x_vals[rand_index]] 
    rand_y = [y_vals[rand_index]] 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    if (i   1) % 200 == 0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))) 
        print('Loss = '   str(sess.run(xentropy, feed_dict={x_data: rand_x, y_target: rand_y}))) 
Step #200 A = [ 3.59597969] 
Loss = [[ 0.00126199]] 
Step #400 A = [ 0.50947344] 
Loss = [[ 0.01149425]] 
Step #600 A = [-0.50994617] 
Loss = [[ 0.14271219]] 
Step #800 A = [-0.76606178] 
Loss = [[ 0.18807337]] 
Step #1000 A = [-0.90859312] 
Loss = [[ 0.02346182]] 
Step #1200 A = [-0.86169094] 
Loss = [[ 0.05427232]] 
Step #1400 A = [-1.08486211] 
Loss = [[ 0.04099189]] 

工作原理

有关回顾和解释,对于这两个示例,我们执行了以下操作:

  1. 创建了数据。这两个示例都需要通过占位符加载数据。
  2. 初始化占位符和变量。这些是非常相似的数据占位符。变量非常相似,它们都有乘法矩阵A,但第一个分类算法有一个偏差项来找到数据中的分裂。
  3. 创建了损失函数,我们使用 L2 损失进行回归,使用交叉熵损失进行分类。
  4. 定义了一种优化算法。两种算法都使用梯度下降。
  5. 迭代随机数据样本以迭代更新我们的变量。

更多

如前所述,优化算法对学习率的选择很敏感。重要的是要以简洁的方式总结这种选择的效果:

学习率大小

优点缺点

用途

较小的学习率

收敛速度较慢但结果更准确

如果解决方案不稳定,请先尝试降低学习率

学习率更高

不太准确,但收敛速度更快

对于某些问题,有助于防止解决方案停滞不前

有时,标准梯度下降算法会显着卡住或减速。当优化卡在马鞍的平坦点时,可能会发生这种情况。为了解决这个问题,还有另一种算法考虑了动量项,它增加了前一步骤的梯度下降值的一小部分。 TensorFlow 内置了MomentumOptimizer()函数。

另一种变体是为我们模型中的每个变量改变优化器步骤。理想情况下,我们希望为较小的移动变量采取较大的步骤,为较快的变化变量采取较短的步骤。我们不会深入研究这种方法的数学,但这种思想的常见实现称为 Adagrad 算法。该算法考虑了变量梯度的整个历史。 TensorFlow 中的函数称为AdagradOptimizer()

有时候,Adagrad 会过早地强调梯度为零,因为它考虑了整个历史。解决方法是限制我们使用的步数。这样做称为 Adadelta 算法。我们可以使用AdadeltaOptimizer()函数来应用它。

还有一些不同的梯度下降算法的其他实现。对于这些,我们会让读者参考 TensorFlow 文档。

另见

有关优化算法和学习率的一些参考,请参阅以下文章和文章:

  • 另见本章的秘籍如下:
    • 在实现损失函数部分。
    • 在实现反向传播部分。
  • Kingma,D.,Jimmy,L.,Adam:一种随机优化方法,ICLR 2015
  • Ruder,S.,梯度下降优化算法概述,2016
  • Zeiler,M.,ADADelta:一种自适应学习率方法,2012

使用批量和随机训练

虽然 TensorFlow 根据反向传播更新我们的模型变量,但它可以同时操作从一个基准观察到一大批数据的任何事物。在一个训练示例上操作可以使得学习过程非常不稳定,而使用太大的批次可能在计算上是昂贵的。选择正确类型的训练对于使我们的机器学习算法融合到解决方案至关重要。

准备

为了使 TensorFlow 计算反向传播的可变梯度,我们必须测量样本或多个样本的损失。随机训练一次只适用于一个随机抽样的数据 - 目标对,就像我们在上一个秘籍中所做的那样。另一种选择是一次放置大部分训练样例并平均梯度计算的损失。训练批次的大小可以一次变化,直到并包括整个数据集。在这里,我们将展示如何将先前的回归示例(使用随机训练)扩展到批量训练。

我们将首先加载numpymatplotlibtensorflow,然后启动图会话,如下所示:

代码语言:javascript复制
import matplotlib as plt 
import numpy as np 
import tensorflow as tf 
sess = tf.Session() 

操作步骤

我们按如下方式处理秘籍:

  1. 我们将从声明批量大小开始。这将是我们将同时通过计算图提供多少数据观察:
代码语言:javascript复制
batch_size = 20
  1. 接下来,我们在模型中声明数据,占位符和变量。我们在这里做的改变是我们改变了占位符的形状。它们现在是两个维度,第一个维度是None,第二个维度是批次中的数据点数。我们可以明确地将它设置为 20,但我们可以推广并使用None值。同样,正如第 1 章,TensorFlow 入门中所述,我们仍然需要确保维度在模型中运行,这不允许我们执行任何非法矩阵操作:
代码语言:javascript复制
x_vals = np.random.normal(1, 0.1, 100) 
y_vals = np.repeat(10., 100) 
x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[1,1])) 
  1. 现在,我们将操作添加到图中,现在将是矩阵乘法而不是常规乘法。请记住,矩阵乘法不是可交换的,因此我们必须在matmul()函数中以正确的顺序输入矩阵:
代码语言:javascript复制
my_output = tf.matmul(x_data, A) 
  1. 我们的loss函数会发生变化,因为我们必须采用批次中每个数据点的所有 L2 损失的平均值。我们通过将先前的损失输出包装在 TensorFlow 的reduce_mean()函数中来实现:
代码语言:javascript复制
loss = tf.reduce_mean(tf.square(my_output - y_target)) 
  1. 我们像以前一样声明我们的优化器并初始化我们的模型变量,如下所示:
代码语言:javascript复制
my_opt = tf.train.GradientDescentOptimizer(0.02) 
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
  1. 最后,我们将循环并迭代训练步骤以优化算法。这部分与以前不同,因为我们希望能够绘制随时间的损失并比较批次与随机训练的收敛。因此,我们初始化一个列表,每隔五个时间间隔存储一次损失函数:
代码语言:javascript复制
loss_batch = [] 
for i in range(100): 
    rand_index = np.random.choice(100, size=batch_size) 
    rand_x = np.transpose([x_vals[rand_index]]) 
    rand_y = np.transpose([y_vals[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    if (i   1) % 5 == 0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))) 
        temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
        print('Loss = '   str(temp_loss)) 
        loss_batch.append(temp_loss) 
  1. 这是 100 次迭代的最终输出。请注意,A的值有一个额外的维度,因为它现在必须是一个 2D 矩阵:
代码语言:javascript复制
Step #100 A = [[ 9.86720943]] 
Loss = 0. 

工作原理

批量训练和随机训练的优化方法和收敛性不同。找到一个好的批量大小可能很困难。为了了解批量与随机指标之间的收敛程度如何不同,建议读者将批量大小更改为各种级别。以下是保存和记录训练循环中随机损失的代码。只需在上一个秘籍中替换此代码:

代码语言:javascript复制
loss_stochastic = [] 
for i in range(100): 
    rand_index = np.random.choice(100) 
    rand_x = [x_vals[rand_index]] 
    rand_y = [y_vals[rand_index]] 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    if (i   1) % 5 == 0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))) 
        temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
        print('Loss = '   str(temp_loss)) 
        loss_stochastic.append(temp_loss) 

下面是为同一回归问题生成随机和批量损失图的代码:

代码语言:javascript复制
plt.plot(range(0, 100, 5), loss_stochastic, 'b-', label='Stochastic Loss') 
plt.plot(range(0, 100, 5), loss_batch, 'r--', label='Batch' Loss, size=20') 
plt.legend(loc='upper right', prop={'size': 11}) 
plt.show() 

我们得到以下绘图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GpgNaZqr-1681566813065)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/3d734ed6-f40b-44e6-a890-7324ac20a592.png)]

图 6:在 100 次迭代中绘制的随机损失和批量损失(批量大小为 20)。请注意,批次损失更加平滑,随机损失更加不稳定。

更多

训练类型

优点

缺点

随机

随机性可能有助于摆脱局部的最小值。

通常,需要更多迭代才能收敛。

批量

更快地找到最小值。

需要更多资源来计算。

把所有东西结合在一起

在本节中,我们将结合到目前为止所示的所有内容,并为鸢尾数据集创建分类器。

准备

鸢尾数据集在第 1 章,TensorFlow 入门中使用数据源秘籍中有更详细的描述。我们将加载这些数据并制作一个简单的二元分类器来预测花是否是山鸢尾的种类。需要说明的是,这个数据集有三个种类,但我们只能预测一种花是单一种,是否是一种花,给我们一个二元分类器。我们将首先加载库和数据,然后相应地转换目标。

操作步骤

我们按如下方式处理秘籍:

  1. 首先,我们加载所需的库并初始化计算图。注意我们也在这里加载matplotlib,因为我们想在之后绘制结果行:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
from sklearn import datasets 
import tensorflow as tf 
sess = tf.Session() 
  1. 接下来,我们加载鸢尾数据。我们还需要将目标数据转换为 1 或 0,无论目标是否为山鸢尾。由于鸢尾数据集将山鸢尾标记为 0,我们将更改所有目标,值为 0 到 1,其他值全部为 0.我们也将只使用两个特征,花瓣长度和花瓣宽度。这两个特征是每个x-value中的第三和第四个条目:
代码语言:javascript复制
iris = datasets.load_iris() 
binary_target = np.array([1. if x==0 else 0. for x in iris.target]) 
iris_2d = np.array([[x[2], x[3]] for x in iris.data]) 
  1. 让我们声明我们的批量大小,数据占位符和模型变量。请记住,可变批量大小的数据占位符将None作为第一个维度:
代码语言:javascript复制
batch_size = 20 
x1_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
x2_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[1, 1])) 
b = tf.Variable(tf.random_normal(shape=[1, 1])) 

请注意,我们可以通过使用dtype=tf.float32来减少浮点数的字节来提高算法的表现(速度)。

  1. 在这里,我们定义线性模型。模型将采用x2 = x1 * A b的形式,如果我们想要找到该行上方或下方的点,我们会在插入等式x2 - x1 * A - b时看到它们是高于还是低于零。我们将通过取该方程的 sigmoid 并从该方程预测 1 或 0 来实现。请记住,TensorFlow 具有内置 sigmoid 的loss函数,因此我们只需要在 sigmoid 函数之前定义模型的输出:
代码语言:javascript复制
my_mult = tf.matmul(x2_data, A) 
my_add = tf.add(my_mult, b) 
my_output = tf.sub(x1_data, my_add) 
  1. 现在,我们使用 TensorFlow 的内置sigmoid_cross_entropy_with_logits()函数添加 sigmoid 交叉熵损失函数:
代码语言:javascript复制
xentropy = tf.nn.sigmoid_cross_entropy_with_logits(my_output, y_target) 
  1. 我们还必须告诉 TensorFlow 如何通过声明优化方法来优化我们的计算图。我们希望最大限度地减少交叉熵损失。我们还会选择0.05作为我们的学习率:
代码语言:javascript复制
my_opt = tf.train.GradientDescentOptimizer(0.05) 
train_step = my_opt.minimize(xentropy) 
  1. 现在,我们创建一个变量初始化操作并告诉 TensorFlow 执行它:
代码语言:javascript复制
init = tf.global_variables_initializer() 
sess.run(init) 
  1. 现在,我们将训练我们的线性模型 1000 次迭代。我们将提供我们需要的三个数据点:花瓣长度,花瓣宽度和目标变量。每 200 次迭代,我们将打印变量值:
代码语言:javascript复制
for i in range(1000): 
    rand_index = np.random.choice(len(iris_2d), size=batch_size) 
    rand_x = iris_2d[rand_index] 
    rand_x1 = np.array([[x[0]] for x in rand_x]) 
    rand_x2 = np.array([[x[1]] for x in rand_x]) 
    rand_y = np.array([[y] for y in binary_target[rand_index]]) 
    sess.run(train_step, feed_dict={x1_data: rand_x1, x2_data: rand_x2, y_target: rand_y}) 
    if (i   1) % 200 == 0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))   ', b = '   str(sess.run(b)))

Step #200 A = [[ 8.67285347]], b = [[-3.47147632]] 
Step #400 A = [[ 10.25393486]], b = [[-4.62928772]] 
Step #600 A = [[ 11.152668]], b = [[-5.4077611]] 
Step #800 A = [[ 11.81016064]], b = [[-5.96689034]] 
Step #1000 A = [[ 12.41202831]], b = [[-6.34769201]] 
  1. 下一组命令提取模型变量并在绘图上绘制线条。结果绘图在它的工作原理…部分:
代码语言:javascript复制
[[slope]] = sess.run(A) 
[[intercept]] = sess.run(b) 
x = np.linspace(0, 3, num=50) 
ablineValues = [] 
for i in x: 
    ablineValues.append(slope*i intercept) 

setosa_x = [a[1] for i,a in enumerate(iris_2d) if binary_target[i]==1] 
setosa_y = [a[0] for i,a in enumerate(iris_2d) if binary_target[i]==1] 
non_setosa_x = [a[1] for i,a in enumerate(iris_2d) if binary_target[i]==0] 
non_setosa_y = [a[0] for i,a in enumerate(iris_2d) if binary_target[i]==0] 
plt.plot(setosa_x, setosa_y, 'rx', ms=10, mew=2, label='setosa') 
plt.plot(non_setosa_x, non_setosa_y, 'ro', label='Non-setosa') 
plt.plot(x, ablineValues, 'b-') 
plt.xlim([0.0, 2.7]) 
plt.ylim([0.0, 7.1]) 
plt.suptitle('Linear' Separator For I.setosa', fontsize=20) 
plt.xlabel('Petal Length') 
plt.ylabel('Petal Width') 
plt.legend(loc='lower right') 
plt.show() 

工作原理

我们的目标是仅使用花瓣宽度和花瓣长度在山鸢尾点和其他两个物种之间拟合一条线。如果我们绘制点和结果线,我们看到我们已经实现了这个:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DEImofrE-1681566813066)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/922f9297-3d22-4eff-a854-a613cead1b35.png)]

图 7:花瓣宽度与花瓣长度的山鸢尾和其它鸢尾的图;实线是我们在 1000 次迭代后实现的线性分离器

更多

虽然我们实现了用一条线分隔两个类的目标,但它可能不是分离两个类的最佳模型。在第 4 章,支持向量机中,我们将讨论支持向量机,它是在特征空间中分离两个类的更好方法。

另见

  • 有关 scikit-learn 鸢尾花数据集实现的信息,请参阅此链接的文档。

评估模型

我们已经学会了如何在 TensorFlow 中训练回归和分类算法。在此之后,我们必须能够评估模型的预测,以确定它的效果。

准备

评估模型非常重要,每个后续模型都将采用某种形式的模型评估。使用 TensorFlow,我们必须将此函数构建到计算图中,并在我们的模型进行训练时和/或完成训练后调用它。

在训练期间评估模型可以让我们深入了解算法,并可以提供调试,改进或完全更改模型的提示。虽然训练期间的评估并不总是必要的,但我们将展示如何使用回归和分类进行评估。

训练结束后,我们需要量化模型对数据的执行方式。理想情况下,我们有一个单独的训练和测试集(甚至是验证集),我们可以在其上评估模型。

当我们想要评估模型时,我们希望在大批数据点上进行评估。如果我们已经实现了批量训练,我们可以重用我们的模型来对这样的批次进行预测。如果我们实现了随机训练,我们可能必须创建一个可以批量处理数据的单独评估器。

如果我们在loss函数中包含对模型输出的转换,例如sigmoid_cross_entropy_with_logits(),我们必须在计算精度计算的预测时考虑到这一点。不要忘记将此包含在您对模型的评估中。

我们要评估的任何模型的另一个重要方面是它是回归还是分类模型。

回归模型试图预测连续数。目标不是类别,而是所需数量。为了评估这些针对实际目标的回归预测,我们需要对两者之间的距离进行综合测量。大多数情况下,有意义的损失函数将满足这些标准。此秘籍向您展示如何将之前的简单回归算法更改为打印出训练循环中的损失并在结束时评估损失。例如,我们将在本章的先前实现反向传播秘籍中重新审视并重写我们的回归示例。

分类模型基于数字输入预测类别。实际目标是 1 和 0 的序列,我们必须衡量我们与预测的真实程度。分类模型的损失函数通常对解释模型的运行情况没有帮助。通常,我们需要某种分类准确率,这通常是正确预测类别的百分比。对于此示例,我们将使用本章中先前实现反向传播秘籍的分类示例。

操作步骤

首先,我们将展示如何评估简单回归模型,该模型简单地适应目标的常数乘法,即 10,如下所示:

  1. 首先,我们首先加载库并创建图,数据,变量和占位符。本节还有一个非常重要的部分。在我们创建数据之后,我们将数据随机分成训练和测试数据集。这很重要,因为我们总是会测试我们的模型,看看它们是否预测良好。在训练数据和测试数据上评估模型还可以让我们看到模型是否过拟合:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
sess = tf.Session() 
x_vals = np.random.normal(1, 0.1, 100) 
y_vals = np.repeat(10., 100) 
x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
batch_size = 25 
train_indices = np.random.choice(len(x_vals), round(len(x_vals)*0.8), replace=False) 
test_indices = np.array(list(set(range(len(x_vals))) - set(train_indices))) 
x_vals_train = x_vals[train_indices] 
x_vals_test = x_vals[test_indices] 
y_vals_train = y_vals[train_indices] 
y_vals_test = y_vals[test_indices] 
A = tf.Variable(tf.random_normal(shape=[1,1])) 
  1. 现在,我们声明我们的模型,loss函数和优化算法。我们还将初始化模型变量A。使用以下代码:
代码语言:javascript复制
my_output = tf.matmul(x_data, A) 
loss = tf.reduce_mean(tf.square(my_output - y_target))
my_opt = tf.train.GradientDescentOptimizer(0.02)
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer() 
sess.run(init) 
  1. 我们正如我们之前看到的那样运行训练循环,如下所示:
代码语言:javascript复制
for i in range(100): 
    rand_index = np.random.choice(len(x_vals_train), size=batch_size) rand_x = np.transpose([x_vals_train[rand_index]]) 
    rand_y = np.transpose([y_vals_train[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    if (i   1) % 25 == 0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))) 
        print('Loss = '   str(sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}))) 
Step #25 A = [[ 6.39879179]] 
Loss = 13.7903 
Step #50 A = [[ 8.64770794]] 
Loss = 2.53685 
Step #75 A = [[ 9.40029907]] 
Loss = 0.818259 
Step #100 A = [[ 9.6809473]] 
Loss = 1.10908 
  1. 现在,为了评估模型,我们将在训练和测试集上输出 MSE(损失函数),如下所示:
代码语言:javascript复制
mse_test = sess.run(loss, feed_dict={x_data: np.transpose([x_vals_test]), y_target: np.transpose([y_vals_test])}) 
mse_train = sess.run(loss, feed_dict={x_data: np.transpose([x_vals_train]), y_target: np.transpose([y_vals_train])}) 
print('MSE' on test:'   str(np.round(mse_test, 2))) 
print('MSE' on train:'   str(np.round(mse_train, 2))) 
MSE on test:1.35 
MSE on train:0.88 

对于分类示例,我们将做一些非常相似的事情。这一次,我们需要创建我们自己的精确度函数,我们可以在最后调用。其中一个原因是我们的损失函数内置了 sigmoid,我们需要单独调用 sigmoid 并测试它以查看我们的类是否正确:

  1. 在同一个脚本中,我们可以重新加载图并创建数据,变量和占位符。请记住,我们还需要将数据和目标分成训练和测试集。使用以下代码:
代码语言:javascript复制
from tensorflow.python.framework import ops 
ops.reset_default_graph() 
sess = tf.Session() 
batch_size = 25 
x_vals = np.concatenate((np.random.normal(-1, 1, 50), np.random.normal(2, 1, 50))) 
y_vals = np.concatenate((np.repeat(0., 50), np.repeat(1., 50))) 
x_data = tf.placeholder(shape=[1, None], dtype=tf.float32) 
y_target = tf.placeholder(shape=[1, None], dtype=tf.float32) 
train_indices = np.random.choice(len(x_vals), round(len(x_vals)*0.8), replace=False) 
test_indices = np.array(list(set(range(len(x_vals))) - set(train_indices))) 
x_vals_train = x_vals[train_indices] 
x_vals_test = x_vals[test_indices] 
y_vals_train = y_vals[train_indices] 
y_vals_test = y_vals[test_indices] 
A = tf.Variable(tf.random_normal(mean=10, shape=[1])) 
  1. 我们现在将模型和损失函数添加到图中,初始化变量,并创建优化过程,如下所示:
代码语言:javascript复制
my_output = tf.add(x_data, A) 
init = tf.initialize_all_variables() 
sess.run(init) 
xentropy = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(my_output, y_target)) 
my_opt = tf.train.GradientDescentOptimizer(0.05) 
train_step = my_opt.minimize(xentropy) 
  1. 现在,我们运行我们的训练循环,如下所示:
代码语言:javascript复制
for i in range(1800): 
    rand_index = np.random.choice(len(x_vals_train), size=batch_size) 
    rand_x = [x_vals_train[rand_index]] 
    rand_y = [y_vals_train[rand_index]] 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    if (i 1) 0==0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))) 
        print('Loss = '   str(sess.run(xentropy, feed_dict={x_data: rand_x, y_target: rand_y}))) 
Step #200 A = [ 6.64970636] 
Loss = 3.39434 
Step #400 A = [ 2.2884655] 
Loss = 0.456173 
Step #600 A = [ 0.29109824] 
Loss = 0.312162 
Step #800 A = [-0.20045301] 
Loss = 0.241349 
Step #1000 A = [-0.33634067] 
Loss = 0.376786 
Step #1200 A = [-0.36866501] 
Loss = 0.271654 
Step #1400 A = [-0.3727718] 
Loss = 0.294866 
Step #1600 A = [-0.39153299] 
Loss = 0.202275 
Step #1800 A = [-0.36630616] 
Loss = 0.358463 
  1. 为了评估模型,我们将创建自己的预测操作。我们将预测操作包装在挤压函数中,因为我们希望使预测和目标形成相同的形状。然后,我们用相等的函数测试相等性。在那之后,我们留下了一个真值和假值的张量,我们将其转换为float32并取平均值。这将产生准确率值。我们将为训练集和测试集评估此函数,如下所示:
代码语言:javascript复制
y_prediction = tf.squeeze(tf.round(tf.nn.sigmoid(tf.add(x_data, A)))) 
correct_prediction = tf.equal(y_prediction, y_target) 
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 
acc_value_test = sess.run(accuracy, feed_dict={x_data: [x_vals_test], y_target: [y_vals_test]}) 
acc_value_train = sess.run(accuracy, feed_dict={x_data: [x_vals_train], y_target: [y_vals_train]}) 
print('Accuracy' on train set: '   str(acc_value_train)) 
print('Accuracy' on test set: '   str(acc_value_test)) 
Accuracy on train set: 0.925 
Accuracy on test set: 0.95 
  1. 通常,查看模型结果(准确率,MSE 等)将有助于我们评估模型。我们可以在这里轻松绘制模型和数据的绘图,因为它是一维的。以下是使用matplotlib使用两个单独的直方图可视化模型和数据的方法:
代码语言:javascript复制
A_result = sess.run(A) 
bins = np.linspace(-5, 5, 50) 
plt.hist(x_vals[0:50], bins, alpha=0.5, label='N(-1,1)', color='white') 
plt.hist(x_vals[50:100], bins[0:50], alpha=0.5, label='N(2,1)', color='red') 
plt.plot((A_result, A_result), (0, 8), 'k--', linewidth=3, label='A = '  str(np.round(A_result, 2))) 
plt.legend(loc='upper right') 
plt.title('Binary Classifier, Accuracy='   str(np.round(acc_value, 2))) 
plt.show() 

工作原理

这导致绘图显示两个单独数据类的直方图中两个类的预测最佳分隔符。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xXFTemXm-1681566813066)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/0f077787-e4de-475b-a0c2-960c1e4a73ad.png)]

图 8:数据和最终模型的可视化。两个正常值以 -1 和 2 为中心,使理论最佳分割为 0.5。在这里,模型发现最接近该数字的最佳分割。

三、线性回归

在本章中,我们将介绍涉及线性回归的秘籍。我们从用矩阵求解线性回归的数学公式开始,然后继续使用 TensorFlow 范例实现标准线性回归和变量。我们将涵盖以下领域:

  • 使用矩阵逆方法
  • 实现分解方法
  • 学习 TensorFlow 回归方式
  • 理解线性回归中的损失函数
  • 实现戴明回归
  • 实现套索和岭回归
  • 实现弹性网络回归
  • 实现逻辑回归

介绍

线性回归可能是统计学,机器学习和一般科学中最重要的算法之一。它是最广泛使用的算法之一,了解如何实现它及其各种风格非常重要。线性回归优于许多其他算法的优点之一是它是非常可解释的。我们最终得到一个数字,用于直接表示该特征如何影响目标或因变量的每个特征。在本章中,我们将介绍线性回归是如何经典实现的,然后继续讨论如何在 TensorFlow 范例中最好地实现它。

请记住,所有代码都可以在 Github 以及 Packt 仓库获得。

使用矩阵逆方法

在这个秘籍中,我们将使用 TensorFlow 用矩阵逆方法求解二维线性回归。

准备

线性回归可以表示为一组矩阵方程,比如Ax = b。在这里,我们感兴趣的是求解矩阵x中的系数。如果我们的观察矩阵(设计矩阵)A不是正方形,我们必须要小心。解决x的解决方案可以表示为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wTU8ZoPN-1681566813066)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/cef9855e-d719-434e-ac00-fa8ab4b351db.png)]

为了证明确实如此,我们将生成二维数据,在 TensorFlow 中解决它,并绘制结果。

操作步骤

我们按如下方式处理秘籍:

  1. 首先,我们加载必要的库,初始化图并创建数据。请参阅以下代码:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
sess = tf.Session() 
x_vals = np.linspace(0, 10, 100) 
y_vals = x_vals   np.random.normal(0, 1, 100) 
  1. 接下来,我们创建要在逆方法中使用的矩阵。我们首先创建A矩阵,它将是x数据列和全 1 的列。然后,我们从y数据创建b矩阵。使用以下代码:
代码语言:javascript复制
x_vals_column = np.transpose(np.matrix(x_vals)) 
ones_column = np.transpose(np.matrix(np.repeat(1, 100))) 
A = np.column_stack((x_vals_column, ones_column)) 
b = np.transpose(np.matrix(y_vals)) 
  1. 然后我们将Ab矩阵转换为张量,如下所示:
代码语言:javascript复制
A_tensor = tf.constant(A) 
b_tensor = tf.constant(b)
  1. 现在我们已经设置了矩阵,我们可以使用 TensorFlow 通过矩阵逆方法解决这个问题,如下所示:
代码语言:javascript复制
tA_A = tf.matmul(tf.transpose(A_tensor), A_tensor) 
tA_A_inv = tf.matrix_inverse(tA_A) 
product = tf.matmul(tA_A_inv, tf.transpose(A_tensor)) 
solution = tf.matmul(product, b_tensor) 
solution_eval = sess.run(solution) 
  1. 我们现在使用以下代码从解,斜率和 y 截距中提取系数:
代码语言:javascript复制
slope = solution_eval[0][0] 
y_intercept = solution_eval[1][0] 
print('slope: '   str(slope)) 
print('y_intercept: '   str(y_intercept)) 
slope: 0.955707151739 
y_intercept: 0.174366829314 
best_fit = [] 
for i in x_vals: 
    best_fit.append(slope*i y_intercept) 

plt.plot(x_vals, y_vals, 'o', label='Data') 
plt.plot(x_vals, best_fit, 'r-', label='Best fit line', linewidth=3) 
plt.legend(loc='upper left') 
plt.show() 

我们得到前面代码的图,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3P04Sb0B-1681566813066)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/eaa2fd52-cdc8-42d9-a43c-c18a9dd445a5.png)]

图 1:数据点和通过矩阵逆方法获得的最佳拟合线

工作原理

与之前的秘籍或本书中的大多数秘籍不同,此处的解决方案仅通过矩阵运算找到。我们将使用的大多数 TensorFlow 算法都是通过训练循环实现的,并利用自动反向传播来更新模型变量。在这里,我们通过实现将模型拟合到数据的直接解决方案来说明 TensorFlow 的多功能性。

我们在这里使用了一个二维数据示例来显示与数据拟合的图。值得注意的是,用于求解系数的公式 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wYgUiXzw-1681566813067)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/32e2728d-bb84-491b-a4e1-e28108f5fff1.png)] 将根据需要扩展到数据中的许多特征(除非存在任何共线性问题)。

实现分解方法

对于这个秘籍,我们将实现一个用于线性回归的矩阵分解方法。具体来说,我们将使用 Cholesky 分解,TensorFlow 中存在相关函数。

准备

在大多数情况下,实现前一个秘籍中的逆方法在数值上效率低,尤其是当矩阵变得非常大时。另一种方法是分解A矩阵并对分解执行矩阵运算。一种方法是在 TensorFlow 中使用内置的 Cholesky 分解方法。

人们对将矩阵分解为更多矩阵如此感兴趣的一个原因是,所得到的矩阵将具有允许我们有效使用某些方法的保证属性。 Cholesky 分解将矩阵分解为下三角矩阵和上三角矩阵,比如LL',使得这些矩阵是彼此的转置。有关此分解属性的更多信息,有许多可用资源来描述它以及如何到达它。在这里,我们将通过将其写为LL'x = b来解决系统Ax = b。我们首先解决Ly = by,然后求解L'x = y得到我们的系数矩阵x

操作步骤

我们按如下方式处理秘籍:

  1. 我们将以与上一个秘籍完全相同的方式设置系统。我们将导入库,初始化图并创建数据。然后,我们将以之前的方式获得我们的A矩阵和b矩阵:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from tensorflow.python.framework import ops 
ops.reset_default_graph() 
sess = tf.Session() 
x_vals = np.linspace(0, 10, 100) 
y_vals = x_vals   np.random.normal(0, 1, 100) 
x_vals_column = np.transpose(np.matrix(x_vals)) 
ones_column = np.transpose(np.matrix(np.repeat(1, 100))) 
A = np.column_stack((x_vals_column, ones_column)) 
b = np.transpose(np.matrix(y_vals)) 
A_tensor = tf.constant(A) 
b_tensor = tf.constant(b) 
  1. 接下来,我们找到方阵的 Cholesky 分解,A^T A
代码语言:javascript复制
tA_A = tf.matmul(tf.transpose(A_tensor), A_tensor) 
L = tf.cholesky(tA_A) 
tA_b = tf.matmul(tf.transpose(A_tensor), b) 
sol1 = tf.matrix_solve(L, tA_b) 
sol2 = tf.matrix_solve(tf.transpose(L), sol1) 

请注意,TensorFlow 函数cholesky()仅返回分解的下对角线部分。这很好,因为上对角矩阵只是下对角矩阵的转置。

  1. 现在我们有了解决方案,我们提取系数:
代码语言:javascript复制
solution_eval = sess.run(sol2) 
slope = solution_eval[0][0] 
y_intercept = solution_eval[1][0] 
print('slope: '   str(slope)) 
print('y_intercept: '   str(y_intercept)) 
slope: 0.956117676145 
y_intercept: 0.136575513864 
best_fit = [] 
for i in x_vals: 
    best_fit.append(slope*i y_intercept) 
plt.plot(x_vals, y_vals, 'o', label='Data') 
plt.plot(x_vals, best_fit, 'r-', label='Best fit line', linewidth=3) 
plt.legend(loc='upper left') 
plt.show() 

绘图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NpZoY504-1681566813067)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/57a190f8-110c-4cd2-902f-3668dc603f65.png)]

图 2:通过 Cholesky 分解获得的数据点和最佳拟合线

工作原理

如您所见,我们得出了与之前秘籍非常相似的答案。请记住,这种分解矩阵然后对碎片执行操作的方式有时会更加高效和数值稳定,尤其是在考虑大型数据矩阵时。

学习 TensorFlow 线性回归方法

虽然使用矩阵和分解方法非常强大,但 TensorFlow 还有另一种解决斜率和截距的方法。 TensorFlow 可以迭代地执行此操作,逐步学习最小化损失的线性回归参数。

准备

在这个秘籍中,我们将遍历批量数据点并让 TensorFlow 更新斜率和y截距。我们将使用内置于 scikit-learn 库中的鸢尾花数据集,而不是生成的数据。具体来说,我们将通过数据点找到最佳线,其中x值是花瓣宽度,y值是萼片长度。我们选择了这两个,因为它们之间似乎存在线性关系,我们将在最后的绘图中看到。我们还将在下一节中详细讨论不同损失函数的影响,但对于这个秘籍,我们将使用 L2 损失函数。

操作步骤

我们按如下方式处理秘籍:

  1. 我们首先加载必要的库,创建图并加载数据:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
from tensorflow.python.framework import ops 
ops.reset_default_graph() 
sess = tf.Session() 
iris = datasets.load_iris() 
x_vals = np.array([x[3] for x in iris.data]) 
y_vals = np.array([y[0] for y in iris.data])
  1. 然后我们声明我们的学习率,批量大小,占位符和模型变量:
代码语言:javascript复制
learning_rate = 0.05 
batch_size = 25 
x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[1,1])) 
b = tf.Variable(tf.random_normal(shape=[1,1])) 
  1. 接下来,我们编写线性模型的公式y = Ax b
代码语言:javascript复制
model_output = tf.add(tf.matmul(x_data, A), b)
  1. 然后,我们声明我们的 L2 损失函数(包括批量的平均值),初始化变量,并声明我们的优化器。请注意,我们选择0.05作为我们的学习率:
代码语言:javascript复制
loss = tf.reduce_mean(tf.square(y_target - model_output)) 
init = tf.global_variables_initializer() 
sess.run(init) 
my_opt = tf.train.GradientDescentOptimizer(learning_rate) 
train_step = my_opt.minimize(loss)
  1. 我们现在可以在随机选择的批次上循环并训练模型。我们将运行 100 个循环并每 25 次迭代打印出变量和损失值。请注意,在这里,我们还保存了每次迭代的损失,以便我们以后可以查看它们:
代码语言:javascript复制
loss_vec = [] 
for i in range(100): 
    rand_index = np.random.choice(len(x_vals), size=batch_size) 
    rand_x = np.transpose([x_vals[rand_index]]) 
    rand_y = np.transpose([y_vals[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec.append(temp_loss) 
    if (i 1)%==0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))   ' b = '   str(sess.run(b))) 
        print('Loss = '   str(temp_loss)) 

Step #25 A = [[ 2.17270374]] b = [[ 2.85338426]] 
Loss = 1.08116 
Step #50 A = [[ 1.70683455]] b = [[ 3.59916329]] 
Loss = 0.796941 
Step #75 A = [[ 1.32762754]] b = [[ 4.08189011]] 
Loss = 0.466912 
Step #100 A = [[ 1.15968263]] b = [[ 4.38497639]] 
Loss = 0.281003
  1. 接下来,我们将提取我们找到的系数并创建一个最合适的线以放入图中:
代码语言:javascript复制
[slope] = sess.run(A) 
[y_intercept] = sess.run(b) 
best_fit = [] 
for i in x_vals: 
    best_fit.append(slope*i y_intercept)
  1. 在这里,我们将创建两个图。第一个是覆盖拟合线的数据。第二个是 100 次迭代中的 L2 损失函数。这是生成两个图的代码。
代码语言:javascript复制
plt.plot(x_vals, y_vals, 'o', label='Data Points') 
plt.plot(x_vals, best_fit, 'r-', label='Best fit line', linewidth=3) 
plt.legend(loc='upper left') 
plt.title('Sepal Length vs Petal Width') 
plt.xlabel('Petal Width') 
plt.ylabel('Sepal Length') 
plt.show() 
plt.plot(loss_vec, 'k-') 
plt.title('L2 Loss per Generation') 
plt.xlabel('Generation') 
plt.ylabel('L2 Loss') 
plt.show() 

此代码生成以下拟合数据和损失图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-18aoJlI7-1681566813067)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/e3f807db-f6b3-4808-8b2e-990ed0c444b8.png)]

图 3:来自鸢尾数据集的数据点(萼片长度与花瓣宽度)重叠在 TensorFlow 中找到的最佳线条拟合。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2oU3jpIn-1681566813067)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/3d3d7762-7528-4101-bd3b-8175f11e0c83.png)]

图 4:用我们的算法拟合数据的 L2 损失;注意损失函数中的抖动,可以通过较大的批量大小减小抖动,或者通过较小的批量大小来增加。

Here is a good place to note how to see whether the model is overfitting or underfitting the data. If our data is broken into test and training sets, and the accuracy is greater on the training set and lower on the test set, then we are overfitting the data. If the accuracy is still increasing on both test and training sets, then the model is underfitting and we should continue training.

工作原理

找到的最佳线不保证是最合适的线。最佳拟合线的收敛取决于迭代次数,批量大小,学习率和损失函数。随着时间的推移观察损失函数总是很好的做法,因为它可以帮助您解决问题或超参数变化。

理解线性回归中的损失函数

了解损失函数在算法收敛中的作用非常重要。在这里,我们将说明 L1 和 L2 损失函数如何影响线性回归中的收敛。

准备

我们将使用与先前秘籍中相同的鸢尾数据集,但我们将更改损失函数和学习率以查看收敛如何变化。

操作步骤

我们按如下方式处理秘籍:

  1. 程序的开始与上一个秘籍相同,直到我们达到我们的损失函数。我们加载必要的库,启动会话,加载数据,创建占位符,并定义我们的变量和模型。需要注意的一点是,我们正在提取学习率和模型迭代。我们这样做是因为我们希望显示快速更改这些参数的效果。使用以下代码:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
sess = tf.Session() 
iris = datasets.load_iris() 
x_vals = np.array([x[3] for x in iris.data]) 
y_vals = np.array([y[0] for y in iris.data]) 
batch_size = 25 
learning_rate = 0.1 # Will not converge with learning rate at 0.4 
iterations = 50 
x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[1,1])) 
b = tf.Variable(tf.random_normal(shape=[1,1])) 
model_output = tf.add(tf.matmul(x_data, A), b)
  1. 我们的损失函数将变为 L1 损失(loss_l1),如下所示:
代码语言:javascript复制
loss_l1 = tf.reduce_mean(tf.abs(y_target - model_output)) 
  1. 现在,我们通过初始化变量,声明我们的优化器以及通过训练循环迭代数据来恢复。请注意,我们也在节省每一代的损失来衡量收敛。使用以下代码:
代码语言:javascript复制
init = tf.global_variables_initializer() 
sess.run(init) 
my_opt_l1 = tf.train.GradientDescentOptimizer(learning_rate) 
train_step_l1 = my_opt_l1.minimize(loss_l1) 
loss_vec_l1 = [] 
for i in range(iterations): 
    rand_index = np.random.choice(len(x_vals), size=batch_size) 
    rand_x = np.transpose([x_vals[rand_index]]) 
    rand_y = np.transpose([y_vals[rand_index]]) 
    sess.run(train_step_l1, feed_dict={x_data: rand_x, y_target: rand_y}) 
    temp_loss_l1 = sess.run(loss_l1, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec_l1.append(temp_loss_l1) 
    if (i 1)%==0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))   ' b = '   str(sess.run(b))) 

plt.plot(loss_vec_l1, 'k-', label='L1 Loss') 
plt.plot(loss_vec_l2, 'r--', label='L2 Loss') 
plt.title('L1 and L2 Loss per Generation') 
plt.xlabel('Generation') 
plt.ylabel('L1 Loss') 
plt.legend(loc='upper right') 
plt.show() 

工作原理

在选择损失函数时,我们还必须选择适合我们问题的相应学习率。在这里,我们将说明两种情况,一种是首选 L2,另一种是首选 L1。

如果我们的学习率很小,我们的收敛会花费更多时间。但是如果我们的学习速度太大,我们的算法就会遇到问题从不收敛。下面是当学习率为 0.05 时,鸢尾线性回归问题的 L1 和 L2 损失的损失函数图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0bdRUn6-1681566813068)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/bf44e506-bc88-41c2-b566-ddf2fe68bbd6.png)]

图 5:鸢尾线性回归问题的学习率为 0.05 的 L1 和 L2 损失

学习率为 0.05 时,似乎 L2 损失是首选,因为它会收敛到较低的损失。下面是我们将学习率提高到 0.4 时的损失函数图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LbgISEgU-1681566813068)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/729d53ef-d7e2-4068-8ae7-2fc6e91c572b.png)]

图 6:鸢尾线性回归问题的 L1 和 L2 损失,学习率为 0.4;请注意,由于 y 轴的高比例,L1 损失不可见

在这里,我们可以看到高学习率可以在 L2 范数中超调,而 L1 范数收敛。

更多

为了理解正在发生的事情,我们应该看看大学习率和小学习率如何影响 L1 范数和 L2 范数。为了使这个可视化,我们查看两个规范的学习步骤的一维表示,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TxezJnvf-1681566813068)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/f15420de-a851-4e7f-8b87-434e1eba5e2d.png)]

图 7:学习率越来越高的 L1 和 L2 范数会发生什么

实现戴明回归

在这个秘籍中,我们将实现戴明回归,这意味着我们需要一种不同的方法来测量模型线和数据点之间的距离。

戴明回归有几个名字。它也称为总回归,正交距离回归(ODR)和最短距离回归。

准备

如果最小二乘线性回归最小化到线的垂直距离,则戴明回归最小化到线的总距离。这种类型的回归可以最小化yx值的误差。

请参阅下图进行比较:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s8G0k7Zm-1681566813068)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/7b0c20a1-43c3-4a07-9785-2f7a5706ad6b.png)]

图 8:常规线性回归和戴明回归之间的差异;左边的线性回归最小化了到线的垂直距离,右边的变形回归最小化了到线的总距离

要实现戴明回归,我们必须修改损失函数。常规线性回归中的损失函数使垂直距离最小化。在这里,我们希望最小化总距离。给定线的斜率和截距,到点的垂直距离是已知的几何公式​​。我们只需要替换此公式并告诉 TensorFlow 将其最小化。

操作步骤

我们按如下方式处理秘籍:

  1. 代码与之前的秘籍非常相似,除非我们进入损失函数。我们首先加载库;开始一个会议;加载数据;声明批量大小;并创建占位符,变量和模型输出,如下所示:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
sess = tf.Session() 
iris = datasets.load_iris() 
x_vals = np.array([x[3] for x in iris.data]) 
y_vals = np.array([y[0] for y in iris.data]) 
batch_size = 50 
x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[1,1])) 
b = tf.Variable(tf.random_normal(shape=[1,1])) 
model_output = tf.add(tf.matmul(x_data, A), b)
  1. 损失函数是由分子和分母组成的几何公式​​。为清楚起见,我们将分别编写这些内容。给定一条线y = mx b和一个点(x0, y0),两者之间的垂直距离可以写成如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGXMMZqu-1681566813069)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/9537f564-ab2b-4b6a-936e-2432e21e2859.png)]

代码语言:javascript复制
deming_numerator = tf.abs(tf.sub(y_target, tf.add(tf.matmul(x_data, A), b))) 
deming_denominator = tf.sqrt(tf.add(tf.square(A),1)) 
loss = tf.reduce_mean(tf.truediv(deming_numerator, deming_denominator)) 
  1. 我们现在初始化变量,声明我们的优化器,并循环遍历训练集以获得我们的参数,如下所示:
代码语言:javascript复制
init = tf.global_variables_initializer() 
sess.run(init) 
my_opt = tf.train.GradientDescentOptimizer(0.1) 
train_step = my_opt.minimize(loss) 
loss_vec = [] 
for i in range(250): 
    rand_index = np.random.choice(len(x_vals), size=batch_size) 
    rand_x = np.transpose([x_vals[rand_index]]) 
    rand_y = np.transpose([y_vals[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec.append(temp_loss) 
    if (i 1)P==0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))   ' b = '   str(sess.run(b))) 
        print('Loss = '   str(temp_loss)) 
  1. 我们可以使用以下代码绘制输出:
代码语言:javascript复制
[slope] = sess.run(A) 
[y_intercept] = sess.run(b) 
best_fit = [] 
for i in x_vals: 
    best_fit.append(slope*i y_intercept)

plt.plot(x_vals, y_vals, 'o', label='Data Points') 
plt.plot(x_vals, best_fit, 'r-', label='Best fit line', linewidth=3) 
plt.legend(loc='upper left') 
plt.title('Sepal Length vs petal Width') 
plt.xlabel('petal Width') 
plt.ylabel('Sepal Length') 
plt.show() 

我们得到上面代码的以下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGPoGCFc-1681566813069)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/e53cec07-65ac-4af7-8a26-f9d5a1c27642.png)]

图 9:对鸢尾数据集进行戴明回归的解决方案

工作原理

戴明回归的方法几乎与常规线性回归相同。关键的区别在于我们如何衡量预测和数据点之间的损失。而不是垂直损失,我们对yx值有垂直损失(或总损失)。

当我们假设xy值中的误差相似时,使用这种类型的回归。根据我们的假设,我们还可以根据误差的差异在距离计算中缩放xy轴。

实现套索和岭回归

还有一些方法可以限制系数对回归输出的影响。这些方法称为正则化方法,两种最常见的正则化方法是套索和岭回归。我们将介绍如何在本文中实现这两个方面。

准备

套索和岭回归与常规线性回归非常相似,除了我们添加正则化项以限制公式中的斜率(或部分斜率)。这可能有多种原因,但一个常见的原因是我们希望限制对因变量产生影响的特征。这可以通过在损失函数中添加一个取决于我们的斜率值A的项来实现。

对于套索回归,如果斜率A超过某个值,我们必须添加一个能大大增加损失函数的项。我们可以使用 TensorFlow 的逻辑运算,但它们没有与之关联的梯度。相反,我们将使用称为连续重阶函数的阶梯函数的连续近似,该函数按比例放大到我们选择的正则化截止值。我们将展示如何在此秘籍中进行套索回归。

对于岭回归,我们只是在 L2 范数中添加一个项,这是斜率系数的缩放 L2 范数。这种修改很简单,并在本秘籍末尾的“更多”部分中显示。

操作步骤

我们按如下方式处理秘籍:

  1. 我们将再次使用鸢尾花数据集并以与以前相同的方式设置我们的脚本。我们先加载库;开始一个会议;加载数据;声明批量大小;并创建占位符,变量和模型输出,如下所示:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
from tensorflow.python.framework import ops 
ops.reset_default_graph() 
sess = tf.Session() 
iris = datasets.load_iris() 
x_vals = np.array([x[3] for x in iris.data]) 
y_vals = np.array([y[0] for y in iris.data]) 
batch_size = 50 
learning_rate = 0.001 
x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[1,1])) 
b = tf.Variable(tf.random_normal(shape=[1,1])) 
model_output = tf.add(tf.matmul(x_data, A), b)
  1. 我们添加了损失函数,它是一个改进的连续 Heaviside 阶梯函数。我们还为0.9设定了套索回归的截止值。这意味着我们希望将斜率系数限制为小于0.9。使用以下代码:
代码语言:javascript复制
lasso_param = tf.constant(0.9) 
heavyside_step = tf.truediv(1., tf.add(1., tf.exp(tf.multiply(-100., tf.subtract(A, lasso_param))))) 
regularization_param = tf.mul(heavyside_step, 99.) 
loss = tf.add(tf.reduce_mean(tf.square(y_target - model_output)), regularization_param) 
  1. 我们现在初始化变量并声明我们的优化器,如下所示:
代码语言:javascript复制
init = tf.global_variables_initializer() 
sess.run(init) 
my_opt = tf.train.GradientDescentOptimizer(learning_rate) 
train_step = my_opt.minimize(loss) 
  1. 我们将训练循环延长了一段时间,因为它可能需要一段时间才能收敛。我们可以看到斜率系数小于0.9。使用以下代码:
代码语言:javascript复制
loss_vec = [] 
for i in range(1500): 
    rand_index = np.random.choice(len(x_vals), size=batch_size) 
    rand_x = np.transpose([x_vals[rand_index]]) 
    rand_y = np.transpose([y_vals[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec.append(temp_loss[0]) 
    if (i 1)00==0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))   ' b = '   str(sess.run(b))) 
        print('Loss = '   str(temp_loss)) 

Step #300 A = [[ 0.82512331]] b = [[ 2.30319238]] 
Loss = [[ 6.84168959]] 
Step #600 A = [[ 0.8200165]] b = [[ 3.45292258]] 
Loss = [[ 2.02759886]] 
Step #900 A = [[ 0.81428504]] b = [[ 4.08901262]] 
Loss = [[ 0.49081498]] 
Step #1200 A = [[ 0.80919558]] b = [[ 4.43668795]] 
Loss = [[ 0.40478843]] 
Step #1500 A = [[ 0.80433637]] b = [[ 4.6360755]] 
Loss = [[ 0.23839757]] 

工作原理

我们通过在线性回归的损失函数中添加连续的 Heaviside 阶跃函数来实现套索回归。由于阶梯函数的陡峭性,我们必须小心步长。步长太大而且不会收敛。对于岭回归,请参阅下一节中所需的更改。

更多

对于岭回归,我们将损失ss函数更改为如下:

代码语言:javascript复制
ridge_param = tf.constant(1.) 
ridge_loss = tf.reduce_mean(tf.square(A)) 
loss = tf.expand_dims(tf.add(tf.reduce_mean(tf.square(y_target - model_output)), tf.multiply(ridge_param, ridge_loss)), 0) 

实现弹性网络回归

弹性网络回归是一种回归类型,通过将 L1 和 L2 正则化项添加到损失函数,将套索回归与岭回归相结合。

准备

在前两个秘籍之后实现弹性网络回归应该是直截了当的,因此我们将在鸢尾数据集上的多元线性回归中实现这一点,而不是像以前那样坚持二维数据。我们将使用花瓣长度,花瓣宽度和萼片宽度来预测萼片长度。

操作步骤

我们按如下方式处理秘籍:

  1. 首先,我们加载必要的库并初始化图,如下所示:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
sess = tf.Session()
  1. 现在,我们加载数据。这次,x数据的每个元素将是三个值的列表而不是一个。使用以下代码:
代码语言:javascript复制
iris = datasets.load_iris() 
x_vals = np.array([[x[1], x[2], x[3]] for x in iris.data]) 
y_vals = np.array([y[0] for y in iris.data]) 
  1. 接下来,我们声明批量大小,占位符,变量和模型输出。这里唯一的区别是我们更改x数据占位符的大小规范,取三个值而不是一个,如下所示:
代码语言:javascript复制
batch_size = 50 
learning_rate = 0.001 
x_data = tf.placeholder(shape=[None, 3], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[3,1])) 
b = tf.Variable(tf.random_normal(shape=[1,1])) 
model_output = tf.add(tf.matmul(x_data, A), b) 
  1. 对于弹性网络,损失函数具有部分斜率的 L1 和 L2 范数。我们创建这些项,然后将它们添加到损失函数中,如下所示:
代码语言:javascript复制
elastic_param1 = tf.constant(1.) 
elastic_param2 = tf.constant(1.) 
l1_a_loss = tf.reduce_mean(tf.abs(A)) 
l2_a_loss = tf.reduce_mean(tf.square(A)) 
e1_term = tf.multiply(elastic_param1, l1_a_loss) 
e2_term = tf.multiply(elastic_param2, l2_a_loss) 
loss = tf.expand_dims(tf.add(tf.add(tf.reduce_mean(tf.square(y_target - model_output)), e1_term), e2_term), 0)
  1. 现在,我们可以初始化变量,声明我们的优化函数,运行训练循环,并拟合我们的系数,如下所示:
代码语言:javascript复制
init = tf.global_variables_initializer() 
sess.run(init) 
my_opt = tf.train.GradientDescentOptimizer(learning_rate) 
train_step = my_opt.minimize(loss) 
loss_vec = [] 
for i in range(1000): 
    rand_index = np.random.choice(len(x_vals), size=batch_size) 
    rand_x = x_vals[rand_index] 
    rand_y = np.transpose([y_vals[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec.append(temp_loss[0]) 
    if (i 1)%0==0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))   ' b = '   str(sess.run(b))) 
        print('Loss = '   str(temp_loss)) 
  1. 这是代码的输出:
代码语言:javascript复制
Step #250 A = [[ 0.42095602] 
 [ 0.1055888 ] 
 [ 1.77064979]] b = [[ 1.76164341]] 
Loss = [ 2.87764359] 
Step #500 A = [[ 0.62762028] 
 [ 0.06065864] 
 [ 1.36294949]] b = [[ 1.87629771]] 
Loss = [ 1.8032167] 
Step #750 A = [[ 0.67953539] 
 [ 0.102514 ] 
 [ 1.06914485]] b = [[ 1.95604002]] 
Loss = [ 1.33256555] 
Step #1000 A = [[ 0.6777274 ] 
 [ 0.16535147] 
 [ 0.8403284 ]] b = [[ 2.02246833]] 
Loss = [ 1.21458709]
  1. 现在,我们可以观察训练迭代的损失,以确保算法收敛,如下所示:
代码语言:javascript复制
plt.plot(loss_vec, 'k-') 
plt.title('Loss per Generation') 
plt.xlabel('Generation') 
plt.ylabel('Loss') 
plt.show() 

我们得到上面代码的以下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W5AMZU9L-1681566813069)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/12a7c632-d523-401b-b9bd-769b8b765f67.png)]

图 10:在 1,000 次训练迭代中绘制的弹性净回归损失

工作原理

这里实现弹性网络回归以及多元线性回归。我们可以看到,利用损失函数中的这些正则化项,收敛速度比先前的秘籍慢。正则化就像在损失函数中添加适当的项一样简单。

实现逻辑回归

对于这个秘籍,我们将实现逻辑回归来预测样本人群中低出生体重的概率。

准备

逻辑回归是将线性回归转换为二元分类的一种方法。这是通过将线性输出转换为 Sigmoid 函数来实现的,该函数将输出在 0 和 1 之间进行缩放。目标是零或一,表示数据点是在一个类还是另一个类中。由于我们预测 0 和 1 之间的数字,如果预测高于指定的截止值,则预测被分类为类值 1,否则分类为 0。出于此示例的目的,我们将指定截断为 0.5,这将使分类像舍入输出一样简单。

我们将用于此示例的数据将是从作者的 GitHub 仓库获得的低出生体重数据。我们将从其他几个因素预测低出生体重。

操作步骤

我们按如下方式处理秘籍:

  1. 我们首先加载库,包括request库,因为我们将通过超链接访问低出生体重数据。我们还发起了一个会议:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
import requests 
from sklearn import datasets 
from sklearn.preprocessing import normalize 
from tensorflow.python.framework import ops 
ops.reset_default_graph() 
sess = tf.Session()
  1. 接下来,我们通过请求模块加载数据并指定我们要使用的特征。我们必须具体,因为一个特征是实际出生体重,我们不想用它来预测出生体重是大于还是小于特定量。我们也不想将 ID 列用作预测器:
代码语言:javascript复制
birth_weight_file = 'birth_weight.csv'
# Download data and create data file if file does not exist in current directory
if not os.path.exists(birth_weight_file):
    birthdata_url = 'https://github.com/nfmcclure/tensorflow_cookbook/raw/master/01_Introduction/07_Working_with_Data_Sources/birthweight_data/birthweight.dat'
    birth_file = requests.get(birthdata_url)
    birth_data = birth_file.text.split('rn')
    birth_header = birth_data[0].split('t')
    birth_data = [[float(x) for x in y.split('t') if len(x)>=1] for y in birth_data[1:] if len(y)>=1]
    with open(birth_weight_file, 'w', newline='') as f:
        writer = csv.writer(f)
        writer.writerow(birth_header)
        writer.writerows(birth_data)

# Read birth weight data into memory
birth_data = []
with open(birth_weight_file, newline='') as csvfile:
    csv_reader = csv.reader(csvfile)
    birth_header = next(csv_reader)
    for row in csv_reader:
        birth_data.append(row)
    birth_data = [[float(x) for x in row] for row in birth_data]
# Pull out target variable
y_vals = np.array([x[0] for x in birth_data])
# Pull out predictor variables (not id, not target, and not birthweight)
x_vals = np.array([x[1:8] for x in birth_data])
  1. 首先,我们将数据集拆分为测试和训练集:
代码语言:javascript复制
train_indices = np.random.choice(len(x_vals), round(len(x_vals)*0.8), replace=False) 
test_indices = np.array(list(set(range(len(x_vals))) - set(train_indices))) 
x_vals_train = x_vals[train_indices] 
x_vals_test = x_vals[test_indices] 
y_vals_train = y_vals[train_indices] 
y_vals_test = y_vals[test_indices]
  1. 当特征在 0 和 1 之间缩放(最小 - 最大缩放)时,逻辑回归收敛效果更好。那么,接下来我们将扩展每个特征:
代码语言:javascript复制
def normalize_cols(m, col_min=np.array([None]), col_max=np.array([None])):
    if not col_min[0]:
        col_min = m.min(axis=0)
    if not col_max[0]:
        col_max = m.max(axis=0)
    return (m-col_min) / (col_max - col_min), col_min, col_max

x_vals_train, train_min, train_max = np.nan_to_num(normalize_cols(x_vals_train))
x_vals_test = np.nan_to_num(normalize_cols(x_vals_test, train_min, train_max))

请注意,在缩放数据集之前,我们将数据集拆分为训练和测试。这是一个重要的区别。我们希望确保测试集完全不影响训练集。如果我们在分裂之前缩放整个集合,那么我们不能保证它们不会相互影响。我们确保从训练组中保存缩放以缩放测试集。

  1. 接下来,我们声明批量大小,占位符,变量和逻辑模型。我们不将输出包装在 sigmoid 中,因为该操作内置于损失函数中。另请注意,每次观察都有七个输入特征,因此x_data占位符的大小为[None, 7]
代码语言:javascript复制
batch_size = 25 
x_data = tf.placeholder(shape=[None, 7], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[7,1])) 
b = tf.Variable(tf.random_normal(shape=[1,1])) 
model_output = tf.add(tf.matmul(x_data, A), b)
  1. 现在,我们声明我们的损失函数,它具有 sigmoid 函数,初始化我们的变量,并声明我们的优化函数:
代码语言:javascript复制
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(model_output, y_target)) 
init = tf.global_variables_initializer() 
sess.run(init) 
my_opt = tf.train.GradientDescentOptimizer(0.01) 
train_step = my_opt.minimize(loss)
  1. 在记录损失函数的同时,我们还希望在训练和测试集上记录分类准确率。因此,我们将创建一个预测函数,返回任何大小的批量的准确率:
代码语言:javascript复制
prediction = tf.round(tf.sigmoid(model_output)) 
predictions_correct = tf.cast(tf.equal(prediction, y_target), tf.float32) 
accuracy = tf.reduce_mean(predictions_correct)
  1. 现在,我们可以开始我们的训练循环并记录损失和准确率:
代码语言:javascript复制
loss_vec = [] 
train_acc = [] 
test_acc = [] 
for i in range(1500): 
    rand_index = np.random.choice(len(x_vals_train), size=batch_size) 
    rand_x = x_vals_train[rand_index] 
    rand_y = np.transpose([y_vals_train[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec.append(temp_loss) 
    temp_acc_train = sess.run(accuracy, feed_dict={x_data: x_vals_train, y_target: np.transpose([y_vals_train])}) 
    train_acc.append(temp_acc_train) 
    temp_acc_test = sess.run(accuracy, feed_dict={x_data: x_vals_test, y_target: np.transpose([y_vals_test])}) 
    test_acc.append(temp_acc_test)
  1. 以下是查看损失和准确率图的代码:
代码语言:javascript复制
plt.plot(loss_vec, 'k-') 
plt.title('Cross' Entropy Loss per Generation') 
plt.xlabel('Generation') 
plt.ylabel('Cross' Entropy Loss') 
plt.show() 
plt.plot(train_acc, 'k-', label='Train Set Accuracy') 
plt.plot(test_acc, 'r--', label='Test Set Accuracy') 
plt.title('Train and Test Accuracy') 
plt.xlabel('Generation') 
plt.ylabel('Accuracy') 
plt.legend(loc='lower right') 
plt.show() 

工作原理

这是迭代和训练和测试精度的损失。由于数据集仅为 189 次观测,因此随着数据集的随机分裂,训练和测试精度图将发生变化。第一个数字是交叉熵损失:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RvTYHVDR-1681566813069)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/0efa3298-7046-432d-8393-ca1765b44175.png)]

图 11:在 1,500 次迭代过程中绘制的交叉熵损失

第二个图显示了训练和测试装置的准确率:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KO0IYaLB-1681566813070)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/a6f50907-4950-402e-b327-053a02deb6b3.png)]Figure 12: Test and train set accuracy plotted over 1,500 generations

四、支持向量机

本章将介绍有关如何在 TensorFlow 中使用,实现和评估支持向量机(SVM)的一些重要秘籍。将涵盖以下领域:

  • 使用线性 SVM
  • 回退到线性回归
  • 在 TensorFlow 中使用核
  • 实现非线性 SVM
  • 实现多类 SVM

本章中先前涵盖的逻辑回归和大多数 SVM 都是二元预测变量。虽然逻辑回归试图找到最大化距离的任何分离线(概率地),但 SVM 还尝试最小化误差,同时最大化类之间的余量。通常,如果问题与训练示例相比具有大量特征,请尝试逻辑回归或线性 SVM。如果训练样本的数量较大,或者数据不是线性可分的,则可以使用具有高斯核的 SVM。

另外,请记住本章的所有代码都可以在 Github 和 Packt 仓库中找到。

介绍

SVM 是二分类的方法。基本思想是在两个类之间找到二维的线性分离线(或更多维度的超平面)。我们首先假设二元类目标是 -1 或 1,而不是先前的 0 或 1 目标。由于可能有许多行分隔两个类,我们定义最佳线性分隔符,以最大化两个类之间的距离:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwoJbYof-1681566813070)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/bc479121-6689-4836-9221-2f694919ce64.png)]

图 1

给定两个可分类ox,我们希望找到两者之间的线性分离器的等式。左侧绘图显示有许多行将两个类分开。右侧绘图显示了唯一的最大边际线。边距宽度由2 / ||A||给出。通过最小化A的 L2 范数找到该线。

我们可以编写如下超平面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4FLfWZZT-1681566813070)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/d459df75-35ba-46f7-b3a0-2e6d934d5636.png)]

这里,A是我们部分斜率的向量,x是输入向量。最大边距的宽度可以显示为 2 除以A的 L2 范数。这个事实有许多证明,但是对于几何思想,求解从 2D 点到直线的垂直距离可以提供前进的动力。

对于线性可分的二元类数据,为了最大化余量,我们最小化A,[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-injRxuHI-1681566813070)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/c8bd7a37-6085-4843-8d01-34b9877ffe39.png)]的 L2 范数。我们还必须将此最小值置于以下约束条件下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3DNdvXCF-1681566813070)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/35825b20-6e93-4136-acb4-f51128ed4c7f.png)]

前面的约束确保我们来自相应类的所有点都在分离线的同一侧。

由于并非所有数据集都是线性可分的,因此我们可以为跨越边界线的点引入损失函数。对于n数据点,我们引入了所谓的软边际损失函数,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MPSfKPMp-1681566813071)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/068234be-29b7-4890-96ef-afd771e361f6.png)]

请注意,如果该点位于边距的正确一侧,则乘积y[i](Ax[i] - b)始终大于 1。这使得损失函数的左手项等于 0,并且对损失函数的唯一影响是余量的大小。

前面的损失函数将寻找线性可分的线,但允许穿过边缘线的点。根据α的值,这可以是硬度或软度量。α的较大值导致更加强调边距的扩大,而α的较小值导致模型更像是一个硬边缘,同时允许数据点跨越边距,如果需要的话。

在本章中,我们将建立一个软边界 SVM,并展示如何将其扩展到非线性情况和多个类。

使用线性 SVM

对于此示例,我们将从鸢尾花数据集创建线性分隔符。我们从前面的章节中知道,萼片长度和花瓣宽度创建了一个线性可分的二分类数据集,用于预测花是否是山鸢尾(I)。

准备

要在 TensorFlow 中实现软可分 SVM,我们将实现特定的损失函数,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zOGiOLH9-1681566813071)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/11e1a288-7294-4c7c-bbee-37e854bbf309.png)]

这里,A是部分斜率的向量,b是截距,x[i]是输入向量,y[i]是实际类,(-1 或 1),α是软可分性正则化参数。

操作步骤

我们按如下方式处理秘籍:

  1. 我们首先加载必要的库。这将包括用于访问鸢尾数据集的scikit-learn数据集库。使用以下代码:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 

要为此练习设置 scikit-learn,我们只需要输入$pip install -U scikit-learn。请注意,它也安装了 Anaconda。

  1. 接下来,我们启动图会话并根据需要加载数据。请记住,我们正在加载鸢尾数据集中的第一个和第四个变量,因为它们是萼片长度和萼片宽度。我们正在加载目标变量,对于山鸢尾将取值 1,否则为 -1。使用以下代码:
代码语言:javascript复制
sess = tf.Session() 
iris = datasets.load_iris() 
x_vals = np.array([[x[0], x[3]] for x in iris.data]) 
y_vals = np.array([1 if y==0 else -1 for y in iris.target])
  1. 我们现在应该将数据集拆分为训练集和测试集。我们将评估训练和测试集的准确率。由于我们知道这个数据集是线性可分的,因此我们应该期望在两个集合上获得 100% 的准确率。要拆分数据,请使用以下代码:
代码语言:javascript复制
train_indices = np.random.choice(len(x_vals), round(len(x_vals)*0.8), replace=False) 
test_indices = np.array(list(set(range(len(x_vals))) - set(train_indices))) 
x_vals_train = x_vals[train_indices] 
x_vals_test = x_vals[test_indices] 
y_vals_train = y_vals[train_indices] 
y_vals_test = y_vals[test_indices] 
  1. 接下来,我们设置批量大小,占位符和模型变量。值得一提的是,使用这种 SVM 算法,我们需要非常大的批量大小来帮助收敛。我们可以想象,对于非常小的批量大小,最大边际线会略微跳跃。理想情况下,我们也会慢慢降低学习率,但现在这已经足够了。此外,A变量将采用2x1形状,因为我们有两个预测变量:萼片长度和花瓣宽度。要进行此设置,我们使用以下代码:
代码语言:javascript复制
batch_size = 100 

x_data = tf.placeholder(shape=[None, 2], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 

A = tf.Variable(tf.random_normal(shape=[2,1])) 
b = tf.Variable(tf.random_normal(shape=[1,1])) 
  1. 我们现在声明我们的模型输出。对于正确分类的点,如果目标是山鸢尾,则返回大于或等于 1 的数字,否则返回小于或等于 -1。模型输出使用以下代码:
代码语言:javascript复制
model_output = tf.subtract(tf.matmul(x_data, A), b) 
  1. 接下来,我们将汇总并声明必要的组件以获得最大的保证金损失。首先,我们将声明一个计算向量的 L2 范数的函数。然后,我们添加边距参数 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5zigb5h1-1681566813071)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/1f304159-a7af-498c-b746-72d49ecadecb.png)]。然后我们宣布我们的分类损失并将这两项加在一起。使用以下代码:
代码语言:javascript复制
l2_norm = tf.reduce_sum(tf.square(A)) 
alpha = tf.constant([0.1]) 
classification_term = tf.reduce_mean(tf.maximum(0., tf.subtract(1., tf.multiply(model_output, y_target)))) 

loss = tf.add(classification _term, tf.multiply(alpha, l2_norm)) 
  1. 现在,我们声明我们的预测和准确率函数,以便我们可以评估训练集和测试集的准确率,如下所示:
代码语言:javascript复制
prediction = tf.sign(model_output) 
accuracy = tf.reduce_mean(tf.cast(tf.equal(prediction, y_target), tf.float32)) 
  1. 在这里,我们将声明我们的优化函数并初始化我们的模型变量;我们在以下代码中执行此操作:
代码语言:javascript复制
my_opt = tf.train.GradientDescentOptimizer(0.01) 
train_step = my_opt.minimize(loss) 

init = tf.global_variables_initializer() 
sess.run(init) 
  1. 我们现在可以开始我们的训练循环,记住我们想要在训练和测试集上记录我们的损失和训练准确率,如下所示:
代码语言:javascript复制
loss_vec = [] 
train_accuracy = [] 
test_accuracy = [] 
for i in range(500): 
   rand_index = np.random.choice(len(x_vals_train), size=batch_size) 
   rand_x = x_vals_train[rand_index] 
    rand_y = np.transpose([y_vals_train[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 

    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec.append(temp_loss) 

    train_acc_temp = sess.run(accuracy, feed_dict={x_data: x_vals_train, y_target: np.transpose([y_vals_train])}) 
    train_accuracy.append(train_acc_temp) 

    test_acc_temp = sess.run(accuracy, feed_dict={x_data: x_vals_test, y_target: np.transpose([y_vals_test])}) 
    test_accuracy.append(test_acc_temp) 

    if (i 1)0==0: 
        print('Step #'   str(i 1)   ' A = '   str(sess.run(A))   ' b = '   str(sess.run(b))) 
        print('Loss = '   str(temp_loss))
  1. 训练期间脚本的输出应如下所示:
代码语言:javascript复制
Step #100 A = [[-0.10763293] 
 [-0.65735245]] b = [[-0.68752676]] 
Loss = [ 0.48756418] 
Step #200 A = [[-0.0650763 ] 
 [-0.89443302]] b = [[-0.73912662]] 
Loss = [ 0.38910741] 
Step #300 A = [[-0.02090022] 
 [-1.12334013]] b = [[-0.79332656]] 
Loss = [ 0.28621092] 
Step #400 A = [[ 0.03189624] 
 [-1.34912157]] b = [[-0.8507266]] 
Loss = [ 0.22397576] 
Step #500 A = [[ 0.05958777] 
 [-1.55989814]] b = [[-0.9000265]] 
Loss = [ 0.20492229] 
  1. 为了绘制输出(拟合,损失和精度),我们必须提取系数并将x值分成山鸢尾和其它鸢尾,如下所示:
代码语言:javascript复制
[[a1], [a2]] = sess.run(A) 
[[b]] = sess.run(b) 
slope = -a2/a1 
y_intercept = b/a1 

x1_vals = [d[1] for d in x_vals] 

best_fit = [] 
for i in x1_vals: 
    best_fit.append(slope*i y_intercept) 

setosa_x = [d[1] for i,d in enumerate(x_vals) if y_vals[i]==1] 
setosa_y = [d[0] for i,d in enumerate(x_vals) if y_vals[i]==1] 
not_setosa_x = [d[1] for i,d in enumerate(x_vals) if y_vals[i]==-1] 
not_setosa_y = [d[0] for i,d in enumerate(x_vals) if y_vals[i]==-1] 
  1. 以下是使用线性分离器拟合,精度和损耗绘制数据的代码:
代码语言:javascript复制
plt.plot(setosa_x, setosa_y, 'o', label='I. setosa') 
plt.plot(not_setosa_x, not_setosa_y, 'x', label='Non-setosa') 
plt.plot(x1_vals, best_fit, 'r-', label='Linear Separator', linewidth=3) 
plt.ylim([0, 10]) 
plt.legend(loc='lower right') 
plt.title('Sepal Length vs Petal Width') 
plt.xlabel('Petal Width') 
plt.ylabel('Sepal Length') 
plt.show() 

plt.plot(train_accuracy, 'k-', label='Training Accuracy') 
plt.plot(test_accuracy, 'r--', label='Test Accuracy') 
plt.title('Train and Test Set Accuracies') 
plt.xlabel('Generation') 
plt.ylabel('Accuracy') 
plt.legend(loc='lower right') 
plt.show() 

plt.plot(loss_vec, 'k-') 
plt.title('Loss per Generation') 
plt.xlabel('Generation') 
plt.ylabel('Loss') 
plt.show() 

以这种方式使用 TensorFlow 来实现 SVD 算法可能导致每次运行的结果略有不同。其原因包括随机训练/测试集拆分以及每个训练批次中不同批次点的选择。此外,在每一代之后慢慢降低学习率是理想的。

得到的图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NlwfUGcR-1681566813071)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/9c53587e-f2d7-4e1f-9af9-1bde936d24da.png)]

图 2:最终线性 SVM 与绘制的两个类别拟合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jW6NO9wQ-1681566813071)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/fb331bce-9d8e-4d8e-928e-8446022dac2c.png)]

图 3:迭代测试和训练集精度;我们确实获得 100% 的准确率,因为这两个类是线性可分的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vmpx8h0a-1681566813072)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/adb5aaf5-d575-4e93-a84c-7f709afcdcb6.png)]

图 4:超过 500 次迭代的最大边际损失图

工作原理

在本文中,我们已经证明使用最大边际损失函数可以实现线性 SVD 模型。

简化为线性回归

SVM 可用于拟合线性回归。在本节中,我们将探讨如何使用 TensorFlow 执行此操作。

准备

可以将相同的最大边际概念应用于拟合线性回归。我们可以考虑最大化包含最多(xy)点的边距,而不是最大化分隔类的边距。为了说明这一点,我们将使用相同的鸢尾数据集,并表明我们可以使用此概念来拟合萼片长度和花瓣宽度之间的线。

相应的损失函数类似于:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1MiHrztw-1681566813072)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/fa49eabe-ad86-4314-8e13-ed19e7e20b27.png)]

这里,ε是边距宽度的一半,如果一个点位于该区域,则损失等于 0。

操作步骤

我们按如下方式处理秘籍:

  1. 首先,我们加载必要的库,启动图,然后加载鸢尾数据集。之后,我们将数据集拆分为训练集和测试集,以显示两者的损失。使用以下代码:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
sess = tf.Session() 
iris = datasets.load_iris() 
x_vals = np.array([x[3] for x in iris.data]) 
y_vals = np.array([y[0] for y in iris.data]) 
train_indices = np.random.choice(len(x_vals), round(len(x_vals)*0.8), replace=False) 
test_indices = np.array(list(set(range(len(x_vals))) - set(train_indices))) 
x_vals_train = x_vals[train_indices] 
x_vals_test = x_vals[test_indices] 
y_vals_train = y_vals[train_indices] 
y_vals_test = y_vals[test_indices]

对于此示例,我们将数据拆分为训练集和测试集。将数据拆分为三个数据集也很常见,其中包括验证集。我们可以使用此验证集来验证我们在训练它们时不会过拟合模型。

  1. 让我们声明我们的批量大小,占位符和变量,并创建我们的线性模型,如下所示:
代码语言:javascript复制
batch_size = 50 

x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 

A = tf.Variable(tf.random_normal(shape=[1,1])) 
b = tf.Variable(tf.random_normal(shape=[1,1])) 

model_output = tf.add(tf.matmul(x_data, A), b) 
  1. 现在,我们宣布我们的损失函数。如前文所述,损失函数实现为ε = 0.5。请记住,epsilon 是我们的损失函数的一部分,它允许软边距而不是硬边距:
代码语言:javascript复制
epsilon = tf.constant([0.5]) 
loss = tf.reduce_mean(tf.maximum(0., tf.subtract(tf.abs(tf.subtract(model_output, y_target)), epsilon))) 
  1. 我们创建一个优化器并接下来初始化我们的变量,如下所示:
代码语言:javascript复制
my_opt = tf.train.GradientDescentOptimizer(0.075) 
train_step = my_opt.minimize(loss) 

init = tf.global_variables_initializer() 
sess.run(init) 
  1. 现在,我们迭代 200 次训练迭代并保存训练和测试损失以便以后绘图:
代码语言:javascript复制
train_loss = [] 
test_loss = [] 
for i in range(200): 
    rand_index = np.random.choice(len(x_vals_train), size=batch_size) 
    rand_x = np.transpose([x_vals_train[rand_index]]) 
    rand_y = np.transpose([y_vals_train[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 

    temp_train_loss = sess.run(loss, feed_dict={x_data: np.transpose([x_vals_train]), y_target: np.transpose([y_vals_train])}) 
    train_loss.append(temp_train_loss) 

    temp_test_loss = sess.run(loss, feed_dict={x_data: np.transpose([x_vals_test]), y_target: np.transpose([y_vals_test])}) 
    test_loss.append(temp_test_loss) 
    if (i 1)P==0: 
        print('-----------') 
        print('Generation: '   str(i)) 
        print('A = '   str(sess.run(A))   ' b = '   str(sess.run(b))) 
        print('Train Loss = '   str(temp_train_loss)) 
        print('Test Loss = '   str(temp_test_loss)) 
  1. 这产生以下输出:
代码语言:javascript复制
Generation: 50 
A = [[ 2.20651722]] b = [[ 2.71290684]] 
Train Loss = 0.609453 
Test Loss = 0.460152 
----------- 
Generation: 100 
A = [[ 1.6440177]] b = [[ 3.75240564]] 
Train Loss = 0.242519 
Test Loss = 0.208901 
----------- 
Generation: 150 
A = [[ 1.27711761]] b = [[ 4.3149066]] 
Train Loss = 0.108192 
Test Loss = 0.119284 
----------- 
Generation: 200 
A = [[ 1.05271816]] b = [[ 4.53690529]] 
Train Loss = 0.0799957 
Test Loss = 0.107551 
  1. 我们现在可以提取我们找到的系数,并获得最佳拟合线的值。出于绘图目的,我们也将获得边距的值。使用以下代码:
代码语言:javascript复制
[[slope]] = sess.run(A) 
[[y_intercept]] = sess.run(b) 
[width] = sess.run(epsilon) 

best_fit = [] 
best_fit_upper = [] 
best_fit_lower = [] 
for i in x_vals: 
  best_fit.append(slope*i y_intercept) 
  best_fit_upper.append(slope*i y_intercept width) 
  best_fit_lower.append(slope*i y_intercept-width) 
  1. 最后,这里是用拟合线和训练测试损失绘制数据的代码:
代码语言:javascript复制
plt.plot(x_vals, y_vals, 'o', label='Data Points') 
plt.plot(x_vals, best_fit, 'r-', label='SVM Regression Line', linewidth=3) 
plt.plot(x_vals, best_fit_upper, 'r--', linewidth=2) 
plt.plot(x_vals, best_fit_lower, 'r--', linewidth=2) 
plt.ylim([0, 10]) 
plt.legend(loc='lower right') 
plt.title('Sepal Length vs Petal Width') 
plt.xlabel('Petal Width') 
plt.ylabel('Sepal Length') 
plt.show() 
plt.plot(train_loss, 'k-', label='Train Set Loss') 
plt.plot(test_loss, 'r--', label='Test Set Loss') 
plt.title('L2 Loss per Generation') 
plt.xlabel('Generation') 
plt.ylabel('L2 Loss') 
plt.legend(loc='upper right') 
plt.show() 

上述代码的图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DTa4ONFf-1681566813072)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/9e656a1b-1414-49d5-a4e8-e2e2d1907737.png)]

图 5:鸢尾数据上有 0.5 个边缘的 SVM 回归(萼片长度与花瓣宽度)

以下是训练迭代中的训练和测试损失:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zZmKiUpM-1681566813072)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/281870d6-9835-4fdd-8db0-8e861aa474d5.png)]

图 6:训练和测试集上每代的 SVM 回归损失

工作原理

直觉上,我们可以将 SVM 回归看作是一个函数,试图尽可能多地在宽度范围内拟合点。该线的拟合对该参数有些敏感。如果我们选择太小的ε,算法将无法适应边距中的许多点。如果我们选择太大的ε,将会有许多行能够适应边距中的所有数据点。我们更喜欢较小的ε,因为距离边缘较近的点比较远的点贡献较少的损失。

在 TensorFlow 中使用核

先前的 SVM 使用线性可分数据。如果我们分离非线性数据,我们可以改变将线性分隔符投影到数据上的方式。这是通过更改 SVM 损失函数中的核来完成的。在本章中,我们将介绍如何更改核并分离非线性可分离数据。

准备

在本文中,我们将激励支持向量机中核的使用。在线性 SVM 部分,我们用特定的损失函数求解了软边界。这种方法的另一种方法是解决所谓的优化问题的对偶。可以证明线性 SVM 问题的对偶性由以下公式给出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YzOkTYPz-1681566813073)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/a311abb6-b9c9-4aef-9cac-88438abb5879.png)]

对此,以下适用:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ov1LE5R7-1681566813073)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/6b8e7a37-0f7a-40e2-9135-838f6ea38141.png)]

这里,模型中的变量将是b向量。理想情况下,此向量将非常稀疏,仅对我们数据集的相应支持向量采用接近 1 和 -1 的值。我们的数据点向量由x[i]表示,我们的目标(1 或 -1)y[i]表示。

前面等式中的核是点积x[i] · y[j],它给出了线性核。该核是一个方形矩阵,填充了数据点i, j的点积。

我们可以将更复杂的函数扩展到更高的维度,而不是仅仅在数据点之间进行点积,而在这些维度中,类可以是线性可分的。这似乎是不必要的复杂,但我们可以选择一个具有以下属性的函数k

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lsIdQNbd-1681566813073)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/eb618f1d-e0d3-49a7-bbec-c8e5ab9049d4.png)]

这里, k被称为核函数。更常见的核是使用高斯核(也称为径向基函数核或 RBF 核)。该核用以下等式描述:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GwTLCKE9-1681566813073)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/91bc4d05-6bc2-4b0d-85ac-70a964eae981.png)]

为了对这个核进行预测,比如说p[i],我们只需在核中的相应方程中用预测点替换,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-arMgVsrJ-1681566813073)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/284cc0b5-9a95-4a09-bf23-39776df87409.png)]

在本节中,我们将讨论如何实现高斯核。我们还将在适当的位置记下在何处替换实现线性核。我们将使用的数据集将手动创建,以显示高斯核更适合在线性核上使用的位置。

操作步骤

我们按如下方式处理秘籍:

  1. 首先,我们加载必要的库并启动图会话,如下所示:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
sess = tf.Session() 
  1. 现在,我们生成数据。我们将生成的数据将是两个同心数据环;每个戒指都属于不同的阶级。我们必须确保类只有 -1 或 1。然后我们将数据分成每个类的xy值以用于绘图目的。为此,请使用以下代码:
代码语言:javascript复制
(x_vals, y_vals) = datasets.make_circles(n_samples=500, factor=.5, noise=.1) 
y_vals = np.array([1 if y==1 else -1 for y in y_vals]) 
class1_x = [x[0] for i,x in enumerate(x_vals) if y_vals[i]==1] 
class1_y = [x[1] for i,x in enumerate(x_vals) if y_vals[i]==1] 
class2_x = [x[0] for i,x in enumerate(x_vals) if y_vals[i]==-1] 
class2_y = [x[1] for i,x in enumerate(x_vals) if y_vals[i]==-1]
  1. 接下来,我们声明批量大小和占位符,并创建我们的模型变量b。对于 SVM,我们倾向于需要更大的批量大小,因为我们需要一个非常稳定的模型,该模型在每次训练生成时都不会波动很大。另请注意,我们为预测点添加了额外的占位符。为了可视化结果,我们将创建一个颜色网格,以查看哪些区域最后属于哪个类。我们这样做如下:
代码语言:javascript复制
batch_size = 250 
x_data = tf.placeholder(shape=[None, 2], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
prediction_grid = tf.placeholder(shape=[None, 2], dtype=tf.float32) 
b = tf.Variable(tf.random_normal(shape=[1,batch_size])) 
  1. 我们现在将创建高斯核。该核可以表示为矩阵运算,如下所示:
代码语言:javascript复制
gamma = tf.constant(-50.0) 
dist = tf.reduce_sum(tf.square(x_data), 1) 
dist = tf.reshape(dist, [-1,1]) 
sq_dists = tf.add(tf.subtract(dist, tf.multiply(2., tf.matmul(x_data, tf.transpose(x_data)))), tf.transpose(dist)) 
my_kernel = tf.exp(tf.multiply(gamma, tf.abs(sq_dists)))  

注意addsubtract操作的sq_dists行中广播的使用。 另外,请注意线性核可以表示为my_kernel = tf.matmul(x_data, tf.transpose(x_data))

  1. 现在,我们宣布了本秘籍中之前所述的双重问题。最后,我们将使用tf.negative()函数最小化损失函数的负值,而不是最大化。我们使用以下代码完成此任务:
代码语言:javascript复制
model_output = tf.matmul(b, my_kernel) 
first_term = tf.reduce_sum(b) 
b_vec_cross = tf.matmul(tf.transpose(b), b) 
y_target_cross = tf.matmul(y_target, tf.transpose(y_target)) 
second_term = tf.reduce_sum(tf.multiply(my_kernel, tf.multiply(b_vec_cross, y_target_cross))) 
loss = tf.negative(tf.subtract(first_term, second_term))
  1. 我们现在创建预测和准确率函数。首先,我们必须创建一个预测核,类似于步骤 4,但是我们拥有带有预测数据的点的核心,而不是点的核。然后预测是模型输出的符号。这实现如下:
代码语言:javascript复制
rA = tf.reshape(tf.reduce_sum(tf.square(x_data), 1),[-1,1]) 
rB = tf.reshape(tf.reduce_sum(tf.square(prediction_grid), 1),[-1,1]) 
pred_sq_dist = tf.add(tf.subtract(rA, tf.multiply(2., tf.matmul(x_data, tf.transpose(prediction_grid)))), tf.transpose(rB)) 
pred_kernel = tf.exp(tf.multiply(gamma, tf.abs(pred_sq_dist))) 

prediction_output = tf.matmul(tf.multiply(tf.transpose(y_target),b), pred_kernel) 
prediction = tf.sign(prediction_output-tf.reduce_mean(prediction_output)) 
accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.squeeze(prediction), tf.squeeze(y_target)), tf.float32)) 

为了实现线性预测核,我们可以编写pred_kernel = tf.matmul(x_data, tf.transpose(prediction_grid))

  1. 现在,我们可以创建一个优化函数并初始化所有变量,如下所示:
代码语言:javascript复制
my_opt = tf.train.GradientDescentOptimizer(0.001) 
train_step = my_opt.minimize(loss) 
init = tf.global_variables_initializer() 
sess.run(init) 
  1. 接下来,我们开始训练循环。我们将记录每代的损耗向量和批次精度。当我们运行准确率时,我们必须放入所有三个占位符,但我们输入x数据两次以获得对点的预测,如下所示:
代码语言:javascript复制
loss_vec = [] 
batch_accuracy = [] 
for i in range(500): 
    rand_index = np.random.choice(len(x_vals), size=batch_size) 
    rand_x = x_vals[rand_index] 
    rand_y = np.transpose([y_vals[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 

    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec.append(temp_loss) 

    acc_temp = sess.run(accuracy, feed_dict={x_data: rand_x, 
                                             y_target: rand_y, 
                                             prediction_grid:rand_x}) 
    batch_accuracy.append(acc_temp) 

    if (i 1)0==0: 
        print('Step #'   str(i 1)) 
        print('Loss = '   str(temp_loss)) 
  1. 这产生以下输出:
代码语言:javascript复制
Step #100 
Loss = -28.0772 
Step #200 
Loss = -3.3628 
Step #300 
Loss = -58.862 
Step #400 
Loss = -75.1121 
Step #500 
Loss = -84.8905 
  1. 为了查看整个空间的输出类,我们将在系统中创建一个预测点网格,并对所有这些预测点进行预测,如下所示:
代码语言:javascript复制
x_min, x_max = x_vals[:, 0].min() - 1, x_vals[:, 0].max()   1 
y_min, y_max = x_vals[:, 1].min() - 1, x_vals[:, 1].max()   1 
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), 
                     np.arange(y_min, y_max, 0.02)) 
grid_points = np.c_[xx.ravel(), yy.ravel()] 
[grid_predictions] = sess.run(prediction, feed_dict={x_data: x_vals, 
                                                   y_target: np.transpose([y_vals]), 
                                                   prediction_grid: grid_points}) 
grid_predictions = grid_predictions.reshape(xx.shape) 
  1. 以下是绘制结果,批次准确率和损失的代码:
代码语言:javascript复制
plt.contourf(xx, yy, grid_predictions, cmap=plt.cm.Paired, alpha=0.8) 
plt.plot(class1_x, class1_y, 'ro', label='Class 1') 
plt.plot(class2_x, class2_y, 'kx', label='Class -1') 
plt.legend(loc='lower right') 
plt.ylim([-1.5, 1.5]) 
plt.xlim([-1.5, 1.5]) 
plt.show() 

plt.plot(batch_accuracy, 'k-', label='Accuracy') 
plt.title('Batch Accuracy') 
plt.xlabel('Generation') 
plt.ylabel('Accuracy') 
plt.legend(loc='lower right') 
plt.show() 

plt.plot(loss_vec, 'k-') 
plt.title('Loss per Generation') 
plt.xlabel('Generation') 
plt.ylabel('Loss') 
plt.show() 

为了简洁起见,我们将仅显示结果图,但我们也可以单独运行绘图代码并查看损失和准确率。

以下屏幕截图说明了线性可分离拟合对我们的非线性数据有多糟糕:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bSuZeo0Y-1681566813074)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/3dc4f147-d736-4b8d-a4af-e004a6f306fc.png)]

图 7:非线性可分离数据上的线性 SVM

以下屏幕截图显示了高斯核可以更好地拟合非线性数据:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uNeJIL8p-1681566813074)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/942b3cec-7ca0-42d5-bc2b-7fddb0a4d4e4.png)]Figure 8: Non-linear SVM with Gaussian kernel results on non-linear ring data

如果我们使用高斯核来分离我们的非线性环数据,我们会得到更好的拟合。

工作原理

有两个重要的代码需要了解:我们如何实现核,以及我们如何为 SVM 双优化问题实现损失函数。我们已经展示了如何实现线性和高斯核,并且高斯核可以分离非线性数据集。

我们还应该提到另一个参数,即高斯核中的伽马值。此参数控制影响点对分离曲率的影响程度。通常选择小值,但它在很大程度上取决于数据集。理想情况下,使用交叉验证等统计技术选择此参数。

对于新点的预测/评估,我们使用以下命令:sess.run(prediction, feed_dict:{x_data: x_vals, y_data: np.transpose([y_vals])})。此评估必须包括原始数据集(x_valsy_vals),因为 SVM 是使用支持向量定义的,由哪些点指定在边界上或不是。

更多

如果我们这样选择,我们可以实现更多核。以下是一些更常见的非线性核列表:

  • 多项式齐次核:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-90714whP-1681566813074)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/3233ad1f-7336-40e8-b9de-895eb041bb5b.png)]

  • 多项式非齐次核:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T1cbNmcz-1681566813074)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/bdb676cb-da55-47f7-a656-2a0406ff4ab3.png)]

  • 双曲正切核:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zGkuLQ51-1681566813075)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/5d9c1c9e-e1ce-4497-9fc4-5f7be14ce1b3.png)]

实现非线性 SVM

对于此秘籍,我们将应用非线性核来拆分数据集。

准备

在本节中,我们将在实际数据上实现前面的高斯核 SVM。我们将加载鸢尾数据集并为山鸢尾创建分类器(与其它鸢尾相比)。我们将看到各种伽马值对分类的影响。

操作步骤

我们按如下方式处理秘籍:

  1. 我们首先加载必要的库,其中包括scikit-learn数据集,以便我们可以加载鸢尾数据。然后,我们将启动图会话。使用以下代码:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
sess = tf.Session() 
  1. 接下来,我们将加载鸢尾数据,提取萼片长度和花瓣宽度,并分离每个类的xy值(以便以后绘图),如下所示:
代码语言:javascript复制
iris = datasets.load_iris() 
x_vals = np.array([[x[0], x[3]] for x in iris.data]) 
y_vals = np.array([1 if y==0 else -1 for y in iris.target]) 
class1_x = [x[0] for i,x in enumerate(x_vals) if y_vals[i]==1] 
class1_y = [x[1] for i,x in enumerate(x_vals) if y_vals[i]==1] 
class2_x = [x[0] for i,x in enumerate(x_vals) if y_vals[i]==-1] 
class2_y = [x[1] for i,x in enumerate(x_vals) if y_vals[i]==-1] 
  1. 现在,我们声明我们的批量大小(首选大批量),占位符和模型变量b,如下所示:
代码语言:javascript复制
batch_size = 100 

x_data = tf.placeholder(shape=[None, 2], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
prediction_grid = tf.placeholder(shape=[None, 2], dtype=tf.float32) 

b = tf.Variable(tf.random_normal(shape=[1,batch_size]))
  1. 接下来,我们声明我们的高斯核。这个核依赖于伽马值,我们将在本文后面的各个伽玛值对分类的影响进行说明。使用以下代码:
代码语言:javascript复制
gamma = tf.constant(-10.0) 
dist = tf.reduce_sum(tf.square(x_data), 1) 
dist = tf.reshape(dist, [-1,1]) 
sq_dists = tf.add(tf.subtract(dist, tf.multiply(2., tf.matmul(x_data, tf.transpose(x_data)))), tf.transpose(dist)) 
my_kernel = tf.exp(tf.multiply(gamma, tf.abs(sq_dists))) 
# We now compute the loss for the dual optimization problem, as follows: 
model_output = tf.matmul(b, my_kernel) 
first_term = tf.reduce_sum(b) 
b_vec_cross = tf.matmul(tf.transpose(b), b) 
y_target_cross = tf.matmul(y_target, tf.transpose(y_target)) 
second_term = tf.reduce_sum(tf.multiply(my_kernel, tf.multiply(b_vec_cross, y_target_cross))) 
loss = tf.negative(tf.subtract(first_term, second_term)) 
  1. 为了使用 SVM 执行预测,我们必须创建预测核函数。之后,我们还会声明一个准确率计算,它只是使用以下代码正确分类的点的百分比:
代码语言:javascript复制
rA = tf.reshape(tf.reduce_sum(tf.square(x_data), 1),[-1,1]) 
rB = tf.reshape(tf.reduce_sum(tf.square(prediction_grid), 1),[-1,1]) 
pred_sq_dist = tf.add(tf.subtract(rA, tf.mul(2., tf.matmul(x_data, tf.transpose(prediction_grid)))), tf.transpose(rB)) 
pred_kernel = tf.exp(tf.multiply(gamma, tf.abs(pred_sq_dist))) 

prediction_output = tf.matmul(tf.multiply(tf.transpose(y_target),b), pred_kernel) 
prediction = tf.sign(prediction_output-tf.reduce_mean(prediction_output)) 
accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.squeeze(prediction), tf.squeeze(y_target)), tf.float32)) 
  1. 接下来,我们声明我们的优化函数并初始化变量,如下所示:
代码语言:javascript复制
my_opt = tf.train.GradientDescentOptimizer(0.01) 
train_step = my_opt.minimize(loss) 
init = tf.initialize_all_variables() 
sess.run(init)
  1. 现在,我们可以开始训练循环了。我们运行循环 300 次迭代并存储损失值和批次精度。为此,我们使用以下实现:
代码语言:javascript复制
loss_vec = [] 
batch_accuracy = [] 
for i in range(300): 
    rand_index = np.random.choice(len(x_vals), size=batch_size) 
    rand_x = x_vals[rand_index] 
    rand_y = np.transpose([y_vals[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 

    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec.append(temp_loss) 

    acc_temp = sess.run(accuracy, feed_dict={x_data: rand_x, 
                                             y_target: rand_y, 
                                             prediction_grid:rand_x}) 
    batch_accuracy.append(acc_temp) 
  1. 为了绘制决策边界,我们将创建xy点的网格并评估我们在所有这些点上创建的预测函数,如下所示:
代码语言:javascript复制
x_min, x_max = x_vals[:, 0].min() - 1, x_vals[:, 0].max()   1 
y_min, y_max = x_vals[:, 1].min() - 1, x_vals[:, 1].max()   1 
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), 
                     np.arange(y_min, y_max, 0.02)) 
grid_points = np.c_[xx.ravel(), yy.ravel()] 
[grid_predictions] = sess.run(prediction, feed_dict={x_data: x_vals, 
                                                   y_target: np.transpose([y_vals]), 
                                                   prediction_grid: grid_points}) 
grid_predictions = grid_predictions.reshape(xx.shape) 
  1. 为简洁起见,我们只展示如何用决策边界绘制点。有关伽马值的图和效果,请参阅本秘籍的下一部分。使用以下代码:
代码语言:javascript复制
plt.contourf(xx, yy, grid_predictions, cmap=plt.cm.Paired, alpha=0.8) 
plt.plot(class1_x, class1_y, 'ro', label='I. setosa') 
plt.plot(class2_x, class2_y, 'kx', label='Non-setosa') 
plt.title('Gaussian SVM Results on Iris Data') 
plt.xlabel('Petal Length') 
plt.ylabel('Sepal Width') 
plt.legend(loc='lower right') 
plt.ylim([-0.5, 3.0]) 
plt.xlim([3.5, 8.5]) 
plt.show() 

工作原理

以下是对四种不同伽玛值(1,10,25 和 100)的山鸢尾结果的分类。注意伽玛值越高,每个单独点对分类边界的影响越大:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vv0jEvNY-1681566813075)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/21a6bf2b-18fd-407c-9854-bca30ac02a84.png)]

图 9:使用具有四个不同伽马值的高斯核 SVM 的山鸢尾的分类结果

实现多类 SVM

我们还可以使用 SVM 对多个类进行分类,而不仅仅是两个类。在本文中,我们将使用多类 SVM 对鸢尾数据集中的三种类型的花进行分类。

准备

通过设计,SVM 算法是二元分类器。但是,有一些策略可以让他们在多个类上工作。两种主要策略称为“一对一”,“一对剩余”。

一对一是一种策略,其中为每个可能的类对创建二分类器。然后,对具有最多投票的类的点进行预测。这可能在计算上很难,因为我们必须为k类创建k!/(k - 2)!2!个分类器。

实现多类分类器的另一种方法是执行一对一策略,我们为k类的每个类创建一个分类器。点的预测类将是创建最大 SVM 边距的类。这是我们将在本节中实现的策略。

在这里,我们将加载鸢尾数据集并使用高斯核执行多类非线性 SVM。鸢尾数据集是理想的,因为有三个类(山鸢尾,弗吉尼亚和杂色鸢尾)。我们将为每个类创建三个高斯核 SVM,并预测存在最高边界的点。

操作步骤

我们按如下方式处理秘籍:

  1. 首先,我们加载我们需要的库并启动图,如下所示:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
sess = tf.Session()
  1. 接下来,我们将加载鸢尾数据集并拆分每个类的目标。我们将仅使用萼片长度和花瓣宽度来说明,因为我们希望能够绘制输出。我们还将每个类的xy值分开,以便最后进行绘图。使用以下代码:
代码语言:javascript复制
iris = datasets.load_iris() 
x_vals = np.array([[x[0], x[3]] for x in iris.data]) 
y_vals1 = np.array([1 if y==0 else -1 for y in iris.target]) 
y_vals2 = np.array([1 if y==1 else -1 for y in iris.target]) 
y_vals3 = np.array([1 if y==2 else -1 for y in iris.target]) 
y_vals = np.array([y_vals1, y_vals2, y_vals3]) 
class1_x = [x[0] for i,x in enumerate(x_vals) if iris.target[i]==0] 
class1_y = [x[1] for i,x in enumerate(x_vals) if iris.target[i]==0] 
class2_x = [x[0] for i,x in enumerate(x_vals) if iris.target[i]==1] 
class2_y = [x[1] for i,x in enumerate(x_vals) if iris.target[i]==1] 
class3_x = [x[0] for i,x in enumerate(x_vals) if iris.target[i]==2] 
class3_y = [x[1] for i,x in enumerate(x_vals) if iris.target[i]==2] 
  1. 与实现非线性 SVM 秘籍相比,我们在此示例中所做的最大改变是,许多维度将发生变化(我们现在有三个分类器而不是一个)。我们还将利用矩阵广播和重塑技术一次计算所有三个 SVM。由于我们一次性完成这一操作,我们的y_target占位符现在具有[3, None]的大小,我们的模型变量b将被初始化为[3, batch_size]。使用以下代码:
代码语言:javascript复制
batch_size = 50 

x_data = tf.placeholder(shape=[None, 2], dtype=tf.float32) 
y_target = tf.placeholder(shape=[3, None], dtype=tf.float32) 
prediction_grid = tf.placeholder(shape=[None, 2], dtype=tf.float32) 

b = tf.Variable(tf.random_normal(shape=[3,batch_size])) 
  1. 接下来,我们计算高斯核。由于这仅取决于输入的 x 数据,因此该代码不会改变先前的秘籍。使用以下代码:
代码语言:javascript复制
gamma = tf.constant(-10.0) 
dist = tf.reduce_sum(tf.square(x_data), 1) 
dist = tf.reshape(dist, [-1,1]) 
sq_dists = tf.add(tf.subtract(dist, tf.multiply(2., tf.matmul(x_data, tf.transpose(x_data)))), tf.transpose(dist)) 
my_kernel = tf.exp(tf.multiply(gamma, tf.abs(sq_dists)))
  1. 一个重大变化是我们将进行批量矩阵乘法。我们将最终得到三维矩阵,我们将希望在第三个索引上广播矩阵乘法。我们没有为此设置数据和目标矩阵。为了使x^T · x等操作跨越额外维度,我们创建一个函数来扩展这样的矩阵,将矩阵重新整形为转置,然后在额外维度上调用 TensorFlow 的batch_matmul。使用以下代码:
代码语言:javascript复制
def reshape_matmul(mat): 
    v1 = tf.expand_dims(mat, 1) 
    v2 = tf.reshape(v1, [3, batch_size, 1]) 
    return tf.batch_matmul(v2, v1)
  1. 创建此函数后,我们现在可以计算双重损失函数,如下所示:
代码语言:javascript复制
model_output = tf.matmul(b, my_kernel) 
first_term = tf.reduce_sum(b) 
b_vec_cross = tf.matmul(tf.transpose(b), b) 
y_target_cross = reshape_matmul(y_target) 

second_term = tf.reduce_sum(tf.multiply(my_kernel, tf.multiply(b_vec_cross, y_target_cross)),[1,2]) 
loss = tf.reduce_sum(tf.negative(tf.subtract(first_term, second_term))) 
  1. 现在,我们可以创建预测核。请注意,我们必须小心reduce_sum函数并且不要在所有三个 SVM 预测中减少,因此我们必须告诉 TensorFlow 不要用第二个索引参数对所有内容求和。使用以下代码:
代码语言:javascript复制
rA = tf.reshape(tf.reduce_sum(tf.square(x_data), 1),[-1,1]) 
rB = tf.reshape(tf.reduce_sum(tf.square(prediction_grid), 1),[-1,1]) 
pred_sq_dist = tf.add(tf.subtract(rA, tf.multiply(2., tf.matmul(x_data, tf.transpose(prediction_grid)))), tf.transpose(rB)) 
pred_kernel = tf.exp(tf.multiply(gamma, tf.abs(pred_sq_dist))) 
  1. 当我们完成预测核时,我们可以创建预测。这里的一个重大变化是预测不是输出的sign()。由于我们正在实现一对一策略,因此预测是具有最大输出的分类器。为此,我们使用 TensorFlow 的内置argmax()函数,如下所示:
代码语言:javascript复制
prediction_output = tf.matmul(tf.mul(y_target,b), pred_kernel) 
prediction = tf.arg_max(prediction_output-tf.expand_dims(tf.reduce_mean(prediction_output,1), 1), 0) 
accuracy = tf.reduce_mean(tf.cast(tf.equal(prediction, tf.argmax(y_target,0)), tf.float32)) 
  1. 现在我们已经拥有了核,损失和预测函数,我们只需要声明我们的优化函数并初始化我们的变量,如下所示:
代码语言:javascript复制
my_opt = tf.train.GradientDescentOptimizer(0.01) 
train_step = my_opt.minimize(loss) 
init = tf.global_variables_initializer() 
sess.run(init)
  1. 该算法收敛速度相对较快,因此我们不必运行训练循环超过 100 次迭代。我们使用以下代码执行此操作:
代码语言:javascript复制
loss_vec = [] 
batch_accuracy = [] 
for i in range(100): 
    rand_index = np.random.choice(len(x_vals), size=batch_size) 
    rand_x = x_vals[rand_index] 
    rand_y = y_vals[:,rand_index] 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 

    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec.append(temp_loss) 

    acc_temp = sess.run(accuracy, feed_dict={x_data: rand_x, y_target: rand_y, prediction_grid:rand_x}) 
    batch_accuracy.append(acc_temp) 

    if (i 1)%==0: 
        print('Step #'   str(i 1)) 
        print('Loss = '   str(temp_loss)) 

Step #25 
Loss = -2.8951 
Step #50 
Loss = -27.9612 
Step #75 
Loss = -26.896 
Step #100 
Loss = -30.2325
  1. 我们现在可以创建点的预测网格并对所有点运行预测函数,如下所示:
代码语言:javascript复制
x_min, x_max = x_vals[:, 0].min() - 1, x_vals[:, 0].max()   1 
y_min, y_max = x_vals[:, 1].min() - 1, x_vals[:, 1].max()   1 
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), 
                     np.arange(y_min, y_max, 0.02)) 
grid_points = np.c_[xx.ravel(), yy.ravel()] 
grid_predictions = sess.run(prediction, feed_dict={x_data: rand_x, 
                                                   y_target: rand_y, 
                                                   prediction_grid: grid_points}) 
grid_predictions = grid_predictions.reshape(xx.shape)
  1. 以下是绘制结果,批量准确率和损失函数的代码。为简洁起见,我们只显示最终结果:
代码语言:javascript复制
plt.contourf(xx, yy, grid_predictions, cmap=plt.cm.Paired, alpha=0.8) 
plt.plot(class1_x, class1_y, 'ro', label='I. setosa') 
plt.plot(class2_x, class2_y, 'kx', label='I. versicolor') 
plt.plot(class3_x, class3_y, 'gv', label='I. virginica') 
plt.title('Gaussian SVM Results on Iris Data') 
plt.xlabel('Petal Length') 
plt.ylabel('Sepal Width') 
plt.legend(loc='lower right') 
plt.ylim([-0.5, 3.0]) 
plt.xlim([3.5, 8.5])  
plt.show() 

plt.plot(batch_accuracy, 'k-', label='Accuracy') 
plt.title('Batch Accuracy') 
plt.xlabel('Generation') 
plt.ylabel('Accuracy') 
plt.legend(loc='lower right') 
plt.show() 

plt.plot(loss_vec, 'k-') 
plt.title('Loss per Generation') 
plt.xlabel('Generation') 
plt.ylabel('Loss') 
plt.show() 

然后我们得到以下绘图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uBuuoduZ-1681566813075)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/42907158-e660-4a92-a2da-897722139ec5.png)]

图 10:在鸢尾数据集上的伽马为 10 的多类(三类)非线性高斯 SVM 的结果

我们观察前面的屏幕截图,其中显示了所有三个鸢尾类,以及为每个类分类的点网格。

工作原理

本文中需要注意的重点是我们如何改变算法以同时优化三个 SVM 模型。我们的模型参数b有一个额外的维度可以考虑所有三个模型。在这里,我们可以看到,由于 TensorFlow 处理额外维度的内置函数,算法扩展到多个类似算法相对容易。

下一章将介绍最近邻方法,这是一种用于预测目的的非常强大的算法。

五、最近邻方法

本章将重点介绍最近邻方法,以及如何在 TensorFlow 中实现它们。我们将首先介绍这些方法,然后我们将说明如何实现各种形式。本章将以地址匹配和图像识别的示例结束。

在本章中,我们将介绍以下内容:

  • 使用最近邻
  • 使用基于文本的距离
  • 计算混合距离函数
  • 使用地址匹配的示例
  • 使用最近邻进行图像识别

请注意,所有最新代码均可在 Github 和 Packt 仓库获得。

介绍

最近邻方法植根于基于距离的概念思想。我们认为我们的训练设定了一个模型,并根据它们与训练集中的点的接近程度对新点进行预测。一种简单的方法是使预测类与最接近的训练数据点类相同。但由于大多数数据集包含一定程度的噪声,因此更常见的方法是采用一组k-最近邻的加权平均值。该方法称为 K 最近邻(KNN)。

给定具有相应目标(y[1], y[2]....y[n])的训练数据集(x[1],x[2].....x[n]),我们可以通过查看一组最近邻来对点z进行预测。实际的预测方法取决于我们是进行回归(连续y[i])还是分类(离散y[i])。

对于离散分类目标,可以通过最大投票方案给出预测,通过到预测点的距离加权:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Vm01fEg-1681566813075)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/f6815331-7ba6-4be4-9719-eeeca8f4dd94.png)]

我们这里的预测f(z)是所有类别j的最大加权值,其中从预测点到训练点的加权距离iφ(d[ij])给出。如果点i在类j.中,l[ij]只是一个指示器函数如果点i在类j中,则指示器函数取值 1,如果不是,则取值 0 另外,k是要考虑的最近点数。

对于连续回归目标,预测由最接近预测的所有k点的加权平均值给出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h0tiKWAJ-1681566813076)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/7bd24242-3c82-47ee-b114-7aeb5922e317.png)]

很明显,预测很大程度上取决于距离度量的选择d

距离度量的常用规范是 L1 和 L2 距离,如下所示:

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H5j92IqL-1681566813076)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/1506c3f9-8094-4485-8d10-dd4dcf414fbe.png)]
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GXHYfQ1H-1681566813076)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/b5116b6c-0f94-40b6-86b6-58611da16ca4.png)]

我们可以选择许多不同规格的距离指标。在本章中,我们将探讨 L1 和 L2 指标,以及编辑和文本距离。

我们还必须选择如何加权距离。对距离进行加权的直接方法是距离本身。远离我们预测的点应该比较近点的影响小。最常见的权重方法是通过距离的归一化逆。我们将在下一个秘籍中实现此方法。

注意,KNN 是一种聚合方法。对于回归,我们正在执行邻居的加权平均。因此,预测将不那么极端,并且与实际目标相比变化较小。这种影响的大小将由算法中邻居的数量k决定。

使用最近邻

我们将通过实现最近邻来预测住房价值来开始本章。这是从最近邻开始的好方法,因为我们将处理数字特征和连续目标。

准备

为了说明如何在 TensorFlow 中使用最近邻进行预测,我们将使用波士顿住房数据集。在这里,我们将预测邻域住房价值中位数作为几个特征的函数。

由于我们考虑训练集训练模型,我们将找到预测点的 KNN,并将计算目标值的加权平均值。

操作步骤

我们按如下方式处理秘籍:

  1. 我们将从加载所需的库并启动图会话开始。我们将使用requests模块从 UCI 机器学习库加载必要的波士顿住房数据:
代码语言:javascript复制
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
import requests 

sess = tf.Session() 
  1. 接下来,我们将使用requests模块加载数据:
代码语言:javascript复制
housing_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data' 
housing_header = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV'] 
cols_used = ['CRIM', 'INDUS', 'NOX', 'RM', 'AGE', 'DIS', 'TAX', 'PTRATIO', 'B', 'LSTAT'] 
num_features = len(cols_used) 
# Request data 
housing_file = requests.get(housing_url) 
# Parse Data 
housing_data = [[float(x) for x in y.split(' ') if len(x)>=1] for y in housing_file.text.split('n') if len(y)>=1] 
  1. 接下来,我们将数据分为依赖和独立的特征。我们将预测最后一个变量MEDV,这是房屋组的中值。我们也不会使用ZNCHASRAD特征,因为它们没有信息或二元性质:
代码语言:javascript复制
y_vals = np.transpose([np.array([y[13] for y in housing_data])]) 
x_vals = np.array([[x for i,x in enumerate(y) if housing_header[i] in cols_used] for y in housing_data]) 

x_vals = (x_vals - x_vals.min(0)) / x_vals.ptp(0) 
  1. 现在,我们将xy值分成训练和测试集。我们将通过随机选择大约 80% 的行来创建训练集,并将剩下的 20% 留给测试集:
代码语言:javascript复制
train_indices = np.random.choice(len(x_vals), round(len(x_vals)*0.8), replace=False) 
test_indices = np.array(list(set(range(len(x_vals))) - set(train_indices))) 
x_vals_train = x_vals[train_indices] 
x_vals_test = x_vals[test_indices] 
y_vals_train = y_vals[train_indices] 
y_vals_test = y_vals[test_indices] 
  1. 接下来,我们将声明k值和批量大小:
代码语言:javascript复制
k = 4 
batch_size=len(x_vals_test)
  1. 我们接下来会申报占位符。请记住,没有模型变量需要训练,因为模型完全由我们的训练集确定:
代码语言:javascript复制
x_data_train = tf.placeholder(shape=[None, num_features], dtype=tf.float32)
x_data_test = tf.placeholder(shape=[None, num_features], dtype=tf.float32)
y_target_train = tf.placeholder(shape=[None, 1], dtype=tf.float32)
y_target_test = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
  1. 接下来,我们将为一批测试点创建距离函数。在这里,我们将说明 L1 距离的使用:
代码语言:javascript复制
distance = tf.reduce_sum(tf.abs(tf.subtract(x_data_train, tf.expand_dims(x_data_test,1))), reduction_indices=2) 

注意,也可以使用 L2 距离函数。我们将距离公式改为distance = tf.sqrt(tf.reduce_sum(tf.square(tf.subtract(x_data_train, tf.expand_dims(x_data_test,1))), reduction_indices=1))

  1. 现在,我们将创建我们的预测函数。为此,我们将使用top_k()函数,该函数返回张量中最大值的值和索引。由于我们想要最小距离的指数,我们将找到k - 最大负距离。我们还将声明目标值的预测和均方误差(MSE):
代码语言:javascript复制
top_k_xvals, top_k_indices = tf.nn.top_k(tf.negative(distance), k=k) 
x_sums = tf.expand_dims(tf.reduce_sum(top_k_xvals, 1),1) 
x_sums_repeated = tf.matmul(x_sums,tf.ones([1, k], tf.float32)) 
x_val_weights = tf.expand_dims(tf.divide(top_k_xvals,x_sums_repeated), 1) 

top_k_yvals = tf.gather(y_target_train, top_k_indices) 
prediction = tf.squeeze(tf.batch_matmul(x_val_weights,top_k_yvals), squeeze_dims=[1]) 
mse = tf.divide(tf.reduce_sum(tf.square(tf.subtract(prediction, y_target_test))), batch_size)
  1. 现在,我们将遍历测试数据并存储预测和准确率值:
代码语言:javascript复制
num_loops = int(np.ceil(len(x_vals_test)/batch_size)) 

for i in range(num_loops): 
    min_index = i*batch_size 
    max_index = min((i 1)*batch_size,len(x_vals_train)) 
    x_batch = x_vals_test[min_index:max_index] 
    y_batch = y_vals_test[min_index:max_index] 
    predictions = sess.run(prediction, feed_dict={x_data_train: x_vals_train, x_data_test: x_batch, y_target_train: y_vals_train, y_target_test: y_batch}) 
    batch_mse = sess.run(mse, feed_dict={x_data_train: x_vals_train, x_data_test: x_batch, y_target_train: y_vals_train, y_target_test: y_batch}) 

    print('Batch #'   str(i 1)   ' MSE: '   str(np.round(batch_mse,3))) 

Batch #1 MSE: 23.153 
  1. 另外,我们可以查看实际目标值与预测值的直方图。看待这一点的一个原因是要注意这样一个事实:使用平均方法,我们无法预测目标的极端:
代码语言:javascript复制
bins = np.linspace(5, 50, 45) 
plt.hist(predictions, bins, alpha=0.5, label='Prediction') 
plt.hist(y_batch, bins, alpha=0.5, label='Actual') 
plt.title('Histogram of Predicted and Actual Values') 
plt.xlabel('Med Home Value in $1,000s') 
plt.ylabel('Frequency') 
plt.legend(loc='upper right') 
plt.show() 

然后我们将获得直方图,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EeDqAblx-1681566813076)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/9900e23a-473d-4a23-8864-30cfcc69e691.png)]

图 1:KNN 的预测值和实际目标值的直方图(其中k=4

一个难以确定的是k的最佳值。对于上图和预测,我们将k=4用于我们的模型。我们之所以选择这个,是因为它给了我们最低的 MSE。这通过交叉验证来验证。如果我们在k的多个值上使用交叉验证,我们将看到k=4给我们一个最小的 MSE。我们在下图中说明了这一点。绘制预测值的方差也是值得的,以表明它会随着我们平均的邻居越多而减少:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WshbdZHz-1681566813076)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/7320b5e5-e41d-4431-9ef1-7fa0b43a5187.png)]

图 2:各种k值的 KNN 预测的 MSE。我们还绘制了测试集上预测值的方差。请注意,随着k的增加,方差会减小。

工作原理

使用最近邻算法,模型是训练集。因此,我们不必在模型中训练任何变量。唯一的参数k是通过交叉验证确定的,以最大限度地减少我们的 MSE。

更多

对于 KNN 的加权,我们选择直接按距离加权。还有其他选择我们也可以考虑。另一种常见方法是通过反平方距离加权。

使用基于文本的距离

最近邻比处理数字更通用。只要我们有一种方法来测量特征之间的距离,我们就可以应用最近邻算法。在本文中,我们将介绍如何使用 TensorFlow 测量文本距离。

准备

在本文中,我们将说明如何在字符串之间使用 TensorFlow 的文本距离度量,Levenshtein 距离(编辑距离)。这将在本章后面重要,因为我们扩展了最近邻方法以包含带有文本的特征。

Levenshtein 距离是从一个字符串到另一个字符串的最小编辑次数。允许的编辑是插入字符,删除字符或用不同的字符替换字符。对于这个秘籍,我们将使用 TensorFlow 的 Levenshtein 距离函数edit_distance()。值得说明这个函数的用法,因为它的用法将适用于后面的章节。

请注意,TensorFlow 的edit_distance()函数仅接受稀疏张量。我们必须创建我们的字符串作为单个字符的稀疏张量。

操作步骤

  1. 首先,我们将加载 TensorFlow 并初始化图:
代码语言:javascript复制
import tensorflow as tf 
sess = tf.Session() 
  1. 然后,我们将说明如何计算两个单词'bear''beer'之间的编辑距离。首先,我们将使用 Python 的list()函数从我们的字符串创建一个字符列表。接下来,我们将从该列表中创建一个稀疏的 3D 矩阵。我们必须告诉 TensorFlow 字符索引,矩阵的形状以及我们在张量中想要的字符。之后,我们可以决定是否要使用总编辑距离(normalize=False)或标准化编辑距离(normalize=True),我们将编辑距离除以第二个单词的长度:
代码语言:javascript复制
hypothesis = list('bear') 
truth = list('beers') 
h1 = tf.SparseTensor([[0,0,0], [0,0,1], [0,0,2], [0,0,3]], 
                     hypothesis, [1,1,1]) 
t1 = tf.SparseTensor([[0,0,0], [0,0,1], [0,0,1], [0,0,3],[0,0,4]], truth, [1,1,1]) 

print(sess.run(tf.edit_distance(h1, t1, normalize=False))) 

[[ 2.]]

TensorFlow 的文档将两个字符串视为提议(假设)字符串和基础事实字符串。我们将在这里用ht张量继续这个表示法。函数SparseTensorValue()是一种在 TensorFlow 中创建稀疏张量的方法。它接受我们希望创建的稀疏张量的索引,值和形状。

  1. 接下来,我们将说明如何将两个单词bearbeer与另一个单词beers进行比较。为了达到这个目的,我们必须复制beers以获得相同数量的可比词:
代码语言:javascript复制
hypothesis2 = list('bearbeer') 
truth2 = list('beersbeers') 
h2 = tf.SparseTensor([[0,0,0], [0,0,1], [0,0,2], [0,0,3], [0,1,0], [0,1,1], [0,1,2], [0,1,3]], hypothesis2, [1,2,4]) 
t2 = tf.SparseTensor([[0,0,0], [0,0,1], [0,0,2], [0,0,3], [0,0,4], [0,1,0], [0,1,1], [0,1,2], [0,1,3], [0,1,4]], truth2, [1,2,5]) 

print(sess.run(tf.edit_distance(h2, t2, normalize=True))) 

[[ 0.40000001  0.2      ]]
  1. 在此示例中显示了将一组单词与另一单词进行比较的更有效方法。我们将事先为假设和基本真实字符串创建索引和字符列表:
代码语言:javascript复制
hypothesis_words = ['bear','bar','tensor','flow'] 
truth_word = ['beers''] 
num_h_words = len(hypothesis_words) 
h_indices = [[xi, 0, yi] for xi,x in enumerate(hypothesis_words) for yi,y in enumerate(x)] 
h_chars = list(''.join(hypothesis_words)) 
h3 = tf.SparseTensor(h_indices, h_chars, [num_h_words,1,1]) 
truth_word_vec = truth_word*num_h_words 
t_indices = [[xi, 0, yi] for xi,x in enumerate(truth_word_vec) for yi,y in enumerate(x)] 
t_chars = list(''.join(truth_word_vec)) 
t3 = tf.SparseTensor(t_indices, t_chars, [num_h_words,1,1]) 

print(sess.run(tf.edit_distance(h3, t3, normalize=True))) 

[[ 0.40000001]
 [ 0.60000002]
 [ 0.80000001]
 [ 1.        ]]
  1. 现在,我们将说明如何使用占位符计算两个单词列表之间的编辑距离。这个概念是一样的,除了我们将SparseTensorValue()而不是稀疏张量。首先,我们将创建一个从单词列表创建稀疏张量的函数:
代码语言:javascript复制
def create_sparse_vec(word_list): 
    num_words = len(word_list) 
    indices = [[xi, 0, yi] for xi,x in enumerate(word_list) for yi,y in enumerate(x)] 
    chars = list(''.join(word_list)) 
    return(tf.SparseTensorValue(indices, chars, [num_words,1,1])) 

hyp_string_sparse = create_sparse_vec(hypothesis_words) 
truth_string_sparse = create_sparse_vec(truth_word*len(hypothesis_words)) 

hyp_input = tf.sparse_placeholder(dtype=tf.string) 
truth_input = tf.sparse_placeholder(dtype=tf.string) 

edit_distances = tf.edit_distance(hyp_input, truth_input, normalize=True) 

feed_dict = {hyp_input: hyp_string_sparse, 
             truth_input: truth_string_sparse} 

print(sess.run(edit_distances, feed_dict=feed_dict)) 

[[ 0.40000001]
 [ 0.60000002]
 [ 0.80000001]
 [ 1.        ]]

工作原理

在这个秘籍中,我们展示了我们可以使用 TensorFlow 以多种方式测量文本距离。这对于在具有文本特征的数据上执行最近邻非常有用。当我们执行地址匹配时,我们将在本章后面看到更多内容。

更多

我们应该讨论其他文本距离指标。这是一个定义表,描述了两个字符串s1s2之间的其他文本距离:

名称

描述

公式

汉明距离

相同位置的相等字符的数量。仅在字符串长度相等时有效。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wy8hFiJ-1681566813077)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/3f9a30b1-0aff-4c8d-b1ca-f00744f177cf.png)],其中I是相等字符的指示函数。

余弦距离

k差异的点积除以k差异的 L2 范数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DOKF3Txi-1681566813077)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/0015e197-fc53-491a-82a0-9d1acfc4b795.png)]

雅克卡距离

共同的字符数除以两个字符串中的字符总和。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LHxVz1kB-1681566813077)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/b9646b5b-e1cf-4fc1-9099-9dd1c9813b07.png)]

使用混合距离函数的计算

在处理具有多个特征的数据观察时,我们应该意识到特征可以在不同的尺度上以不同的方式缩放。在这个方案中,我们将考虑到这一点,以改善我们的住房价值预测。

准备

扩展最近邻算法很重要,要考虑不同缩放的变量。在这个例子中,我们将说明如何缩放不同变量的距离函数。具体来说,我们将距离函数作为特征方差的函数进行缩放。

加权距离函数的关键是使用权重矩阵。用矩阵运算写的距离函数变为以下公式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ltJfzReg-1681566813077)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/4b4a519d-305e-4b3b-85b9-2b05a51aaeb7.png)]

这里,A是一个对角线权重矩阵,我们将用它来缩放每个特征的距离度量。

在本文中,我们将尝试在波士顿住房价值数据集上改进我们的 MSE。该数据集是不同尺度上的特征的一个很好的例子,并且最近邻算法将受益于缩放距离函数。

操作步骤

我们将按如下方式处理秘籍:

  1. 首先,我们将加载必要的库并启动图会话:
代码语言:javascript复制
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import requests
sess = tf.Session() 
  1. 接下来,我们将加载数据并将其存储在 NumPy 数组中。再次注意,我们只会使用某些列进行预测。我们不使用 id,也不使用方差非常低的变量:
代码语言:javascript复制
housing_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data' 
housing_header = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV'] 
cols_used = ['CRIM', 'INDUS', 'NOX', 'RM', 'AGE', 'DIS', 'TAX', 'PTRATIO', 'B', 'LSTAT'] 
num_features = len(cols_used) 
housing_file = requests.get(housing_url) 
housing_data = [[float(x) for x in y.split(' ') if len(x)>=1] for y in housing_file.text.split('n') if len(y)>=1] 
y_vals = np.transpose([np.array([y[13] for y in housing_data])]) 
x_vals = np.array([[x for i,x in enumerate(y) if housing_header[i] in cols_used] for y in housing_data]) 
  1. 现在,我们将x值缩放到 0 到 1 之间,最小 - 最大缩放:
代码语言:javascript复制
x_vals = (x_vals - x_vals.min(0)) / x_vals.ptp(0)
  1. 然后,我们将创建对角线权重矩阵,该矩阵将通过特征的标准偏差提供距离度量的缩放:
代码语言:javascript复制
weight_diagonal = x_vals.std(0) 
weight_matrix = tf.cast(tf.diag(weight_diagonal), dtype=tf.float32)
  1. 现在,我们将数据分成训练和测试集。我们还将声明k,最近邻的数量,并使批量大小等于测试集大小:
代码语言:javascript复制
train_indices = np.random.choice(len(x_vals), round(len(x_vals)*0.8), replace=False) 
test_indices = np.array(list(set(range(len(x_vals))) - set(train_indices))) 
x_vals_train = x_vals[train_indices] 
x_vals_test = x_vals[test_indices] 
y_vals_train = y_vals[train_indices] 
y_vals_test = y_vals[test_indices] 
k = 4 
batch_size=len(x_vals_test) 
  1. 我们将声明接下来需要的占位符。我们有四个占位符 - 训练和测试集的[​​HTG0] - 输入和y - 目标:
代码语言:javascript复制
x_data_train = tf.placeholder(shape=[None, num_features], dtype=tf.float32) 
x_data_test = tf.placeholder(shape=[None, num_features], dtype=tf.float32) 
y_target_train = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target_test = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
  1. 现在,我们可以声明我们的距离函数。为了便于阅读,我们将把距离函数分解为其组件。请注意,我们必须按批量大小平铺权重矩阵,并使用batch_matmul()函数在批量大小中执行批量矩阵乘法:
代码语言:javascript复制
subtraction_term =  tf.subtract(x_data_train, tf.expand_dims(x_data_test,1)) 
first_product = tf.batch_matmul(subtraction_term, tf.tile(tf.expand_dims(weight_matrix,0), [batch_size,1,1])) 
second_product = tf.batch_matmul(first_product, tf.transpose(subtraction_term, perm=[0,2,1])) 
distance = tf.sqrt(tf.batch_matrix_diag_part(second_product))
  1. 在我们计算每个测试点的所有训练距离之后,我们将需要返回顶部 KNN。我们可以使用top_k()函数执行此操作。由于此函数返回最大值,并且我们想要最小距离,因此我们返回最大的负距离值。然后,我们将预测作为顶部k邻居的距离的加权平均值:
代码语言:javascript复制
top_k_xvals, top_k_indices = tf.nn.top_k(tf.neg(distance), k=k) 
x_sums = tf.expand_dims(tf.reduce_sum(top_k_xvals, 1),1) 
x_sums_repeated = tf.matmul(x_sums,tf.ones([1, k], tf.float32)) 
x_val_weights = tf.expand_dims(tf.div(top_k_xvals,x_sums_repeated), 1) 
top_k_yvals = tf.gather(y_target_train, top_k_indices) 
prediction = tf.squeeze(tf.batch_matmul(x_val_weights,top_k_yvals), squeeze_dims=[1]) 
  1. 为了评估我们的模型,我们将计算预测的 MSE:
代码语言:javascript复制
mse = tf.divide(tf.reduce_sum(tf.square(tf.subtract(prediction, y_target_test))), batch_size) 
  1. 现在,我们可以遍历我们的测试批次并计算每个的 MSE:
代码语言:javascript复制
num_loops = int(np.ceil(len(x_vals_test)/batch_size))
for i in range(num_loops):
    min_index = i*batch_size
    max_index = min((i 1)*batch_size,len(x_vals_train))
    x_batch = x_vals_test[min_index:max_index]
    y_batch = y_vals_test[min_index:max_index]
    predictions = sess.run(prediction, feed_dict={x_data_train: x_vals_train, x_data_test: x_batch, y_target_train: y_vals_train, y_target_test: y_batch})
    batch_mse = sess.run(mse, feed_dict={x_data_train: x_vals_train, x_data_test: x_batch, y_target_train: y_vals_train, y_target_test: y_batch})
    print('Batch #'   str(i 1)   ' MSE: '   str(np.round(batch_mse,3))) 

Batch #1 MSE: 21.322
  1. 作为最终比较,我们可以使用以下代码绘制实际测试集的住房值分布和测试集的预测:
代码语言:javascript复制
bins = np.linspace(5, 50, 45) 
plt.hist(predictions, bins, alpha=0.5, label='Prediction') 
plt.hist(y_batch, bins, alpha=0.5, label='Actual') 
plt.title('Histogram of Predicted and Actual Values') 
plt.xlabel('Med Home Value in $1,000s') 
plt.ylabel('Frequency') 
plt.legend(loc='upper right') 
plt.show() 

我们将获得前面代码的以下直方图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cucNSBge-1681566813077)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/5d0b3e2a-6c96-4934-9065-a903836c40e9.png)]

图 3:波士顿数据集上预测房屋价值和实际房屋价值的两个直方图;这一次,我们为每个特征不同地缩放了距离函数

工作原理

我们通过引入一种缩放每个特征的距离函数的方法来减少测试集上的 MSE。在这里,我们通过特征标准偏差的因子来缩放距离函数。这提供了更准确的测量视图,其中测量哪些点是最近的邻居。由此,我们还将顶部k邻域的加权平均值作为距离的函数,以获得住房价值预测。

更多

该缩放因子还可以用于最近邻距离计算中的向下加权或向上加权的特征。这在我们比某些特征更信任某些特征的情况下非常有用。

使用地址匹配的示例

现在我们已经测量了数值和文本距离,我们将花一些时间学习如何将它们组合起来测量具有文本和数字特征的观察之间的距离。

准备

最近邻是一种用于地址匹配的好算法。地址匹配是一种记录匹配,其中我们在多个数据集中具有地址并且想要匹配它们。在地址匹配中,我们可能在地址,不同城市或不同的邮政编码中存在拼写错误,但它们可能都指向相同的地址。在地址的数字和字符组件上使用最近邻算法可以帮助我们识别实际上相同的地址。

在此示例中,我们将生成两个数据集。每个数据集将包含街道地址和邮政编码。但是,一个数据集在街道地址中存在大量拼写错误。我们将非拼写数据集作为我们的黄金标准,并将为每个拼写错误地址返回一个地址,该地址最接近字符串距离(对于街道)和数字距离(对于邮政编码)的函数。

代码的第一部分将侧重于生成两个数据集。然后,代码的第二部分将运行测试集并返回训练集中最接近的地址。

操作步骤

我们将按如下方式处理秘籍:

  1. 我们将从加载必要的库开始:
代码语言:javascript复制
import random 
import string 
import numpy as np 
import tensorflow as tf 
  1. 我们现在将创建参考数据集。为了显示简洁的输出,我们只会使每个数据集由10地址组成(但它可以运行更多):
代码语言:javascript复制
n = 10 street_names = ['abbey', 'baker', 'canal', 'donner', 'elm']
street_types = ['rd', 'st', 'ln', 'pass', 'ave'] 
rand_zips = [random.randint(65000,65999) for i in range(5)] 
numbers = [random.randint(1, 9999) for i in range(n)] 
streets = [random.choice(street_names) for i in range(n)] 
street_suffs = [random.choice(street_types) for i in range(n)] 
zips = [random.choice(rand_zips) for i in range(n)] 
full_streets = [str(x)   ' '   y   ' '   z for x,y,z in zip(numbers, streets, street_suffs)] 
reference_data = [list(x) for x in zip(full_streets,zips)] 
  1. 要创建测试集,我们需要一个函数,它将在字符串中随机创建一个拼写错误并返回结果字符串:
代码语言:javascript复制
def create_typo(s, prob=0.75):
    if random.uniform(0,1) < prob:
        rand_ind = random.choice(range(len(s)))
        s_list = list(s)
        s_list[rand_ind]=random.choice(string.ascii_lowercase)
        s = ''.join(s_list)
    return s

typo_streets = [create_typo(x) for x in streets]
typo_full_streets = [str(x)   ' '   y   ' '   z for x,y,z in zip(numbers, typo_streets, street_suffs)]
test_data = [list(x) for x in zip(typo_full_streets,zips)] 
  1. 现在,我们可以初始化图会话并声明我们需要的占位符。我们在每个测试和参考集中需要四个占位符,我们需要一个地址和邮政编码占位符:
代码语言:javascript复制
sess = tf.Session() 
test_address = tf.sparse_placeholder( dtype=tf.string) 
test_zip = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
ref_address = tf.sparse_placeholder(dtype=tf.string) 
ref_zip = tf.placeholder(shape=[None, n], dtype=tf.float32) 
  1. 现在,我们将声明数字拉链距离和地址字符串的编辑距离:
代码语言:javascript复制
zip_dist = tf.square(tf.subtract(ref_zip, test_zip)) 
address_dist = tf.edit_distance(test_address, ref_address, normalize=True)
  1. 我们现在将拉链距离和地址距离转换为相似之处。对于相似性,当两个输入完全相同时,我们想要1的相似性,当它们非常不同时,我们想要0附近。对于拉链距离,我们可以通过获取距离,从最大值减去,然后除以距离的范围来实现。对于地址相似性,由于距离已经在01之间缩放,我们只需从 1 中减去它以获得相似性:
代码语言:javascript复制
zip_max = tf.gather(tf.squeeze(zip_dist), tf.argmax(zip_dist, 1)) 
zip_min = tf.gather(tf.squeeze(zip_dist), tf.argmin(zip_dist, 1)) 
zip_sim = tf.divide(tf.subtract(zip_max, zip_dist), tf.subtract(zip_max, zip_min)) 
address_sim = tf.subtract(1., address_dist) 
  1. 为了结合两个相似度函数,我们将采用两者的加权平均值。对于这个秘籍,我们对地址和邮政编码给予同等重视。我们可以根据我们对每个特征的信任程度来改变这一点。然后,我们将返回参考集的最高相似度的索引:
代码语言:javascript复制
address_weight = 0.5 
zip_weight = 1. - address_weight 
weighted_sim = tf.add(tf.transpose(tf.multiply(address_weight, address_sim)), tf.multiply(zip_weight, zip_sim)) 
top_match_index = tf.argmax(weighted_sim, 1) 
  1. 为了在 TensorFlow 中使用编辑距离,我们必须将地址字符串转换为稀疏向量。在本章的先前秘籍中,使用基于文本的距离,我们创建了以下函数,我们也将在此秘籍中使用它:
代码语言:javascript复制
def sparse_from_word_vec(word_vec): 
    num_words = len(word_vec) 
    indices = [[xi, 0, yi] for xi,x in enumerate(word_vec) for yi,y in enumerate(x)] 
    chars = list(''.join(word_vec)) 
    # Now we return our sparse vector 
    return tf.SparseTensorValue(indices, chars, [num_words,1,1])
  1. 我们需要将参考数据集中的地址和邮政编码分开,以便在循环测试集时将它们提供给占位符:
代码语言:javascript复制
reference_addresses = [x[0] for x in reference_data] 
reference_zips = np.array([[x[1] for x in reference_data]])
  1. 我们需要使用我们在步骤 8 中创建的函数创建稀疏张量参考地址集:
代码语言:javascript复制
sparse_ref_set = sparse_from_word_vec(reference_addresses)
  1. 现在,我们可以循环遍历测试集的每个条目,并返回它最接近的引用集的索引。我们将为每个条目打印测试和参考集。如您所见,我们在此生成的数据集中获得了很好的结果:
代码语言:javascript复制
for i in range(n): 
    test_address_entry = test_data[i][0] 
    test_zip_entry = [[test_data[i][1]]] 

    # Create sparse address vectors 
    test_address_repeated = [test_address_entry] * n 
    sparse_test_set = sparse_from_word_vec(test_address_repeated) 

    feeddict={test_address: sparse_test_set, 
               test_zip: test_zip_entry, 
               ref_address: sparse_ref_set, 
               ref_zip: reference_zips} 
    best_match = sess.run(top_match_index, feed_dict=feeddict)
    best_street = reference_addresses[best_match[0]]
    [best_zip] = reference_zips[0][best_match]
    [[test_zip_]] = test_zip_entry
    print('Address: '   str(test_address_entry)   ', '   str(test_zip_))
    print('Match : '   str(best_street)   ', '   str(best_zip))

我们将得到以下结果:

代码语言:javascript复制
Address: 8659 beker ln, 65463 
Match  : 8659 baker ln, 65463 
Address: 1048 eanal ln, 65681 
Match  : 1048 canal ln, 65681 
Address: 1756 vaker st, 65983 
Match  : 1756 baker st, 65983 
Address: 900 abbjy pass, 65983 
Match  : 900 abbey pass, 65983 
Address: 5025 canal rd, 65463 
Match  : 5025 canal rd, 65463 
Address: 6814 elh st, 65154 
Match  : 6814 elm st, 65154 
Address: 3057 cagal ave, 65463 
Match  : 3057 canal ave, 65463 
Address: 7776 iaker ln, 65681 
Match  : 7776 baker ln, 65681 
Address: 5167 caker rd, 65154
代码语言:javascript复制
Match  : 5167 baker rd, 65154 
Address: 8765 donnor st, 65154 
Match  : 8765 donner st, 65154 

工作原理

在像这样的地址匹配问题中要弄清楚的一个难点是权重的值以及如何缩放距离。这可能需要对数据本身进行一些探索和洞察。此外,在处理地址时,我们应该考虑除此处使用的组件之外的组件。我们可以将街道号码视为街道地址的独立组成部分,甚至可以包含其他组成部分,例如城市和州。

处理数字地址组件时,请注意它们可以被视为数字(具有数字距离)或字符(具有编辑距离)。由您决定选择哪个。请注意,如果我们认为邮政编码中的拼写错误来自人为错误而不是计算机映射错误,我们可以考虑使用邮政编码的编辑距离。

为了了解拼写错误如何影响结果,我们鼓励读者更改拼写错误函数以进行更多拼写错误或更频繁的拼写错误,并增加数据集大小以查看此算法的工作情况。

使用最近邻进行图像识别

最近邻也可用于图像识别。图像识别数据集的问题世界是 MNIST 手写数字数据集。由于我们将在后面的章节中将此数据集用于各种神经网络图像识别算法,因此将结果与非神经网络算法进行比较将会很棒。

准备

MNIST 数字数据集由数千个大小为28×28像素的标记图像组成。虽然这被认为是一个小图像,但它对于最近邻算法总共有 784 个像素(或特征)。我们将通过考虑最近的k邻居(k=4,在该示例中)的模式预测来计算该分类问题的最近邻预测。

操作步骤

我们将按如下方式处理秘籍:

  1. 我们将从加载必要的库开始。请注意,我们还将导入 Python 图像库(PIL),以便能够绘制预测输出的样本。 TensorFlow 有一个内置方法来加载我们将使用的 MNIST 数据集,如下所示:
代码语言:javascript复制
import random 
import numpy as np 
import tensorflow as tf 
import matplotlib.pyplot as plt 
from PIL import Image 
from tensorflow.examples.tutorials.mnist import input_data 
  1. 现在,我们将启动图会话并以单热编码形式加载 MNIST 数据:
代码语言:javascript复制
sess = tf.Session() 
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

单热编码是更适合数值计算的分类值的数值表示。这里,我们有 10 个类别(数字 0-9),并将它们表示为长度为 10 的 0-1 向量。例如,类别 0 由向量1,0,0,0,0,0表示,类别 1 用0,1,0,0,0,0表示,依此类推。

  1. 因为 MNIST 数据集很大并且计算数万个输入上的 784 个特征之间的距离在计算上是困难的,所以我们将采样一组较小的图像来训练。此外,我们将选择一个可被 6 整除的测试集编号,仅用于绘图目的,因为我们将绘制最后一批六个图像以查看结果的示例:
代码语言:javascript复制
train_size = 1000 
test_size = 102 
rand_train_indices = np.random.choice(len(mnist.train.images), train_size, replace=False) 
rand_test_indices = np.random.choice(len(mnist.test.images), test_size, replace=False) 
x_vals_train = mnist.train.images[rand_train_indices] 
x_vals_test = mnist.test.images[rand_test_indices] 
y_vals_train = mnist.train.labels[rand_train_indices] 
y_vals_test = mnist.test.labels[rand_test_indices]
  1. 我们将声明我们的k值和批量大小:
代码语言:javascript复制
k = 4 
batch_size=6 
  1. 现在,我们将初始化将添加到图中的占位符:
代码语言:javascript复制
x_data_train = tf.placeholder(shape=[None, 784], dtype=tf.float32) 
x_data_test = tf.placeholder(shape=[None, 784], dtype=tf.float32) 
y_target_train = tf.placeholder(shape=[None, 10], dtype=tf.float32) 
y_target_test = tf.placeholder(shape=[None, 10], dtype=tf.float32) 
  1. 然后我们将声明我们的距离度量。在这里,我们将使用 L1 度量(绝对值):
代码语言:javascript复制
distance = tf.reduce_sum(tf.abs(tf.subtract(x_data_train, tf.expand_dims(x_data_test,1))), reduction_indices=2) 

请注意,我们也可以使用以下代码来改变距离函数:distance = tf.sqrt(tf.reduce_sum(tf.square(tf.subtract(x_data_train, tf.expand_dims(x_data_test,1))), reduction_indices=1))

  1. 现在,我们将找到最接近的顶级k图像并预测模式。该模式将在单热编码索引上执行,计数最多:
代码语言:javascript复制
top_k_xvals, top_k_indices = tf.nn.top_k(tf.negative(distance), k=k) 
prediction_indices = tf.gather(y_target_train, top_k_indices) 
count_of_predictions = tf.reduce_sum(prediction_indices, reduction_indices=1) 
prediction = tf.argmax(count_of_predictions) 
  1. 我们现在可以遍历我们的测试集,计算预测并存储它们,如下所示:
代码语言:javascript复制
num_loops = int(np.ceil(len(x_vals_test)/batch_size)) 
test_output = [] 
actual_vals = [] 
for i in range(num_loops): 
    min_index = i*batch_size 
    max_index = min((i 1)*batch_size,len(x_vals_train)) 
    x_batch = x_vals_test[min_index:max_index] 
    y_batch = y_vals_test[min_index:max_index] 
    predictions = sess.run(prediction, feed_dict={x_data_train: x_vals_train, x_data_test: x_batch, y_target_train: y_vals_train, y_target_test: y_batch}) 
    test_output.extend(predictions) 
    actual_vals.extend(np.argmax(y_batch, axis=1)) 
  1. 现在我们已经保存了实际和预测的输出,我们可以计算出准确率。由于我们对测试/训练数据集进行随机抽样,这会发生变化,但最终我们的准确率值应该在 80%-90% 左右:
代码语言:javascript复制
accuracy = sum([1./test_size for i in range(test_size) if test_output[i]==actual_vals[i]]) 
print('Accuracy on test set: '   str(accuracy)) 
Accuracy on test set: 0.8333333333333325 
  1. 以下是绘制前面批量结果的代码:
代码语言:javascript复制
actuals = np.argmax(y_batch, axis=1) 
Nrows = 2 
Ncols = 3 
for i in range(len(actuals)): 
    plt.subplot(Nrows, Ncols, i 1) 
    plt.imshow(np.reshape(x_batch[i], [28,28]), cmap='Greys_r') 
    plt.title('Actual: '   str(actuals[i])   ' Pred: '   str(predictions[i]), fontsize=10) 
    frame = plt.gca() 
    frame.axes.get_xaxis().set_visible(False) 
    frame.axes.get_yaxis().set_visible(False) 

结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EC6Yi8jJ-1681566813078)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/37d1bd73-254f-4a86-b749-9d0b476f388b.png)]

图 4:我们运行最近邻预测的最后一批六个图像。我们可以看到,我们并没有完全正确地获得所有图像。

工作原理

给定足够的计算时间和计算资源,我们可以使测试和训练集更大。这可能会提高我们的准确率,也是防止过拟合的常用方法。另外,请注意,此算法需要进一步探索理想的k值进行选择。可以在数据集上进行一组交叉验证实验后选择k值。

更多

我们还可以使用最近邻算法来评估用户看不见的数字。有关使用此模型评估用户输入数字的方法,请参阅在线仓库。

在本章中,我们探讨了如何使用 KNN 算法进行回归和分类。我们讨论了距离函数的不同用法,以及如何将它们混合在一起。我们鼓励读者探索不同的距离度量,权重和k值,以优化这些方法的准确率。

1 人点赞