TensorFlow-机器学习秘籍中文第二版-全-
TensorFlow 机器学习秘籍中文第二版(全)
一、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 算法的一般流程。大多数秘籍将遵循以下大纲:
- 导入或生成数据集:我们所有的机器学习算法都依赖于数据集。在本书中,我们将生成数据或使用外部数据集源。有时,最好依赖生成的数据,因为我们只想知道预期的结果。大多数情况下,我们将访问给定秘籍的公共数据集。有关访问这些数据集的详细信息,请参见本章第 8 节的其他资源。
- 转换和正则化数据:通常,输入数据集不会出现在图片中。 TensorFlow 期望我们需要转换 TensorFlow,以便它们获得可接受的形状。数据通常不在我们的算法所期望的正确维度或类型中。在我们使用之前,我们必须转换数据。大多数算法也期望归一化数据,我们也会在这里看一下。 TensorFlow 具有内置函数,可以为您正则化数据,如下所示:
import tensorflow as tf
data = tf.nn.batch_norm_with_global_normalization(...)
- 将数据集划分为训练集,测试集和验证集:我们通常希望在我们训练过的不同集上测试我们的算法。此外,许多算法需要超参数调整,因此我们留出一个验证集来确定最佳的超参数集。
- 设置算法参数(超参数):我们的算法通常有一组参数,我们在整个过程中保持不变。例如,这可以是我们选择的迭代次数,学习率或其他固定参数。将这些一起初始化是一种良好的做法,以便读者或用户可以轻松找到它们,如下所示:
learning_rate = 0.01
batch_size = 100
iterations = 1000
- 初始化变量和占位符:TensorFlow 依赖于知道它能够和不能修改的内容。 TensorFlow 将在优化期间修改/调整变量(模型权重/偏差)以最小化损失函数。为此,我们通过占位符提供数据。我们需要初始化变量和占位符的大小和类型,以便 TensorFlow 知道会发生什么。 TensorFlow 还需要知道期望的数据类型。对于本书的大部分内容,我们将使用
float32。 TensorFlow 还提供float64和float16。请注意,用于精度的更多字节会导致算法速度变慢,但使用较少会导致精度降低。请参阅以下代码:
a_var = tf.constant(42)
x_input = tf.placeholder(tf.float32, [None, input_size])
y_input = tf.placeholder(tf.float32, [None, num_classes])
- 定义模型结构:在获得数据并初始化变量和占位符之后,我们必须定义模型。这是通过构建计算图来完成的。我们将在第 2 章,TensorFlow 方法中更详细地讨论计算图 TensorFlow 秘籍中的运算中的计算图。此示例的模型将是线性模型(
y = mx + b):
y_pred = tf.add(tf.mul(x_input, weight_matrix), b_matrix)
-
声明损失函数:定义模型后,我们必须能够评估输出。这是我们宣布损失函数的地方。损失函数非常重要,因为它告诉我们预测距实际值有多远。在第 2 章,TensorFlow 方法中的实现反向传播秘籍中更详细地探讨了不同类型的损失函数。在这里,我们实现了 n 点的均方误差,即:
![]()
loss = tf.reduce_mean(tf.square(y_actual - y_pred))
- 初始化和训练模型:现在我们已经完成了所有工作,我们需要创建图实例,通过占位符输入数据,让 TensorFlow 更改变量以更好地预测我们的训练数据。这是初始化计算图的一种方法:
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(...)
- 评估模型:一旦我们构建并训练了模型,我们应该通过查看通过某些指定标准对新数据的处理程度来评估模型。我们对训练和测试装置进行评估,这些评估将使我们能够看到模型是否过拟合。我们将在后面的秘籍中解决这个问题。
- 调整超参数:大多数情况下,我们希望根据模型的表现返回并更改一些超参数。然后,我们使用不同的超参数重复前面的步骤,并在验证集上评估模型。
- 部署/预测新结果:了解如何对新数据和未见数据进行预测也很重要。一旦我们对他们进行了训练,我们就可以对所有模型进行此操作。
工作原理
在 TensorFlow 中,我们必须先设置数据,变量,占位符和模型,然后才能告诉程序训练和更改变量以改进预测。 TensorFlow 通过计算图完成此任务。这些计算图是没有递归的有向图,这允许计算并行性。为此,我们需要为 TensorFlow 创建一个最小化的损失函数。 TensorFlow 通过修改计算图中的变量来实现此目的。 TensorFlow 知道如何修改变量,因为它跟踪模型中的计算并自动计算变量梯度(如何更改每个变量)以最小化损失。因此,我们可以看到进行更改和尝试不同数据源是多么容易。
另见
有关 TensorFlow 的更多介绍和资源,请参阅官方文档和教程:
声明变量和张量
张量是 TensorFlow 用于在计算图上操作的主要数据结构。我们可以将这些张量声明为变量和/或将它们作为占位符提供。要做到这一点,首先,我们必须学习如何创建张量。
tensor是指广义向量或矩阵的数学术语。如果向量是一维的并且矩阵是二维的,则张量是 n 维的(其中n可以是 1,2 或甚至更大)。
准备
当我们创建一个张量并将其声明为变量时,TensorFlow 会在我们的计算图中创建几个图结构。同样重要的是要指出,仅通过创建张量,TensorFlow 不会向计算图中添加任何内容。 TensorFlow 仅在运行初始化变量的操作后执行此操作。有关更多信息,请参阅下一节有关变量和占位符的内容。
操作步骤
在这里,我们将介绍我们可以在 TensorFlow 中创建张量的主要方法:
1.固定张量:
-
- 在下面的代码中,我们创建了一个零填充张量:
zero_tsr = tf.zeros([row_dim, col_dim])
-
- 在下面的代码中,我们创建了一个填充张量:
ones_tsr = tf.ones([row_dim, col_dim])
-
- 在下面的代码中,我们创建了一个常量填充张量:
filled_tsr = tf.fill([row_dim, col_dim], 42)
-
- 在下面的代码中,我们从现有常量中创建一个张量:
constant_tsr = tf.constant([1,2,3])
请注意,
tf.constant()函数可用于将值广播到数组中,通过编写tf.constant(42, [row_dim, col_dim])来模仿tf.fill()的行为。
- 相似形状的张量:我们还可以根据其他张量的形状初始化变量,如下所示:
zeros_similar = tf.zeros_like(constant_tsr)
ones_similar = tf.ones_like(constant_tsr)
请注意,由于这些张量依赖于先前的张量,我们必须按顺序初始化它们。尝试一次初始化所有张量将导致错误。有关变量和占位符,请参阅下一节末尾的“更多”小节。
- 序列张量:TensorFlow 允许我们指定包含定义间隔的张量。以下函数与 NumPy 的
linspace()输出和range()输出非常相似。请参阅以下函数:
linear_tsr = tf.linspace(start=0, stop=1, start=3)
得到的张量具有[0.0,0.5,1.0]的序列。请注意,此函数包含指定的停止值。有关更多信息,请参阅以下函数:
integer_seq_tsr = tf.range(start=6, limit=15, delta=3)
结果是序列[6,9,12]。请注意,此函数不包括限制值。
- 随机张量:以下生成的随机数来自均匀分布:
randunif_tsr = tf.random_uniform([row_dim, col_dim], minval=0, maxval=1)
注意,这种随机均匀分布来自包含minval但不包括maxval(minval >= x < maxval)的区间。
要从正态分布中获取随机抽取的张量,可以运行以下代码:
randnorm_tsr = tf.random_normal([row_dim, col_dim], mean=0.0, stddev=1.0)
有时候我们想要生成在某些范围内保证的正常随机值。truncated_normal()函数总是在指定均值的两个标准偏差内选择正常值:
runcnorm_tsr = tf.truncated_normal([row_dim, col_dim], mean=0.0, stddev=1.0)
我们可能也对随机化数组条目感兴趣。要做到这一点,有两个函数可以帮助我们:random_shuffle()和random_crop()。以下代码执行此操作:
shuffled_output = tf.random_shuffle(input_tensor)
cropped_output = tf.random_crop(input_tensor, crop_size)
在本书的后面,我们将有兴趣随机裁剪大小(高度,宽度,3)的图像,其中有三种颜色光谱。要修复cropped_output中的大小,您必须在该大小中为其指定最大大小:
cropped_image = tf.random_crop(my_image, [height/2, width/2, 3])
工作原理
一旦我们决定如何创建张量,我们也可以通过在Variable()函数中包含张量来创建相应的变量,如下所示(下一节将详细介绍):
my_var = tf.Variable(tf.zeros([row_dim, col_dim]))
更多
我们不仅限于内置函数:我们可以使用convert_to_tensor()函数将任何 NumPy 数组转换为 Python 列表,或将常量转换为张量。注意,如果我们希望概括函数内部的计算,该函数也接受张量作为输入。
使用占位符和变量
占位符和变量是在 TensorFlow 中使用计算图的关键工具。我们必须了解它们之间的区别以及何时最好地利用它们对我们有利。
准备
与数据最重要的区别之一是它是占位符还是变量。变量是算法的模型参数,TensorFlow 跟踪如何更改这些参数以优化算法。占位符是允许您提供特定类型和形状的数据的对象,或者取决于计算图的结果,例如计算的预期结果。
操作步骤
创建变量的主要方法是使用Variable()函数,该函数将张量作为输入并输出变量。这只是声明,我们仍然需要初始化变量。初始化是将变量与相应方法放在计算图上的内容。以下是创建和初始化变量的示例:
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占位符并运行身份操作。代码如下所示,结果图如下:
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})将返回误差。
工作原理
将变量初始化为零张量的计算图如下图所示:

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

图 2:初始化占位符的计算图
灰色阴影区域是对所涉及的操作和常数的非常详细的视图。细节较少的主要计算图是右上角灰色区域之外的较小图。
更多
在计算图运行期间,我们必须告诉 TensorFlow 何时初始化我们创建的变量。虽然每个变量都有一个initializer方法,但最常用的方法是使用辅助函数,即global_variables_initializer()。此函数在图中创建一个初始化我们创建的所有变量的操作,如下所示:
initializer_op = tf.global_variables_initializer()
但是如果我们想根据初始化另一个变量的结果来初始化变量,我们必须按照我们想要的顺序初始化变量,如下所示:
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 为我们提供了易于使用的操作来执行此类矩阵计算。对于以下所有示例,我们首先通过运行以下代码来创建图会话:
import tensorflow as tf
sess = tf.Session()
操作步骤
我们将按如下方式处理秘籍:
- 创建矩阵:我们可以从 NumPy 数组或嵌套列表创建二维矩阵,正如我们在创建和使用张量秘籍中所描述的那样。我们还可以使用张量创建函数并为
zeros(),ones(),truncated_normal()等函数指定二维形状。 TensorFlow 还允许我们使用diag()函数从一维数组或列表创建对角矩阵,如下所示:
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),我们将重新初始化随机变量并最终得到不同的随机值。
- 加法,减法和乘法:要添加,减去或相乘相同维度的矩阵,TensorFlow 使用以下函数:
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()函数具有参数,用于指定是否在乘法之前转置参数或每个矩阵是否稀疏。
请注意,未明确定义矩阵除法。虽然许多人将矩阵划分定义为乘以逆,但与实数除法相比,它基本上是不同的。
- 转置:转置矩阵(翻转列和行),如下所示:
print(sess.run(tf.transpose(C)))
[[ 0.67124544 0.26766731 0.99068872]
[ 0.25006068 0.86560275 0.58411312]]
同样,值得一提的是,重新初始化为我们提供了与以前不同的值。
- 行列式:要计算行列式,请使用以下内容:
print(sess.run(tf.matrix_determinant(D)))
-38.0
- 逆:要查找方阵的逆,请参阅以下内容:
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 分解。
- 分解:对于 Cholesky 分解,请使用以下内容:
print(sess.run(tf.cholesky(identity_matrix)))
[[ 1\. 0\. 1.]
[ 0\. 1\. 0.]
[ 0\. 0\. 1.]]
- 特征值和特征向量:对于特征值和特征向量,请使用以下代码:
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 还为我们提供了更多我们应该了解的操作以及如何在继续操作之前使用它们。同样,我们可以通过运行以下代码来创建图会话:
import tensorflow as tf
sess = tf.Session()
操作步骤
TensorFlow 对张量有标准操作,即add(),sub(),mul()和div()。请注意,除非另有说明,否则本节中的所有操作都将按元素评估输入:
- TensorFlow 提供了
div()和相关函数的一些变体。 - 值得一提的是
div()返回与输入相同的类型。这意味着如果输入是整数,它确实返回了分区的底线(类似于 Python2)。要返回 Python3 版本,它在分割之前将整数转换为浮点数并始终返回浮点数,TensorFlow 提供truediv()函数,如下所示:
print(sess.run(tf.div(3, 4)))
0
print(sess.run(tf.truediv(3, 4)))
0.75
- 如果我们有浮点数并想要整数除法,我们可以使用
floordiv()函数。请注意,这仍然会返回一个浮点数,但它会向下舍入到最接近的整数。这个函数如下:
print(sess.run(tf.floordiv(3.0,4.0)))
0.0
- 另一个重要函数是
mod()。此函数返回除法后的余数。它如下:
print(sess.run(tf.mod(22.0, 5.0)))
2.0
- 两个张量之间的交叉积通过
cross()函数实现。请记住,交叉乘积仅针对两个三维向量定义,因此它只接受两个三维张量。以下代码说明了这种用法:
print(sess.run(tf.cross([1., 0., 0.], [0., 1., 0.])))
[ 0\. 0\. 1.0]
- 这是一个更常见的数学函数的紧凑列表。所有这些函数都以元素方式运行:
abs() |
输入张量的绝对值 |
ceil() |
输入张量的向上取整函数 |
cos() |
输入张量的余弦函数 |
exp() |
输入张量的基于e指数 |
floor() |
输入张量的向下取整函数 |
inv() |
输入张量的乘法逆(1 / x) |
log() |
输入张量的自然对数 |
maximum() |
两个张量的逐元素最大值 |
minimum() |
两个张量的逐元素最小值 |
neg() |
输入张量的反转 |
pow() |
第一个张量元素的第二个张量元素次幂 |
round() |
输入张量的舍入 |
rsqrt() |
输入张量的平方根倒数 |
sign() |
返回 -1,0 或 1,具体取决于张量的符号 |
sin() |
输入张量的正弦函数 |
sqrt() |
输入张量的平方根 |
square() |
输入张量的平方 |
- 专业数学函数:有一些特殊的数学函数可以在机器学习中使用,值得一提,TensorFlow 为它们提供了内置函数。同样,除非另有说明,否则这些函数在元素方面运行:
digamma() |
Psi 函数,lgamma()函数的导数 |
erf() |
张量的逐元素高斯误差函数 |
erfc() |
张量的互补误差函数 |
igamma() |
较低正则化的不完全伽玛函数 |
igammac() |
较高正则化的不完全伽马函数 |
lbeta() |
Beta 函数绝对值的自然对数 |
lgamma() |
伽玛函数绝对值的自然对数 |
squared_difference() |
两个张量之间差异的平方 |
工作原理
重要的是要知道我们可以使用哪些函数,以便我们可以将它们添加到我们的计算图中。我们将主要关注前面的函数。我们还可以生成许多不同的自定义函数作为前面的组合,如下所示:
# Tangent function (tan(pi/4)=1)
print(sess.run(tf.tan(3.1416/4.)))
1.0
更多
如果我们希望向我们未在此处列出的图添加其他操作,我们必须从前面的函数创建自己的操作。以下是我们之前未使用的操作示例,我们可以将其添加到图中。我们选择使用以下代码添加自定义多项式函数3x^2 - x + 10:
def custom_polynomial(value):
return tf.sub(3 * tf.square(value), value) + 10
print(sess.run(custom_polynomial(11)))
362
实现激活函数
激活函数是神经网络近似非线性输出并适应非线性特征的关键。他们将非线性运算引入神经网络。如果我们小心选择了哪些激活函数以及放置它们的位置,它们是非常强大的操作,我们可以告诉 TensorFlow 适合和优化。
准备
当我们开始使用神经网络时,我们将定期使用激活函数,因为激活函数是任何神经网络的重要组成部分。激活函数的目标只是调整权重和偏差。在 TensorFlow 中,激活函数是作用于张量的非线性操作。它们是以与先前的数学运算类似的方式运行的函数。激活函数有很多用途,但主要的概念是它们在对输出进行归一化的同时在图中引入了非线性。使用以下命令启动 TensorFlow 图:
import tensorflow as tf
sess = tf.Session()
操作步骤
激活函数存在于 TensorFlow 中的神经网络(nn)库中。除了使用内置激活函数外,我们还可以使用 TensorFlow 操作设计自己的函数。我们可以导入预定义的激活函数(import tensorflow.nn as nn)或显式,并在函数调用中写入nn。在这里,我们选择明确每个函数调用:
- 被整流的线性单元,称为 ReLU,是将非线性引入神经网络的最常见和最基本的方式。这个函数叫做
max(0,x)。它是连续的,但不是平滑的。它看起来如下:
print(sess.run(tf.nn.relu([-3., 3., 10.])))
[ 0\. 3\. 10.]
- 有时我们会想要限制前面的 ReLU 激活函数的线性增加部分。我们可以通过将
max(0,x)函数嵌套到min()函数中来实现。 TensorFlow 具有的实现称为 ReLU6 函数。这被定义为min(max(0,x),6)。这是硬 sigmoid 函数的一个版本,并且计算速度更快,并且不会消失(无穷小接近零)或爆炸值。当我们在第 8 章,卷积神经网络和第 9 章,循环神经网络中讨论更深层的神经网络时,这将派上用场。它看起来如下:
print(sess.run(tf.nn.relu6([-3., 3., 10.])))
[ 0\. 3\. 6.]
- Sigmoid 函数是最常见的连续和平滑激活函数。它也被称为逻辑函数,其形式为
1 / (1 + exp(-x))。 Sigmoid 函数不经常使用,因为它倾向于在训练期间将反向传播项置零。它看起来如下:
print(sess.run(tf.nn.sigmoid([-1., 0., 1.])))
[ 0.26894143 0.5 0.7310586 ]
我们应该知道一些激活函数不是以零为中心的,例如 sigmoid。这将要求我们在大多数计算图算法中使用之前将数据归零。
- 另一个平滑激活函数是超切线。超正切函数与 sigmoid 非常相似,除了它的范围在 0 和 1 之间,它的范围在 -1 和 1 之间。该函数具有双曲正弦与双曲余弦的比率的形式。写这个的另一种方法是
(exp(x) - exp(-x)) / (exp(x) + exp(-x))。此激活函数如下:
print(sess.run(tf.nn.tanh([-1., 0., 1.])))
[-0.76159418 0\. 0.76159418 ]
softsign函数也可用作激活函数。该函数的形式是x / (|x| + 1)。softsign函数应该是符号函数的连续(但不是平滑)近似。请参阅以下代码:
print(sess.run(tf.nn.softsign([-1., 0., -1.])))
[-0.5 0\. 0.5]
- 另一个函数是
softplus函数,是 ReLU 函数的流畅版本。该函数的形式是log(exp(x) + 1)。它看起来如下:
print(sess.run(tf.nn.softplus([-1., 0., -1.])))
[ 0.31326166 0.69314718 1.31326163]
当输入增加时,
softplus函数变为无穷大,而softsign函数变为 1.然而,当输入变小时,softplus函数接近零,softsign函数变为 -1。
- 指数线性单元(ELU)与 softplus 函数非常相似,只是底部渐近线为 -1 而不是 0.如果
x < 0其他x,则形式为exp(x) + 1。它看起来如下:
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 和双曲正切函数:

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

图 4:Sigmoid,双曲正切(tanh)和 softsign 激活函数
这是 sigmoid,双曲正切(tanh)和 softsign 激活函数。这些激活函数都是平滑的,具有S n形状。请注意,这些函数有两个水平渐近线。
使用数据源
对于本书的大部分内容,我们将依赖数据集的使用来适应机器学习算法。本节介绍如何通过 TensorFlow 和 Python 访问每个数据集。
一些数据源依赖于外部网站的维护,以便您可以访问数据。如果这些网站更改或删除此数据,则可能需要更新本节中的以下某些代码。您可以在作者的 GitHub 页面上找到更新的代码。
准备
在 TensorFlow 中,我们将使用的一些数据集构建在 Python 库中,一些将需要 Python 脚本下载,一些将通过互联网手动下载。几乎所有这些数据集都需要有效的互联网连接,以便您可以检索它们。
操作步骤
- 鸢尾数据:该数据集可以说是机器学习中使用的最经典的数据集,也可能是所有统计数据。它是一个数据集,可以测量三种不同类型鸢尾花的萼片长度,萼片宽度,花瓣长度和花瓣宽度:山鸢尾,弗吉尼亚和杂色鸢尾。总共有 150 个测量值,这意味着每个物种有 50 个测量值。要在 Python 中加载数据集,我们将使用 scikit-learn 的数据集函数,如下所示:
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}
- 出生体重数据:该数据最初来自 Baystate Medical Center,Springfield,Mass 1986(1)。该数据集包含出生体重的测量以及母亲和家族病史的其他人口统计学和医学测量。有 11 个变量的 189 个观测值。以下代码显示了如何在 Python 中访问此数据:
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('\r\n')
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
- 波士顿住房数据:卡内基梅隆大学在其 StatLib 库中维护着一个数据集库。这些数据可通过加州大学欧文分校的机器学习库轻松访问。有 506 个房屋价值观察,以及各种人口统计数据和住房属性(14 个变量)。以下代码显示了如何通过 Keras 库在 Python 中访问此数据:
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
- MNIST 手写数据:MNIST(混合国家标准与技术研究院)数据集是较大的 NIST 手写数据库的子集。 MNIST 手写数据集托管在 Yann LeCun 的网站上。它是一个包含 70,000 个单元数字图像(0-9)的数据库,其中带标签的约 60,000 个用于训练集,10,000 个用于测试集。 TensorFlow 在图像识别中经常使用此数据集,TensorFlow 提供了访问此数据的内置函数。在机器学习中,提供验证数据以防止过拟合(目标泄漏)也很重要。因此,TensorFlow 将 5000 列训练图像留在验证集中。以下代码显示了如何在 Python 中访问此数据:
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.]
- 垃圾邮件文本数据。 UCI 的机器学习数据集库还包含垃圾短信文本消息数据集。我们可以访问此
.zip文件并获取垃圾邮件文本数据,如下所示:
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...
- 电影评论数据:来自康奈尔大学的 Bo Pang 发布了一个电影评论数据集,将评论分为好或坏(3)。您可以在以下网站上找到数据。要下载,提取和转换此数据,我们可以运行以下代码:
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 .
- CIFAR-10 图像数据:加拿大高级研究院发布了一个图像集,其中包含 8000 万个带标签的彩色图像(每个图像缩放为
32 x 32像素)。有 10 种不同的目标类别(飞机,汽车,鸟类等)。 CIFAR-10 是包含 60,000 张图像的子集。训练集中有 50,000 个图像,测试集中有 10,000 个。由于我们将以多种方式使用此数据集,并且因为它是我们较大的数据集之一,因此我们不会在每次需要时运行脚本。要获取此数据集,请导航至此链接并下载 CIFAR-10 数据集。我们将在相应的章节中介绍如何使用此数据集。 - 莎士比亚文本数据的作品:古登堡项目(5)是一个发布免费书籍电子版的项目。他们一起编辑了莎士比亚的所有作品。以下代码显示了如何通过 Python 访问此文本文件:
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
- 英语 - 德语句子翻译数据:Tatoeba 项目 收集多种语言的句子翻译。他们的数据已在 CC 协议下发布。根据这些数据,ManyThings.org 编译了可供下载的文本文件中的句子到句子的翻译。在这里,我们将使用英语 - 德语翻译文件,但您可以将 URL 更改为您想要使用的语言:
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 还建立了一个网站,您可以在视觉上探索训练神经网络,同时更改参数和数据集。访问此链接,探讨不同的设置如何影响神经网络的训练。
-
斯坦福大学有一个在线教学大纲和详细的视觉识别卷积神经网络课程笔记。
二、TensorFlow 的方式
在本章中,我们将介绍 TensorFlow 如何运作的关键组件。然后,我们将它们组合在一起以创建一个简单的分类器并评估结果。到本章结束时,您应该了解以下内容:
- 计算图中的操作
- 分层嵌套操作
- 使用多个层
- 实现损失函数
- 实现反向传播
- 使用批量和随机训练
- 把所有东西结合在一起
- 评估模型
介绍
现在我们已经介绍了 TensorFlow 如何创建张量,并使用变量和占位符,我们将介绍如何在计算图中对这些对象进行操作。由此,我们可以设置一个简单的分类器,看看它的表现如何。
此外,请记住,本书中的当前和更新代码可以在 GitHub 上在线获取。
计算图中的操作
现在我们可以将对象放入计算图中,我们将介绍对这些对象起作用的操作。
准备
要启动图,我们加载 TensorFlow 并创建一个会话,如下所示:
import tensorflow as tf
sess = tf.Session()
操作步骤
在这个例子中,我们将结合我们学到的东西并将列表中的每个数字提供给图中的操作并打印输出:
首先,我们宣布我们的张量和占位符。在这里,我们将创建一个 NumPy 数组来提供给我们的操作:
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}))
上述代码的输出如下:
3.0
9.0
15.0
21.0
27.0
工作原理
本节中的代码在计算图上创建数据和操作。下图是计算图的样子:

图 1:x_data占位符以及乘法常数输入到乘法运算中
对嵌套操作分层
在本文中,我们将学习如何在同一计算图上放置多个操作。
准备
了解如何将操作链接在一起非常重要。这将在计算图中设置分层操作。对于演示,我们将占位符乘以两个矩阵,然后执行加法。我们将以三维 NumPy 数组的形式提供两个矩阵:
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矩阵,如下所示:
- 首先,我们创建要输入的数据和相应的占位符:
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))
- 接下来,我们创建将用于矩阵乘法和加法的常量:
m1 = tf.constant([[1.], [0.], [-1.], [2.], [4.]])
m2 = tf.constant([[2.]])
a1 = tf.constant([[10.]])
- 现在,我们声明操作并将它们添加到图中:
prod1 = tf.matmul(x_data, m1)
prod2 = tf.matmul(prod1, m2)
add1 = tf.add(prod2, a1)
- 最后,我们为图提供数据:
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。以下是我们的分层图如下所示:

图 2:向上传播到图时的数据大小
更多
在通过图运行数据之前,我们必须声明数据形状并知道操作的结果形状。这并非总是如此。可能有一两个我们事先不知道的维度,或者一些可能变化的维度。为实现此目的,我们将可以改变(或未知)的维度或维度指定为值None。例如,要使先前的数据占位符具有未知数量的列,我们将编写以下行:
x_data = tf.placeholder(tf.float32, shape=(3,None))
这允许我们打破矩阵乘法规则,但我们仍然必须遵守乘法常数必须具有相同行数的事实。当我们将数据输入图时,我们可以动态生成或重新整形x_data。当我们以不同批次大小的多批次提供数据时,这将在后面的章节中派上用场。
虽然使用
None作为大小允许我们使用可变大小的大小,但在填充大小时始终建议尽可能明确。如果我们将大小标准化为固定大小,那么我们应该明确地将该大小写为大小。建议将None用作维度,以限制数据的批量大小(或我们一次计算的数据点数)。
使用多个层
现在我们已经介绍了多个操作,我们将介绍如何连接具有通过它们传播的数据的各个层。
准备
在本文中,我们将介绍如何最好地连接各种层,包括自定义层。我们将生成和使用的数据将代表小型随机图像。最好通过一个简单的例子来理解这种类型的操作,看看我们如何使用一些内置层来执行计算。我们将探索的第一层称为移动窗口。我们将在 2D 图像上执行小的移动窗口平均值,然后第二层将是自定义操作层。
在本节中,我们将看到计算图可能变得庞大且难以查看。为了解决这个问题,我们还将介绍命名操作和创建层范围的方法。首先,加载numpy和tensorflow,然后使用以下命令创建图:
import tensorflow as tf
import numpy as np
sess = tf.Session()
操作步骤
我们按如下方式处理秘籍:
- 首先,我们使用 NumPy 创建示例 2D 图像。该图像将是
4 x 4像素图像。我们将在四个方面创建它;第一个和最后一个维度的大小为 1。请注意,某些 TensorFlow 图像函数将在四维图像上运行。这四个维度是图像编号,高度,宽度和通道,为了使其成为一个具有一个通道的图像,我们将两个维度设置为1,如下所示:
x_shape = [1, 4, 4, 1]
x_val = np.random.uniform(size=x_shape)
- 现在,我们必须在图中创建占位符,我们可以在其中提供示例图像,如下所示:
x_data = tf.placeholder(tf.float32, shape=x_shape)
- 为了在我们的
4 x 4图像上创建一个移动窗口平均值,我们将使用一个内置函数,它将在形状窗口2 x 2上收敛一个常量。我们将使用的函数是conv2d();此函数在图像处理和 TensorFlow 中非常常用。此函数采用窗口的分段产品和我们指定的过滤器。我们还必须在两个方向上指定移动窗口的步幅。在这里,我们将计算四个移动窗口平均值:左上角,右上角,左下角和右下角四个像素。我们通过创建2 x 2窗口并在每个方向上具有长度2的步幅来实现这一点。为取平均值,我们将2 x 2窗口用0.25的常数卷积,如下:
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是步幅。
- 现在,我们定义一个自定义层,它将在移动窗口平均值的
2 x 2输出上运行。自定义函数将首先将输入乘以另一个2 x 2矩阵张量,然后为每个条目添加 1。在此之后,我们取每个元素的 sigmoid 并返回2 x 2矩阵。由于矩阵乘法仅对二维矩阵进行操作,因此我们需要删除大小为1的图像的额外维度。 TensorFlow 可以使用内置的squeeze()函数执行此操作。在这里,我们定义新层:
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)
- 现在,我们必须将新层放在图上。我们将使用命名范围执行此操作,以便它在计算图上可识别和可折叠/可扩展,如下所示:
with tf.name_scope('Custom_Layer') as scope:
custom_layer1 = custom_layer(mov_avg_layer)
- 现在,我们只需输入
4 x 4图像来替换占位符并告诉 TensorFlow 运行图,如下所示:
print(sess.run(custom_layer1, feed_dict={x_data: x_val}))
[[ 0.91914582 0.96025133]
[ 0.87262219 0.9469803 ]]
工作原理
通过命名操作和层范围,可视化绘图看起来更好。我们可以折叠和展开自定义层,因为我们在命名范围内创建了它。在下图中,请参阅左侧的折叠版本和右侧的展开版本:

图 3:具有两层的计算图
第一层名为Moving_Avg_Window。第二个是名为Custom_Layer的操作集合。它在左侧折叠并在右侧展开。
实现损失函数
损失函数对于机器学习算法非常重要。它们测量模型输出与目标(真值)值之间的距离。在这个秘籍中,我们在 TensorFlow 中展示了各种损失函数实现。
准备
为了优化我们的机器学习算法,我们需要评估结果。评估 TensorFlow 中的结果取决于指定损失函数。损失函数告诉 TensorFlow 预测与期望结果相比有多好或多坏。在大多数情况下,我们将有一组数据和一个目标来训练我们的算法。损失函数将目标与预测进行比较,并给出两者之间的数值距离。
对于这个秘籍,我们将介绍我们可以在 TensorFlow 中实现的主要损失函数。
要了解不同损失函数的运行方式,我们将在此秘籍中绘制它们。我们将首先启动一个计算图并加载matplotlib,一个 Python 绘图库,如下所示:
import matplotlib.pyplot as plt
import tensorflow as tf
操作步骤
- 首先,我们将讨论回归的损失函数,这意味着预测连续的因变量。首先,我们将创建一个预测序列和一个作为张量的目标。我们将在 -1 和 1 之间输出 500 个值的结果。有关输出的绘图,请参阅“工作原理”部分。使用以下代码:
x_vals = tf.linspace(-1., 1., 500)
target = tf.constant(0.)
- L2 范数损失也称为欧几里德损失函数。它只是到目标的距离的平方。在这里,我们将计算损失函数,就像目标为零一样。 L2 范数是一个很大的损失函数,因为它在目标附近非常弯曲,并且算法可以使用这个事实来越慢地收敛到目标,越接近零。我们可以按如下方式实现:
l2_y_vals = tf.square(target - x_vals)
l2_y_out = sess.run(l2_y_vals)
TensorFlow 具有 L2 范数的内置形式,称为
nn.l2_loss()。这个函数实际上是 L2 范数的一半。换句话说,它与前一个相同,但除以 2。
- L1 范数损失也称为绝对损失函数。我们不是平衡差异,而是取绝对值。 L1 范数对于异常值比 L2 范数更好,因为对于较大的值,它不是那么陡峭。需要注意的一个问题是 L1 范数在目标处不平滑,这可能导致算法收敛不好。它看起来如下:
l1_y_vals = tf.abs(target - x_vals)
l1_y_out = sess.run(l1_y_vals)
- 伪 Huber 损失是 Huber 损失函数的连续且平滑的近似。这种损失函数试图通过在目标附近凸起并且对于极值不太陡峭来充分利用 L1 和 L2 范数。表格取决于额外的参数
delta,它决定了它的陡峭程度。我们将绘制两种形式,delta1 = 0.25和delta2 = 5,以显示差异,如下所示:
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)并且如果数字高于截止值,则将结果分类为该类别。在这里,我们考虑分类输出的各种损失函数:
- 首先,我们需要重新定义我们的预测(
x_vals)和target。我们将保存输出并在下一节中绘制它们。使用以下内容:
x_vals = tf.linspace(-3., 5., 500)
target = tf.constant(1.)
targets = tf.fill([500,], 1.)
- 铰链损失主要用于支持向量机,但也可用于神经网络。它旨在计算两个目标类 1 和 -1 之间的损失。在下面的代码中,我们使用目标值
1,因此我们的预测越接近 1,损失值越低:
hinge_y_vals = tf.maximum(0., 1\. - tf.multiply(target, x_vals))
hinge_y_out = sess.run(hinge_y_vals)
- 二元情形的交叉熵损失有时也称为逻辑损失函数。它是在我们预测两个 0 或 1 类时出现的。我们希望测量从实际类(0 或 1)到预测值的距离,预测值通常是介于 0 和 1 之间的实数。为了测量这个距离,我们可以使用信息论中的交叉熵公式,如下:
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)
- Sigmoid 交叉熵损失与之前的损失函数非常相似,除了我们在将它们置于交叉熵损失之前使用 sigmoid 函数转换 x 值,如下所示:
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)
- 加权交叉熵损失是 Sigmoid 交叉熵损失的加权版本。我们对积极目标给予了重视。举个例子,我们将正面目标加权 0.5,如下:
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)
- Softmax 交叉熵损失在非标准化输出上运行。当只有一个目标类别而不是多个目标类别时,此函数用于测量损失。因此,函数通过 softmax 函数将输出转换为概率分布,然后根据真实概率分布计算损失函数,如下所示:
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]
- 稀疏 softmax 交叉熵损失与前一个相同,除了目标是概率分布,它是哪个类别为真的索引。我们只传递真值的类别的索引,而不是稀疏的全零目标向量,其值为 1,如下所示:
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绘制回归损失函数:
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()
我们得到以下图作为上述代码的输出:

图 4:绘制各种回归损失函数
以下是如何使用matplotlib绘制各种分类损失函数:
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()
我们从前面的代码中得到以下图:
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。我们得到这个数字是因为如果m1和m2是两个正常函数的平均值,那么加到它们以将它们等距离转换为零的值将是 - (m1 + m2) / 2。我们将在第二个例子中看到 TensorFlow 如何达到该数字。
虽然指定良好的学习率有助于算法的收敛,但我们还必须指定一种优化。从前两个例子中,我们使用标准梯度下降。这是通过GradientDescentOptimizer() TensorFlow 函数实现的。
操作步骤
以下是回归示例的工作原理:
- 我们首先加载
numpy和tensorflow数值 Python 包:
import numpy as np
import tensorflow as tf
- 现在,我们启动图会话:
sess = tf.Session()
- 接下来,我们创建数据,占位符和
A变量:
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]))
- 我们将乘法运算添加到图中:
my_output = tf.mul(x_data, A)
- 接下来,我们在乘法输出和目标数据之间添加 L2
Loss函数:
loss = tf.square(my_output - y_target)
- 现在,我们必须声明一种优化图中变量的方法。我们声明了一种优化算法。大多数优化算法需要知道每次迭代中的步进距离。该距离由学习率控制。如果我们的学习率太大,我们的算法可能会超过最小值,但如果我们的学习率太小,我们的算法可能需要很长时间才能收敛;这与消失和爆炸的梯度问题有关。学习率对收敛有很大影响,我们将在本节末尾讨论这个问题。虽然我们在这里使用标准梯度下降算法,但是有许多不同的优化算法可以不同地运行,并且可以根据问题做得更好或更差。有关不同优化算法的精彩概述,请参阅 Sebastian Ruder 在本文末尾的另见部分中的文章:
my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.02)
train_step = my_opt.minimize(loss)
- 现在我们可以初始化我们的模型变量:
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.
- 最后一步是循环我们的训练算法并告诉 TensorFlow 多次训练。我们将这样做 101 次,并且每 25 次迭代打印出结果。为了训练,我们将选择随机
x和y条目并通过图提供。 TensorFlow 将自动计算损失,并略微改变A偏差以最小化损失:
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 函数将两个分为两个不同的类:
- 首先,我们重置图并重新初始化图会话:
from tensorflow.python.framework import ops
ops.reset_default_graph()
sess = tf.Session()
- 接下来,我们从两个不同的正态分布
N(-1, 1)和N(3, 1)中提取数据。我们还将生成目标标签,数据占位符和偏差变量A:
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。
- 接下来,我们将转换操作添加到图中。请记住,我们不必将它包装在 sigmoid 函数中,因为损失函数将为我们执行此操作:
my_output = tf.add(x_data, A)
- 由于特定损失函数需要具有与之关联的额外维度的批量数据(添加的维度,即批次编号),因此我们将使用
expand_dims()函数为输出添加额外维度。在下一节中,我们将讨论如何在训练中使用可变大小的批次。现在,我们将再次使用一个随机数据点:
my_output_expanded = tf.expand_dims(my_output, 0)
y_target_expanded = tf.expand_dims(y_target, 0)
- 接下来,我们将初始化我们的一个变量
A:
init = tf.initialize_all_variables()
sess.run(init)
- 现在,我们宣布我们的损失函数。我们将使用带有未缩放的对率的交叉熵,它使用 sigmoid 函数对它们进行转换。在名为
nn.sigmoid_cross_entropy_with_logits()的神经网络包中,TensorFlow 为我们提供了这一函数。如前所述,它希望参数具有特定的维度,因此我们必须相应地使用扩展的输出和目标:
xentropy = tf.nn.sigmoid_cross_entropy_with_logits( my_output_expanded, y_target_expanded)
- 与回归示例一样,我们需要向图中添加优化器函数,以便 TensorFlow 知道如何更新图中的偏差变量:
my_opt = tf.train.GradientDescentOptimizer(0.05)
train_step = my_opt.minimize(xentropy)
- 最后,我们循环遍历随机选择的数据点数百次并相应地更新
A变量。每 200 次迭代,我们将打印出A的值和损失:
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]]
工作原理
有关回顾和解释,对于这两个示例,我们执行了以下操作:
- 创建了数据。这两个示例都需要通过占位符加载数据。
- 初始化占位符和变量。这些是非常相似的数据占位符。变量非常相似,它们都有乘法矩阵
A,但第一个分类算法有一个偏差项来找到数据中的分裂。 - 创建了损失函数,我们使用 L2 损失进行回归,使用交叉熵损失进行分类。
- 定义了一种优化算法。两种算法都使用梯度下降。
- 迭代随机数据样本以迭代更新我们的变量。
更多
如前所述,优化算法对学习率的选择很敏感。重要的是要以简洁的方式总结这种选择的效果:
| 学习率大小 | 优点缺点 | 用途 |
|---|---|---|
| 较小的学习率 | 收敛速度较慢但结果更准确 | 如果解决方案不稳定,请先尝试降低学习率 |
| 学习率更高 | 不太准确,但收敛速度更快 | 对于某些问题,有助于防止解决方案停滞不前 |
有时,标准梯度下降算法会显着卡住或减速。当优化卡在马鞍的平坦点时,可能会发生这种情况。为了解决这个问题,还有另一种算法考虑了动量项,它增加了前一步骤的梯度下降值的一小部分。 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 计算反向传播的可变梯度,我们必须测量样本或多个样本的损失。随机训练一次只适用于一个随机抽样的数据 - 目标对,就像我们在上一个秘籍中所做的那样。另一种选择是一次放置大部分训练样例并平均梯度计算的损失。训练批次的大小可以一次变化,直到并包括整个数据集。在这里,我们将展示如何将先前的回归示例(使用随机训练)扩展到批量训练。
我们将首先加载numpy,matplotlib和tensorflow,然后启动图会话,如下所示:
import matplotlib as plt
import numpy as np
import tensorflow as tf
sess = tf.Session()
操作步骤
我们按如下方式处理秘籍:
- 我们将从声明批量大小开始。这将是我们将同时通过计算图提供多少数据观察:
batch_size = 20
- 接下来,我们在模型中声明数据,占位符和变量。我们在这里做的改变是我们改变了占位符的形状。它们现在是两个维度,第一个维度是
None,第二个维度是批次中的数据点数。我们可以明确地将它设置为 20,但我们可以推广并使用None值。同样,正如第 1 章,TensorFlow 入门中所述,我们仍然需要确保维度在模型中运行,这不允许我们执行任何非法矩阵操作:
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]))
- 现在,我们将操作添加到图中,现在将是矩阵乘法而不是常规乘法。请记住,矩阵乘法不是可交换的,因此我们必须在
matmul()函数中以正确的顺序输入矩阵:
my_output = tf.matmul(x_data, A)
- 我们的
loss函数会发生变化,因为我们必须采用批次中每个数据点的所有 L2 损失的平均值。我们通过将先前的损失输出包装在 TensorFlow 的reduce_mean()函数中来实现:
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)
- 最后,我们将循环并迭代训练步骤以优化算法。这部分与以前不同,因为我们希望能够绘制随时间的损失并比较批次与随机训练的收敛。因此,我们初始化一个列表,每隔五个时间间隔存储一次损失函数:
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)
- 这是 100 次迭代的最终输出。请注意,
A的值有一个额外的维度,因为它现在必须是一个 2D 矩阵:
Step #100 A = [[ 9.86720943]]
Loss = 0\.
工作原理
批量训练和随机训练的优化方法和收敛性不同。找到一个好的批量大小可能很困难。为了了解批量与随机指标之间的收敛程度如何不同,建议读者将批量大小更改为各种级别。以下是保存和记录训练循环中随机损失的代码。只需在上一个秘籍中替换此代码:
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)
下面是为同一回归问题生成随机和批量损失图的代码:
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()
我们得到以下绘图:

图 6:在 100 次迭代中绘制的随机损失和批量损失(批量大小为 20)。请注意,批次损失更加平滑,随机损失更加不稳定。
更多
| 训练类型 | 优点 | 缺点 |
|---|---|---|
| 随机 | 随机性可能有助于摆脱局部的最小值。 | 通常,需要更多迭代才能收敛。 |
| 批量 | 更快地找到最小值。 | 需要更多资源来计算。 |
把所有东西结合在一起
在本节中,我们将结合到目前为止所示的所有内容,并为鸢尾数据集创建分类器。
准备
鸢尾数据集在第 1 章,TensorFlow 入门中使用数据源秘籍中有更详细的描述。我们将加载这些数据并制作一个简单的二元分类器来预测花是否是山鸢尾的种类。需要说明的是,这个数据集有三个种类,但我们只能预测一种花是单一种,是否是一种花,给我们一个二元分类器。我们将首先加载库和数据,然后相应地转换目标。
操作步骤
我们按如下方式处理秘籍:
- 首先,我们加载所需的库并初始化计算图。注意我们也在这里加载
matplotlib,因为我们想在之后绘制结果行:
import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets
import tensorflow as tf
sess = tf.Session()
- 接下来,我们加载鸢尾数据。我们还需要将目标数据转换为 1 或 0,无论目标是否为山鸢尾。由于鸢尾数据集将山鸢尾标记为 0,我们将更改所有目标,值为 0 到 1,其他值全部为 0.我们也将只使用两个特征,花瓣长度和花瓣宽度。这两个特征是每个
x-value中的第三和第四个条目:
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])
- 让我们声明我们的批量大小,数据占位符和模型变量。请记住,可变批量大小的数据占位符将
None作为第一个维度:
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来减少浮点数的字节来提高算法的表现(速度)。
- 在这里,我们定义线性模型。模型将采用
x2 = x1 * A + b的形式,如果我们想要找到该行上方或下方的点,我们会在插入等式x2 - x1 * A - b时看到它们是高于还是低于零。我们将通过取该方程的 sigmoid 并从该方程预测 1 或 0 来实现。请记住,TensorFlow 具有内置 sigmoid 的loss函数,因此我们只需要在 sigmoid 函数之前定义模型的输出:
my_mult = tf.matmul(x2_data, A)
my_add = tf.add(my_mult, b)
my_output = tf.sub(x1_data, my_add)
- 现在,我们使用 TensorFlow 的内置
sigmoid_cross_entropy_with_logits()函数添加 sigmoid 交叉熵损失函数:
xentropy = tf.nn.sigmoid_cross_entropy_with_logits(my_output, y_target)
- 我们还必须告诉 TensorFlow 如何通过声明优化方法来优化我们的计算图。我们希望最大限度地减少交叉熵损失。我们还会选择
0.05作为我们的学习率:
my_opt = tf.train.GradientDescentOptimizer(0.05)
train_step = my_opt.minimize(xentropy)
- 现在,我们创建一个变量初始化操作并告诉 TensorFlow 执行它:
init = tf.global_variables_initializer()
sess.run(init)
- 现在,我们将训练我们的线性模型 1000 次迭代。我们将提供我们需要的三个数据点:花瓣长度,花瓣宽度和目标变量。每 200 次迭代,我们将打印变量值:
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]]
- 下一组命令提取模型变量并在绘图上绘制线条。结果绘图在它的工作原理...部分:
[[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()
工作原理
我们的目标是仅使用花瓣宽度和花瓣长度在山鸢尾点和其他两个物种之间拟合一条线。如果我们绘制点和结果线,我们看到我们已经实现了这个:

图 7:花瓣宽度与花瓣长度的山鸢尾和其它鸢尾的图;实线是我们在 1000 次迭代后实现的线性分离器
更多
虽然我们实现了用一条线分隔两个类的目标,但它可能不是分离两个类的最佳模型。在第 4 章,支持向量机中,我们将讨论支持向量机,它是在特征空间中分离两个类的更好方法。
另见
- 有关 scikit-learn 鸢尾花数据集实现的信息,请参阅此链接的文档。
评估模型
我们已经学会了如何在 TensorFlow 中训练回归和分类算法。在此之后,我们必须能够评估模型的预测,以确定它的效果。
准备
评估模型非常重要,每个后续模型都将采用某种形式的模型评估。使用 TensorFlow,我们必须将此函数构建到计算图中,并在我们的模型进行训练时和/或完成训练后调用它。
在训练期间评估模型可以让我们深入了解算法,并可以提供调试,改进或完全更改模型的提示。虽然训练期间的评估并不总是必要的,但我们将展示如何使用回归和分类进行评估。
训练结束后,我们需要量化模型对数据的执行方式。理想情况下,我们有一个单独的训练和测试集(甚至是验证集),我们可以在其上评估模型。
当我们想要评估模型时,我们希望在大批数据点上进行评估。如果我们已经实现了批量训练,我们可以重用我们的模型来对这样的批次进行预测。如果我们实现了随机训练,我们可能必须创建一个可以批量处理数据的单独评估器。
如果我们在
loss函数中包含对模型输出的转换,例如sigmoid_cross_entropy_with_logits(),我们必须在计算精度计算的预测时考虑到这一点。不要忘记将此包含在您对模型的评估中。
我们要评估的任何模型的另一个重要方面是它是回归还是分类模型。
回归模型试图预测连续数。目标不是类别,而是所需数量。为了评估这些针对实际目标的回归预测,我们需要对两者之间的距离进行综合测量。大多数情况下,有意义的损失函数将满足这些标准。此秘籍向您展示如何将之前的简单回归算法更改为打印出训练循环中的损失并在结束时评估损失。例如,我们将在本章的先前实现反向传播秘籍中重新审视并重写我们的回归示例。
分类模型基于数字输入预测类别。实际目标是 1 和 0 的序列,我们必须衡量我们与预测的真实程度。分类模型的损失函数通常对解释模型的运行情况没有帮助。通常,我们需要某种分类准确率,这通常是正确预测类别的百分比。对于此示例,我们将使用本章中先前实现反向传播秘籍的分类示例。
操作步骤
首先,我们将展示如何评估简单回归模型,该模型简单地适应目标的常数乘法,即 10,如下所示:
- 首先,我们首先加载库并创建图,数据,变量和占位符。本节还有一个非常重要的部分。在我们创建数据之后,我们将数据随机分成训练和测试数据集。这很重要,因为我们总是会测试我们的模型,看看它们是否预测良好。在训练数据和测试数据上评估模型还可以让我们看到模型是否过拟合:
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]))
- 现在,我们声明我们的模型,
loss函数和优化算法。我们还将初始化模型变量A。使用以下代码:
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)
- 我们正如我们之前看到的那样运行训练循环,如下所示:
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
- 现在,为了评估模型,我们将在训练和测试集上输出 MSE(损失函数),如下所示:
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 并测试它以查看我们的类是否正确:
- 在同一个脚本中,我们可以重新加载图并创建数据,变量和占位符。请记住,我们还需要将数据和目标分成训练和测试集。使用以下代码:
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]))
- 我们现在将模型和损失函数添加到图中,初始化变量,并创建优化过程,如下所示:
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)
- 现在,我们运行我们的训练循环,如下所示:
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)%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 = [ 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
- 为了评估模型,我们将创建自己的预测操作。我们将预测操作包装在挤压函数中,因为我们希望使预测和目标形成相同的形状。然后,我们用相等的函数测试相等性。在那之后,我们留下了一个真值和假值的张量,我们将其转换为
float32并取平均值。这将产生准确率值。我们将为训练集和测试集评估此函数,如下所示:
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
- 通常,查看模型结果(准确率,MSE 等)将有助于我们评估模型。我们可以在这里轻松绘制模型和数据的绘图,因为它是一维的。以下是使用
matplotlib使用两个单独的直方图可视化模型和数据的方法:
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()
工作原理
这导致绘图显示两个单独数据类的直方图中两个类的预测最佳分隔符。

图 8:数据和最终模型的可视化。两个正常值以 -1 和 2 为中心,使理论最佳分割为 0.5。在这里,模型发现最接近该数字的最佳分割。
三、线性回归
在本章中,我们将介绍涉及线性回归的秘籍。我们从用矩阵求解线性回归的数学公式开始,然后继续使用 TensorFlow 范例实现标准线性回归和变量。我们将涵盖以下领域:
- 使用矩阵逆方法
- 实现分解方法
- 学习 TensorFlow 回归方式
- 理解线性回归中的损失函数
- 实现戴明回归
- 实现套索和岭回归
- 实现弹性网络回归
- 实现逻辑回归
介绍
线性回归可能是统计学,机器学习和一般科学中最重要的算法之一。它是最广泛使用的算法之一,了解如何实现它及其各种风格非常重要。线性回归优于许多其他算法的优点之一是它是非常可解释的。我们最终得到一个数字,用于直接表示该特征如何影响目标或因变量的每个特征。在本章中,我们将介绍线性回归是如何经典实现的,然后继续讨论如何在 TensorFlow 范例中最好地实现它。
使用矩阵逆方法
在这个秘籍中,我们将使用 TensorFlow 用矩阵逆方法求解二维线性回归。
准备
线性回归可以表示为一组矩阵方程,比如Ax = b。在这里,我们感兴趣的是求解矩阵x中的系数。如果我们的观察矩阵(设计矩阵)A不是正方形,我们必须要小心。解决x的解决方案可以表示为:

为了证明确实如此,我们将生成二维数据,在 TensorFlow 中解决它,并绘制结果。
操作步骤
我们按如下方式处理秘籍:
- 首先,我们加载必要的库,初始化图并创建数据。请参阅以下代码:
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)
- 接下来,我们创建要在逆方法中使用的矩阵。我们首先创建
A矩阵,它将是x数据列和全 1 的列。然后,我们从y数据创建b矩阵。使用以下代码:
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和b矩阵转换为张量,如下所示:
A_tensor = tf.constant(A)
b_tensor = tf.constant(b)
- 现在我们已经设置了矩阵,我们可以使用 TensorFlow 通过矩阵逆方法解决这个问题,如下所示:
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)
- 我们现在使用以下代码从解,斜率和 y 截距中提取系数:
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()
我们得到前面代码的图,如下所示:

图 1:数据点和通过矩阵逆方法获得的最佳拟合线
工作原理
与之前的秘籍或本书中的大多数秘籍不同,此处的解决方案仅通过矩阵运算找到。我们将使用的大多数 TensorFlow 算法都是通过训练循环实现的,并利用自动反向传播来更新模型变量。在这里,我们通过实现将模型拟合到数据的直接解决方案来说明 TensorFlow 的多功能性。
我们在这里使用了一个二维数据示例来显示与数据拟合的图。值得注意的是,用于求解系数的公式
将根据需要扩展到数据中的许多特征(除非存在任何共线性问题)。
实现分解方法
对于这个秘籍,我们将实现一个用于线性回归的矩阵分解方法。具体来说,我们将使用 Cholesky 分解,TensorFlow 中存在相关函数。
准备
在大多数情况下,实现前一个秘籍中的逆方法在数值上效率低,尤其是当矩阵变得非常大时。另一种方法是分解A矩阵并对分解执行矩阵运算。一种方法是在 TensorFlow 中使用内置的 Cholesky 分解方法。
人们对将矩阵分解为更多矩阵如此感兴趣的一个原因是,所得到的矩阵将具有允许我们有效使用某些方法的保证属性。 Cholesky 分解将矩阵分解为下三角矩阵和上三角矩阵,比如L和L',使得这些矩阵是彼此的转置。有关此分解属性的更多信息,有许多可用资源来描述它以及如何到达它。在这里,我们将通过将其写为LL'x = b来解决系统Ax = b。我们首先解决Ly = b的y,然后求解L'x = y得到我们的系数矩阵x。
操作步骤
我们按如下方式处理秘籍:
- 我们将以与上一个秘籍完全相同的方式设置系统。我们将导入库,初始化图并创建数据。然后,我们将以之前的方式获得我们的
A矩阵和b矩阵:
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)
- 接下来,我们找到方阵的 Cholesky 分解,
Aᐪ A:
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()仅返回分解的下对角线部分。这很好,因为上对角矩阵只是下对角矩阵的转置。
- 现在我们有了解决方案,我们提取系数:
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()
绘图如下:

图 2:通过 Cholesky 分解获得的数据点和最佳拟合线
工作原理
如您所见,我们得出了与之前秘籍非常相似的答案。请记住,这种分解矩阵然后对碎片执行操作的方式有时会更加高效和数值稳定,尤其是在考虑大型数据矩阵时。
学习 TensorFlow 线性回归方法
虽然使用矩阵和分解方法非常强大,但 TensorFlow 还有另一种解决斜率和截距的方法。 TensorFlow 可以迭代地执行此操作,逐步学习最小化损失的线性回归参数。
准备
在这个秘籍中,我们将遍历批量数据点并让 TensorFlow 更新斜率和y截距。我们将使用内置于 scikit-learn 库中的鸢尾花数据集,而不是生成的数据。具体来说,我们将通过数据点找到最佳线,其中x值是花瓣宽度,y值是萼片长度。我们选择了这两个,因为它们之间似乎存在线性关系,我们将在最后的绘图中看到。我们还将在下一节中详细讨论不同损失函数的影响,但对于这个秘籍,我们将使用 L2 损失函数。
操作步骤
我们按如下方式处理秘籍:
- 我们首先加载必要的库,创建图并加载数据:
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])
- 然后我们声明我们的学习率,批量大小,占位符和模型变量:
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]))
- 接下来,我们编写线性模型的公式
y = Ax + b:
model_output = tf.add(tf.matmul(x_data, A), b)
- 然后,我们声明我们的 L2 损失函数(包括批量的平均值),初始化变量,并声明我们的优化器。请注意,我们选择
0.05作为我们的学习率:
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)
- 我们现在可以在随机选择的批次上循环并训练模型。我们将运行 100 个循环并每 25 次迭代打印出变量和损失值。请注意,在这里,我们还保存了每次迭代的损失,以便我们以后可以查看它们:
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)%25==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
- 接下来,我们将提取我们找到的系数并创建一个最合适的线以放入图中:
[slope] = sess.run(A)
[y_intercept] = sess.run(b)
best_fit = []
for i in x_vals:
best_fit.append(slope*i+y_intercept)
- 在这里,我们将创建两个图。第一个是覆盖拟合线的数据。第二个是 100 次迭代中的 L2 损失函数。这是生成两个图的代码。
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()
此代码生成以下拟合数据和损失图。

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

图 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 损失函数如何影响线性回归中的收敛。
准备
我们将使用与先前秘籍中相同的鸢尾数据集,但我们将更改损失函数和学习率以查看收敛如何变化。
操作步骤
我们按如下方式处理秘籍:
- 程序的开始与上一个秘籍相同,直到我们达到我们的损失函数。我们加载必要的库,启动会话,加载数据,创建占位符,并定义我们的变量和模型。需要注意的一点是,我们正在提取学习率和模型迭代。我们这样做是因为我们希望显示快速更改这些参数的效果。使用以下代码:
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)
- 我们的损失函数将变为 L1 损失(
loss_l1),如下所示:
loss_l1 = tf.reduce_mean(tf.abs(y_target - model_output))
- 现在,我们通过初始化变量,声明我们的优化器以及通过训练循环迭代数据来恢复。请注意,我们也在节省每一代的损失来衡量收敛。使用以下代码:
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)%25==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 损失的损失函数图:

图 5:鸢尾线性回归问题的学习率为 0.05 的 L1 和 L2 损失
学习率为 0.05 时,似乎 L2 损失是首选,因为它会收敛到较低的损失。下面是我们将学习率提高到 0.4 时的损失函数图:

图 6:鸢尾线性回归问题的 L1 和 L2 损失,学习率为 0.4;请注意,由于 y 轴的高比例,L1 损失不可见
在这里,我们可以看到高学习率可以在 L2 范数中超调,而 L1 范数收敛。
更多
为了理解正在发生的事情,我们应该看看大学习率和小学习率如何影响 L1 范数和 L2 范数。为了使这个可视化,我们查看两个规范的学习步骤的一维表示,如下所示:

图 7:学习率越来越高的 L1 和 L2 范数会发生什么
实现戴明回归
在这个秘籍中,我们将实现戴明回归,这意味着我们需要一种不同的方法来测量模型线和数据点之间的距离。
戴明回归有几个名字。它也称为总回归,正交距离回归(ODR)和最短距离回归。
准备
如果最小二乘线性回归最小化到线的垂直距离,则戴明回归最小化到线的总距离。这种类型的回归可以最小化y和x值的误差。
请参阅下图进行比较:

图 8:常规线性回归和戴明回归之间的差异;左边的线性回归最小化了到线的垂直距离,右边的变形回归最小化了到线的总距离
要实现戴明回归,我们必须修改损失函数。常规线性回归中的损失函数使垂直距离最小化。在这里,我们希望最小化总距离。给定线的斜率和截距,到点的垂直距离是已知的几何公式。我们只需要替换此公式并告诉 TensorFlow 将其最小化。
操作步骤
我们按如下方式处理秘籍:
- 代码与之前的秘籍非常相似,除非我们进入损失函数。我们首先加载库;开始一个会议;加载数据;声明批量大小;并创建占位符,变量和模型输出,如下所示:
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)
- 损失函数是由分子和分母组成的几何公式。为清楚起见,我们将分别编写这些内容。给定一条线
y = mx + b和一个点(x0, y0),两者之间的垂直距离可以写成如下:

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))
- 我们现在初始化变量,声明我们的优化器,并循环遍历训练集以获得我们的参数,如下所示:
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)%50==0:
print('Step #' + str(i+1) + ' A = ' + str(sess.run(A)) + ' b = ' + str(sess.run(b)))
print('Loss = ' + str(temp_loss))
- 我们可以使用以下代码绘制输出:
[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()
我们得到上面代码的以下图:

图 9:对鸢尾数据集进行戴明回归的解决方案
工作原理
戴明回归的方法几乎与常规线性回归相同。关键的区别在于我们如何衡量预测和数据点之间的损失。而不是垂直损失,我们对y和x值有垂直损失(或总损失)。
当我们假设
x和y值中的误差相似时,使用这种类型的回归。根据我们的假设,我们还可以根据误差的差异在距离计算中缩放x和y轴。
实现套索和岭回归
还有一些方法可以限制系数对回归输出的影响。这些方法称为正则化方法,两种最常见的正则化方法是套索和岭回归。我们将介绍如何在本文中实现这两个方面。
准备
套索和岭回归与常规线性回归非常相似,除了我们添加正则化项以限制公式中的斜率(或部分斜率)。这可能有多种原因,但一个常见的原因是我们希望限制对因变量产生影响的特征。这可以通过在损失函数中添加一个取决于我们的斜率值A的项来实现。
对于套索回归,如果斜率A超过某个值,我们必须添加一个能大大增加损失函数的项。我们可以使用 TensorFlow 的逻辑运算,但它们没有与之关联的梯度。相反,我们将使用称为连续重阶函数的阶梯函数的连续近似,该函数按比例放大到我们选择的正则化截止值。我们将展示如何在此秘籍中进行套索回归。
对于岭回归,我们只是在 L2 范数中添加一个项,这是斜率系数的缩放 L2 范数。这种修改很简单,并在本秘籍末尾的“更多”部分中显示。
操作步骤
我们按如下方式处理秘籍:
- 我们将再次使用鸢尾花数据集并以与以前相同的方式设置我们的脚本。我们先加载库;开始一个会议;加载数据;声明批量大小;并创建占位符,变量和模型输出,如下所示:
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)
- 我们添加了损失函数,它是一个改进的连续 Heaviside 阶梯函数。我们还为
0.9设定了套索回归的截止值。这意味着我们希望将斜率系数限制为小于0.9。使用以下代码:
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)
- 我们现在初始化变量并声明我们的优化器,如下所示:
init = tf.global_variables_initializer()
sess.run(init)
my_opt = tf.train.GradientDescentOptimizer(learning_rate)
train_step = my_opt.minimize(loss)
- 我们将训练循环延长了一段时间,因为它可能需要一段时间才能收敛。我们可以看到斜率系数小于
0.9。使用以下代码:
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)%300==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函数更改为如下:
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 正则化项添加到损失函数,将套索回归与岭回归相结合。
准备
在前两个秘籍之后实现弹性网络回归应该是直截了当的,因此我们将在鸢尾数据集上的多元线性回归中实现这一点,而不是像以前那样坚持二维数据。我们将使用花瓣长度,花瓣宽度和萼片宽度来预测萼片长度。
操作步骤
我们按如下方式处理秘籍:
- 首先,我们加载必要的库并初始化图,如下所示:
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from sklearn import datasets
sess = tf.Session()
- 现在,我们加载数据。这次,
x数据的每个元素将是三个值的列表而不是一个。使用以下代码:
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])
- 接下来,我们声明批量大小,占位符,变量和模型输出。这里唯一的区别是我们更改
x数据占位符的大小规范,取三个值而不是一个,如下所示:
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)
- 对于弹性网络,损失函数具有部分斜率的 L1 和 L2 范数。我们创建这些项,然后将它们添加到损失函数中,如下所示:
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)
- 现在,我们可以初始化变量,声明我们的优化函数,运行训练循环,并拟合我们的系数,如下所示:
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)%250==0:
print('Step #' + str(i+1) + ' A = ' + str(sess.run(A)) + ' b = ' + str(sess.run(b)))
print('Loss = ' + str(temp_loss))
- 这是代码的输出:
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]
- 现在,我们可以观察训练迭代的损失,以确保算法收敛,如下所示:
plt.plot(loss_vec, 'k-')
plt.title('Loss per Generation')
plt.xlabel('Generation')
plt.ylabel('Loss')
plt.show()
我们得到上面代码的以下图:

图 10:在 1,000 次训练迭代中绘制的弹性净回归损失
工作原理
这里实现弹性网络回归以及多元线性回归。我们可以看到,利用损失函数中的这些正则化项,收敛速度比先前的秘籍慢。正则化就像在损失函数中添加适当的项一样简单。
实现逻辑回归
对于这个秘籍,我们将实现逻辑回归来预测样本人群中低出生体重的概率。
准备
逻辑回归是将线性回归转换为二元分类的一种方法。这是通过将线性输出转换为 Sigmoid 函数来实现的,该函数将输出在 0 和 1 之间进行缩放。目标是零或一,表示数据点是在一个类还是另一个类中。由于我们预测 0 和 1 之间的数字,如果预测高于指定的截止值,则预测被分类为类值 1,否则分类为 0。出于此示例的目的,我们将指定截断为 0.5,这将使分类像舍入输出一样简单。
我们将用于此示例的数据将是从作者的 GitHub 仓库获得的低出生体重数据。我们将从其他几个因素预测低出生体重。
操作步骤
我们按如下方式处理秘籍:
- 我们首先加载库,包括
request库,因为我们将通过超链接访问低出生体重数据。我们还发起了一个会议:
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()
- 接下来,我们通过请求模块加载数据并指定我们要使用的特征。我们必须具体,因为一个特征是实际出生体重,我们不想用它来预测出生体重是大于还是小于特定量。我们也不想将 ID 列用作预测器:
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('\r\n')
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])
- 首先,我们将数据集拆分为测试和训练集:
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]
- 当特征在 0 和 1 之间缩放(最小 - 最大缩放)时,逻辑回归收敛效果更好。那么,接下来我们将扩展每个特征:
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))
请注意,在缩放数据集之前,我们将数据集拆分为训练和测试。这是一个重要的区别。我们希望确保测试集完全不影响训练集。如果我们在分裂之前缩放整个集合,那么我们不能保证它们不会相互影响。我们确保从训练组中保存缩放以缩放测试集。
- 接下来,我们声明批量大小,占位符,变量和逻辑模型。我们不将输出包装在 sigmoid 中,因为该操作内置于损失函数中。另请注意,每次观察都有七个输入特征,因此
x_data占位符的大小为[None, 7]。
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)
- 现在,我们声明我们的损失函数,它具有 sigmoid 函数,初始化我们的变量,并声明我们的优化函数:
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)
- 在记录损失函数的同时,我们还希望在训练和测试集上记录分类准确率。因此,我们将创建一个预测函数,返回任何大小的批量的准确率:
prediction = tf.round(tf.sigmoid(model_output))
predictions_correct = tf.cast(tf.equal(prediction, y_target), tf.float32)
accuracy = tf.reduce_mean(predictions_correct)
- 现在,我们可以开始我们的训练循环并记录损失和准确率:
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)
- 以下是查看损失和准确率图的代码:
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 次观测,因此随着数据集的随机分裂,训练和测试精度图将发生变化。第一个数字是交叉熵损失:

图 11:在 1,500 次迭代过程中绘制的交叉熵损失
第二个图显示了训练和测试装置的准确率:
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 目标。由于可能有许多行分隔两个类,我们定义最佳线性分隔符,以最大化两个类之间的距离:

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

这里,A是我们部分斜率的向量,x是输入向量。最大边距的宽度可以显示为 2 除以A的 L2 范数。这个事实有许多证明,但是对于几何思想,求解从 2D 点到直线的垂直距离可以提供前进的动力。
对于线性可分的二元类数据,为了最大化余量,我们最小化A,
的 L2 范数。我们还必须将此最小值置于以下约束条件下:

前面的约束确保我们来自相应类的所有点都在分离线的同一侧。
由于并非所有数据集都是线性可分的,因此我们可以为跨越边界线的点引入损失函数。对于n数据点,我们引入了所谓的软边际损失函数,如下所示:

请注意,如果该点位于边距的正确一侧,则乘积y[i](Ax[i] - b)始终大于 1。这使得损失函数的左手项等于 0,并且对损失函数的唯一影响是余量的大小。
前面的损失函数将寻找线性可分的线,但允许穿过边缘线的点。根据α的值,这可以是硬度或软度量。α的较大值导致更加强调边距的扩大,而α的较小值导致模型更像是一个硬边缘,同时允许数据点跨越边距,如果需要的话。
在本章中,我们将建立一个软边界 SVM,并展示如何将其扩展到非线性情况和多个类。
使用线性 SVM
对于此示例,我们将从鸢尾花数据集创建线性分隔符。我们从前面的章节中知道,萼片长度和花瓣宽度创建了一个线性可分的二分类数据集,用于预测花是否是山鸢尾(I)。
准备
要在 TensorFlow 中实现软可分 SVM,我们将实现特定的损失函数,如下所示:

这里,A是部分斜率的向量,b是截距,xᵢ是输入向量,yᵢ是实际类,(-1 或 1),α是软可分性正则化参数。
操作步骤
我们按如下方式处理秘籍:
- 我们首先加载必要的库。这将包括用于访问鸢尾数据集的
scikit-learn数据集库。使用以下代码:
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。使用以下代码:
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])
- 我们现在应该将数据集拆分为训练集和测试集。我们将评估训练和测试集的准确率。由于我们知道这个数据集是线性可分的,因此我们应该期望在两个集合上获得 100% 的准确率。要拆分数据,请使用以下代码:
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]
- 接下来,我们设置批量大小,占位符和模型变量。值得一提的是,使用这种 SVM 算法,我们需要非常大的批量大小来帮助收敛。我们可以想象,对于非常小的批量大小,最大边际线会略微跳跃。理想情况下,我们也会慢慢降低学习率,但现在这已经足够了。此外,
A变量将采用2x1形状,因为我们有两个预测变量:萼片长度和花瓣宽度。要进行此设置,我们使用以下代码:
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。模型输出使用以下代码:
model_output = tf.subtract(tf.matmul(x_data, A), b)
- 接下来,我们将汇总并声明必要的组件以获得最大的保证金损失。首先,我们将声明一个计算向量的 L2 范数的函数。然后,我们添加边距参数
。然后我们宣布我们的分类损失并将这两项加在一起。使用以下代码:
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))
- 现在,我们声明我们的预测和准确率函数,以便我们可以评估训练集和测试集的准确率,如下所示:
prediction = tf.sign(model_output)
accuracy = tf.reduce_mean(tf.cast(tf.equal(prediction, y_target), tf.float32))
- 在这里,我们将声明我们的优化函数并初始化我们的模型变量;我们在以下代码中执行此操作:
my_opt = tf.train.GradientDescentOptimizer(0.01)
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
- 我们现在可以开始我们的训练循环,记住我们想要在训练和测试集上记录我们的损失和训练准确率,如下所示:
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)%100==0:
print('Step #' + str(i+1) + ' A = ' + str(sess.run(A)) + ' b = ' + str(sess.run(b)))
print('Loss = ' + str(temp_loss))
- 训练期间脚本的输出应如下所示:
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]
- 为了绘制输出(拟合,损失和精度),我们必须提取系数并将
x值分成山鸢尾和其它鸢尾,如下所示:
[[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]
- 以下是使用线性分离器拟合,精度和损耗绘制数据的代码:
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 算法可能导致每次运行的结果略有不同。其原因包括随机训练/测试集拆分以及每个训练批次中不同批次点的选择。此外,在每一代之后慢慢降低学习率是理想的。
得到的图如下:

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

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

图 4:超过 500 次迭代的最大边际损失图
工作原理
在本文中,我们已经证明使用最大边际损失函数可以实现线性 SVD 模型。
简化为线性回归
SVM 可用于拟合线性回归。在本节中,我们将探讨如何使用 TensorFlow 执行此操作。
准备
可以将相同的最大边际概念应用于拟合线性回归。我们可以考虑最大化包含最多(x,y)点的边距,而不是最大化分隔类的边距。为了说明这一点,我们将使用相同的鸢尾数据集,并表明我们可以使用此概念来拟合萼片长度和花瓣宽度之间的线。
相应的损失函数类似于:

这里,ε是边距宽度的一半,如果一个点位于该区域,则损失等于 0。
操作步骤
我们按如下方式处理秘籍:
- 首先,我们加载必要的库,启动图,然后加载鸢尾数据集。之后,我们将数据集拆分为训练集和测试集,以显示两者的损失。使用以下代码:
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]
对于此示例,我们将数据拆分为训练集和测试集。将数据拆分为三个数据集也很常见,其中包括验证集。我们可以使用此验证集来验证我们在训练它们时不会过拟合模型。
- 让我们声明我们的批量大小,占位符和变量,并创建我们的线性模型,如下所示:
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)
- 现在,我们宣布我们的损失函数。如前文所述,损失函数实现为
ε = 0.5。请记住,epsilon 是我们的损失函数的一部分,它允许软边距而不是硬边距:
epsilon = tf.constant([0.5])
loss = tf.reduce_mean(tf.maximum(0., tf.subtract(tf.abs(tf.subtract(model_output, y_target)), epsilon)))
- 我们创建一个优化器并接下来初始化我们的变量,如下所示:
my_opt = tf.train.GradientDescentOptimizer(0.075)
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
- 现在,我们迭代 200 次训练迭代并保存训练和测试损失以便以后绘图:
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)%50==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))
- 这产生以下输出:
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
- 我们现在可以提取我们找到的系数,并获得最佳拟合线的值。出于绘图目的,我们也将获得边距的值。使用以下代码:
[[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)
- 最后,这里是用拟合线和训练测试损失绘制数据的代码:
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()
上述代码的图如下:

图 5:鸢尾数据上有 0.5 个边缘的 SVM 回归(萼片长度与花瓣宽度)
以下是训练迭代中的训练和测试损失:

图 6:训练和测试集上每代的 SVM 回归损失
工作原理
直觉上,我们可以将 SVM 回归看作是一个函数,试图尽可能多地在2ε宽度范围内拟合点。该线的拟合对该参数有些敏感。如果我们选择太小的ε,算法将无法适应边距中的许多点。如果我们选择太大的ε,将会有许多行能够适应边距中的所有数据点。我们更喜欢较小的ε,因为距离边缘较近的点比较远的点贡献较少的损失。
在 TensorFlow 中使用核
先前的 SVM 使用线性可分数据。如果我们分离非线性数据,我们可以改变将线性分隔符投影到数据上的方式。这是通过更改 SVM 损失函数中的核来完成的。在本章中,我们将介绍如何更改核并分离非线性可分离数据。
准备
在本文中,我们将激励支持向量机中核的使用。在线性 SVM 部分,我们用特定的损失函数求解了软边界。这种方法的另一种方法是解决所谓的优化问题的对偶。可以证明线性 SVM 问题的对偶性由以下公式给出:

对此,以下适用:

这里,模型中的变量将是b向量。理想情况下,此向量将非常稀疏,仅对我们数据集的相应支持向量采用接近 1 和 -1 的值。我们的数据点向量由xᵢ表示,我们的目标(1 或 -1)yᵢ表示。
前面等式中的核是点积x[i] · y[j],它给出了线性核。该核是一个方形矩阵,填充了数据点i, j的点积。
我们可以将更复杂的函数扩展到更高的维度,而不是仅仅在数据点之间进行点积,而在这些维度中,类可以是线性可分的。这似乎是不必要的复杂,但我们可以选择一个具有以下属性的函数k:

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

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

在本节中,我们将讨论如何实现高斯核。我们还将在适当的位置记下在何处替换实现线性核。我们将使用的数据集将手动创建,以显示高斯核更适合在线性核上使用的位置。
操作步骤
我们按如下方式处理秘籍:
- 首先,我们加载必要的库并启动图会话,如下所示:
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from sklearn import datasets
sess = tf.Session()
- 现在,我们生成数据。我们将生成的数据将是两个同心数据环;每个戒指都属于不同的阶级。我们必须确保类只有 -1 或 1。然后我们将数据分成每个类的
x和y值以用于绘图目的。为此,请使用以下代码:
(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]
- 接下来,我们声明批量大小和占位符,并创建我们的模型变量
b。对于 SVM,我们倾向于需要更大的批量大小,因为我们需要一个非常稳定的模型,该模型在每次训练生成时都不会波动很大。另请注意,我们为预测点添加了额外的占位符。为了可视化结果,我们将创建一个颜色网格,以查看哪些区域最后属于哪个类。我们这样做如下:
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]))
- 我们现在将创建高斯核。该核可以表示为矩阵运算,如下所示:
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)))
注意
add和subtract操作的sq_dists行中广播的使用。 另外,请注意线性核可以表示为my_kernel = tf.matmul(x_data, tf.transpose(x_data))。
- 现在,我们宣布了本秘籍中之前所述的双重问题。最后,我们将使用
tf.negative()函数最小化损失函数的负值,而不是最大化。我们使用以下代码完成此任务:
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))
- 我们现在创建预测和准确率函数。首先,我们必须创建一个预测核,类似于步骤 4,但是我们拥有带有预测数据的点的核心,而不是点的核。然后预测是模型输出的符号。这实现如下:
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))。
- 现在,我们可以创建一个优化函数并初始化所有变量,如下所示:
my_opt = tf.train.GradientDescentOptimizer(0.001)
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
- 接下来,我们开始训练循环。我们将记录每代的损耗向量和批次精度。当我们运行准确率时,我们必须放入所有三个占位符,但我们输入
x数据两次以获得对点的预测,如下所示:
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)%100==0:
print('Step #' + str(i+1))
print('Loss = ' + str(temp_loss))
- 这产生以下输出:
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
- 为了查看整个空间的输出类,我们将在系统中创建一个预测点网格,并对所有这些预测点进行预测,如下所示:
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)
- 以下是绘制结果,批次准确率和损失的代码:
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()
为了简洁起见,我们将仅显示结果图,但我们也可以单独运行绘图代码并查看损失和准确率。
以下屏幕截图说明了线性可分离拟合对我们的非线性数据有多糟糕:

图 7:非线性可分离数据上的线性 SVM
以下屏幕截图显示了高斯核可以更好地拟合非线性数据:
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_vals和y_vals),因为 SVM 是使用支持向量定义的,由哪些点指定在边界上或不是。
更多
如果我们这样选择,我们可以实现更多核。以下是一些更常见的非线性核列表:
- 多项式齐次核:

- 多项式非齐次核:

- 双曲正切核:

实现非线性 SVM
对于此秘籍,我们将应用非线性核来拆分数据集。
准备
在本节中,我们将在实际数据上实现前面的高斯核 SVM。我们将加载鸢尾数据集并为山鸢尾创建分类器(与其它鸢尾相比)。我们将看到各种伽马值对分类的影响。
操作步骤
我们按如下方式处理秘籍:
- 我们首先加载必要的库,其中包括
scikit-learn数据集,以便我们可以加载鸢尾数据。然后,我们将启动图会话。使用以下代码:
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from sklearn import datasets
sess = tf.Session()
- 接下来,我们将加载鸢尾数据,提取萼片长度和花瓣宽度,并分离每个类的
x和y值(以便以后绘图),如下所示:
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]
- 现在,我们声明我们的批量大小(首选大批量),占位符和模型变量
b,如下所示:
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]))
- 接下来,我们声明我们的高斯核。这个核依赖于伽马值,我们将在本文后面的各个伽玛值对分类的影响进行说明。使用以下代码:
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))
- 为了使用 SVM 执行预测,我们必须创建预测核函数。之后,我们还会声明一个准确率计算,它只是使用以下代码正确分类的点的百分比:
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))
- 接下来,我们声明我们的优化函数并初始化变量,如下所示:
my_opt = tf.train.GradientDescentOptimizer(0.01)
train_step = my_opt.minimize(loss)
init = tf.initialize_all_variables()
sess.run(init)
- 现在,我们可以开始训练循环了。我们运行循环 300 次迭代并存储损失值和批次精度。为此,我们使用以下实现:
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)
- 为了绘制决策边界,我们将创建
x,y点的网格并评估我们在所有这些点上创建的预测函数,如下所示:
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)
- 为简洁起见,我们只展示如何用决策边界绘制点。有关伽马值的图和效果,请参阅本秘籍的下一部分。使用以下代码:
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)的山鸢尾结果的分类。注意伽玛值越高,每个单独点对分类边界的影响越大:

图 9:使用具有四个不同伽马值的高斯核 SVM 的山鸢尾的分类结果
实现多类 SVM
我们还可以使用 SVM 对多个类进行分类,而不仅仅是两个类。在本文中,我们将使用多类 SVM 对鸢尾数据集中的三种类型的花进行分类。
准备
通过设计,SVM 算法是二元分类器。但是,有一些策略可以让他们在多个类上工作。两种主要策略称为“一对一”,“一对剩余”。
一对一是一种策略,其中为每个可能的类对创建二分类器。然后,对具有最多投票的类的点进行预测。这可能在计算上很难,因为我们必须为k类创建k!/(k - 2)!2!个分类器。
实现多类分类器的另一种方法是执行一对一策略,我们为k类的每个类创建一个分类器。点的预测类将是创建最大 SVM 边距的类。这是我们将在本节中实现的策略。
在这里,我们将加载鸢尾数据集并使用高斯核执行多类非线性 SVM。鸢尾数据集是理想的,因为有三个类(山鸢尾,弗吉尼亚和杂色鸢尾)。我们将为每个类创建三个高斯核 SVM,并预测存在最高边界的点。
操作步骤
我们按如下方式处理秘籍:
- 首先,我们加载我们需要的库并启动图,如下所示:
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from sklearn import datasets
sess = tf.Session()
- 接下来,我们将加载鸢尾数据集并拆分每个类的目标。我们将仅使用萼片长度和花瓣宽度来说明,因为我们希望能够绘制输出。我们还将每个类的
x和y值分开,以便最后进行绘图。使用以下代码:
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]
- 与实现非线性 SVM 秘籍相比,我们在此示例中所做的最大改变是,许多维度将发生变化(我们现在有三个分类器而不是一个)。我们还将利用矩阵广播和重塑技术一次计算所有三个 SVM。由于我们一次性完成这一操作,我们的
y_target占位符现在具有[3, None]的大小,我们的模型变量b将被初始化为[3, batch_size]。使用以下代码:
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]))
- 接下来,我们计算高斯核。由于这仅取决于输入的 x 数据,因此该代码不会改变先前的秘籍。使用以下代码:
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)))
- 一个重大变化是我们将进行批量矩阵乘法。我们将最终得到三维矩阵,我们将希望在第三个索引上广播矩阵乘法。我们没有为此设置数据和目标矩阵。为了使
xᐪ · x等操作跨越额外维度,我们创建一个函数来扩展这样的矩阵,将矩阵重新整形为转置,然后在额外维度上调用 TensorFlow 的batch_matmul。使用以下代码:
def reshape_matmul(mat):
v1 = tf.expand_dims(mat, 1)
v2 = tf.reshape(v1, [3, batch_size, 1])
return tf.batch_matmul(v2, v1)
- 创建此函数后,我们现在可以计算双重损失函数,如下所示:
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)))
- 现在,我们可以创建预测核。请注意,我们必须小心
reduce_sum函数并且不要在所有三个 SVM 预测中减少,因此我们必须告诉 TensorFlow 不要用第二个索引参数对所有内容求和。使用以下代码:
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)))
- 当我们完成预测核时,我们可以创建预测。这里的一个重大变化是预测不是输出的
sign()。由于我们正在实现一对一策略,因此预测是具有最大输出的分类器。为此,我们使用 TensorFlow 的内置argmax()函数,如下所示:
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))
- 现在我们已经拥有了核,损失和预测函数,我们只需要声明我们的优化函数并初始化我们的变量,如下所示:
my_opt = tf.train.GradientDescentOptimizer(0.01)
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
- 该算法收敛速度相对较快,因此我们不必运行训练循环超过 100 次迭代。我们使用以下代码执行此操作:
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)%25==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
- 我们现在可以创建点的预测网格并对所有点运行预测函数,如下所示:
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)
- 以下是绘制结果,批量准确率和损失函数的代码。为简洁起见,我们只显示最终结果:
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()
然后我们得到以下绘图:

图 10:在鸢尾数据集上的伽马为 10 的多类(三类)非线性高斯 SVM 的结果
我们观察前面的屏幕截图,其中显示了所有三个鸢尾类,以及为每个类分类的点网格。
工作原理
本文中需要注意的重点是我们如何改变算法以同时优化三个 SVM 模型。我们的模型参数b有一个额外的维度可以考虑所有三个模型。在这里,我们可以看到,由于 TensorFlow 处理额外维度的内置函数,算法扩展到多个类似算法相对容易。
下一章将介绍最近邻方法,这是一种用于预测目的的非常强大的算法。
五、最近邻方法
本章将重点介绍最近邻方法,以及如何在 TensorFlow 中实现它们。我们将首先介绍这些方法,然后我们将说明如何实现各种形式。本章将以地址匹配和图像识别的示例结束。
在本章中,我们将介绍以下内容:
- 使用最近邻
- 使用基于文本的距离
- 计算混合距离函数
- 使用地址匹配的示例
- 使用最近邻进行图像识别
介绍
最近邻方法植根于基于距离的概念思想。我们认为我们的训练设定了一个模型,并根据它们与训练集中的点的接近程度对新点进行预测。一种简单的方法是使预测类与最接近的训练数据点类相同。但由于大多数数据集包含一定程度的噪声,因此更常见的方法是采用一组k-最近邻的加权平均值。该方法称为 K 最近邻(KNN)。
给定具有相应目标(y[1], y[2]....y[n])的训练数据集(x[1],x[2].....x[n]),我们可以通过查看一组最近邻来对点z进行预测。实际的预测方法取决于我们是进行回归(连续yᵢ)还是分类(离散yᵢ)。
对于离散分类目标,可以通过最大投票方案给出预测,通过到预测点的距离加权:

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

很明显,预测很大程度上取决于距离度量的选择d。
距离度量的常用规范是 L1 和 L2 距离,如下所示:
我们可以选择许多不同规格的距离指标。在本章中,我们将探讨 L1 和 L2 指标,以及编辑和文本距离。
我们还必须选择如何加权距离。对距离进行加权的直接方法是距离本身。远离我们预测的点应该比较近点的影响小。最常见的权重方法是通过距离的归一化逆。我们将在下一个秘籍中实现此方法。
注意,KNN 是一种聚合方法。对于回归,我们正在执行邻居的加权平均。因此,预测将不那么极端,并且与实际目标相比变化较小。这种影响的大小将由算法中邻居的数量
k决定。
使用最近邻
我们将通过实现最近邻来预测住房价值来开始本章。这是从最近邻开始的好方法,因为我们将处理数字特征和连续目标。
准备
为了说明如何在 TensorFlow 中使用最近邻进行预测,我们将使用波士顿住房数据集。在这里,我们将预测邻域住房价值中位数作为几个特征的函数。
由于我们考虑训练集训练模型,我们将找到预测点的 KNN,并将计算目标值的加权平均值。
操作步骤
我们按如下方式处理秘籍:
- 我们将从加载所需的库并启动图会话开始。我们将使用
requests模块从 UCI 机器学习库加载必要的波士顿住房数据:
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import requests
sess = tf.Session()
- 接下来,我们将使用
requests模块加载数据:
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]
- 接下来,我们将数据分为依赖和独立的特征。我们将预测最后一个变量
MEDV,这是房屋组的中值。我们也不会使用ZN,CHAS和RAD特征,因为它们没有信息或二元性质:
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)
- 现在,我们将
x和y值分成训练和测试集。我们将通过随机选择大约 80% 的行来创建训练集,并将剩下的 20% 留给测试集:
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值和批量大小:
k = 4
batch_size=len(x_vals_test)
- 我们接下来会申报占位符。请记住,没有模型变量需要训练,因为模型完全由我们的训练集确定:
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)
- 接下来,我们将为一批测试点创建距离函数。在这里,我们将说明 L1 距离的使用:
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))。
- 现在,我们将创建我们的预测函数。为此,我们将使用
top_k()函数,该函数返回张量中最大值的值和索引。由于我们想要最小距离的指数,我们将找到k- 最大负距离。我们还将声明目标值的预测和均方误差(MSE):
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)
- 现在,我们将遍历测试数据并存储预测和准确率值:
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
- 另外,我们可以查看实际目标值与预测值的直方图。看待这一点的一个原因是要注意这样一个事实:使用平均方法,我们无法预测目标的极端:
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()
然后我们将获得直方图,如下所示:

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

图 2:各种k值的 KNN 预测的 MSE。我们还绘制了测试集上预测值的方差。请注意,随着k的增加,方差会减小。
工作原理
使用最近邻算法,模型是训练集。因此,我们不必在模型中训练任何变量。唯一的参数k是通过交叉验证确定的,以最大限度地减少我们的 MSE。
更多
对于 KNN 的加权,我们选择直接按距离加权。还有其他选择我们也可以考虑。另一种常见方法是通过反平方距离加权。
使用基于文本的距离
最近邻比处理数字更通用。只要我们有一种方法来测量特征之间的距离,我们就可以应用最近邻算法。在本文中,我们将介绍如何使用 TensorFlow 测量文本距离。
准备
在本文中,我们将说明如何在字符串之间使用 TensorFlow 的文本距离度量,Levenshtein 距离(编辑距离)。这将在本章后面重要,因为我们扩展了最近邻方法以包含带有文本的特征。
Levenshtein 距离是从一个字符串到另一个字符串的最小编辑次数。允许的编辑是插入字符,删除字符或用不同的字符替换字符。对于这个秘籍,我们将使用 TensorFlow 的 Levenshtein 距离函数edit_distance()。值得说明这个函数的用法,因为它的用法将适用于后面的章节。
请注意,TensorFlow 的
edit_distance()函数仅接受稀疏张量。我们必须创建我们的字符串作为单个字符的稀疏张量。
操作步骤
- 首先,我们将加载 TensorFlow 并初始化图:
import tensorflow as tf
sess = tf.Session()
- 然后,我们将说明如何计算两个单词
'bear'和'beer'之间的编辑距离。首先,我们将使用 Python 的list()函数从我们的字符串创建一个字符列表。接下来,我们将从该列表中创建一个稀疏的 3D 矩阵。我们必须告诉 TensorFlow 字符索引,矩阵的形状以及我们在张量中想要的字符。之后,我们可以决定是否要使用总编辑距离(normalize=False)或标准化编辑距离(normalize=True),我们将编辑距离除以第二个单词的长度:
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 的文档将两个字符串视为提议(假设)字符串和基础事实字符串。我们将在这里用
h和t张量继续这个表示法。函数SparseTensorValue()是一种在 TensorFlow 中创建稀疏张量的方法。它接受我们希望创建的稀疏张量的索引,值和形状。
- 接下来,我们将说明如何将两个单词
bear和beer与另一个单词beers进行比较。为了达到这个目的,我们必须复制beers以获得相同数量的可比词:
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 ]]
- 在此示例中显示了将一组单词与另一单词进行比较的更有效方法。我们将事先为假设和基本真实字符串创建索引和字符列表:
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\. ]]
- 现在,我们将说明如何使用占位符计算两个单词列表之间的编辑距离。这个概念是一样的,除了我们将
SparseTensorValue()而不是稀疏张量。首先,我们将创建一个从单词列表创建稀疏张量的函数:
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 以多种方式测量文本距离。这对于在具有文本特征的数据上执行最近邻非常有用。当我们执行地址匹配时,我们将在本章后面看到更多内容。
更多
我们应该讨论其他文本距离指标。这是一个定义表,描述了两个字符串s1和s2之间的其他文本距离:
| 名称 | 描述 | 公式 |
|---|---|---|
| 汉明距离 | 相同位置的相等字符的数量。仅在字符串长度相等时有效。 | ,其中I是相等字符的指示函数。 |
| 余弦距离 | k差异的点积除以k差异的 L2 范数。 |
![]() |
| 雅克卡距离 | 共同的字符数除以两个字符串中的字符总和。 | ![]() |
使用混合距离函数的计算
在处理具有多个特征的数据观察时,我们应该意识到特征可以在不同的尺度上以不同的方式缩放。在这个方案中,我们将考虑到这一点,以改善我们的住房价值预测。
准备
扩展最近邻算法很重要,要考虑不同缩放的变量。在这个例子中,我们将说明如何缩放不同变量的距离函数。具体来说,我们将距离函数作为特征方差的函数进行缩放。
加权距离函数的关键是使用权重矩阵。用矩阵运算写的距离函数变为以下公式:

这里,A是一个对角线权重矩阵,我们将用它来缩放每个特征的距离度量。
在本文中,我们将尝试在波士顿住房价值数据集上改进我们的 MSE。该数据集是不同尺度上的特征的一个很好的例子,并且最近邻算法将受益于缩放距离函数。
操作步骤
我们将按如下方式处理秘籍:
- 首先,我们将加载必要的库并启动图会话:
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import requests
sess = tf.Session()
- 接下来,我们将加载数据并将其存储在 NumPy 数组中。再次注意,我们只会使用某些列进行预测。我们不使用 id,也不使用方差非常低的变量:
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])
- 现在,我们将
x值缩放到 0 到 1 之间,最小 - 最大缩放:
x_vals = (x_vals - x_vals.min(0)) / x_vals.ptp(0)
- 然后,我们将创建对角线权重矩阵,该矩阵将通过特征的标准偏差提供距离度量的缩放:
weight_diagonal = x_vals.std(0)
weight_matrix = tf.cast(tf.diag(weight_diagonal), dtype=tf.float32)
- 现在,我们将数据分成训练和测试集。我们还将声明
k,最近邻的数量,并使批量大小等于测试集大小:
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)
- 我们将声明接下来需要的占位符。我们有四个占位符 - 训练和测试集的[HTG0] - 输入和
y- 目标:
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)
- 现在,我们可以声明我们的距离函数。为了便于阅读,我们将把距离函数分解为其组件。请注意,我们必须按批量大小平铺权重矩阵,并使用
batch_matmul()函数在批量大小中执行批量矩阵乘法:
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))
- 在我们计算每个测试点的所有训练距离之后,我们将需要返回顶部 KNN。我们可以使用
top_k()函数执行此操作。由于此函数返回最大值,并且我们想要最小距离,因此我们返回最大的负距离值。然后,我们将预测作为顶部k邻居的距离的加权平均值:
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])
- 为了评估我们的模型,我们将计算预测的 MSE:
mse = tf.divide(tf.reduce_sum(tf.square(tf.subtract(prediction, y_target_test))), batch_size)
- 现在,我们可以遍历我们的测试批次并计算每个的 MSE:
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
- 作为最终比较,我们可以使用以下代码绘制实际测试集的住房值分布和测试集的预测:
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()
我们将获得前面代码的以下直方图:

图 3:波士顿数据集上预测房屋价值和实际房屋价值的两个直方图;这一次,我们为每个特征不同地缩放了距离函数
工作原理
我们通过引入一种缩放每个特征的距离函数的方法来减少测试集上的 MSE。在这里,我们通过特征标准偏差的因子来缩放距离函数。这提供了更准确的测量视图,其中测量哪些点是最近的邻居。由此,我们还将顶部k邻域的加权平均值作为距离的函数,以获得住房价值预测。
更多
该缩放因子还可以用于最近邻距离计算中的向下加权或向上加权的特征。这在我们比某些特征更信任某些特征的情况下非常有用。
使用地址匹配的示例
现在我们已经测量了数值和文本距离,我们将花一些时间学习如何将它们组合起来测量具有文本和数字特征的观察之间的距离。
准备
最近邻是一种用于地址匹配的好算法。地址匹配是一种记录匹配,其中我们在多个数据集中具有地址并且想要匹配它们。在地址匹配中,我们可能在地址,不同城市或不同的邮政编码中存在拼写错误,但它们可能都指向相同的地址。在地址的数字和字符组件上使用最近邻算法可以帮助我们识别实际上相同的地址。
在此示例中,我们将生成两个数据集。每个数据集将包含街道地址和邮政编码。但是,一个数据集在街道地址中存在大量拼写错误。我们将非拼写数据集作为我们的黄金标准,并将为每个拼写错误地址返回一个地址,该地址最接近字符串距离(对于街道)和数字距离(对于邮政编码)的函数。
代码的第一部分将侧重于生成两个数据集。然后,代码的第二部分将运行测试集并返回训练集中最接近的地址。
操作步骤
我们将按如下方式处理秘籍:
- 我们将从加载必要的库开始:
import random
import string
import numpy as np
import tensorflow as tf
- 我们现在将创建参考数据集。为了显示简洁的输出,我们只会使每个数据集由
10地址组成(但它可以运行更多):
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)]
- 要创建测试集,我们需要一个函数,它将在字符串中随机创建一个拼写错误并返回结果字符串:
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)]
- 现在,我们可以初始化图会话并声明我们需要的占位符。我们在每个测试和参考集中需要四个占位符,我们需要一个地址和邮政编码占位符:
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)
- 现在,我们将声明数字拉链距离和地址字符串的编辑距离:
zip_dist = tf.square(tf.subtract(ref_zip, test_zip))
address_dist = tf.edit_distance(test_address, ref_address, normalize=True)
- 我们现在将拉链距离和地址距离转换为相似之处。对于相似性,当两个输入完全相同时,我们想要
1的相似性,当它们非常不同时,我们想要0附近。对于拉链距离,我们可以通过获取距离,从最大值减去,然后除以距离的范围来实现。对于地址相似性,由于距离已经在0和1之间缩放,我们只需从 1 中减去它以获得相似性:
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)
- 为了结合两个相似度函数,我们将采用两者的加权平均值。对于这个秘籍,我们对地址和邮政编码给予同等重视。我们可以根据我们对每个特征的信任程度来改变这一点。然后,我们将返回参考集的最高相似度的索引:
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)
- 为了在 TensorFlow 中使用编辑距离,我们必须将地址字符串转换为稀疏向量。在本章的先前秘籍中,使用基于文本的距离,我们创建了以下函数,我们也将在此秘籍中使用它:
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])
- 我们需要将参考数据集中的地址和邮政编码分开,以便在循环测试集时将它们提供给占位符:
reference_addresses = [x[0] for x in reference_data]
reference_zips = np.array([[x[1] for x in reference_data]])
- 我们需要使用我们在步骤 8 中创建的函数创建稀疏张量参考地址集:
sparse_ref_set = sparse_from_word_vec(reference_addresses)
- 现在,我们可以循环遍历测试集的每个条目,并返回它最接近的引用集的索引。我们将为每个条目打印测试和参考集。如您所见,我们在此生成的数据集中获得了很好的结果:
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))
我们将得到以下结果:
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
Match : 5167 baker rd, 65154
Address: 8765 donnor st, 65154
Match : 8765 donner st, 65154
工作原理
在像这样的地址匹配问题中要弄清楚的一个难点是权重的值以及如何缩放距离。这可能需要对数据本身进行一些探索和洞察。此外,在处理地址时,我们应该考虑除此处使用的组件之外的组件。我们可以将街道号码视为街道地址的独立组成部分,甚至可以包含其他组成部分,例如城市和州。
处理数字地址组件时,请注意它们可以被视为数字(具有数字距离)或字符(具有编辑距离)。由您决定选择哪个。请注意,如果我们认为邮政编码中的拼写错误来自人为错误而不是计算机映射错误,我们可以考虑使用邮政编码的编辑距离。
为了了解拼写错误如何影响结果,我们鼓励读者更改拼写错误函数以进行更多拼写错误或更频繁的拼写错误,并增加数据集大小以查看此算法的工作情况。
使用最近邻进行图像识别
最近邻也可用于图像识别。图像识别数据集的问题世界是 MNIST 手写数字数据集。由于我们将在后面的章节中将此数据集用于各种神经网络图像识别算法,因此将结果与非神经网络算法进行比较将会很棒。
准备
MNIST 数字数据集由数千个大小为28×28像素的标记图像组成。虽然这被认为是一个小图像,但它对于最近邻算法总共有 784 个像素(或特征)。我们将通过考虑最近的k邻居(k=4,在该示例中)的模式预测来计算该分类问题的最近邻预测。
操作步骤
我们将按如下方式处理秘籍:
- 我们将从加载必要的库开始。请注意,我们还将导入 Python 图像库(PIL),以便能够绘制预测输出的样本。 TensorFlow 有一个内置方法来加载我们将使用的 MNIST 数据集,如下所示:
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
- 现在,我们将启动图会话并以单热编码形式加载 MNIST 数据:
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表示,依此类推。
- 因为 MNIST 数据集很大并且计算数万个输入上的 784 个特征之间的距离在计算上是困难的,所以我们将采样一组较小的图像来训练。此外,我们将选择一个可被 6 整除的测试集编号,仅用于绘图目的,因为我们将绘制最后一批六个图像以查看结果的示例:
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]
- 我们将声明我们的
k值和批量大小:
k = 4
batch_size=6
- 现在,我们将初始化将添加到图中的占位符:
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)
- 然后我们将声明我们的距离度量。在这里,我们将使用 L1 度量(绝对值):
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))。
- 现在,我们将找到最接近的顶级
k图像并预测模式。该模式将在单热编码索引上执行,计数最多:
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)
- 我们现在可以遍历我们的测试集,计算预测并存储它们,如下所示:
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))
- 现在我们已经保存了实际和预测的输出,我们可以计算出准确率。由于我们对测试/训练数据集进行随机抽样,这会发生变化,但最终我们的准确率值应该在 80%-90% 左右:
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
- 以下是绘制前面批量结果的代码:
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)
结果如下:

图 4:我们运行最近邻预测的最后一批六个图像。我们可以看到,我们并没有完全正确地获得所有图像。
工作原理
给定足够的计算时间和计算资源,我们可以使测试和训练集更大。这可能会提高我们的准确率,也是防止过拟合的常用方法。另外,请注意,此算法需要进一步探索理想的k值进行选择。可以在数据集上进行一组交叉验证实验后选择k值。
更多
我们还可以使用最近邻算法来评估用户看不见的数字。有关使用此模型评估用户输入数字的方法,请参阅在线仓库。
在本章中,我们探讨了如何使用 KNN 算法进行回归和分类。我们讨论了距离函数的不同用法,以及如何将它们混合在一起。我们鼓励读者探索不同的距离度量,权重和k值,以优化这些方法的准确率。
六、神经网络
在本章中,我们将介绍神经网络以及如何在 TensorFlow 中实现它们。大多数后续章节将基于神经网络,因此学习如何在 TensorFlow 中使用它们非常重要。在开始使用多层网络之前,我们将首先介绍神经网络的基本概念。在上一节中,我们将创建一个神经网络,学习如何玩井字棋。
在本章中,我们将介绍以下秘籍:
- 实现操作门
- 使用门和激活函数
- 实现单层神经网络
- 实现不同的层
- 使用多层网络
- 改进线性模型的预测
- 学习玩井字棋
读者可以在 Github 和 Packt 仓库中找到本章中的所有代码。
介绍
神经网络目前在诸如图像和语音识别,阅读手写,理解文本,图像分割,对话系统,自动驾驶汽车等任务中打破记录。虽然这些上述任务中的一些将在后面的章节中介绍,但重要的是将神经网络作为一种易于实现的机器学习算法引入,以便我们以后可以对其进行扩展。
神经网络的概念已经存在了几十年。然而,它最近才获得牵引力,因为我们现在具有训练大型网络的计算能力,因为处理能力,算法效率和数据大小的进步。
神经网络基本上是应用于输入数据矩阵的一系列操作。这些操作通常是加法和乘法的集合,然后是非线性函数的应用。我们已经看到的一个例子是逻辑回归,我们在第 3 章,线性回归中看到了这一点。逻辑回归是部分斜率 - 特征乘积的总和,其后是应用 Sigmoid 函数,这是非线性的。神经网络通过允许操作和非线性函数的任意组合(包括绝对值,最大值,最小值等的应用)来进一步概括这一点。
神经网络的重要技巧称为反向传播。反向传播是一种允许我们根据学习率和损失函数输出更新模型变量的过程。我们使用反向传播来更新第 3 章,线性回归和第 4 章,支持向量机中的模型变量。
关于神经网络的另一个重要特征是非线性激活函数。由于大多数神经网络只是加法和乘法运算的组合,因此它们无法对非线性数据集进行建模。为了解决这个问题,我们在神经网络中使用了非线性激活函数。这将允许神经网络适应大多数非线性情况。
重要的是要记住,正如我们在许多算法中所看到的,神经网络对我们选择的超参数敏感。在本章中,我们将探讨不同学习率,损失函数和优化程序的影响。
学习神经网络的资源更多,更深入,更详细地涵盖了该主题。这些资源如下:
-
[深度学习,麻省理工学院出版社出版的一本书,Goodfellow 等人,2016]http://www.deeplearningbook.org)。
-
迈克尔·尼尔森(Michael Nielsen)有一本名为“神经网络与深度学习”的在线书籍。
-
对于一个更实用的方法和神经网络的介绍,Andrej Karpathy 用 JavaScript 实例写了一个很棒的总结,称为黑客的神经网络指南。
-
另一个总结深度学习的网站被 Ian Goodfellow,Yoshua Bengio 和 Aaron Courville 称为初学者深度学习。
实现操作门
神经网络最基本的概念之一是作为操作门操作。在本节中,我们将从乘法操作开始作为门,然后再继续考虑嵌套门操作。
准备
我们将实现的第一个操作门是f(x) = a · x。为优化此门,我们将a输入声明为变量,将x输入声明为占位符。这意味着 TensorFlow 将尝试更改a值而不是x值。我们将创建损失函数作为输出和目标值之间的差异,即 50。
第二个嵌套操作门将是f(x) = a · x + b。同样,我们将a和b声明为变量,将x声明为占位符。我们再次将输出优化到目标值 50。值得注意的是,第二个例子的解决方案并不是唯一的。有许多模型变量组合可以使输出为 50.对于神经网络,我们并不关心中间模型变量的值,而是更加强调所需的输出。
将这些操作视为我们计算图上的操作门。下图描绘了前面两个示例:

图 1:本节中的两个操作门示例
操作步骤
要在 TensorFlow 中实现第一个操作门f(x) = a · x并将输出训练为值 50,请按照下列步骤操作:
- 首先加载
TensorFlow并创建图会话,如下所示:
import tensorflow as tf
sess = tf.Session()
- 现在我们需要声明我们的模型变量,输入数据和占位符。我们使输入数据等于值
5,因此得到 50 的乘法因子将为 10(即5X10=50),如下所示:
a = tf.Variable(tf.constant(4.))
x_val = 5.
x_data = tf.placeholder(dtype=tf.float32)
- 接下来,我们使用以下输入将操作添加到计算图中:
multiplication = tf.multiply(a, x_data)
- 我们现在将损失函数声明为输出与
50的期望目标值之间的 L2 距离,如下所示:
loss = tf.square(tf.subtract(multiplication, 50.))
- 现在我们初始化我们的模型变量并将我们的优化算法声明为标准梯度下降,如下所示:
init = tf.global_variables_initializer()
sess.run(init)
my_opt = tf.train.GradientDescentOptimizer(0.01)
train_step = my_opt.minimize(loss)
- 我们现在可以将模型输出优化到
50的期望值。我们通过连续输入 5 的输入值并反向传播损失来将模型变量更新为10的值,如下所示:
print('Optimizing a Multiplication Gate Output to 50.')
for i in range(10):
sess.run(train_step, feed_dict={x_data: x_val})
a_val = sess.run(a)
mult_output = sess.run(multiplication, feed_dict={x_data: x_val})
print(str(a_val) + ' * ' + str(x_val) + ' = ' + str(mult_output))
- 上一步应该产生以下输出:
Optimizing a Multiplication Gate Output to 50\.
7.0 * 5.0 = 35.0
8.5 * 5.0 = 42.5
9.25 * 5.0 = 46.25
9.625 * 5.0 = 48.125
9.8125 * 5.0 = 49.0625
9.90625 * 5.0 = 49.5312
9.95312 * 5.0 = 49.7656
9.97656 * 5.0 = 49.8828
9.98828 * 5.0 = 49.9414
9.99414 * 5.0 = 49.9707
接下来,我们将对两个嵌套的操作门f(x) = a · x + b进行相同的操作。
- 我们将以与前面示例完全相同的方式开始,但将初始化两个模型变量
a和b,如下所示:
from tensorflow.python.framework import ops
ops.reset_default_graph()
sess = tf.Session()
a = tf.Variable(tf.constant(1.))
b = tf.Variable(tf.constant(1.))
x_val = 5\.
x_data = tf.placeholder(dtype=tf.float32)
two_gate = tf.add(tf.multiply(a, x_data), b)
loss = tf.square(tf.subtract(two_gate, 50.))
my_opt = tf.train.GradientDescentOptimizer(0.01)
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
- 我们现在优化模型变量以将输出训练到
50的目标值,如下所示:
print('Optimizing Two Gate Output to 50.')
for i in range(10):
# Run the train step
sess.run(train_step, feed_dict={x_data: x_val})
# Get the a and b values
a_val, b_val = (sess.run(a), sess.run(b))
# Run the two-gate graph output
two_gate_output = sess.run(two_gate, feed_dict={x_data: x_val})
print(str(a_val) + ' * ' + str(x_val) + ' + ' + str(b_val) + ' = ' + str(two_gate_output))
- 上一步应该产生以下输出:
Optimizing Two Gate Output to 50\.
5.4 * 5.0 + 1.88 = 28.88
7.512 * 5.0 + 2.3024 = 39.8624
8.52576 * 5.0 + 2.50515 = 45.134
9.01236 * 5.0 + 2.60247 = 47.6643
9.24593 * 5.0 + 2.64919 = 48.8789
9.35805 * 5.0 + 2.67161 = 49.4619
9.41186 * 5.0 + 2.68237 = 49.7417
9.43769 * 5.0 + 2.68754 = 49.876
9.45009 * 5.0 + 2.69002 = 49.9405
9.45605 * 5.0 + 2.69121 = 49.9714
这里需要注意的是,第二个例子的解决方案并不是唯一的。这在神经网络中并不重要,因为所有参数都被调整为减少损失。这里的最终解决方案将取决于
a和b的初始值。如果这些是随机初始化的,而不是值 1,我们会看到每次迭代的模型变量的不同结束值。
工作原理
我们通过 TensorFlow 的隐式反向传播实现了计算门的优化。 TensorFlow 跟踪我们的模型的操作和变量值,并根据我们的优化算法规范和损失函数的输出进行调整。
我们可以继续扩展操作门,同时跟踪哪些输入是变量,哪些输入是数据。这对于跟踪是很重要的,因为 TensorFlow 将更改所有变量以最小化损失而不是数据,这被声明为占位符。
每个训练步骤自动跟踪计算图并自动更新模型变量的隐式能力是 TensorFlow 的强大功能之一,也是它如此强大的原因之一。
使用门和激活函数
现在我们可以将操作门连接在一起,我们希望通过激活函数运行计算图输出。在本节中,我们将介绍常见的激活函数。
准备
在本节中,我们将比较和对比两种不同的激活函数:Sigmoid 和整流线性单元(ReLU)。回想一下,这两个函数由以下公式给出:


在这个例子中,我们将创建两个具有相同结构的单层神经网络,除了一个将通过 sigmoid 激活并且一个将通过 ReLU 激活。损失函数将由距离值 0.75 的 L2 距离控制。我们将从正态分布(Normal(mean=2, sd=0.1))中随机抽取批量数据,然后将输出优化为 0.75。
操作步骤
我们按如下方式处理秘籍:
- 我们将首先加载必要的库并初始化图。这也是我们可以提出如何使用 TensorFlow 设置随机种子的好点。由于我们将使用 NumPy 和 TensorFlow 中的随机数生成器,因此我们需要为两者设置随机种子。使用相同的随机种子集,我们应该能够复制结果。我们通过以下输入执行此操作:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
sess = tf.Session()
tf.set_random_seed(5)
np.random.seed(42)
- 现在我们需要声明我们的批量大小,模型变量,数据和占位符来输入数据。我们的计算图将包括将我们的正态分布数据输入到两个相似的神经网络中,这两个神经网络的区别仅在于激活函数。结束,如下所示:
batch_size = 50
a1 = tf.Variable(tf.random_normal(shape=[1,1]))
b1 = tf.Variable(tf.random_uniform(shape=[1,1]))
a2 = tf.Variable(tf.random_normal(shape=[1,1]))
b2 = tf.Variable(tf.random_uniform(shape=[1,1]))
x = np.random.normal(2, 0.1, 500)
x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32)
- 接下来,我们将声明我们的两个模型,即 sigmoid 激活模型和 ReLU 激活模型,如下所示:
sigmoid_activation = tf.sigmoid(tf.add(tf.matmul(x_data, a1), b1))
relu_activation = tf.nn.relu(tf.add(tf.matmul(x_data, a2), b2))
- 损失函数将是模型输出与值 0.75 之间的平均 L2 范数,如下所示:
loss1 = tf.reduce_mean(tf.square(tf.subtract(sigmoid_activation, 0.75)))
loss2 = tf.reduce_mean(tf.square(tf.subtract(relu_activation, 0.75)))
- 现在我们需要声明我们的优化算法并初始化我们的变量,如下所示:
my_opt = tf.train.GradientDescentOptimizer(0.01)
train_step_sigmoid = my_opt.minimize(loss1)
train_step_relu = my_opt.minimize(loss2)
init = tf.global_variable_initializer()
sess.run(init)
- 现在,我们将针对两个模型循环我们的 750 次迭代训练,如下面的代码块所示。我们还将保存损失输出和激活输出值,以便稍后进行绘图:
loss_vec_sigmoid = []
loss_vec_relu = []
activation_sigmoid = []
activation_relu = []
for i in range(750):
rand_indices = np.random.choice(len(x), size=batch_size)
x_vals = np.transpose([x[rand_indices]])
sess.run(train_step_sigmoid, feed_dict={x_data: x_vals})
sess.run(train_step_relu, feed_dict={x_data: x_vals})
loss_vec_sigmoid.append(sess.run(loss1, feed_dict={x_data: x_vals}))
loss_vec_relu.append(sess.run(loss2, feed_dict={x_data: x_vals}))
activation_sigmoid.append(np.mean(sess.run(sigmoid_activation, feed_dict={x_data: x_vals})))
activation_relu.append(np.mean(sess.run(relu_activation, feed_dict={x_data: x_vals})))
- 要绘制损失和激活输出,我们需要输入以下代码:
plt.plot(activation_sigmoid, 'k-', label='Sigmoid Activation')
plt.plot(activation_relu, 'r--', label='Relu Activation')
plt.ylim([0, 1.0])
plt.title('Activation Outputs')
plt.xlabel('Generation')
plt.ylabel('Outputs')
plt.legend(loc='upper right')
plt.show()
plt.plot(loss_vec_sigmoid, 'k-', label='Sigmoid Loss')
plt.plot(loss_vec_relu, 'r--', label='Relu Loss')
plt.ylim([0, 1.0])
plt.title('Loss per Generation')
plt.xlabel('Generation')
plt.ylabel('Loss')
plt.legend(loc='upper right')
plt.show()
激活输出需要绘制,如下图所示:

图 2:来自具有 Sigmoid 激活的网络和具有 ReLU 激活的网络的计算图输出
两个神经网络使用类似的架构和目标(0.75),但有两个不同的激活函数,sigmoid 和 ReLU。重要的是要注意 ReLU 激活网络收敛到比 sigmoid 激活所需的 0.75 目标更快,如下图所示:

图 3:该图描绘了 Sigmoid 和 ReLU 激活网络的损耗值。注意迭代开始时 ReLU 损失的极端程度
工作原理
由于 ReLU 激活函数的形式,它比 sigmoid 函数更频繁地返回零值。我们认为这种行为是一种稀疏性。这种稀疏性导致收敛速度加快,但失去了受控梯度。另一方面,Sigmoid 函数具有非常良好控制的梯度,并且不会冒 ReLU 激活所带来的极值的风险,如下图所示:
| 激活函数 | 优点 | 缺点 |
|---|---|---|
| Sigmoid | 不太极端的输出 | 收敛速度较慢 |
| RELU | 更快地收敛 | 可能有极端的输出值 |
更多
在本节中,我们比较了神经网络的 ReLU 激活函数和 Sigmoid 激活函数。还有许多其他激活函数通常用于神经网络,但大多数属于两个类别之一;第一类包含形状类似于 sigmoid 函数的函数,如 arctan,hypertangent,heavyiside step 等;第二类包含形状的函数,例如 ReLU 函数,例如 softplus,leaky ReLU 等。我们在本节中讨论的关于比较这两个函数的大多数内容都适用于任何类别的激活。然而,重要的是要注意激活函数的选择对神经网络的收敛和输出有很大影响。
实现单层神经网络
我们拥有实现对真实数据进行操作的神经网络所需的所有工具,因此在本节中我们将创建一个神经网络,其中一个层在Iris数据集上运行。
准备
在本节中,我们将实现一个具有一个隐藏层的神经网络。重要的是要理解完全连接的神经网络主要基于矩阵乘法。因此,重要的是数据和矩阵的大小正确排列。
由于这是一个回归问题,我们将使用均方误差作为损失函数。
操作步骤
我们按如下方式处理秘籍:
- 要创建计算图,我们首先加载以下必要的库:
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from sklearn import datasets
- 现在我们将加载
Iris数据并将长度存储为目标值。然后我们将使用以下代码启动图会话:
iris = datasets.load_iris()
x_vals = np.array([x[0:3] for x in iris.data])
y_vals = np.array([x[3] for x in iris.data])
sess = tf.Session()
- 由于数据集较小,我们需要设置种子以使结果可重现,如下所示:
seed = 2
tf.set_random_seed(seed)
np.random.seed(seed)
- 为了准备数据,我们将创建一个 80-20 训练测试分割,并通过最小 - 最大缩放将 x 特征标准化为 0 到 1 之间,如下所示:
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]
def normalize_cols(m):
col_max = m.max(axis=0)
col_min = m.min(axis=0)
return (m-col_min) / (col_max - col_min)
x_vals_train = np.nan_to_num(normalize_cols(x_vals_train))
x_vals_test = np.nan_to_num(normalize_cols(x_vals_test))
- 现在,我们将使用以下代码声明数据和目标的批量大小和占位符:
batch_size = 50
x_data = tf.placeholder(shape=[None, 3], dtype=tf.float32)
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32)
- 重要的是要用适当的形状声明我们的模型变量。我们可以将隐藏层的大小声明为我们希望的任何大小;在下面的代码块中,我们将其设置为有五个隐藏节点:
hidden_layer_nodes = 5
A1 = tf.Variable(tf.random_normal(shape=[3,hidden_layer_nodes]))
b1 = tf.Variable(tf.random_normal(shape=[hidden_layer_nodes]))
A2 = tf.Variable(tf.random_normal(shape=[hidden_layer_nodes,1]))
b2 = tf.Variable(tf.random_normal(shape=[1]))
- 我们现在分两步宣布我们的模型。第一步是创建隐藏层输出,第二步是创建模型的
final_output,如下所示:
请注意,我们的模型从三个输入特征到五个隐藏节点,最后到一个输出值。
hidden_output = tf.nn.relu(tf.add(tf.matmul(x_data, A1), b1))
final_output = tf.nn.relu(tf.add(tf.matmul(hidden_output, A2), b2))
- 我们作为
loss函数的均方误差如下:
loss = tf.reduce_mean(tf.square(y_target - final_output))
- 现在我们将声明我们的优化算法并使用以下代码初始化我们的变量:
my_opt = tf.train.GradientDescentOptimizer(0.005)
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
- 接下来,我们循环我们的训练迭代。我们还将初始化两个列表,我们可以存储我们的训练和
test_loss函数。在每个循环中,我们还希望从训练数据中随机选择一个批量以适合模型,如下所示:
# First we initialize the loss vectors for storage.
loss_vec = []
test_loss = []
for i in range(500):
# We select a random set of indices for the batch.
rand_index = np.random.choice(len(x_vals_train), size=batch_size)
# We then select the training values
rand_x = x_vals_train[rand_index]
rand_y = np.transpose([y_vals_train[rand_index]])
# Now we run the training step
sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y})
# We save the training loss
temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y})
loss_vec.append(np.sqrt(temp_loss))
# Finally, we run the test-set loss and save it.
test_temp_loss = sess.run(loss, feed_dict={x_data: x_vals_test, y_target: np.transpose([y_vals_test])})
test_loss.append(np.sqrt(test_temp_loss))
if (i+1)%50==0:
print('Generation: ' + str(i+1) + '. Loss = ' + str(temp_loss))
- 我们可以用
matplotlib和以下代码绘制损失:
plt.plot(loss_vec, 'k-', label='Train Loss')
plt.plot(test_loss, 'r--', label='Test Loss')
plt.title('Loss (MSE) per Generation')
plt.xlabel('Generation')
plt.ylabel('Loss')
plt.legend(loc='upper right')
plt.show()
我们通过绘制下图来继续秘籍:

图 4:我们绘制了训练和测试装置的损失(MSE)。请注意,我们在 200 代之后略微过拟合模型,因为测试 MSE 不会进一步下降,但训练 MSE 确实
工作原理
我们的模型现已可视化为神经网络图,如下图所示:

图 5:上图是我们的神经网络的可视化,在隐藏层中有五个节点。我们馈送三个值:萼片长度(S.L),萼片宽度(S.W.)和花瓣长度(P.L.)。目标将是花瓣宽度。总的来说,模型中总共有 26 个变量
更多
请注意,通过查看测试和训练集上的loss函数,我们可以确定模型何时开始过拟合训练数据。我们还可以看到训练损失并不像测试装置那样平稳。这是因为有两个原因:第一个原因是我们使用的批量小于测试集,尽管不是很多;第二个原因是由于我们正在训练训练组,而测试装置不会影响模型的变量。
实现不同的层
了解如何实现不同的层非常重要。在前面的秘籍中,我们实现了完全连接的层。在本文中,我们将进一步扩展我们对各层的了解。
准备
我们已经探索了如何连接数据输入和完全连接的隐藏层,但是 TensorFlow 中有更多类型的层是内置函数。最常用的层是卷积层和最大池化层。我们将向您展示如何使用输入数据和完全连接的数据创建和使用此类层。首先,我们将研究如何在一维数据上使用这些层,然后在二维数据上使用这些层。
虽然神经网络可以以任何方式分层,但最常见的用途之一是使用卷积层和完全连接的层来首先创建特征。如果我们有太多的特征,通常会有一个最大池化层。在这些层之后,通常引入非线性层作为激活函数。我们将在第 8 章卷积神经网络中考虑的卷积神经网络(CNN)通常具有卷积,最大池化,激活,卷积,最大池化和激活形式。
操作步骤
我们将首先看一维数据。我们需要使用以下步骤为此任务生成随机数据数组:
- 我们首先加载我们需要的库并启动图会话,如下所示:
import tensorflow as tf
import numpy as np
sess = tf.Session()
- 现在我们可以初始化我们的数据(长度为
25的 NumPy 数组)并创建占位符,我们将通过以下代码提供它:
data_size = 25
data_1d = np.random.normal(size=data_size)
x_input_1d = tf.placeholder(dtype=tf.float32, shape=[data_size])
- 接下来,我们将定义一个将构成卷积层的函数。然后我们将声明一个随机过滤器并创建卷积层,如下所示:
请注意,许多 TensorFlow 的层函数都是为处理 4D 数据而设计的(
4D = [batch size, width, height, and channels])。我们需要修改输入数据和输出数据,以扩展或折叠所需的额外维度。对于我们的示例数据,我们的批量大小为 1,宽度为 1,高度为 25,通道大小为 1。要扩展大小,我们使用expand_dims()函数,并且为了折叠大小,我们使用squeeze()函数。另请注意,我们可以使用output_size=(W-F+2P)/S+1公式计算卷积层的输出大小,其中W是输入大小,F是滤镜大小,P是填充大小,S是步幅大小。
def conv_layer_1d(input_1d, my_filter):
# Make 1d input into 4d
input_2d = tf.expand_dims(input_1d, 0)
input_3d = tf.expand_dims(input_2d, 0)
input_4d = tf.expand_dims(input_3d, 3)
# Perform convolution
convolution_output = tf.nn.conv2d(input_4d, filter=my_filter, strides=[1,1,1,1], padding="VALID")
# Now drop extra dimensions
conv_output_1d = tf.squeeze(convolution_output)
return(conv_output_1d)
my_filter = tf.Variable(tf.random_normal(shape=[1,5,1,1]))
my_convolution_output = conv_layer_1d(x_input_1d, my_filter)
- 默认情况下,TensorFlow 的激活函数将按元素方式执行。这意味着我们只需要在感兴趣的层上调用激活函数。我们通过创建激活函数然后在图上初始化它来完成此操作,如下所示:
def activation(input_1d):
return tf.nn.relu(input_1d)
my_activation_output = activation(my_convolution_output)
- 现在我们将声明一个最大池化层函数。此函数将在我们的一维向量上的移动窗口上创建一个最大池化。对于此示例,我们将其初始化为宽度为 5,如下所示:
TensorFlow 的最大池化参数与卷积层的参数非常相似。虽然最大池化参数没有过滤器,但它确实有
size,stride和padding选项。由于我们有一个带有有效填充的 5 的窗口(没有零填充),因此我们的输出数组将减少 4 个条目。
def max_pool(input_1d, width):
# First we make the 1d input into 4d.
input_2d = tf.expand_dims(input_1d, 0)
input_3d = tf.expand_dims(input_2d, 0)
input_4d = tf.expand_dims(input_3d, 3)
# Perform the max pool operation
pool_output = tf.nn.max_pool(input_4d, ksize=[1, 1, width, 1], strides=[1, 1, 1, 1], padding='VALID')
pool_output_1d = tf.squeeze(pool_output)
return pool_output_1d
my_maxpool_output = max_pool(my_activation_output, width=5)
- 我们将要连接的最后一层是完全连接的层。在这里,我们想要创建一个多特征函数,输入一维数组并输出指示的数值。还要记住,要使用 1D 数组进行矩阵乘法,我们必须将维度扩展为 2D,如下面的代码块所示:
def fully_connected(input_layer, num_outputs):
# Create weights
weight_shape = tf.squeeze(tf.stack([tf.shape(input_layer), [num_outputs]]))
weight = tf.random_normal(weight_shape, stddev=0.1)
bias = tf.random_normal(shape=[num_outputs])
# Make input into 2d
input_layer_2d = tf.expand_dims(input_layer, 0)
# Perform fully connected operations
full_output = tf.add(tf.matmul(input_layer_2d, weight), bias)
# Drop extra dimensions
full_output_1d = tf.squeeze(full_output)
return full_output_1d
my_full_output = fully_connected(my_maxpool_output, 5)
- 现在我们将初始化所有变量,运行图并打印每个层的输出,如下所示:
init = tf.global_variable_initializer()
sess.run(init)
feed_dict = {x_input_1d: data_1d}
# Convolution Output
print('Input = array of length 25')
print('Convolution w/filter, length = 5, stride size = 1, results in an array of length 21:')
print(sess.run(my_convolution_output, feed_dict=feed_dict))
# Activation Output
print('Input = the above array of length 21')
print('ReLU element wise returns the array of length 21:')
print(sess.run(my_activation_output, feed_dict=feed_dict))
# Maxpool Output
print('Input = the above array of length 21')
print('MaxPool, window length = 5, stride size = 1, results in the array of length 17:')
print(sess.run(my_maxpool_output, feed_dict=feed_dict))
# Fully Connected Output
print('Input = the above array of length 17')
print('Fully connected layer on all four rows with five outputs:')
print(sess.run(my_full_output, feed_dict=feed_dict))
- 上一步应该产生以下输出:
Input = array of length 25
Convolution w/filter, length = 5, stride size = 1, results in an array of length 21:
[-0.91608119 1.53731811 -0.7954089 0.5041104 1.88933098
-1.81099761 0.56695032 1.17945457 -0.66252393 -1.90287709
0.87184119 0.84611893 -5.25024986 -0.05473572 2.19293165
-4.47577858 -1.71364677 3.96857905 -2.0452652 -1.86647367
-0.12697852]
Input = the above array of length 21
ReLU element wise returns the array of length 21:
[ 0\. 1.53731811 0\. 0.5041104 1.88933098
0\. 0\. 1.17945457 0\. 0\.
0.87184119 0.84611893 0\. 0\. 2.19293165
0\. 0\. 3.96857905 0\. 0\.
0\. ]
Input = the above array of length 21
MaxPool, window length = 5, stride size = 1, results in the array of length 17:
[ 1.88933098 1.88933098 1.88933098 1.88933098 1.88933098
1.17945457 1.17945457 1.17945457 0.87184119 0.87184119
2.19293165 2.19293165 2.19293165 3.96857905 3.96857905
3.96857905 3.96857905]
Input = the above array of length 17
Fully connected layer on all four rows with five outputs:
[ 1.23588216 -0.42116445 1.44521213 1.40348077 -0.79607368]
对于神经网络,一维数据非常重要。时间序列,信号处理和一些文本嵌入被认为是一维的并且经常在神经网络中使用。
我们现在将以相同的顺序考虑相同类型的层,但是对于二维数据:
- 我们将从清除和重置计算图开始,如下所示:
ops.reset_default_graph()
sess = tf.Session()
- 然后我们将初始化我们的输入数组,使其为
10x10矩阵,然后我们将为具有相同形状的图初始化占位符,如下所示:
data_size = [10,10]
data_2d = np.random.normal(size=data_size)
x_input_2d = tf.placeholder(dtype=tf.float32, shape=data_size)
- 就像在一维示例中一样,我们现在需要声明卷积层函数。由于我们的数据已经具有高度和宽度,我们只需要将其扩展为二维(批量大小为 1,通道大小为 1),以便我们可以使用
conv2d()函数对其进行操作。对于滤波器,我们将使用随机2x2滤波器,两个方向的步幅为 2,以及有效填充(换句话说,没有零填充)。因为我们的输入矩阵是10x10,我们的卷积输出将是5x5,如下所示:
def conv_layer_2d(input_2d, my_filter):
# First, change 2d input to 4d
input_3d = tf.expand_dims(input_2d, 0)
input_4d = tf.expand_dims(input_3d, 3)
# Perform convolution
convolution_output = tf.nn.conv2d(input_4d, filter=my_filter, strides=[1,2,2,1], padding="VALID")
# Drop extra dimensions
conv_output_2d = tf.squeeze(convolution_output)
return(conv_output_2d)
my_filter = tf.Variable(tf.random_normal(shape=[2,2,1,1]))
my_convolution_output = conv_layer_2d(x_input_2d, my_filter)
- 激活函数在逐个元素的基础上工作,因此我们现在可以创建激活操作并使用以下代码在图上初始化它:
def activation(input_2d):
return tf.nn.relu(input_2d)
my_activation_output = activation(my_convolution_output)
- 我们的最大池化层与一维情况非常相似,只是我们必须声明最大池化窗口的宽度和高度。就像我们的卷积 2D 层一样,我们只需要扩展到两个维度,如下所示:
def max_pool(input_2d, width, height):
# Make 2d input into 4d
input_3d = tf.expand_dims(input_2d, 0)
input_4d = tf.expand_dims(input_3d, 3)
# Perform max pool
pool_output = tf.nn.max_pool(input_4d, ksize=[1, height, width, 1], strides=[1, 1, 1, 1], padding='VALID')
# Drop extra dimensions
pool_output_2d = tf.squeeze(pool_output)
return pool_output_2d
my_maxpool_output = max_pool(my_activation_output, width=2, height=2)
- 我们的全连接层与一维输出非常相似。我们还应该注意到,此层的 2D 输入被视为一个对象,因此我们希望每个条目都连接到每个输出。为了实现这一点,我们需要完全展平二维矩阵,然后将其展开以进行矩阵乘法,如下所示:
def fully_connected(input_layer, num_outputs):
# Flatten into 1d
flat_input = tf.reshape(input_layer, [-1])
# Create weights
weight_shape = tf.squeeze(tf.stack([tf.shape(flat_input), [num_outputs]]))
weight = tf.random_normal(weight_shape, stddev=0.1)
bias = tf.random_normal(shape=[num_outputs])
# Change into 2d
input_2d = tf.expand_dims(flat_input, 0)
# Perform fully connected operations
full_output = tf.add(tf.matmul(input_2d, weight), bias)
# Drop extra dimensions
full_output_2d = tf.squeeze(full_output)
return full_output_2d
my_full_output = fully_connected(my_maxpool_output, 5)
- 现在我们需要初始化变量并使用以下代码为我们的操作创建一个馈送字典:
init = tf.global_variables_initializer()
sess.run(init)
feed_dict = {x_input_2d: data_2d}
- 每个层的输出应如下所示:
# Convolution Output
print('Input = [10 X 10] array')
print('2x2 Convolution, stride size = [2x2], results in the [5x5] array:')
print(sess.run(my_convolution_output, feed_dict=feed_dict))
# Activation Output
print('Input = the above [5x5] array')
print('ReLU element wise returns the [5x5] array:')
print(sess.run(my_activation_output, feed_dict=feed_dict))
# Max Pool Output
print('Input = the above [5x5] array')
print('MaxPool, stride size = [1x1], results in the [4x4] array:')
print(sess.run(my_maxpool_output, feed_dict=feed_dict))
# Fully Connected Output
print('Input = the above [4x4] array')
print('Fully connected layer on all four rows with five outputs:')
print(sess.run(my_full_output, feed_dict=feed_dict))
- 上一步应该产生以下输出:
Input = [10 X 10] array
2x2 Convolution, stride size = [2x2], results in the [5x5] array:
[[ 0.37630892 -1.41018617 -2.58821273 -0.32302785 1.18970704]
[-4.33685207 1.97415686 1.0844903 -1.18965471 0.84643292]
[ 5.23706436 2.46556497 -0.95119286 1.17715418 4.1117816 ]
[ 5.86972761 1.2213701 1.59536231 2.66231227 2.28650784]
[-0.88964868 -2.75502229 4.3449688 2.67776585 -2.23714781]]
Input = the above [5x5] array
ReLU element wise returns the [5x5] array:
[[ 0.37630892 0\. 0\. 0\. 1.18970704]
[ 0\. 1.97415686 1.0844903 0\. 0.84643292]
[ 5.23706436 2.46556497 0\. 1.17715418 4.1117816 ]
[ 5.86972761 1.2213701 1.59536231 2.66231227 2.28650784]
[ 0\. 0\. 4.3449688 2.67776585 0\. ]]
Input = the above [5x5] array
MaxPool, stride size = [1x1], results in the [4x4] array:
[[ 1.97415686 1.97415686 1.0844903 1.18970704]
[ 5.23706436 2.46556497 1.17715418 4.1117816 ]
[ 5.86972761 2.46556497 2.66231227 4.1117816 ]
[ 5.86972761 4.3449688 4.3449688 2.67776585]]
Input = the above [4x4] array
Fully connected layer on all four rows with five outputs:
[-0.6154139 -1.96987963 -1.88811922 0.20010889 0.32519674]
工作原理
我们现在应该知道如何在 TensorFlow 中使用一维和二维数据中的卷积和最大池化层。无论输入的形状如何,我们最终都得到相同的大小输出。这对于说明神经网络层的灵活性很重要。本节还应该再次向我们强调形状和大小在神经网络操作中的重要性。
使用多层神经网络
我们现在将通过在低出生体重数据集上使用多层神经网络将我们对不同层的知识应用于实际数据。
准备
现在我们知道如何创建神经网络并使用层,我们将应用此方法,以预测低出生体重数据集中的出生体重。我们将创建一个具有三个隐藏层的神经网络。低出生体重数据集包括实际出生体重和出生体重是否高于或低于 2,500 克的指标变量。在这个例子中,我们将目标设为实际出生体重(回归),然后在最后查看分类的准确率。最后,我们的模型应该能够确定出生体重是否小于 2,500 克。
操作步骤
我们按如下方式处理秘籍:
- 我们将首先加载库并初始化我们的计算图,如下所示:
import tensorflow as tf
import matplotlib.pyplot as plt
import os
import csv
import requests
import numpy as np
sess = tf.Session()
- 我们现在将使用
requests模块从网站加载数据。在此之后,我们将数据拆分为感兴趣的特征和目标值,如下所示:
# Name of data file
birth_weight_file = 'birth_weight.csv'
birthdata_url = 'https://github.com/nfmcclure/tensorflow_cookbook/raw/master' \
'/01_Introduction/07_Working_with_Data_Sources/birthweight_data/birthweight.dat'
# Download data and create data file if file does not exist in current directory
if not os.path.exists(birth_weight_file):
birth_file = requests.get(birthdata_url)
birth_data = birth_file.text.split('\r\n')
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") as f:
writer = csv.writer(f)
writer.writerows([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])
- 为了帮助实现可重复性,我们现在需要为 NumPy 和 TensorFlow 设置随机种子。然后我们声明我们的批量大小如下:
seed = 4
tf.set_random_seed(seed)
np.random.seed(seed)
batch_size = 100
- 接下来,我们将数据分成 80-20 训练测试分组。在此之后,我们需要正则化我们的输入特征,使它们在 0 到 1 之间,具有最小 - 最大缩放比例,如下所示:
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]
# Normalize by column (min-max norm)
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)
归一化输入特征是一种常见的特征转换,尤其适用于神经网络。如果我们的数据以 0 到 1 的中心为激活函数,它将有助于收敛。
- 由于我们有多个层具有相似的初始化变量,我们现在需要创建一个函数来初始化权重和偏差。我们使用以下代码执行此操作:
def init_weight(shape, st_dev):
weight = tf.Variable(tf.random_normal(shape, stddev=st_dev))
return weight
def init_bias(shape, st_dev):
bias = tf.Variable(tf.random_normal(shape, stddev=st_dev))
return bias
- 我们现在需要初始化占位符。将有八个输入特征和一个输出,出生重量以克为单位,如下所示:
x_data = tf.placeholder(shape=[None, 8], dtype=tf.float32)
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32)
- 对于所有三个隐藏层,完全连接的层将使用三次。为了防止重复代码,我们将在初始化模型时创建一个层函数,如下所示:
def fully_connected(input_layer, weights, biases):
layer = tf.add(tf.matmul(input_layer, weights), biases)
return tf.nn.relu(layer)
- 现在是时候创建我们的模型了。对于每个层(和输出层),我们将初始化权重矩阵,偏置矩阵和完全连接的层。对于此示例,我们将使用大小为 25,10 和 3 的隐藏层:
我们使用的模型将有 522 个变量适合。为了得到这个数字,我们可以看到数据和第一个隐藏层之间有
8*25 +25=225变量。如果我们以这种方式继续添加它们,我们将有225+260+33+4=522变量。这远远大于我们在逻辑回归模型中使用的九个变量。
# Create second layer (25 hidden nodes)
weight_1 = init_weight(shape=[8, 25], st_dev=10.0)
bias_1 = init_bias(shape=[25], st_dev=10.0)
layer_1 = fully_connected(x_data, weight_1, bias_1)
# Create second layer (10 hidden nodes)
weight_2 = init_weight(shape=[25, 10], st_dev=10.0)
bias_2 = init_bias(shape=[10], st_dev=10.0)
layer_2 = fully_connected(layer_1, weight_2, bias_2)
# Create third layer (3 hidden nodes)
weight_3 = init_weight(shape=[10, 3], st_dev=10.0)
bias_3 = init_bias(shape=[3], st_dev=10.0)
layer_3 = fully_connected(layer_2, weight_3, bias_3)
# Create output layer (1 output value)
weight_4 = init_weight(shape=[3, 1], st_dev=10.0)
bias_4 = init_bias(shape=[1], st_dev=10.0)
final_output = fully_connected(layer_3, weight_4, bias_4)
- 我们现在将使用 L1 损失函数(绝对值),声明我们的优化器(使用 Adam 优化),并按如下方式初始化变量:
loss = tf.reduce_mean(tf.abs(y_target - final_output))
my_opt = tf.train.AdamOptimizer(0.05)
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
虽然我们在前一步骤中用于 Adam 优化函数的学习率是 0.05,但有研究表明较低的学习率始终产生更好的结果。对于这个秘籍,由于数据的一致性和快速收敛的需要,我们使用了更大的学习率。
- 接下来,我们需要训练我们的模型进行 200 次迭代。我们还将包含存储
train和test损失的代码,选择随机批量大小,并每 25 代打印一次状态,如下所示:
# Initialize the loss vectors
loss_vec = []
test_loss = []
for i in range(200):
# Choose random indices for batch selection
rand_index = np.random.choice(len(x_vals_train), size=batch_size)
# Get random batch
rand_x = x_vals_train[rand_index]
rand_y = np.transpose([y_vals_train[rand_index]])
# Run the training step
sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y})
# Get and store the train loss
temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y})
loss_vec.append(temp_loss)
# Get and store the test loss
test_temp_loss = sess.run(loss, feed_dict={x_data: x_vals_test, y_target: np.transpose([y_vals_test])})
test_loss.append(test_temp_loss)
if (i+1)%25==0:
print('Generation: ' + str(i+1) + '. Loss = ' + str(temp_loss))
- 上一步应该产生以下输出:
Generation: 25\. Loss = 5922.52
Generation: 50\. Loss = 2861.66
Generation: 75\. Loss = 2342.01
Generation: 100\. Loss = 1880.59
Generation: 125\. Loss = 1394.39
Generation: 150\. Loss = 1062.43
Generation: 175\. Loss = 834.641
Generation: 200\. Loss = 848.54
- 以下是使用
matplotlib绘制训练和测试损失的代码片段:
plt.plot(loss_vec, 'k-', label='Train Loss')
plt.plot(test_loss, 'r--', label='Test Loss')
plt.title('Loss per Generation')
plt.xlabel('Generation')
plt.ylabel('Loss')
plt.legend(loc='upper right')
plt.show()
我们通过绘制下图来继续秘籍:

图 6:在上图中,我们绘制了我们训练的神经网络的训练和测试损失,以克数表示出生体重。请注意,大约 30 代后我们已经达到了良好的模型
- 我们现在想将我们的出生体重结果与我们之前的后勤结果进行比较。使用逻辑线性回归(如第 3 章中的实现逻辑回归秘籍,线性回归),我们在数千次迭代后获得了大约 60% 的准确率结果。为了将其与我们在上一节中所做的进行比较,我们需要输出训练并测试回归结果,并通过创建指标(如果它们高于或低于 2,500 克)将其转换为分类结果。要找出模型的准确率,我们需要使用以下代码:
actuals = np.array([x[1] for x in birth_data])
test_actuals = actuals[test_indices]
train_actuals = actuals[train_indices]
test_preds = [x[0] for x in sess.run(final_output, feed_dict={x_data: x_vals_test})]
train_preds = [x[0] for x in sess.run(final_output, feed_dict={x_data: x_vals_train})]
test_preds = np.array([1.0 if x<2500.0 else 0.0 for x in test_preds])
train_preds = np.array([1.0 if x<2500.0 else 0.0 for x in train_preds])
# Print out accuracies
test_acc = np.mean([x==y for x,y in zip(test_preds, test_actuals)])
train_acc = np.mean([x==y for x,y in zip(train_preds, train_actuals)])
print('On predicting the category of low birthweight from regression output (<2500g):')
print('Test Accuracy: {}'.format(test_acc))
print('Train Accuracy: {}'.format(train_acc))
- 上一步应该产生以下输出:
Test Accuracy: 0.631578947368421
Train Accuracy: 0.7019867549668874
工作原理
在这个秘籍中,我们创建了一个回归神经网络,其中包含三个完全连接的隐藏层,以预测低出生体重数据集的出生体重。当将其与物流输出进行比较以预测高于或低于 2,500 克时,我们获得了类似的结果并且在更少的几代中实现了它们。在下一个方案中,我们将尝试通过使其成为多层逻辑类神经网络来改进逻辑回归。
改进线性模型的预测
在前面的秘籍中,我们注意到我们拟合的参数数量远远超过等效的线性模型。在这个秘籍中,我们将尝试通过使用神经网络来改进我们的低出生体重的逻辑模型。
准备
对于这个秘籍,我们将加载低出生体重数据,并使用神经网络与两个隐藏的完全连接的层与 sigmoid 激活,以适应低出生体重的概率。
操作步骤
我们按如下方式处理秘籍:
- 我们首先加载库并初始化我们的计算图,如下所示:
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import requests
sess = tf.Session()
- 接下来,我们按照前面的秘籍加载,提取和标准化我们的数据,除了在这里我们将使用低出生体重指示变量作为我们的目标而不是实际出生体重,如下所示:
# Name of data file
birth_weight_file = 'birth_weight.csv'
birthdata_url = 'https://github.com/nfmcclure/tensorflow_cookbook/raw/master' \
'/01_Introduction/07_Working_with_Data_Sources/birthweight_data/birthweight.dat'
# Download data and create data file if file does not exist in current directory
if not os.path.exists(birth_weight_file):
birth_file = requests.get(birthdata_url)
birth_data = birth_file.text.split('\r\n')
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") as f:
writer = csv.writer(f)
writer.writerows([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])
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]
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))
- 接下来,我们需要声明我们的批量大小和数据的占位符,如下所示:
batch_size = 90
x_data = tf.placeholder(shape=[None, 7], dtype=tf.float32)
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32)
- 如前所述,我们现在需要声明在模型中初始化变量和层的函数。为了创建更好的逻辑函数,我们需要创建一个在输入层上返回逻辑层的函数。换句话说,我们将使用完全连接的层并为每个层返回一个 sigmoid 元素。重要的是要记住我们的损失函数将包含最终的 sigmoid,因此我们要在最后一层指定我们不会返回输出的 sigmoid,如下所示:
def init_variable(shape):
return tf.Variable(tf.random_normal(shape=shape))
# Create a logistic layer definition
def logistic(input_layer, multiplication_weight, bias_weight, activation = True):
linear_layer = tf.add(tf.matmul(input_layer, multiplication_weight), bias_weight)
if activation:
return tf.nn.sigmoid(linear_layer)
else:
return linear_layer
- 现在我们将声明三个层(两个隐藏层和一个输出层)。我们将首先为每个层初始化权重和偏差矩阵,并按如下方式定义层操作:
# First logistic layer (7 inputs to 14 hidden nodes)
A1 = init_variable(shape=[7,14])
b1 = init_variable(shape=[14])
logistic_layer1 = logistic(x_data, A1, b1)
# Second logistic layer (14 hidden inputs to 5 hidden nodes)
A2 = init_variable(shape=[14,5])
b2 = init_variable(shape=[5])
logistic_layer2 = logistic(logistic_layer1, A2, b2)
# Final output layer (5 hidden nodes to 1 output)
A3 = init_variable(shape=[5,1])
b3 = init_variable(shape=[1])
final_output = logistic(logistic_layer2, A3, b3, activation=False)
- 接下来,我们声明我们的损失(交叉熵)和优化算法,并初始化以下变量:
# Create loss function
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=final_output, labels=y_target))
# Declare optimizer
my_opt = tf.train.AdamOptimizer(learning_rate = 0.002)
train_step = my_opt.minimize(loss)
# Initialize variables
init = tf.global_variables_initializer()
sess.run(init)
交叉熵是一种测量概率之间距离的方法。在这里,我们想要测量确定性(0 或 1)与模型概率(
0 < x < 1)之间的差异。 TensorFlow 使用内置的 sigmoid 函数实现交叉熵。这也是超参数调整的一部分,因为我们更有可能找到最佳的损失函数,学习率和针对当前问题的优化算法。为简洁起见,我们不包括超参数调整。
- 为了评估和比较我们的模型与以前的模型,我们需要在图上创建预测和精度操作。这将允许我们提供整个测试集并确定准确率,如下所示:
prediction = tf.round(tf.nn.sigmoid(final_output))
predictions_correct = tf.cast(tf.equal(prediction, y_target), tf.float32)
accuracy = tf.reduce_mean(predictions_correct)
- 我们现在准备开始我们的训练循环。我们将训练 1500 代并保存模型损失并训练和测试精度以便以后进行绘图。我们的训练循环使用以下代码启动:
# Initialize loss and accuracy vectors loss_vec = [] train_acc = [] test_acc = []
for i in range(1500):
# Select random indicies for batch selection
rand_index = np.random.choice(len(x_vals_train), size=batch_size)
# Select batch
rand_x = x_vals_train[rand_index]
rand_y = np.transpose([y_vals_train[rand_index]])
# Run training step
sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y})
# Get training loss
temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y})
loss_vec.append(temp_loss)
# Get training accuracy
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)
# Get test accuracy
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)
if (i+1)%150==0:
print('Loss = '' + str(temp_loss))
- 上一步应该产生以下输出:
Loss = 0.696393
Loss = 0.591708
Loss = 0.59214
Loss = 0.505553
Loss = 0.541974
Loss = 0.512707
Loss = 0.590149
Loss = 0.502641
Loss = 0.518047
Loss = 0.502616
- 以下代码块说明了如何使用
matplotlib绘制交叉熵损失以及训练和测试集精度:
# Plot loss over time
plt.plot(loss_vec, 'k-')
plt.title('Cross Entropy Loss per Generation')
plt.xlabel('Generation')
plt.ylabel('Cross Entropy Loss')
plt.show()
# Plot train and test accuracy
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()
我们得到每代交叉熵损失的图如下:

图 7:超过 1500 次迭代的训练损失
在大约 50 代之内,我们已经达到了良好的模式。在我们继续训练时,我们可以看到在剩余的迭代中获得的很少,如下图所示:

图 8:训练组和测试装置的准确率
正如您在上图中所看到的,我们很快就找到了一个好模型。
工作原理
在考虑使用神经网络建模数据时,您必须考虑优缺点。虽然我们的模型比以前的模型融合得更快,并且可能具有更高的准确率,但这需要付出代价;我们正在训练更多的模型变量,并且更有可能过拟合。为了检查是否发生过拟合,我们会查看测试和训练集的准确率。如果训练集的准确率继续增加而测试集的精度保持不变或甚至略微下降,我们可以假设过拟合正在发生。
为了对抗欠拟合,我们可以增加模型深度或训练模型以进行更多迭代。为了解决过拟合问题,我们可以为模型添加更多数据或添加正则化技术。
同样重要的是要注意我们的模型变量不像线性模型那样可解释。神经网络模型具有比线性模型更难解释的系数,因为它们解释了模型中特征的重要性。
学习玩井字棋
为了展示适应性神经网络的可用性,我们现在将尝试使用神经网络来学习井字棋的最佳动作。我们将知道井字棋是一种确定性游戏,并且最佳动作已经知道。
准备
为了训练我们的模型,我们将使用一系列的棋盘位置,然后对许多不同的棋盘进行最佳的最佳响应。我们可以通过仅考虑在对称性方面不同的棋盘位置来减少要训练的棋盘数量。井字棋棋盘的非同一性变换是 90 度,180 度和 270 度的旋转(在任一方向上),水平反射和垂直反射。鉴于这个想法,我们将使用最佳移动的候选棋盘名单,应用两个随机变换,然后将其输入神经网络进行学习。
由于井字棋是一个确定性的游戏,值得注意的是,无论谁先走,都应该赢或抽。我们希望能够以最佳方式响应我们的动作并最终获得平局的模型。
如果我们将X标注为 1,将O标注为 -1,将空格标注为 0,则下图说明了我们如何将棋盘位置和最佳移动视为一行数据:
Figure 9: Here, we illustrate how to consider a board and an optimal move as a row of data. Note that X = 1, O = -1, and empty spaces are 0, and we start indexing at 0
除了模型损失,要检查我们的模型如何执行,我们将做两件事。我们将执行的第一项检查是从训练集中删除位置和最佳移动行。这将使我们能够看到神经网络模型是否可以推广它以前从未见过的移动。我们将评估模型的第二种方法是在最后实际对抗它。
可以在此秘籍的 GitHub 目录 和 Packt 仓库中找到可能的棋盘列表和最佳移动。
操作步骤
我们按如下方式处理秘籍:
- 我们需要从为此脚本加载必要的库开始,如下所示:
import tensorflow as tf
import matplotlib.pyplot as plt
import csv
import random
import numpy as np
import random
- 接下来,我们声明以下批量大小来训练我们的模型:
batch_size = 50
- 为了使棋盘更容易可视化,我们将创建一个输出带
X和O的井字棋棋盘的函数。这是通过以下代码完成的:
def print_board(board):
symbols = ['O', ' ', 'X']
board_plus1 = [int(x) + 1 for x in board]
board_line1 = ' {} | {} | {}'.format(symbols[board_plus1[0]],
symbols[board_plus1[1]],
symbols[board_plus1[2]])
board_line2 = ' {} | {} | {}'.format(symbols[board_plus1[3]],
symbols[board_plus1[4]],
symbols[board_plus1[5]])
board_line3 = ' {} | {} | {}'.format(symbols[board_plus1[6]],
symbols[board_plus1[7]],
symbols[board_plus1[8]])
print(board_line1)
print('___________')
print(board_line2)
print('___________')
print(board_line3)
- 现在我们必须创建一个函数,它将返回一个新的棋盘和一个转换下的最佳响应位置。这是通过以下代码完成的:
def get_symmetry(board, response, transformation):
'''
:param board: list of integers 9 long:
opposing mark = -1
friendly mark = 1
empty space = 0
:param transformation: one of five transformations on a board:
rotate180, rotate90, rotate270, flip_v, flip_h
:return: tuple: (new_board, new_response)
'''
if transformation == 'rotate180':
new_response = 8 - response
return board[::-1], new_response
elif transformation == 'rotate90':
new_response = [6, 3, 0, 7, 4, 1, 8, 5, 2].index(response)
tuple_board = list(zip(*[board[6:9], board[3:6], board[0:3]]))
return [value for item in tuple_board for value in item], new_response
elif transformation == 'rotate270':
new_response = [2, 5, 8, 1, 4, 7, 0, 3, 6].index(response)
tuple_board = list(zip(*[board[0:3], board[3:6], board[6:9]]))[::-1]
return [value for item in tuple_board for value in item], new_response
elif transformation == 'flip_v':
new_response = [6, 7, 8, 3, 4, 5, 0, 1, 2].index(response)
return board[6:9] + board[3:6] + board[0:3], new_response
elif transformation == 'flip_h':
# flip_h = rotate180, then flip_v
new_response = [2, 1, 0, 5, 4, 3, 8, 7, 6].index(response)
new_board = board[::-1]
return new_board[6:9] + new_board[3:6] + new_board[0:3], new_response
else:
raise ValueError('Method not implmented.')
def get_moves_from_csv(csv_file):
'''
:param csv_file: csv file location containing the boards w/ responses
:return: moves: list of moves with index of best response
'''
moves = []
with open(csv_file, 'rt') as csvfile:
reader = csv.reader(csvfile, delimiter=',')
for row in reader:
moves.append(([int(x) for x in row[0:9]],int(row[9])))
return moves
- 现在我们需要将所有内容组合在一起以创建一个函数,该函数将返回随机转换的棋盘和响应。这是通过以下代码完成的:
def get_rand_move(moves, rand_transforms=2):
# This function performs random transformations on a board.
(board, response) = random.choice(moves)
possible_transforms = ['rotate90', 'rotate180', 'rotate270', 'flip_v', 'flip_h']
for i in range(rand_transforms):
random_transform = random.choice(possible_transforms)
(board, response) = get_symmetry(board, response, random_transform)
return board, response
- 接下来,我们需要初始化图会话,加载数据,并创建一个训练集,如下所示:
sess = tf.Session()
moves = get_moves_from_csv('base_tic_tac_toe_moves.csv')
# Create a train set:
train_length = 500
train_set = []
for t in range(train_length):
train_set.append(get_rand_move(moves))
- 请记住,我们希望从我们的训练集中删除一个棋盘和一个最佳响应,以查看该模型是否可以推广以实现最佳移动。以下棋盘的最佳举措将是在第 6 号指数进行:
test_board = [-1, 0, 0, 1, -1, -1, 0, 0, 1]
train_set = [x for x in train_set if x[0] != test_board]
- 我们现在可以创建函数来创建模型变量和模型操作。请注意,我们在以下模型中不包含
softmax()激活函数,因为它包含在损失函数中:
def init_weights(shape):
return tf.Variable(tf.random_normal(shape))
def model(X, A1, A2, bias1, bias2):
layer1 = tf.nn.sigmoid(tf.add(tf.matmul(X, A1), bias1))
layer2 = tf.add(tf.matmul(layer1, A2), bias2)
return layer2
- 现在我们需要声明我们的占位符,变量和模型,如下所示:
X = tf.placeholder(dtype=tf.float32, shape=[None, 9])
Y = tf.placeholder(dtype=tf.int32, shape=[None])
A1 = init_weights([9, 81])
bias1 = init_weights([81])
A2 = init_weights([81, 9])
bias2 = init_weights([9])
model_output = model(X, A1, A2, bias1, bias2)
- 接下来,我们需要声明我们的
loss函数,它将是最终输出对率的平均 softmax(非标准化输出)。然后我们将声明我们的训练步骤和优化器。如果我们希望将来能够对抗我们的模型,我们还需要创建一个预测操作,如下所示:
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=model_output, labels=Y))
train_step = tf.train.GradientDescentOptimizer(0.025).minimize(loss)
prediction = tf.argmax(model_output, 1)
- 我们现在可以使用以下代码初始化变量并循环遍历神经网络的训练:
# Initialize variables
init = tf.global_variables_initializer()
sess.run(init)
loss_vec = []
for i in range(10000):
# Select random indices for batch
rand_indices = np.random.choice(range(len(train_set)), batch_size, replace=False)
# Get batch
batch_data = [train_set[i] for i in rand_indices]
x_input = [x[0] for x in batch_data]
y_target = np.array([y[1] for y in batch_data])
# Run training step
sess.run(train_step, feed_dict={X: x_input, Y: y_target})
# Get training loss
temp_loss = sess.run(loss, feed_dict={X: x_input, Y: y_target})
loss_vec.append(temp_loss)
if i%500==0:
print('iteration ' + str(i) + ' Loss: ' + str(temp_loss))
- 以下是绘制模型训练损失所需的代码:
plt.plot(loss_vec, 'k-', label='Loss')
plt.title('Loss (MSE) per Generation')
plt.xlabel('Generation')
plt.ylabel('Loss')
plt.show()
我们应该得到以下每代损失的绘图:

图 10:超过 10,000 次迭代的井字棋训练组损失
在上图中,我们绘制了训练步骤的损失。
- 为了测试模型,我们需要看看它是如何在我们从训练集中删除的测试棋盘上执行的。我们希望模型可以推广和预测移动的最佳索引,这将是索引号 6。大多数时候模型将成功,如下所示:
test_boards = [test_board]
feed_dict = {X: test_boards}
logits = sess.run(model_output, feed_dict=feed_dict)
predictions = sess.run(prediction, feed_dict=feed_dict)
print(predictions)
- 上一步应该产生以下输出:
[6]
- 为了评估我们的模型,我们需要与我们训练的模型进行对比。要做到这一点,我们必须创建一个能够检查胜利的函数。这样,我们的程序将知道何时停止要求更多动作。这是通过以下代码完成的:
def check(board):
wins = [[0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6]]
for i in range(len(wins)):
if board[wins[i][0]]==board[wins[i][1]]==board[wins[i][2]]==1.:
return 1
elif board[wins[i][0]]==board[wins[i][1]]==board[wins[i][2]]==-1.:
return 1
return 0
- 现在我们可以使用我们的模型循环播放游戏。我们从一个空白棋盘(全零)开始,我们要求用户输入一个索引(0-8),然后我们将其输入到模型中进行预测。对于模型的移动,我们采用最大的可用预测,也是一个开放空间。从这个游戏中,我们可以看到我们的模型并不完美,如下所示:
game_tracker = [0., 0., 0., 0., 0., 0., 0., 0., 0.]
win_logical = False
num_moves = 0
while not win_logical:
player_index = input('Input index of your move (0-8): ')
num_moves += 1
# Add player move to game
game_tracker[int(player_index)] = 1\.
# Get model's move by first getting all the logits for each index
[potential_moves] = sess.run(model_output, feed_dict={X: [game_tracker]})
# Now find allowed moves (where game tracker values = 0.0)
allowed_moves = [ix for ix,x in enumerate(game_tracker) if x==0.0]
# Find best move by taking argmax of logits if they are in allowed moves
model_move = np.argmax([x if ix in allowed_moves else -999.0 for ix,x in enumerate(potential_moves)])
# Add model move to game
game_tracker[int(model_move)] = -1\.
print('Model has moved')
print_board(game_tracker)
# Now check for win or too many moves
if check(game_tracker)==1 or num_moves>=5:
print('Game Over!')
win_logical = True
- 上一步应该产生以下交互输出:
Input index of your move (0-8): 4
Model has moved
O | |
___________
| X |
___________
| |
Input index of your move (0-8): 6
Model has moved
O | |
___________
| X |
___________
X | | O
Input index of your move (0-8): 2
Model has moved
O | | X
___________
O | X |
___________
X | | O
Game Over!
工作原理
在本节中,我们通过馈送棋盘位置和九维向量训练神经网络来玩井字棋,并预测最佳响应。我们只需要喂几个可能的井字棋棋盘并对每个棋盘应用随机变换以增加训练集大小。
为了测试我们的算法,我们删除了一个特定棋盘的所有实例,并查看我们的模型是否可以推广以预测最佳响应。最后,我们针对我们的模型玩了一个示例游戏。虽然它还不完善,但仍有不同的架构和训练程序可用于改进它。
七、自然语言处理
在本章中,我们将向您介绍如何使用 TensorFlow 中的文本。我们将首先介绍单词嵌入如何使用词袋方法,然后我们将继续实现更高级的嵌入,如 word2vec 和 doc2vec。
在本章中,我们将介绍以下主题:
- 使用词袋
- 实现 TF-IDF
- 使用 Skip-Gram 嵌入
- 使用 CBOW 嵌入
- 使用 word2vec 进行预测
- 使用 doc2vec 进行情感分析
请注意,读者可以在 Github 和 Packt 仓库中找到本章的所有代码。
介绍
到目前为止,我们只考虑过主要使用数字输入的机器学习算法。如果我们想要使用文本,我们必须找到一种方法将文本转换为数字。有很多方法可以做到这一点,我们将在本章中探讨一些常用的方法。
如果我们考虑句子TensorFlow makes machine learning easy,我们可以按照我们观察它们的顺序将单词转换为数字。这将使句子成为1 2 3 4 5。然后,当我们看到一个新句子machine learning is easy时,我们可以将其翻译为3 4 0 5,,表示我们没有看到的索引为零的单词。通过这两个例子,我们将词汇量限制为六个数字。对于大块文本,我们可以选择我们想要保留多少单词,并且通常保留最常用的单词,用零索引标记其他所有单词。
如果单词learning的数值为 4,单词makes的数值为 2,则自然会认为learning是makes的两倍。由于我们不希望单词之间存在这种类型的数字关系,我们可以假设这些数字代表的是类别,而不是关系数字。
另一个问题是这两个句子的大小不同。我们所做的每个观察(在这种情况下,句子)需要具有与我们希望创建的模型相同的大小输入。为了解决这个问题,我们必须在稀疏向量中创建每个句子,如果该单词出现在该索引中,则该特定索引中的值为 1:
TensorFlow |
makes |
machine |
learning |
easy |
|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 |
first_sentence = [0,1,1,1,1,1]
为了进一步解释前面的向量,我们的词汇由六个不同的单词组成(五个已知单词和一个未知单词)。对于这些单词中的每一个,我们要么具有零值或 1 值。零表示单词不出现在我们的句子中,1 表示它至少出现一次。因此值为零表示该单词不会出现,值为 1 表示它出现
machine |
learning |
is |
easy |
|---|---|---|---|
| 3 | 4 | 0 | 5 |
second_sentence = [1,0,0,1,1,1]
这种方法的缺点是我们失去了任何词序的指示。两个句子TensorFlow makes machine learning easy和machine learning makes TensorFlow easy将产生相同的句子向量。
值得注意的是,这些向量的长度等于我们选择的词汇量的大小。选择非常大的词汇量是很常见的,因此这些句子向量可能非常稀疏。这种类型的嵌入称为词袋。我们将在下一节中实现这一点。
另一个缺点是单词is和TensorFlow具有相同的数字索引值:1。有意义的是,单词is可能不如单词TensorFlow的出现重要。
我们将在本章中探索不同类型的嵌入,试图解决这些问题,但首先我们将开始实现字袋算法。
使用词袋嵌入
在本节中,我们将首先向您展示如何使用 TensorFlow 中的词袋嵌入。这种映射是我们在介绍中介绍的。在这里,我们将向您展示如何使用此类嵌入进行垃圾邮件预测。
准备
为了说明如何在文本数据集中使用词袋,我们将使用来自 UCI 机器学习数据仓库的垃圾邮件电话文本数据库。这是垃圾邮件或非垃圾邮件(非垃圾邮件)的电话短信集合。我们将下载此数据,将其存储以备将来使用,然后继续使用词袋方法来预测文本是否为垃圾邮件。将在词袋算法上运行的模型将是没有隐藏层的逻辑模型。我们将使用批量大小为 1 的随机训练,并在最后的保持测试集上计算精度。
操作步骤
对于这个例子,我们将首先获取数据,正则化和分割文本,通过嵌入函数运行它,并训练逻辑函数来预测垃圾邮件:
- 第一项任务是为此任务导入必要的库。在通常的库中,我们需要一个
.zip文件库来解压缩来自 UCI 机器学习网站的数据,我们从中检索它:
import tensorflow as tf
import matplotlib.pyplot as plt
import os
import numpy as np
import csv
import string
import requests
import io
from zipfile import ZipFile
from tensorflow.contrib import learn
sess = tf.Session()
- 我们不会在每次运行脚本时下载文本数据,而是保存它并检查文件之前是否已保存。如果我们想要更改脚本的参数,这可以防止我们反复下载数据。下载此数据后,我们将提取输入和目标数据,并将目标更改为
1以查找垃圾邮件,将0更改为非垃圾邮件:
save_file_name = os.path.join('temp','temp_spam_data.csv')
if os.path.isfile(save_file_name):
text_data = []
with open(save_file_name, 'r') as temp_output_file:
reader = csv.reader(temp_output_file)
for row in reader:
text_data.append(row)
else:
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')
# Format Data
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]
# And write to csv
with open(save_file_name, 'w') as temp_output_file:
writer = csv.writer(temp_output_file)
writer.writerows(text_data)
texts = [x[1] for x in text_data]
target = [x[0] for x in text_data]
# Relabel 'spam' as 1, 'ham' as 0
target = [1 if x=='spam' else 0 for x in target]
- 为了减少潜在的词汇量,我们将文本正则化。为此,我们消除了文本中大小写和数字的影响。使用以下代码:
# Convert to lower case
texts = [x.lower() for x in texts]
# Remove punctuation
texts = [''.join(c for c in x if c not in string.punctuation) for x in texts]
# Remove numbers
texts = [''.join(c for c in x if c not in '0123456789') for x in texts]
# Trim extra whitespace
texts = [' '.join(x.split()) for x in texts]
- 我们还必须确定最大句子大小。为此,我们将查看数据集中文本长度的直方图。我们可以看到一个很好的截止可能是 25 个字左右。使用以下代码:
# Plot histogram of text lengths
text_lengths = [len(x.split()) for x in texts]
text_lengths = [x for x in text_lengths if x < 50]
plt.hist(text_lengths, bins=25)
plt.title('Histogram of # of Words in Texts')
sentence_size = 25
min_word_freq = 3
由此,我们将得到以下绘图:

图 1:数据中每个文本中单词数的直方图。
We use this to establish a maximum length of words to consider in each text. We set this to 25 words, but it can easily be set to 30 or 40 as well.
- TensorFlow 有一个内置的处理工具,用于确定名为
VocabularyProcessor()的词汇嵌入,它位于learn.preprocessing库中。请注意,您可能会使用此函数获得已弃用的警告:
vocab_processor = learn.preprocessing.VocabularyProcessor(sentence_size, min_frequency=min_word_freq)
vocab_processor.fit_transform(texts)
transformed_texts = np.array([x for x in vocab_processor.transform(texts)])
embedding_size = len(np.unique(transformed_texts))
- 现在我们将数据分成 80-20 训练和测试集:
train_indices = np.random.choice(len(texts), round(len(texts)*0.8), replace=False)
test_indices = np.array(list(set(range(len(texts))) - set(train_indices)))
texts_train = [x for ix, x in enumerate(texts) if ix in train_indices]
texts_test = [x for ix, x in enumerate(texts) if ix in test_indices]
target_train = [x for ix, x in enumerate(target) if ix in train_indices]
target_test = [x for ix, x in enumerate(target) if ix in test_indices]
- 接下来,我们声明单词的嵌入矩阵。句子词将被翻译成指数。这些索引将被转换为单热编码的向量,我们可以使用单位矩阵创建,这将是我们的单词嵌入的大小。我们将使用此矩阵查找每个单词的稀疏向量,并将它们一起添加到稀疏句子向量中。使用以下代码执行此操作:
identity_mat = tf.diag(tf.ones(shape=[embedding_size]))
- 由于我们最终会执行逻辑回归来预测垃圾邮件的概率,因此我们需要声明逻辑回归变量。然后我们也可以声明我们的数据占位符。值得注意的是,
x_data输入占位符应该是整数类型,因为它将用于查找我们的单位矩阵的行索引。 TensorFlow 要求此查找为整数:
A = tf.Variable(tf.random_normal(shape=[embedding_size,1]))
b = tf.Variable(tf.random_normal(shape=[1,1]))
# Initialize placeholders
x_data = tf.placeholder(shape=[sentence_size], dtype=tf.int32)
y_target = tf.placeholder(shape=[1, 1], dtype=tf.float32)
- 现在我们将使用 TensorFlow 的嵌入查找函数,它将句子中单词的索引映射到我们单位矩阵的单热编码向量。当我们有这个矩阵时,我们通过总结上述单词向量来创建句子向量。使用以下代码执行此操作:
x_embed = tf.nn.embedding_lookup(identity_mat, x_data)
x_col_sums = tf.reduce_sum(x_embed, 0)
- 现在我们为每个句子都有固定长度的句子向量,我们想要进行逻辑回归。为此,我们需要声明实际的模型操作。由于我们一次只做一个数据点(随机训练),我们将扩展输入的维度并对其进行线性回归操作。请记住,TensorFlow 具有包含 sigmoid 函数的损失函数,因此我们不需要在此输出中包含它:
x_col_sums_2D = tf.expand_dims(x_col_sums, 0)
model_output = tf.add(tf.matmul(x_col_sums_2D, A), b)
- 我们现在将声明损失函数,预测操作和优化函数来训练模型。使用以下代码执行此操作:
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=model_output, labels=y_target))
# Prediction operation
prediction = tf.sigmoid(model_output)
# Declare optimizer
my_opt = tf.train.GradientDescentOptimizer(0.001)
train_step = my_opt.minimize(loss)
- 接下来,我们将在开始训练生成之前初始化图变量:
init = tf.global_variables_initializer()
sess.run(init)
- 现在我们将开始对句子进行迭代。 TensorFlow 的
vocab_processor.fit()函数是一次运行一个句子的生成器。我们将利用这一优势,以便我们可以对物流模型进行随机训练。为了更好地了解准确率趋势,我们将保留过去 50 个训练步骤的平均值。如果我们只是绘制当前的一个,我们会看到 1 或 0,这取决于我们是否预测训练数据是否正确。使用以下代码执行此操作:
loss_vec = []
train_acc_all = []
train_acc_avg = []
for ix, t in enumerate(vocab_processor.fit_transform(texts_train)):
y_data = [[target_train[ix]]]
sess.run(train_step, feed_dict={x_data: t, y_target: y_data})
temp_loss = sess.run(loss, feed_dict={x_data: t, y_target: y_data})
loss_vec.append(temp_loss)
if (ix+1)%10==0:
print('Training Observation #{}: Loss= {}'.format(ix+1, temp_loss))
# Keep trailing average of past 50 observations accuracy
# Get prediction of single observation
[[temp_pred]] = sess.run(prediction, feed_dict={x_data:t, y_target:y_data})
# Get True/False if prediction is accurate
train_acc_temp = target_train[ix]==np.round(temp_pred)
train_acc_all.append(train_acc_temp)
if len(train_acc_all) >= 50:
train_acc_avg.append(np.mean(train_acc_all[-50:]))
- 这产生以下输出:
Starting Training Over 4459 Sentences.
Training Observation #10: Loss = 5.45322
Training Observation #20: Loss = 3.58226
Training Observation #30: Loss = 0.0
...
Training Observation #4430: Loss = 1.84636
Training Observation #4440: Loss = 1.46626e-05
Training Observation #4450: Loss = 0.045941
- 为了获得测试集的准确率,我们重复前面的过程,但仅限于预测操作,而不是测试集的训练操作:
print('Getting Test Set Accuracy')
test_acc_all = []
for ix, t in enumerate(vocab_processor.fit_transform(texts_test)):
y_data = [[target_test[ix]]]
if (ix+1)%50==0:
print('Test Observation #{}'.format(ix+1))
# Keep trailing average of past 50 observations accuracy
# Get prediction of single observation
[[temp_pred]] = sess.run(prediction, feed_dict={x_data:t, y_target:y_data})
# Get True/False if prediction is accurate
test_acc_temp = target_test[ix]==np.round(temp_pred)
test_acc_all.append(test_acc_temp)
print('\nOverall Test Accuracy: {}'.format(np.mean(test_acc_all)))
Getting Test Set Accuracy For 1115 Sentences.
Test Observation #10
Test Observation #20
Test Observation #30
...
Test Observation #1000
Test Observation #1050
Test Observation #1100
Overall Test Accuracy: 0.8035874439461883
工作原理
在本例中,我们使用了来自 UCI 机器学习库的垃圾邮件文本数据。我们使用 TensorFlow 的词汇处理函数来创建标准化词汇表来处理和创建句子向量,这些句子向量是每个文本的单词向量的总和。我们使用这个句子向量与逻辑回归并获得 80% 准确率模型来预测特定文本是否是垃圾邮件。
更多
值得一提的是限制句子(或文本)大小的动机。在此示例中,我们将文本大小限制为 25 个单词。这是词袋的常见做法,因为它限制了文本长度对预测的影响。你可以想象,如果我们找到一个单词,例如meeting,它可以预测文本是非垃圾邮件(而不是垃圾邮件),那么垃圾邮件可能会通过在最后输入该单词的多次出现来实现。实际上,这是目标数据不平衡的常见问题。在这种情况下可能会出现不平衡的数据,因为垃圾邮件可能很难找到,而非垃圾邮件可能很容易找到。由于这个事实,我们创建的词汇可能严重偏向于我们数据的非垃圾邮件部分中表示的单词(更多非垃圾邮件意味着更多的单词在非垃圾邮件中表示而不是垃圾邮件)。如果我们允许无限长度的文本,那么垃圾邮件发送者可能会利用这一点并创建非常长的文本,这些文本在我们的逻辑模型中触发非垃圾邮件词因素的概率更高。
在下一节中,我们将尝试通过使用单词出现的频率来更好地解决此问题,以确定单词嵌入的值。
实现 TF-IDF
由于我们可以为每个单词选择嵌入,我们可能会决定更改某些单词的加权。一种这样的策略是增加有用的单词和减轻过度常见或罕见单词的权重。我们将在此秘籍中探索的嵌入是尝试实现此目的。
准备
TF-IDF 是一个缩写,代表文本频率 - 反向文档频率。该术语基本上是每个单词的文本频率和反向文档频率的乘积。
在前面的秘籍中,我们介绍了词袋方法,它为句子中每个单词的出现赋值为 1。这可能并不理想,因为每个类别的句子(前一个秘籍中的垃圾邮件和非垃圾邮件)很可能具有the,and和其他单词的相同频率,而诸如Viagra和sale之类的单词]可能应该更加重视查明文本是否是垃圾邮件。
首先,我们要考虑词频。在这里,我们考虑单个条目中单词出现的频率。这部分(TF)的目的是找到在每个条目中看起来很重要的项。
但是the和and等词可能会在每个条目中频繁出现。我们希望减轻这些单词的重要性,因此将前面的文本频率(TF)乘以整个文档频率的倒数可能有助于找到重要的单词。然而,由于文本集(语料库)可能非常大,因此通常采用逆文档频率的对数。这为我们留下了每个文档条目中每个单词的 TF-IDF 的以下公式:

这里w[tf]是文档中的单词频率,w[df]是所有文档中这些单词的总频率。有意义的是,TF-IDF 的高值可能表示在确定文档内容时非常重要的单词。
创建 TF-IDF 向量要求我们将所有文本加载到内存中,并在开始训练模型之前计算每个单词的出现次数。因此,它没有在 TensorFlow 中完全实现,因此我们将使用 scikit-learn 来创建我们的 TF-IDF 嵌入,但是使用 TensorFlow 来适应逻辑模型。
操作步骤
我们将按如下方式处理秘籍:
- 我们将从加载必要的库开始。这次,我们正在为我们的文本加载 scikit-learn TF-IDF 预处理库。使用以下代码执行此操作:
import tensorflow as tf
import matplotlib.pyplot as plt
import csv
import numpy as np
import os
import string
import requests
import io
import nltk
from zipfile import ZipFile
from sklearn.feature_extraction.text import TfidfVectorizer
- 我们将开始一个图会话,并为我们的词汇表声明我们的批量大小和最大特征大小:
sess = tf.Session()
batch_size= 200
max_features = 1000
- 接下来,我们将从 Web 或我们的
temp数据文件夹中加载数据(如果我们之前已保存过)。使用以下代码执行此操作:
save_file_name = os.path.join('temp','temp_spam_data.csv')
if os.path.isfile(save_file_name):
text_data = []
with open(save_file_name, 'r') as temp_output_file:
reader = csv.reader(temp_output_file)
for row in reader:
text_data.append(row)
else:
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')
# Format Data
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]
# And write to csv
with open(save_file_name, 'w') as temp_output_file:
writer = csv.writer(temp_output_file)
writer.writerows(text_data)
texts = [x[1] for x in text_data]
target = [x[0] for x in text_data]
# Relabel 'spam' as 1, 'ham' as 0
target = [1\. if x=='spam' else 0\. for x in target]
- 就像前面的秘籍一样,我们将通过将所有内容转换为小写,删除标点符号并删除数字来减少词汇量:
# Lower case
texts = [x.lower() for x in texts]
# Remove punctuation
texts = [''.join(c for c in x if c not in string.punctuation) for x in texts]
# Remove numbers
texts = [''.join(c for c in x if c not in '0123456789') for x in texts]
# Trim extra whitespace
texts = [' '.join(x.split()) for x in texts]
- 为了使用 scikt-learn 的 TF-IDF 处理函数,我们必须告诉它如何标记我们的句子。通过这个,我们只是指如何将句子分解为相应的单词。我们已经为我们构建了一个很好的标记器:
nltk包可以很好地将句子分解为相应的单词:
def tokenizer(text):
words = nltk.word_tokenize(text)
return words
# Create TF-IDF of texts
tfidf = TfidfVectorizer(tokenizer=tokenizer, stop_words='english', max_features=max_features)
sparse_tfidf_texts = tfidf.fit_transform(texts)
- 接下来,我们将数据集分解为测试和训练集。使用以下代码执行此操作:
train_indices = np.random.choice(sparse_tfidf_texts.shape[0], round(0.8*sparse_tfidf_texts.shape[0]), replace=False)
test_indices = np.array(list(set(range(sparse_tfidf_texts.shape[0])) - set(train_indices)))
texts_train = sparse_tfidf_texts[train_indices]
texts_test = sparse_tfidf_texts[test_indices]
target_train = np.array([x for ix, x in enumerate(target) if ix in train_indices])
target_test = np.array([x for ix, x in enumerate(target) if ix in test_indices])
- 现在我们声明我们的逻辑回归模型变量和我们的数据占位符:
A = tf.Variable(tf.random_normal(shape=[max_features,1]))
b = tf.Variable(tf.random_normal(shape=[1,1]))
# Initialize placeholders
x_data = tf.placeholder(shape=[None, max_features], dtype=tf.float32)
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32)
- 我们现在可以声明模型操作和损失函数。请记住,逻辑回归的 sigmoid 部分在我们的损失函数中。使用以下代码执行此操作:
model_output = tf.add(tf.matmul(x_data, A), b)
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=model_output, labels=y_target))
- 我们将预测和精度函数添加到图中,以便在我们的模型训练时我们可以看到训练和测试集的准确率:
prediction = tf.round(tf.sigmoid(model_output))
predictions_correct = tf.cast(tf.equal(prediction, y_target), tf.float32)
accuracy = tf.reduce_mean(predictions_correct)
- 然后我们将声明一个优化器并初始化我们的图变量:
my_opt = tf.train.GradientDescentOptimizer(0.0025)
train_step = my_opt.minimize(loss)
# Intitialize Variables
init = tf.global_variables_initializer()
sess.run(init)
- 我们现在将训练我们的模型超过 10,000 代,并记录每 100 代的测试/训练损失和准确率,每 500 代打印一次。使用以下代码执行此操作:
train_loss = []
test_loss = []
train_acc = []
test_acc = []
i_data = []
for i in range(10000):
rand_index = np.random.choice(texts_train.shape[0], size=batch_size)
rand_x = texts_train[rand_index].todense()
rand_y = np.transpose([target_train[rand_index]])
sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y})
# Only record loss and accuracy every 100 generations
if (i+1)%100==0:
i_data.append(i+1)
train_loss_temp = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y})
train_loss.append(train_loss_temp)
test_loss_temp = sess.run(loss, feed_dict={x_data: texts_test.todense(), y_target: np.transpose([target_test])})
test_loss.append(test_loss_temp)
train_acc_temp = sess.run(accuracy, feed_dict={x_data: rand_x, y_target: rand_y})
train_acc.append(train_acc_temp)
test_acc_temp = sess.run(accuracy, feed_dict={x_data: texts_test.todense(), y_target: np.transpose([target_test])})
test_acc.append(test_acc_temp)
if (i+1)%500==0:
acc_and_loss = [i+1, train_loss_temp, test_loss_temp, train_acc_temp, test_acc_temp]
acc_and_loss = [np.round(x,2) for x in acc_and_loss]
print('Generation # {}. Train Loss (Test Loss): {:.2f} ({:.2f}). Train Acc (Test Acc): {:.2f} ({:.2f})'.format(*acc_and_loss))
- 这产生以下输出:
Generation # 500\. Train Loss (Test Loss): 0.69 (0.73). Train Acc (Test Acc): 0.62 (0.57)
Generation # 1000\. Train Loss (Test Loss): 0.62 (0.63). Train Acc (Test Acc): 0.68 (0.66)
...
Generation # 9500\. Train Loss (Test Loss): 0.39 (0.45). Train Acc (Test Acc): 0.89 (0.85)
Generation # 10000\. Train Loss (Test Loss): 0.48 (0.45). Train Acc (Test Acc): 0.84 (0.85)
以下是绘制训练和测试装置的准确率和损耗的绘图:

图 2:根据 TF-IDF 值构建的物流垃圾邮件模型的交叉熵损失
训练和测试精度图如下:

图 3:根据 TF-IDF 值构建的逻辑垃圾邮件模型的训练和测试集精度
工作原理
使用模型的 TF-IDF 值增加了我们对先前的词袋模型的预测,从 80% 的准确率到接近 90% 的准确率。我们通过使用 scikit-learn 的 TF-IDF 词汇处理函数并使用这些 TF-IDF 值进行逻辑回归来实现这一目标。
更多
虽然我们可能已经解决了重要性这个问题,但我们还没有解决字序问题。词袋和 TF-IDF 都没有考虑句子中的单词的顺序特征。我们将在接下来的几节中尝试解决这个问题,这将向我们介绍 word2vec 技术。
使用 Skip-Gram 嵌入
在之前的秘籍中,我们在训练模型之前决定了我们的文本嵌入。使用神经网络,我们可以使嵌入值成为训练过程的一部分。我们将探索的第一个这样的方法叫做 Skip-Gram 嵌入。
准备
在此秘籍之前,我们没有考虑与创建单词嵌入相关的单词顺序。 2013 年初,Tomas Mikolov 和谷歌的其他研究人员撰写了一篇关于创建解决这个问题的单词嵌入的论文,他们将他们的方法命名为 word2vec。
基本思想是创建捕获单词关系方面的单词嵌入。我们试图了解各种单词如何相互关联。这些嵌入可能如何表现的一些示例如下:
king - man + woman = queen
India pale ale - hops + malt = stout
如果我们只考虑它们之间的位置关系,我们可能会实现这样的数字表示。如果我们能够分析足够大的相关文档来源,我们可能会发现在我们的文本中,king,man和queen这两个词在彼此之间相互提及。如果我们也知道man和woman以不同的方式相关,那么我们可以得出结论,man是king,因为woman是queen,依此类推。
为了找到这样的嵌入,我们将使用一个神经网络来预测给定输入字的周围单词。我们可以轻松地切换它并尝试在给定一组周围单词的情况下预测目标单词,但我们将从前面的方法开始。两者都是 word2vec 过程的变体,但是从目标词预测周围词(上下文)的前述方法称为 Skip-Gram 模型。在下一个秘籍中,我们将实现另一个方法,从上下文预测目标词,这称为连续词袋方法(CBOW):

图 4:word2vec 的 Skip-Gram 实现的图示。 Skip-Gram 预测目标词的上下文窗口(每侧窗口大小为 1)。
对于这个秘籍,我们将在康奈尔大学的一组电影评论数据上实现 Skip-Gram 模型。 word2vec 的 CBOW 方法将在下一个秘籍中实现。
操作步骤
对于这个秘籍,我们将创建几个辅助函数。这些函数将加载数据,正则化文本,生成词汇表并生成数据批量。只有在这之后我们才开始训练我们的单词嵌入。为了清楚起见,我们不是预测任何目标变量,而是我们将拟合单词嵌入:
- 首先,我们将加载必要的库并启动图会话:
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import random
import os
import string
import requests
import collections
import io
import tarfile
import urllib.request
from nltk.corpus import stopwords
sess = tf.Session()
- 然后我们声明一些模型参数。我们将一次查看 50 对单词嵌入(批量大小)。每个单词的嵌入大小将是一个长度为 200 的向量,我们只考虑 10,000 个最常用的单词(每隔一个单词将被归类为未知单词)。我们将训练 5 万代,并每 500 代打印一次。然后我们将声明一个我们将在损失函数中使用的
num_sampled变量(我们将在后面解释),并且我们还声明了我们的 Skip-Gram 窗口大小。在这里,我们将窗口大小设置为 2,因此我们将查看目标每侧的周围两个单词。我们将通过名为nltk的 Python 包设置我们的停用词。我们还想要一种方法来检查我们的单词嵌入是如何执行的,因此我们将选择一些常见的电影评论单词并从每 2,000 次迭代中打印出最近的邻居单词:
batch_size = 50
embedding_size = 200
vocabulary_size = 10000
generations = 50000
print_loss_every = 500
num_sampled = int(batch_size/2)
window_size = 2
stops = stopwords.words('english')
print_valid_every = 2000
valid_words = ['cliche', 'love', 'hate', 'silly', 'sad']
- 接下来,我们将声明我们的数据加载函数,该函数会检查以确保在下载之前我们没有下载数据。否则,如果之前保存了数据,它将从磁盘加载数据。使用以下代码执行此操作:
def load_movie_data():
save_folder_name = 'temp'
pos_file = os.path.join(save_folder_name, 'rt-polarity.pos')
neg_file = os.path.join(save_folder_name, 'rt-polarity.neg')
# Check if files are already downloaded
if os.path.exists(save_folder_name):
pos_data = []
with open(pos_file, 'r') as temp_pos_file:
for row in temp_pos_file:
pos_data.append(row)
neg_data = []
with open(neg_file, 'r') as temp_neg_file:
for row in temp_neg_file:
neg_data.append(row)
else: # If not downloaded, download and save
movie_data_url = 'http://www.cs.cornell.edu/people/pabo/movie-review-data/rt-polaritydata.tar.gz'
stream_data = urllib.request.urlopen(movie_data_url)
tmp = io.BytesIO()
while True:
s = stream_data.read(16384)
if not s:
break
tmp.write(s)
stream_data.close()
tmp.seek(0)
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
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()
# Write to file
if not os.path.exists(save_folder_name):
os.makedirs(save_folder_name)
# Save files
with open(pos_file, 'w') as pos_file_handler:
pos_file_handler.write(''.join(pos_data))
with open(neg_file, 'w') as neg_file_handler:
neg_file_handler.write(''.join(neg_data))
texts = pos_data + neg_data
target = [1]*len(pos_data) + [0]*len(neg_data)
return(texts, target)
texts, target = load_movie_data()
- 接下来,我们将为文本创建正则化函数。此函数将输入字符串列表并使其为小写,删除标点,删除数字,删除额外的空格,并删除停用词。使用以下代码执行此操作:
def normalize_text(texts, stops):
# Lower case
texts = [x.lower() for x in texts]
# Remove punctuation
texts = [''.join(c for c in x if c not in string.punctuation) for x in texts]
# Remove numbers
texts = [''.join(c for c in x if c not in '0123456789') for x in texts]
# Remove stopwords
texts = [' '.join([word for word in x.split() if word not in (stops)]) for x in texts]
# Trim extra whitespace
texts = [' '.join(x.split()) for x in texts]
return(texts)
texts = normalize_text(texts, stops)
- 为了确保我们所有的电影评论都能提供信息,我们应该确保它们足够长,以包含重要的单词关系。我们会随意将其设置为三个或更多单词:
target = [target[ix] for ix, x in enumerate(texts) if len(x.split()) > 2]
texts = [x for x in texts if len(x.split()) > 2]
- 为了构建我们的词汇表,我们将创建一个函数来创建一个带有计数的单词字典。任何不常见的词都不会使我们的词汇量大小被截止,将被标记为
RARE。使用以下代码执行此操作:
def build_dictionary(sentences, vocabulary_size):
# Turn sentences (list of strings) into lists of words
split_sentences = [s.split() for s in sentences]
words = [x for sublist in split_sentences for x in sublist]
# Initialize list of [word, word_count] for each word, starting with unknown
count = [['RARE', -1]]
# Now add most frequent words, limited to the N-most frequent (N=vocabulary size)
count.extend(collections.Counter(words).most_common(vocabulary_size-1))
# Now create the dictionary
word_dict = {}
# For each word, that we want in the dictionary, add it, then make it the value of the prior dictionary length
for word, word_count in count:
word_dict[word] = len(word_dict)
return(word_dict)
- 我们需要一个函数将一个句子列表转换为单词索引列表,我们可以将它们传递给嵌入查找函数。使用以下代码执行此操作:
def text_to_numbers(sentences, word_dict):
# Initialize the returned data
data = []
for sentence in sentences:
sentence_data = []
# For each word, either use selected index or rare word index
for word in sentence:
if word in word_dict:
word_ix = word_dict[word]
else:
word_ix = 0
sentence_data.append(word_ix)
data.append(sentence_data)
return data
- 现在我们可以实际创建我们的字典并将我们的句子列表转换为单词索引列表:
word_dictionary = build_dictionary(texts, vocabulary_size)
word_dictionary_rev = dict(zip(word_dictionary.values(), word_dictionary.keys()))
text_data = text_to_numbers(texts, word_dictionary)
- 从前面的单词字典中,我们可以查找我们在步骤 2 中选择的验证字的索引。使用以下代码执行此操作:
valid_examples = [word_dictionary[x] for x in valid_words]
- 我们现在将创建一个将返回 Skip-Gram 批次的函数。我们想训练一对单词,其中一个单词是训练输入(来自我们窗口中心的目标单词),另一个单词是从窗口中选择的。例如,句子
the cat in the hat可能导致(输入,输出)对,如下所示:(the,in),(cat,in),(the,in),(hat,in)如果是目标词,我们每个方向的窗口大小为 2:
def generate_batch_data(sentences, batch_size, window_size, method='skip_gram'):
# Fill up data batch
batch_data = []
label_data = []
while len(batch_data) < batch_size:
# select random sentence to start
rand_sentence = np.random.choice(sentences)
# Generate consecutive windows to look at
window_sequences = [rand_sentence[max((ix-window_size),0):(ix+window_size+1)] for ix, x in enumerate(rand_sentence)]
# Denote which element of each window is the center word of interest
label_indices = [ix if ix<window_size else window_size for ix,x in enumerate(window_sequences)]
# Pull out center word of interest for each window and create a tuple for each window
if method=='skip_gram':
batch_and_labels = [(x[y], x[:y] + x[(y+1):]) for x,y in zip(window_sequences, label_indices)]
# Make it in to a big list of tuples (target word, surrounding word)
tuple_data = [(x, y_) for x,y in batch_and_labels for y_ in y]
else:
raise ValueError('Method {} not implmented yet.'.format(method))
# extract batch and labels
batch, labels = [list(x) for x in zip(*tuple_data)]
batch_data.extend(batch[:batch_size])
label_data.extend(labels[:batch_size])
# Trim batch and label at the end
batch_data = batch_data[:batch_size]
label_data = label_data[:batch_size]
# Convert to numpy array
batch_data = np.array(batch_data)
label_data = np.transpose(np.array([label_data]))
return batch_data, label_data
- 我们现在可以初始化嵌入矩阵,声明占位符,并初始化嵌入查找函数。使用以下代码执行此操作:
embeddings = tf.Variable(tf.random_uniform([vocabulary_size,
embedding_size], -1.0, 1.0))
# Create data/target placeholders
x_inputs = tf.placeholder(tf.int32, shape=[batch_size])
y_target = tf.placeholder(tf.int32, shape=[batch_size, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
# Lookup the word embedding:
embed = tf.nn.embedding_lookup(embeddings, x_inputs)
- 损失函数应该是诸如
softmax之类的东西,它计算预测错误单词类别时的损失。但由于我们的目标有 10,000 个不同的类别,因此非常稀疏。这种稀疏性导致关于模型的拟合或收敛的问题。为了解决这个问题,我们将使用称为噪声对比误差的损失函数。这种 NCE 损失函数通过预测单词类与随机噪声预测将我们的问题转化为二元预测。num_sampled参数指定批量变成随机噪声的程度:
nce_weights = tf.Variable(tf.truncated_normal([vocabulary_size,
embedding_size], stddev=1.0 / np.sqrt(embedding_size)))
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weights,
biases=nce_biases,
inputs=embed,
labels=y_target,
num_sampled=num_sampled,
num_classes=vocabulary_size))
- 现在我们需要创建一种方法来查找附近的单词到我们的验证单词。我们将通过计算验证集和所有单词嵌入之间的余弦相似性来完成此操作,然后我们可以为每个验证字打印出最接近的单词集。使用以下代码执行此操作:
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keepdims=True))
normalized_embeddings = embeddings / norm
valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)
similarity = tf.matmul(valid_embeddings, normalized_embeddings, transpose_b=True)
- 我们现在声明我们的优化函数并初始化我们的模型变量:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0).minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
- 现在我们可以训练我们的嵌入并在训练期间打印损失和最接近我们验证集的单词。使用以下代码执行此操作:
loss_vec = []
loss_x_vec = []
for i in range(generations):
batch_inputs, batch_labels = generate_batch_data(text_data, batch_size, window_size)
feed_dict = {x_inputs : batch_inputs, y_target : batch_labels}
# Run the train step
sess.run(optimizer, feed_dict=feed_dict)
# Return the loss
if (i+1) % print_loss_every == 0:
loss_val = sess.run(loss, feed_dict=feed_dict)
loss_vec.append(loss_val)
loss_x_vec.append(i+1)
print("Loss at step {} : {}".format(i+1, loss_val))
# Validation: Print some random words and top 5 related words
if (i+1) % print_valid_every == 0:
sim = sess.run(similarity, feed_dict=feed_dict)
for j in range(len(valid_words)):
valid_word = word_dictionary_rev[valid_examples[j]]
top_k = 5 # number of nearest neighbors
nearest = (-sim[j, :]).argsort()[1:top_k+1]
log_str = "Nearest to {}:".format(valid_word)
for k in range(top_k):
close_word = word_dictionary_rev[nearest[k]]
log_str = "%s %s," % (log_str, close_word)
print(log_str)
在前面的代码中,我们在调用
argsort方法之前采用相似矩阵的否定。我们这样做是因为我们想要找到从最高相似度值到最低相似度值的索引,而不是相反。
- 这产生以下输出:
Loss at step 500 : 13.387781143188477
Loss at step 1000 : 7.240757465362549
Loss at step 49500 : 0.9395825862884521
Loss at step 50000 : 0.30323168635368347
Nearest to cliche: walk, intrigue, brim, eileen, dumber,
Nearest to love: plight, fiction, complete, lady, bartleby,
Nearest to hate: style, throws, players, fearlessness, astringent,
Nearest to silly: delivers, meow, regain, nicely, anger,
Nearest to sad: dizzying, variety, existing, environment, tunney,
工作原理
我们通过Skip-Gram方法在电影评论数据集上训练了一个 word2vec 模型。我们下载了数据,将单词转换为带有字典的索引,并将这些索引号用作嵌入查找,我们对其进行了训练,以便附近的单词可以相互预测。
更多
乍一看,我们可能期望验证集的附近单词集合是同义词。事实并非如此,因为很少有同义词实际上在句子中彼此相邻。我们真正得到的是预测我们的数据集中哪些单词彼此接近。我们希望使用这样的嵌入将使预测更容易。
为了使用这些嵌入,我们必须使它们可重用并保存它们。我们将通过实现 CBOW 嵌入在下一个秘籍中执行此操作。
使用 CBOW 嵌入
在这个秘籍中,我们将实现 word2vec 的 CBOW(连续词袋)方法。它与Skip-Gram方法非常相似,除了我们预测来自环境词周围窗口的单个目标词。
准备
在这个秘籍中,我们将实现 word2vec 的CBOW方法。它与Skip-Gram方法非常相似,只是我们预测来自环境词周围窗口的单个目标词。
在前面的示例中,我们将窗口和目标的每个组合视为一组配对的输入和输出,但是使用 CBOW,我们将周围的窗口嵌入添加到一起以获得一个嵌入来预测目标字嵌入:

图 5:如何在一个例子的窗口上创建 CBOW 嵌入数据的描述(每侧窗口大小为 1)
大多数代码都保持不变,除了我们需要改变我们创建嵌入的方式以及如何从句子生成数据。
为了使代码更易于阅读,我们已将所有主要函数移动到同一目录中名为text_helpers.py的单独文件中。此函数保存数据加载,文本正则化,字典创建和批量生成函数。除非另有说明,否则这些函数与使用 Skip-Gram 嵌入秘籍中显示的完全相同。
操作步骤
我们将按如下方式处理秘籍:
- 我们将首先加载必要的库,包括前面提到的
text_helpers.py脚本,我们将把我们的函数用于文本加载和操作。然后我们将开始一个图会话:
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import random
import os
import pickle
import string
import requests
import collections
import io
import tarfile
import urllib.request
import text_helpers
from nltk.corpus import stopwords
sess = tf.Session()
- 我们要确保在开始保存之前存在临时数据和参数保存文件夹。使用以下代码检查:
# Make a saving directory if it doesn't exist
data_folder_name = 'temp'
if not os.path.exists(data_folder_name):
os.makedirs(data_folder_name)
- 然后我们将声明模型的参数,这与我们在上一个秘籍中对
Skip-Gram方法所做的类似:
# Declare model parameters
batch_size = 500
embedding_size = 200
vocabulary_size = 2000
generations = 50000
model_learning_rate = 0.001
num_sampled = int(batch_size/2
window_size = 3
# Add checkpoints to training
save_embeddings_every = 5000
print_valid_every = 5000
print_loss_every = 100
# Declare stop words
stops = stopwords.words('english')
# We pick some test words. We are expecting synonyms to appear
valid_words = ['love', 'hate', 'happy', 'sad', 'man', 'woman']
- 我们已将数据加载和文本正则化函数移动到我们在开始时导入的单独文件中,此文件在 Github 仓库和 Packt 仓库中都可用。现在我们可以打电话给他们我们也只想要包含三个或更多单词的评论。使用以下代码:
texts, target = text_helpers.load_movie_data(data_folder_name) texts = text_helpers.normalize_text(texts, stops) # Texts must contain at least 3 words target = [target[ix] for ix, x in enumerate(texts) if len(x.split()) > 2] texts = [x for x in texts if len(x.split()) > 2]
- 现在我们将创建我们的词汇词典,这将帮助我们查找单词。当我们想要打印出最接近我们验证集的单词时,我们还需要一个反向字典来查找索引中的单词:
word_dictionary = text_helpers.build_dictionary(texts,
vocabulary_size)
word_dictionary_rev = dict(zip(word_dictionary.values(), word_dictionary.keys()))
text_data = text_helpers.text_to_numbers(texts, word_dictionary)
# Get validation word keys
valid_examples = [word_dictionary[x] for x in valid_words]
- 接下来,我们将初始化我们想要拟合的单词嵌入,并声明模型数据占位符。使用以下代码执行此操作:
embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
# Create data/target placeholders
x_inputs = tf.placeholder(tf.int32, shape=[batch_size,
2*window_size])
y_target = tf.placeholder(tf.int32, shape=[batch_size, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
- 我们现在可以创建一种处理嵌入一词的方法。由于 CBOW 模型添加了上下文窗口的嵌入,我们将创建一个循环并将所有嵌入添加到窗口中:
# Lookup the word embeddings and
# Add together window embeddings:
embed = tf.zeros([batch_size, embedding_size])
for element in range(2*window_size):
embed += tf.nn.embedding_lookup(embeddings, x_inputs[:, element])
- 我们将使用 TensorFlow 中内置的噪声对比误差损失函数,因为我们的分类输出太稀疏,无法使 softmax 收敛,如下所示:
# NCE loss parameters
nce_weights = tf.Variable(tf.truncated_normal([vocabulary_size,
embedding_size], stddev=1.0 / np.sqrt(embedding_size)))
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
# Declare loss function (NCE)
loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weights,
biases=nce_biases,
inputs=embed,
labels=y_target,
num_sampled=num_sampled,
num_classes=vocabulary_size))
- 就像我们在 Skip-Gram 秘籍中所做的那样,我们将使用余弦相似性来打印离我们的验证字数据集最近的单词,以了解我们的嵌入如何工作。使用以下代码执行此操作:
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keepdims=True))
normalized_embeddings = embeddings / norm
valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)
similarity = tf.matmul(valid_embeddings, normalized_embeddings, transpose_b=True)
- 要保存嵌入,我们必须加载 TensorFlow
train.Saver方法。这个方法默认保存整个图,但是我们可以给它一个参数来保存嵌入变量,我们也可以给它一个特定的名称。在这里,我们给它的名称与图中的变量名称相同:
saver = tf.train.Saver({"embeddings": embeddings})
- 我们现在将声明我们的优化函数并初始化我们的模型变量。使用以下代码执行此操作:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=model_learning_rate).minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
- 最后,我们可以遍历我们的训练步骤,打印出损失,并将我们指定的嵌入和字典保存到:
loss_vec = []
loss_x_vec = []
for i in range(generations):
batch_inputs, batch_labels = text_helpers.generate_batch_data(text_data, batch_size, window_size, method='cbow')
feed_dict = {x_inputs : batch_inputs, y_target : batch_labels}
# Run the train step
sess.run(optimizer, feed_dict=feed_dict)
# Return the loss
if (i+1) % print_loss_every == 0:
loss_val = sess.run(loss, feed_dict=feed_dict)
loss_vec.append(loss_val)
loss_x_vec.append(i+1)
print('Loss at step {} : {}'.format(i+1, loss_val))
# Validation: Print some random words and top 5 related words
if (i+1) % print_valid_every == 0:
sim = sess.run(similarity, feed_dict=feed_dict)
for j in range(len(valid_words)):
valid_word = word_dictionary_rev[valid_examples[j]]
top_k = 5 # number of nearest neighbors
nearest = (-sim[j, :]).argsort()[1:top_k+1]
log_str = "Nearest to {}:".format(valid_word)
for k in range(top_k):
close_word = word_dictionary_rev[nearest[k]]
print_str = '{} {},'.format(log_str, close_word)
print(print_str)
# Save dictionary + embeddings
if (i+1) % save_embeddings_every == 0:
# Save vocabulary dictionary
with open(os.path.join(data_folder_name,'movie_vocab.pkl'), 'wb') as f:
pickle.dump(word_dictionary, f)
# Save embeddings
model_checkpoint_path = os.path.join(os.getcwd(),data_folder_name,'cbow_movie_embeddings.ckpt')
save_path = saver.save(sess, model_checkpoint_path)
print('Model saved in file: {}'.format(save_path))
- 这产生以下输出:
Loss at step 100 : 62.04829025268555
Loss at step 200 : 33.182334899902344
...
Loss at step 49900 : 1.6794960498809814
Loss at step 50000 : 1.5071022510528564
Nearest to love: clarity, cult, cliched, literary, memory,
Nearest to hate: bringing, gifted, almost, next, wish,
Nearest to happy: ensemble, fall, courage, uneven, girls,
Nearest to sad: santa, devoid, biopic, genuinely, becomes,
Nearest to man: project, stands, none, soul, away,
Nearest to woman: crush, even, x, team, ensemble,
Model saved in file: .../temp/cbow_movie_embeddings.ckpt
text_helpers.py文件中除了一个函数之外的所有函数都具有直接来自上一个秘籍的函数。我们将通过添加cbow方法对generate_batch_data()函数稍加补充,如下所示:
elif method=='cbow':
batch_and_labels = [(x[:y] + x[(y+1):], x[y]) for x,y in zip(window_sequences, label_indices)]
# Only keep windows with consistent 2*window_size
batch_and_labels = [(x,y) for x,y in batch_and_labels if len(x)==2*window_size]
batch, labels = [list(x) for x in zip(*batch_and_labels)]
工作原理
此秘籍与使用 Skip-Gram 创建嵌入非常相似。主要区别在于我们如何生成数据并组合嵌入。
对于这个秘籍,我们加载数据,正则化文本,创建词汇词典,使用字典查找嵌入,组合嵌入,并训练神经网络来预测目标词。
更多
值得注意的是,CBOW方法训练周围窗口的累加嵌入以预测目标字。这样做的一个结果是来自 word2vec 的CBOW方法具有Skip-Gram方法缺乏的平滑效果,并且认为这对于较小的文本数据集可能是优选的是合理的。
使用 word2vec 进行预测
在本文中,我们将使用先前学习的嵌入策略来执行分类。
准备
现在我们已经创建并保存了 CBOW 字嵌入,我们需要使用它们来对电影数据集进行情感预测。在本文中,我们将学习如何加载和使用预先训练的嵌入,并使用这些嵌入来通过训练逻辑线性模型来预测好的或坏的评论来执行情感分析。
情感分析是一项非常艰巨的任务,因为人类语言使得很难掌握所谓意义的真实含义的微妙之处和细微差别。讽刺,笑话和含糊不清的引用都使这项任务成倍增加。我们将在电影评论数据集上创建一个简单的逻辑回归,以查看我们是否可以从我们在上一个秘籍中创建并保存的 CBOW 嵌入中获取任何信息。由于本文的重点是加载和使用已保存的嵌入,我们不会追求更复杂的模型。
操作步骤
我们将按如下方式处理秘籍:
- 我们将首先加载必要的库并启动图会话:
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import random
import os
import pickle
import string
import requests
import collections
import io
import tarfile
import urllib.request
import text_helpers
from nltk.corpus import stopwords
sess = tf.Session()
- 现在我们将声明模型参数。嵌入大小应与我们用于创建前面的 CBOW 嵌入的嵌入大小相同。使用以下代码执行此操作:
embedding_size = 200
vocabulary_size = 2000
batch_size = 100
max_words = 100
stops = stopwords.words('english')
- 我们将从我们创建的
text_helpers.py文件加载和转换文本数据。使用以下代码执行此操作:
texts, target = text_helpers.load_movie_data()
# Normalize text
print('Normalizing Text Data')
texts = text_helpers.normalize_text(texts, stops)
# Texts must contain at least 3 words
target = [target[ix] for ix, x in enumerate(texts) if len(x.split()) > 2]
texts = [x for x in texts if len(x.split()) > 2]
train_indices = np.random.choice(len(target), round(0.8*len(target)), replace=False)
test_indices = np.array(list(set(range(len(target))) - set(train_indices)))
texts_train = [x for ix, x in enumerate(texts) if ix in train_indices]
texts_test = [x for ix, x in enumerate(texts) if ix in test_indices]
target_train = np.array([x for ix, x in enumerate(target) if ix in train_indices])
target_test = np.array([x for ix, x in enumerate(target) if ix in test_indices])
- 我们现在加载我们在拟合 CBOW 嵌入时创建的单词字典。重要的是我们加载它以便我们具有从单词到嵌入索引的完全相同的映射,如下所示:
dict_file = os.path.join(data_folder_name, 'movie_vocab.pkl')
word_dictionary = pickle.load(open(dict_file, 'rb'))
- 我们现在可以使用我们的单词字典将我们加载的句子数据转换为数字
numpy数组:
text_data_train = np.array(text_helpers.text_to_numbers(texts_train, word_dictionary))
text_data_test = np.array(text_helpers.text_to_numbers(texts_test, word_dictionary))
- 由于电影评论的长度不同,我们将它们标准化,因此它们的长度都相同。在我们的例子中,我们将其设置为 100 个单词。如果评论少于 100 个单词,我们将用零填充它。使用以下代码执行此操作:
text_data_train = np.array([x[0:max_words] for x in [y+[0]*max_words for y in text_data_train]])
text_data_test = np.array([x[0:max_words] for x in [y+[0]*max_words for y in text_data_test]])
- 现在我们将声明我们的模型变量和占位符以进行逻辑回归。使用以下代码执行此操作:
A = tf.Variable(tf.random_normal(shape=[embedding_size,1]))
b = tf.Variable(tf.random_normal(shape=[1,1]))
# Initialize placeholders
x_data = tf.placeholder(shape=[None, max_words], dtype=tf.int32)
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32)
- 为了让 TensorFlow 恢复我们预先训练的嵌入,我们必须首先给
Saver方法一个变量来恢复,所以我们将创建一个嵌入变量,其形状与我们将加载的嵌入相同:
embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
- 现在我们将
embedding_lookup函数放在图上,并将句子中所有单词的平均嵌入。使用以下代码执行此操作:
embed = tf.nn.embedding_lookup(embeddings, x_data)
# Take average of all word embeddings in documents
embed_avg = tf.reduce_mean(embed, 1)
- 接下来,我们将声明我们的模型操作和损失函数,记住我们的损失函数已经内置了 sigmoid 操作,如下所示:
model_output = tf.add(tf.matmul(embed_avg, A), b)
# Declare loss function (Cross Entropy loss)
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=model_output, labels=y_target))
- 现在我们将向图添加预测和精度函数,以便我们可以在使用以下代码训练模型时评估精度:
prediction = tf.round(tf.sigmoid(model_output))
predictions_correct = tf.cast(tf.equal(prediction, y_target), tf.float32)
accuracy = tf.reduce_mean(predictions_correct)
- 我们将声明我们的优化函数并初始化以下模型变量:
my_opt = tf.train.AdagradOptimizer(0.005)
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
- 现在我们有一个随机初始化嵌入,我们可以告诉
Saver方法将我们之前的 CBOW 嵌入加载到嵌入变量中。使用以下代码执行此操作:
model_checkpoint_path = os.path.join(data_folder_name,'cbow_movie_embeddings.ckpt')
saver = tf.train.Saver({"embeddings": embeddings})
saver.restore(sess, model_checkpoint_path)
- 现在我们可以开始训练几代。请注意,我们每 100 代就可以节省训练和测试损失和准确率。我们只会每 500 代打印一次模型状态,如下所示:
train_loss = []
test_loss = []
train_acc = []
test_acc = []
i_data = []
for i in range(10000):
rand_index = np.random.choice(text_data_train.shape[0], size=batch_size)
rand_x = text_data_train[rand_index]
rand_y = np.transpose([target_train[rand_index]])
sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y})
# Only record loss and accuracy every 100 generations
if (i+1)%100==0:
i_data.append(i+1)
train_loss_temp = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y})
train_loss.append(train_loss_temp)
test_loss_temp = sess.run(loss, feed_dict={x_data: text_data_test, y_target: np.transpose([target_test])})
test_loss.append(test_loss_temp)
train_acc_temp = sess.run(accuracy, feed_dict={x_data: rand_x, y_target: rand_y})
train_acc.append(train_acc_temp)
test_acc_temp = sess.run(accuracy, feed_dict={x_data: text_data_test, y_target: np.transpose([target_test])})
test_acc.append(test_acc_temp)
if (i+1)%500==0:
acc_and_loss = [i+1, train_loss_temp, test_loss_temp, train_acc_temp, test_acc_temp]
acc_and_loss = [np.round(x,2) for x in acc_and_loss]
print('Generation # {}. Train Loss (Test Loss): {:.2f} ({:.2f}). Train Acc (Test Acc): {:.2f} ({:.2f})'.format(*acc_and_loss))
- 结果如下:
Generation # 500\. Train Loss (Test Loss): 0.70 (0.71). Train Acc (Test Acc): 0.52 (0.48)
Generation # 1000\. Train Loss (Test Loss): 0.69 (0.72). Train Acc (Test Acc): 0.56 (0.47)
...
Generation # 9500\. Train Loss (Test Loss): 0.69 (0.70). Train Acc (Test Acc): 0.57 (0.55)
Generation # 10000\. Train Loss (Test Loss): 0.70 (0.70). Train Acc (Test Acc): 0.59 (0.55)
- 以下是绘制训练和测试损失和准确率的代码,我们每 100 代保存一次:
# Plot loss over time
plt.plot(i_data, train_loss, 'k-', label='Train Loss')
plt.plot(i_data, test_loss, 'r--', label='Test Loss', linewidth=4)
plt.title('Cross Entropy Loss per Generation')
plt.xlabel('Generation')
plt.ylabel('Cross Entropy Loss')
plt.legend(loc='upper right')
plt.show()
# Plot train and test accuracy
plt.plot(i_data, train_acc, 'k-', label='Train Set Accuracy')
plt.plot(i_data, test_acc, 'r--', label='Test Set Accuracy', linewidth=4)
plt.title('Train and Test Accuracy')
plt.xlabel('Generation')
plt.ylabel('Accuracy')
plt.legend(loc='lower right')
plt.show()
每代交叉熵损失的图如下:
Figure 6: Here we observe the train and test loss over 10,000 generations
上述代码的训练图和测试精度如下:

图 7:我们可以观察到训练和测试装置的准确率正在缓慢提高 10,000 代。值得注意的是,该模型表现非常差,并且仅比随机预测器略好。
工作原理
我们加载了我们之前的 CBOW 嵌入并对平均嵌入评论进行了逻辑回归。这里要注意的重要方法是我们如何将模型变量从磁盘加载到当前模型中已经初始化的变量。我们还必须记住在训练嵌入之前存储和加载我们创建的词汇表。使用相同的嵌入时,从单词到嵌入索引具有相同的映射非常重要。
更多
我们可以看到,我们在预测情感方面几乎达到了 60% 的准确率。例如,要知道单词great;背后的含义是一项艰巨的任务,它可以在评论中用于消极或积极的背景。
为了解决这个问题,我们希望以某种方式为文档本身创建嵌入并解决情感问题。通常,整个评论是积极的,或者整个评论是否定的。我们可以利用这个优势,我们将在下面的使用 doc2vec 以获取情感分析方法中查看如何执行此操作。
使用 doc2vec 进行情感分析
既然我们知道如何训练单词嵌入,我们也可以扩展这些方法以进行文档嵌入。我们将在以下部分中探讨如何执行此操作。
准备
在前面关于 word2vec 方法的部分中,我们设法捕获了单词之间的位置关系。我们没有做的是捕捉单词与它们来自的文档(或电影评论)之间的关系。 word2vec 的一个扩展来捕获文档效果,称为 doc2vec。
doc2vec 的基本思想是引入文档嵌入,以及可能有助于捕获文档基调的单词嵌入。例如,只知道单词movie和love彼此接近可能无法帮助我们确定评论的情感。评论可能是谈论他们如何热爱电影或他们如何不爱电影。但是如果评论足够长并且在文档中找到了更多否定词,那么我们可以采用可以帮助我们预测后续词语的整体语气。
Doc2vec 只是为文档添加了一个额外的嵌入矩阵,并使用一个单词窗口加上文档索引来预测下一个单词。文档中的所有文字窗口都具有相同的文档索引。值得一提的是,考虑如何将文档嵌入与单词嵌入相结合是很重要的。我们通过对它们求和来将单词嵌入组合在单词窗口中。将这些嵌入与文档嵌入相结合有两种主要方式:通常,文档嵌入要么添加到单词嵌入中,要么连接到单词嵌入的末尾。如果我们添加两个嵌入,我们将文档嵌入大小限制为与嵌入字大小相同的大小。如果我们连接,我们解除了这个限制,但增加了逻辑回归必须处理的变量数量。为了便于说明,我们将向您展示如何处理此秘籍中的连接。但总的来说,对于较小的数据集,添加是更好的选择。
第一步是将文档和单词嵌入适用于整个电影评论集。然后我们将进行训练测试分组,训练逻辑模型,看看我们是否可以更准确地预测评论情感。
操作步骤
我们将按如下方式处理秘籍:
- 我们将从加载必要的库并启动图会话开始,如下所示:
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import random
import os
import pickle
import string
import requests
import collections
import io
import tarfile
import urllib.request
import text_helpers
from nltk.corpus import stopwords
sess = tf.Session()
- 我们将加载电影评论语料库,就像我们在前两个秘籍中所做的那样。使用以下代码执行此操作:
texts, target = text_helpers.load_movie_data()
- 我们将声明模型参数,如下所示:
batch_size = 500
vocabulary_size = 7500
generations = 100000
model_learning_rate = 0.001
embedding_size = 200 # Word embedding size
doc_embedding_size = 100 # Document embedding size
concatenated_size = embedding_size + doc_embedding_size
num_sampled = int(batch_size/2)
window_size = 3 # How many words to consider to the left.
# Add checkpoints to training
save_embeddings_every = 5000
print_valid_every = 5000
print_loss_every = 100
# Declare stop words
stops = stopwords.words('english')
# We pick a few test words.
valid_words = ['love', 'hate', 'happy', 'sad', 'man', 'woman']
- 我们将正则化电影评论,并确保每个电影评论都大于所需的窗口大小。使用以下代码执行此操作:
texts = text_helpers.normalize_text(texts, stops)
# Texts must contain at least as much as the prior window size
target = [target[ix] for ix, x in enumerate(texts) if len(x.split()) > window_size]
texts = [x for x in texts if len(x.split()) > window_size]
assert(len(target)==len(texts))
- 现在我们将创建我们的单词字典。请务必注意,我们不必创建文档字典。文件索引只是文件的索引;每个文档都有一个唯一的索引:
word_dictionary = text_helpers.build_dictionary(texts, vocabulary_size)
word_dictionary_rev = dict(zip(word_dictionary.values(), word_dictionary.keys()))
text_data = text_helpers.text_to_numbers(texts, word_dictionary)
# Get validation word keys
valid_examples = [word_dictionary[x] for x in valid_words]
- 接下来,我们将定义单词嵌入和文档嵌入。然后我们将声明我们的噪声对比损失参数。使用以下代码执行此操作:
embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
doc_embeddings = tf.Variable(tf.random_uniform([len(texts), doc_embedding_size], -1.0, 1.0))
# NCE loss parameters
nce_weights = tf.Variable(tf.truncated_normal([vocabulary_size, concatenated_size],
stddev=1.0 / np.sqrt(concatenated_size)))
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
- 我们现在将声明 doc2vec 索引和目标词索引的占位符。请注意,输入索引的大小是窗口大小加 1。这是因为我们生成的每个数据窗口都有一个附加的文档索引,如下所示:
x_inputs = tf.placeholder(tf.int32, shape=[None, window_size + 1])
y_target = tf.placeholder(tf.int32, shape=[None, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
- 现在我们必须创建嵌入函数,它将单词嵌入加在一起,然后在最后连接文档嵌入。使用以下代码执行此操作:
embed = tf.zeros([batch_size, embedding_size])
for element in range(window_size):
embed += tf.nn.embedding_lookup(embeddings, x_inputs[:, element])
doc_indices = tf.slice(x_inputs, [0,window_size],[batch_size,1])
doc_embed = tf.nn.embedding_lookup(doc_embeddings,doc_indices)
# concatenate embeddings
final_embed = tf.concat(axis=1, values=)
- 我们还需要声明一组验证词的余弦距离,我们可以经常打印出来以观察 doc2vec 模型的进度。使用以下代码执行此操作:
loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weights,
biases=nce_biases,
labels=y_target,
inputs=final_embed,
num_sampled=num_sampled,
num_classes=vocabulary_size))
# Create optimizer
optimizer =
tf.train.GradientDescentOptimizer(learning_rate=model_learning_rate)
train_step = optimizer.minimize(loss)
- 我们还需要从一组验证单词中声明余弦距离,我们可以经常打印出来以观察 doc2vec 模型的进度。使用以下代码执行此操作:
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1,
keep_dims=True))
normalized_embeddings = embeddings / norm
valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings,
valid_dataset)
similarity = tf.matmul(valid_embeddings, normalized_embeddings,
transpose_b=True)
- 为了以后保存我们的嵌入,我们将创建一个模型
saver函数。然后我们可以初始化变量,这是我们开始训练单词嵌入之前的最后一步:
saver = tf.train.Saver({"embeddings": embeddings, "doc_embeddings":
doc_embeddings})
init = tf.global_variables_initializer()
sess.run(init)
loss_vec = []
loss_x_vec = []
for i in range(generations):
batch_inputs, batch_labels = text_helpers.generate_batch_data(text_data, batch_size,
window_size, method='doc2vec')
feed_dict = {x_inputs : batch_inputs, y_target : batch_labels}
# Run the train step
sess.run(train_step, feed_dict=feed_dict)
# Return the loss
if (i+1) % print_loss_every == 0:
loss_val = sess.run(loss, feed_dict=feed_dict)
loss_vec.append(loss_val)
loss_x_vec.append(i+1)
print('Loss at step {} : {}'.format(i+1, loss_val))
# Validation: Print some random words and top 5 related words
if (i+1) % print_valid_every == 0:
sim = sess.run(similarity, feed_dict=feed_dict)
for j in range(len(valid_words)):
valid_word = word_dictionary_rev[valid_examples[j]]
top_k = 5 # number of nearest neighbors
nearest = (-sim[j, :]).argsort()[1:top_k+1]
log_str = "Nearest to {}:".format(valid_word)
for k in range(top_k):
close_word = word_dictionary_rev[nearest[k]]
log_str = '{} {},'.format(log_str, close_word)
print(log_str)
# Save dictionary + embeddings
if (i+1) % save_embeddings_every == 0:
# Save vocabulary dictionary
with open(os.path.join(data_folder_name,'movie_vocab.pkl'), 'wb') as f:
pickle.dump(word_dictionary, f)
# Save embeddings
model_checkpoint_path = os.path.join(os.getcwd(),data_folder_name,'doc2vec_movie_embeddings.ckpt')
save_path = saver.save(sess, model_checkpoint_path)
print('Model saved in file: {}'.format(save_path))
- 这产生以下输出:
Loss at step 100 : 126.176816940307617
Loss at step 200 : 89.608322143554688
...
Loss at step 99900 : 17.733346939086914
Loss at step 100000 : 17.384489059448242
Nearest to love: ride, with, by, its, start,
Nearest to hate: redundant, snapshot, from, performances, extravagant,
Nearest to happy: queen, chaos, them, succumb, elegance,
Nearest to sad: terms, pity, chord, wallet, morality,
Nearest to man: of, teen, an, our, physical,
Nearest to woman: innocuous, scenes, prove, except, lady,
Model saved in file: /.../temp/doc2vec_movie_embeddings.ckpt
- 现在我们已经训练了 doc2vec 嵌入,我们可以在逻辑回归中使用这些嵌入来预测评论情感。首先,我们为逻辑回归设置了一些参数。使用以下代码执行此操作:
max_words = 20 # maximum review word length
logistic_batch_size = 500 # training batch size
- 我们现在将数据集拆分为训练集和测试集:
train_indices = np.sort(np.random.choice(len(target),
round(0.8*len(target)), replace=False))
test_indices = np.sort(np.array(list(set(range(len(target))) -
set(train_indices))))
texts_train = [x for ix, x in enumerate(texts) if ix in train_indices]
texts_test = [x for ix, x in enumerate(texts) if ix in test_indices]
target_train = np.array([x for ix, x in enumerate(target) if ix in train_indices])
target_test = np.array([x for ix, x in enumerate(target) if ix in test_indices])
- 接下来,我们将评论转换为数字单词索引,并将每个评论填充或裁剪为 20 个单词,如下所示:
text_data_train = np.array(text_helpers.text_to_numbers(texts_train, word_dictionary)) text_data_test = np.array(text_helpers.text_to_numbers(texts_test, word_dictionary)) # Pad/crop movie reviews to specific length text_data_train = np.array([x[0:max_words] for x in [y+[0]*max_words for y in text_data_train]]) text_data_test = np.array([x[0:max_words] for x in [y+[0]*max_words for y in text_data_test]])
- 现在我们将声明图中与逻辑回归模型相关的部分。我们将添加数据占位符,变量,模型操作和损失函数,如下所示:
# Define Logistic placeholders
log_x_inputs = tf.placeholder(tf.int32, shape=[None, max_words + 1])
log_y_target = tf.placeholder(tf.int32, shape=[None, 1])
A = tf.Variable(tf.random_normal(shape=[concatenated_size,1]))
b = tf.Variable(tf.random_normal(shape=[1,1]))
# Declare logistic model (sigmoid in loss function)
model_output = tf.add(tf.matmul(log_final_embed, A), b)
# Declare loss function (Cross Entropy loss)
logistic_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=model_output,
labels=tf.cast(log_y_target, tf.float32)))
- 我们需要创建另一个嵌入函数。前半部分中的嵌入函数在三个单词(和文档索引)的较小窗口上进行训练,以预测下一个单词。在这里,我们将采用相同的方式进行 20 字复习。使用以下代码执行此操作:
# Add together element embeddings in window:
log_embed = tf.zeros([logistic_batch_size, embedding_size])
for element in range(max_words):
log_embed += tf.nn.embedding_lookup(embeddings, log_x_inputs[:, element])
log_doc_indices = tf.slice(log_x_inputs, [0,max_words],[logistic_batch_size,1])
log_doc_embed = tf.nn.embedding_lookup(doc_embeddings,log_doc_indices)
# concatenate embeddings
log_final_embed = tf.concat(1, [log_embed, tf.squeeze(log_doc_embed)])
- 接下来,我们将在图上创建预测和准确率函数,以便我们可以在训练生成过程中评估模型的表现。然后我们将声明一个优化函数并初始化所有变量:
prediction = tf.round(tf.sigmoid(model_output))
predictions_correct = tf.cast(tf.equal(prediction, tf.cast(log_y_target, tf.float32)), tf.float32)
accuracy = tf.reduce_mean(predictions_correct)
# Declare optimizer
logistic_opt = tf.train.GradientDescentOptimizer(learning_rate=0.01)
logistic_train_step = logistic_opt.minimize(logistic_loss, var_list=[A, b])
# Intitialize Variables
init = tf.global_variables_initializer()
sess.run(init)
- 现在我们可以开始 Logistic 模型训练了:
train_loss = []
test_loss = []
train_acc = []
test_acc = []
i_data = []
for i in range(10000):
rand_index = np.random.choice(text_data_train.shape[0], size=logistic_batch_size)
rand_x = text_data_train[rand_index]
# Append review index at the end of text data
rand_x_doc_indices = train_indices[rand_index]
rand_x = np.hstack((rand_x, np.transpose([rand_x_doc_indices])))
rand_y = np.transpose([target_train[rand_index]])
feed_dict = {log_x_inputs : rand_x, log_y_target : rand_y}
sess.run(logistic_train_step, feed_dict=feed_dict)
# Only record loss and accuracy every 100 generations
if (i+1)%100==0:
rand_index_test = np.random.choice(text_data_test.shape[0], size=logistic_batch_size)
rand_x_test = text_data_test[rand_index_test]
# Append review index at the end of text data
rand_x_doc_indices_test = test_indices[rand_index_test]
rand_x_test = np.hstack((rand_x_test, np.transpose([rand_x_doc_indices_test])))
rand_y_test = np.transpose([target_test[rand_index_test]])
test_feed_dict = {log_x_inputs: rand_x_test, log_y_target: rand_y_test}
i_data.append(i+1)
train_loss_temp = sess.run(logistic_loss, feed_dict=feed_dict)
train_loss.append(train_loss_temp)
test_loss_temp = sess.run(logistic_loss, feed_dict=test_feed_dict)
test_loss.append(test_loss_temp)
train_acc_temp = sess.run(accuracy, feed_dict=feed_dict)
train_acc.append(train_acc_temp)
test_acc_temp = sess.run(accuracy, feed_dict=test_feed_dict)
test_acc.append(test_acc_temp)
if (i+1)%500==0:
acc_and_loss = [i+1, train_loss_temp, test_loss_temp, train_acc_temp, test_acc_temp]
acc_and_loss = [np.round(x,2) for x in acc_and_loss]
print('Generation # {}. Train Loss (Test Loss): {:.2f} ({:.2f}). Train Acc (Test Acc): {:.2f} ({:.2f})'.format(*acc_and_loss))
- 这产生以下输出:
Generation # 500\. Train Loss (Test Loss): 5.62 (7.45). Train Acc (Test Acc): 0.52 (0.48) Generation # 10000\. Train Loss (Test Loss): 2.35 (2.51). Train Acc (Test Acc): 0.59 (0.58)
- 我们还应该注意到,我们在名为 doc2vec 的
text_helpers.generate_batch_data()函数中创建了一个单独的数据批量生成方法,我们在本文的第一部分中使用它来训练 doc2vec 嵌入。以下是与该方法有关的该函数的摘录:
def generate_batch_data(sentences, batch_size, window_size, method='skip_gram'):
# Fill up data batch
batch_data = []
label_data = []
while len(batch_data) < batch_size:
# select random sentence to start
rand_sentence_ix = int(np.random.choice(len(sentences), size=1))
rand_sentence = sentences[rand_sentence_ix]
# Generate consecutive windows to look at
window_sequences = [rand_sentence[max((ix-window_size),0):(ix+window_size+1)] for ix, x in enumerate(rand_sentence)]
# Denote which element of each window is the center word of interest
label_indices = [ix if ix<window_size else window_size for ix,x in enumerate(window_sequences)]
# Pull out center word of interest for each window and create a tuple for each window
if method=='skip_gram':
...
elif method=='cbow':
...
elif method=='doc2vec':
# For doc2vec we keep LHS window only to predict target word
batch_and_labels = [(rand_sentence[i:i+window_size], rand_sentence[i+window_size]) for i in range(0, len(rand_sentence)-window_size)]
batch, labels = [list(x) for x in zip(*batch_and_labels)]
# Add document index to batch!! Remember that we must extract the last index in batch for the doc-index
batch = [x + [rand_sentence_ix] for x in batch]
else:
raise ValueError('Method {} not implmented yet.'.format(method))
# extract batch and labels
batch_data.extend(batch[:batch_size])
label_data.extend(labels[:batch_size])
# Trim batch and label at the end
batch_data = batch_data[:batch_size]
label_data = label_data[:batch_size]
# Convert to numpy array
batch_data = np.array(batch_data)
label_data = np.transpose(np.array([label_data]))
return batch_data, label_data
工作原理
在这个秘籍中,我们进行了两个训练循环。第一个是适合 doc2vec 嵌入,第二个循环是为了适应电影情感的逻辑回归。
虽然我们没有大幅度提高情感预测准确率(它仍然略低于 60%),但我们在电影语料库中成功实现了 doc2vec 的连接版本。为了提高我们的准确率,我们应该为 doc2vec 嵌入和可能更复杂的模型尝试不同的参数,因为逻辑回归可能无法捕获自然语言中的所有非线性行为。
八、卷积神经网络
卷积神经网络(CNN)负责过去几年中图像识别的重大突破。在本章中,我们将介绍以下主题:
- 实现简单的 CNN
- 实现高级的 CNN
- 重新训练现有的 CNN 模型
- 应用 Stylenet 和神经式项目
- 实现 DeepDream
介绍
在数学中,卷积是应用于另一个函数的输出的函数。在我们的例子中,我们将考虑在图像上应用矩阵乘法(滤波器)。出于我们的目的,我们将图像视为数字矩阵。这些数字可以表示像素或甚至图像属性。我们将应用于这些矩阵的卷积运算包括在图像上移动固定宽度的滤波器并应用逐元素乘法来得到我们的结果。
有关图像卷积如何工作的概念性理解,请参见下图:

图 1:如何在图像上应用卷积滤镜(长度与宽度之间的深度),以创建新的特征层。这里,我们有一个2x2卷积滤波器,在5x5输入的有效空间中操作,两个方向的步幅为 1。结果是4x4矩阵
CNN 还具有满足更多要求的其他操作,例如引入非线性(ReLU)或聚合参数(最大池化)以及其他类似操作。上图是在5x5数组上应用卷积运算的示例,其中卷积滤波器是2x2矩阵。步长为 1,我们只考虑有效的展示位置。此操作中的可训练变量将是2x2滤波器权重。在卷积之后,通常会跟进聚合操作,例如最大池化。如果我们在两个方向上采用步幅为 2 的2x2区域的最大值,下图提供了最大池如何操作的示例:

图 2:最大池化操作如何运行的示例。这里,我们有一个2x2窗口,在4x4输入的有效空间上操作,两个方向的步幅为 2。结果是2x2矩阵
虽然我们将首先创建自己的 CNN 进行图像识别,但强烈建议您使用现有的架构,我们将在本章的其余部分中进行操作。
通常采用预先训练好的网络并使用新数据集对其进行重新训练,并在最后使用新的完全连接层。这种方法非常有用,我们将在重新训练现有的 CNN 模型秘籍中进行说明,我们将重新训练现有的架构以改进我们的 CIFAR-10 预测。
实现简单的 CNN
在本文中,我们将开发一个四层卷积神经网络,以提高我们预测 MNIST 数字的准确率。前两个卷积层将各自由卷积-ReLU-最大池化操作组成,最后两个层将是完全连接的层。
准备
为了访问 MNIST 数据,TensorFlow 有一个examples.tutorials包,它具有很好的数据集加载函数。加载数据后,我们将设置模型变量,创建模型,批量训练模型,然后可视化损失,准确率和一些样本数字。
操作步骤
执行以下步骤:
- 首先,我们将加载必要的库并启动图会话:
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow.python.framework import ops
ops.reset_default_graph()
sess = tf.Session()
- 接下来,我们将加载数据并将图像转换为
28x28数组:
data_dir = 'temp'
mnist = input_data.read_data_sets(data_dir, one_hot=False)
train_xdata = np.array([np.reshape(x, (28,28)) for x in mnist.train.images])
test_xdata = np.array([np.reshape(x, (28,28)) for x in mnist.test.images])
train_labels = mnist.train.labels
test_labels = mnist.test.labels
请注意,此处下载的 MNIST 数据集还包括验证集。此验证集通常与测试集的大小相同。如果我们进行任何超参数调整或模型选择,最好将其加载到其他测试中。
- 现在我们将设置模型参数。请记住,图像的深度(通道数)为 1,因为这些图像是灰度的:
batch_size = 100
learning_rate = 0.005
evaluation_size = 500
image_width = train_xdata[0].shape[0]
image_height = train_xdata[0].shape[1]
target_size = max(train_labels) + 1
num_channels = 1
generations = 500
eval_every = 5
conv1_features = 25
conv2_features = 50
max_pool_size1 = 2
max_pool_size2 = 2
fully_connected_size1 = 100
- 我们现在可以声明数据的占位符。我们将声明我们的训练数据变量和测试数据变量。我们将针对训练和评估规模使用不同的批量大小。您可以根据可用于训练和评估的物理内存来更改这些内容:
x_input_shape = (batch_size, image_width, image_height, num_channels)
x_input = tf.placeholder(tf.float32, shape=x_input_shape)
y_target = tf.placeholder(tf.int32, shape=(batch_size))
eval_input_shape = (evaluation_size, image_width, image_height, num_channels)
eval_input = tf.placeholder(tf.float32, shape=eval_input_shape)
eval_target = tf.placeholder(tf.int32, shape=(evaluation_size))
- 我们将使用我们在前面步骤中设置的参数声明我们的卷积权重和偏差:
conv1_weight = tf.Variable(tf.truncated_normal([4, 4, num_channels, conv1_features], stddev=0.1, dtype=tf.float32))
conv1_bias = tf.Variable(tf.zeros([conv1_features],dtype=tf.float32))
conv2_weight = tf.Variable(tf.truncated_normal([4, 4, conv1_features, conv2_features], stddev=0.1, dtype=tf.float32))
conv2_bias = tf.Variable(tf.zeros([conv2_features],dtype=tf.float32))
- 接下来,我们将为模型的最后两层声明完全连接的权重和偏差:
resulting_width = image_width // (max_pool_size1 * max_pool_size2)
resulting_height = image_height // (max_pool_size1 * max_pool_size2)
full1_input_size = resulting_width * resulting_height*conv2_features
full1_weight = tf.Variable(tf.truncated_normal([full1_input_size, fully_connected_size1], stddev=0.1, dtype=tf.float32))
full1_bias = tf.Variable(tf.truncated_normal([fully_connected_size1], stddev=0.1, dtype=tf.float32))
full2_weight = tf.Variable(tf.truncated_normal([fully_connected_size1, target_size], stddev=0.1, dtype=tf.float32))
full2_bias = tf.Variable(tf.truncated_normal([target_size], stddev=0.1, dtype=tf.float32))
- 现在我们将宣布我们的模型。我们首先创建一个模型函数。请注意,该函数将在全局范围内查找所需的层权重和偏差。此外,为了使完全连接的层工作,我们将第二个卷积层的输出展平,这样我们就可以在完全连接的层中使用它:
def my_conv_net(input_data):
# First Conv-ReLU-MaxPool Layer
conv1 = tf.nn.conv2d(input_data, conv1_weight, strides=[1, 1, 1, 1], padding='SAME')
relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_bias))
max_pool1 = tf.nn.max_pool(relu1, ksize=[1, max_pool_size1, max_pool_size1, 1], strides=[1, max_pool_size1, max_pool_size1, 1], padding='SAME')
# Second Conv-ReLU-MaxPool Layer
conv2 = tf.nn.conv2d(max_pool1, conv2_weight, strides=[1, 1, 1, 1], padding='SAME')
relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_bias))
max_pool2 = tf.nn.max_pool(relu2, ksize=[1, max_pool_size2, max_pool_size2, 1], strides=[1, max_pool_size2, max_pool_size2, 1], padding='SAME')
# Transform Output into a 1xN layer for next fully connected layer
final_conv_shape = max_pool2.get_shape().as_list()
final_shape = final_conv_shape[1] * final_conv_shape[2] * final_conv_shape[3]
flat_output = tf.reshape(max_pool2, [final_conv_shape[0], final_shape])
# First Fully Connected Layer
fully_connected1 = tf.nn.relu(tf.add(tf.matmul(flat_output, full1_weight), full1_bias))
# Second Fully Connected Layer
final_model_output = tf.add(tf.matmul(fully_connected1, full2_weight), full2_bias)
return final_model_output
- 接下来,我们可以在训练和测试数据上声明模型:
model_output = my_conv_net(x_input)
test_model_output = my_conv_net(eval_input)
- 我们将使用的损失函数是 softmax 函数。我们使用稀疏 softmax,因为我们的预测只是一个类别,而不是多个类别。我们还将使用一个对对率而不是缩放概率进行操作的损失函数:
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=model_output, labels=y_target))
- 接下来,我们将创建一个训练和测试预测函数。然后我们还将创建一个准确率函数来确定模型在每个批次上的准确率:
prediction = tf.nn.softmax(model_output)
test_prediction = tf.nn.softmax(test_model_output)
# Create accuracy function
def get_accuracy(logits, targets):
batch_predictions = np.argmax(logits, axis=1)
num_correct = np.sum(np.equal(batch_predictions, targets))
return 100\. * num_correct/batch_predictions.shape[0]
- 现在我们将创建我们的优化函数,声明训练步骤,并初始化所有模型变量:
my_optimizer = tf.train.MomentumOptimizer(learning_rate, 0.9)
train_step = my_optimizer.minimize(loss)
# Initialize Variables
init = tf.global_variables_initializer()
sess.run(init)
- 我们现在可以开始训练我们的模型。我们以随机选择的批次循环数据。我们经常选择在训练上评估模型并测试批次并记录准确率和损失。我们可以看到,经过 500 代,我们可以在测试数据上快速达到 96%-97% 的准确率:
train_loss = []
train_acc = []
test_acc = []
for i in range(generations):
rand_index = np.random.choice(len(train_xdata), size=batch_size)
rand_x = train_xdata[rand_index]
rand_x = np.expand_dims(rand_x, 3)
rand_y = train_labels[rand_index]
train_dict = {x_input: rand_x, y_target: rand_y}
sess.run(train_step, feed_dict=train_dict)
temp_train_loss, temp_train_preds = sess.run([loss, prediction], feed_dict=train_dict)
temp_train_acc = get_accuracy(temp_train_preds, rand_y)
if (i+1) % eval_every == 0:
eval_index = np.random.choice(len(test_xdata), size=evaluation_size)
eval_x = test_xdata[eval_index]
eval_x = np.expand_dims(eval_x, 3)
eval_y = test_labels[eval_index]
test_dict = {eval_input: eval_x, eval_target: eval_y}
test_preds = sess.run(test_prediction, feed_dict=test_dict)
temp_test_acc = get_accuracy(test_preds, eval_y)
# Record and print results
train_loss.append(temp_train_loss)
train_acc.append(temp_train_acc)
test_acc.append(temp_test_acc)
acc_and_loss = [(i+1), temp_train_loss, temp_train_acc, temp_test_acc]
acc_and_loss = [np.round(x,2) for x in acc_and_loss]
print('Generation # {}. Train Loss: {:.2f}. Train Acc (Test Acc): {:.2f} ({:.2f})'.format(*acc_and_loss))
- 这产生以下输出:
Generation # 5\. Train Loss: 2.37\. Train Acc (Test Acc): 7.00 (9.80)
Generation # 10\. Train Loss: 2.16\. Train Acc (Test Acc): 31.00 (22.00)
Generation # 15\. Train Loss: 2.11\. Train Acc (Test Acc): 36.00 (35.20)
...
Generation # 490\. Train Loss: 0.06\. Train Acc (Test Acc): 98.00 (97.40)
Generation # 495\. Train Loss: 0.10\. Train Acc (Test Acc): 98.00 (95.40)
Generation # 500\. Train Loss: 0.14\. Train Acc (Test Acc): 98.00 (96.00)
- 以下是使用
Matplotlib绘制损耗和精度的代码:
eval_indices = range(0, generations, eval_every)
# Plot loss over time
plt.plot(eval_indices, train_loss, 'k-')
plt.title('Softmax Loss per Generation')
plt.xlabel('Generation')
plt.ylabel('Softmax Loss')
plt.show()
# Plot train and test accuracy
plt.plot(eval_indices, train_acc, 'k-', label='Train Set Accuracy')
plt.plot(eval_indices, 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()
然后我们得到以下图:

图 3:左图是我们 500 代训练中的训练和测试集精度。右图是超过 500 代的 softmax 损失值。
- 如果我们想要绘制最新批次结果的样本,下面是绘制由六个最新结果组成的样本的代码:
# Plot the 6 of the last batch results:
actuals = rand_y[0:6]
predictions = np.argmax(temp_train_preds,axis=1)[0:6]
images = np.squeeze(rand_x[0:6])
Nrows = 2
Ncols = 3
for i in range(6):
plt.subplot(Nrows, Ncols, i+1)
plt.imshow(np.reshape(images[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)
我们得到前面代码的以下输出:

图 4:六个随机图像的绘图,标题中包含实际值和预测值。右下图预计是 3,而事实上它是 1
工作原理
我们提高了 MNIST 数据集的表现,并构建了一个模型,在从头开始训练时,可快速达到约 97% 的准确率。我们的前两层是卷积,ReLU 和最大池化的组合。第二层是完全连接的层。我们以 100 个批次进行了训练,并研究了我们训练的几代的准确率和损失。最后,我们还绘制了六个随机数字和每个数字的预测/实际值。
CNN 非常适合图像识别。造成这种情况的部分原因是卷积层创建了自己的低级特征,当它们遇到重要的部分图像时会被激活。这种类型的模型自己创建特征并将其用于预测。
更多
在过去几年中,CNN 模型在图像识别方面取得了巨大进步。正在探索许多新颖的想法,并且经常发现新的架构。该领域的一个很好的论文库是一个名为 Arxiv.org 的仓库网站,由康奈尔大学创建和维护。 Arxiv.org 包括许多领域的一些最新论文,包括计算机科学和计算机科学子领域,如计算机视觉和图像识别。
另见
以下列出了一些可用于了解 CNN 的优秀资源:
实现高级的 CNN
能够扩展 CNN 模型以进行图像识别非常重要,这样我们才能理解如何增加网络的深度。如果我们有足够的数据,这可能会提高我们预测的准确率。扩展 CNN 网络的深度是以标准方式完成的:我们只是重复卷积,最大池和 ReLU,直到我们对深度感到满意为止。许多更精确的图像识别网络以这种方式操作。
准备
在本文中,我们将实现一种更先进的读取图像数据的方法,并使用更大的 CNN 在 CIFAR10 数据集上进行图像识别。该数据集具有 60,000 个32x32图像,这些图像恰好属于十个可能类别中的一个。图像的潜在类别是飞机,汽车,鸟,猫,鹿,狗,青蛙,马,船和卡车。另见“另见”部分中的第一个要点。
大多数图像数据集太大而无法放入内存中。我们可以使用 TensorFlow 设置一个图像管道,一次从一个文件中一次读取。我们通过设置图像阅读器,然后创建在图像阅读器上运行的批量队列来完成此操作。
此外,对于图像识别数据,通常在将图像发送之前随机扰动图像以进行训练。在这里,我们将随机裁剪,翻转和更改亮度。
此秘籍是TensorFlow CIFAR-10 官方教程的改编版本,可在本章末尾的“另见”部分中找到。我们将教程浓缩为一个脚本,我们将逐行完成并解释所有必要的代码。我们还将一些常量和参数恢复为原始引用的纸张值;我们将在适当的步骤中标记这一点。
操作步骤
执行以下步骤:
- 首先,我们加载必要的库并启动图会话:
import os
import sys
import tarfile
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from six.moves import urllib
sess = tf.Session()
- 现在我们将声明一些模型参数。我们的批量大小为 128(用于训练和测试)。我们将每 50 代输出一次状态,总共运行 20,000 代。每 500 代,我们将评估一批测试数据。然后我们将声明一些图像参数,高度和宽度,以及随机裁剪图像的大小。有三个通道(红色,绿色和蓝色),我们有十个不同的目标。然后我们将声明我们将从队列中存储数据和图像批次的位置:
batch_size = 128
output_every = 50
generations = 20000
eval_every = 500
image_height = 32
image_width = 32
crop_height = 24
crop_width = 24
num_channels = 3
num_targets = 10
data_dir = 'temp'
extract_folder = 'cifar-10-batches-bin'
- 建议您在我们向好的模型迈进时降低学习率,因此我们将以指数方式降低学习率:初始学习率将设置为 0.1,并且我们将以 250% 的指数方式将其降低 10% 代。确切的公式将由
0.1 · 0.9^(x / 250)给出,其中x是当前世代号。默认情况下,此值会持续降低,但 TensorFlow 会接受仅更新学习率的阶梯参数。这里我们设置一些参数供将来使用:
learning_rate = 0.1
lr_decay = 0.9
num_gens_to_wait = 250\.
- 现在我们将设置参数,以便我们可以读取二进制 CIFAR-10 图像:
image_vec_length = image_height * image_width * num_channels
record_length = 1 + image_vec_length
- 接下来,我们将设置数据目录和 URL 以下载 CIFAR-10 图像,如果我们还没有它们:
data_dir = 'temp'
if not os.path.exists(data_dir):
os.makedirs(data_dir)
cifar10_url = 'http://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz'
data_file = os.path.join(data_dir, 'cifar-10-binary.tar.gz')
if not os.path.isfile(data_file):
# Download file
filepath, _ = urllib.request.urlretrieve(cifar10_url, data_file)
# Extract file
tarfile.open(filepath, 'r:gz').extractall(data_dir)
- 我们将设置记录阅读器并使用以下
read_cifar_files()函数返回随机失真的图像。首先,我们需要声明一个读取固定字节长度的记录读取器对象。在我们读取图像队列之后,我们将图像和标签分开。最后,我们将使用 TensorFlow 的内置图像修改函数随机扭曲图像:
def read_cifar_files(filename_queue, distort_images = True):
reader = tf.FixedLengthRecordReader(record_bytes=record_length)
key, record_string = reader.read(filename_queue)
record_bytes = tf.decode_raw(record_string, tf.uint8)
# Extract label
image_label = tf.cast(tf.slice(record_bytes, [0], [1]), tf.int32)
# Extract image
image_extracted = tf.reshape(tf.slice(record_bytes, [1], [image_vec_length]), [num_channels, image_height, image_width])
# Reshape image
image_uint8image = tf.transpose(image_extracted, [1, 2, 0])
reshaped_image = tf.cast(image_uint8image, tf.float32)
# Randomly Crop image
final_image = tf.image.resize_image_with_crop_or_pad(reshaped_image, crop_width, crop_height)
if distort_images:
# Randomly flip the image horizontally, change the brightness and contrast
final_image = tf.image.random_flip_left_right(final_image)
final_image = tf.image.random_brightness(final_image,max_delta=63)
final_image = tf.image.random_contrast(final_image,lower=0.2, upper=1.8)
# Normalize whitening
final_image = tf.image.per_image_standardization(final_image)
return final_image, image_label
- 现在我们将声明一个函数,它将填充我们的图像管道以供批量器使用。我们首先需要设置一个我们想要读取的图像文件列表,并定义如何使用通过预构建的 TensorFlow 函数创建的输入生成器对象来读取它们。输入生成器可以传递给我们在上一步中创建的读取函数:
read_cifar_files()。然后我们将在队列中设置批量阅读器:shuffle_batch():
def input_pipeline(batch_size, train_logical=True):
if train_logical:
files = [os.path.join(data_dir, extract_folder, 'data_batch_{}.bin'.format(i)) for i in range(1,6)]
else:
files = [os.path.join(data_dir, extract_folder, 'test_batch.bin')]
filename_queue = tf.train.string_input_producer(files)
image, label = read_cifar_files(filename_queue)
min_after_dequeue = 1000
capacity = min_after_dequeue + 3 * batch_size
example_batch, label_batch = tf.train.shuffle_batch([image, label], batch_size, capacity, min_after_dequeue)
return example_batch, label_batch
正确设置
min_after_dequeue很重要。此参数负责设置用于采样的图像缓冲区的最小大小。TensorFlow 官方文档建议将其设置为(#threads + error margin)*batch_size。请注意,将其设置为更大的大小会导致更均匀的混洗,因为它正在从队列中的更大数据集进行混洗,但是在此过程中也将使用更多内存。
- 接下来,我们可以声明我们的模型函数。我们将使用的模型有两个卷积层,后面是三个完全连接的层。为了使变量声明更容易,我们首先声明两个变量函数。两个卷积层将分别创建 64 个特征。第一个完全连接的层将第二个卷积层与 384 个隐藏节点连接起来。第二个完全连接的操作将这 384 个隐藏节点连接到 192 个隐藏节点。最后的隐藏层操作将 192 个节点连接到我们试图预测的 10 个输出类。请参阅以下
#前面的内联注释:
def cifar_cnn_model(input_images, batch_size, train_logical=True):
def truncated_normal_var(name, shape, dtype):
return tf.get_variable(name=name, shape=shape, dtype=dtype, initializer=tf.truncated_normal_initializer(stddev=0.05))
def zero_var(name, shape, dtype):
return tf.get_variable(name=name, shape=shape, dtype=dtype, initializer=tf.constant_initializer(0.0))
# First Convolutional Layer
with tf.variable_scope('conv1') as scope:
# Conv_kernel is 5x5 for all 3 colors and we will create 64 features
conv1_kernel = truncated_normal_var(name='conv_kernel1', shape=[5, 5, 3, 64], dtype=tf.float32)
# We convolve across the image with a stride size of 1
conv1 = tf.nn.conv2d(input_images, conv1_kernel, [1, 1, 1, 1], padding='SAME')
# Initialize and add the bias term
conv1_bias = zero_var(name='conv_bias1', shape=[64], dtype=tf.float32)
conv1_add_bias = tf.nn.bias_add(conv1, conv1_bias)
# ReLU element wise
relu_conv1 = tf.nn.relu(conv1_add_bias)
# Max Pooling
pool1 = tf.nn.max_pool(relu_conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],padding='SAME', name='pool_layer1')
# Local Response Normalization
norm1 = tf.nn.lrn(pool1, depth_radius=5, bias=2.0, alpha=1e-3, beta=0.75, name='norm1')
# Second Convolutional Layer
with tf.variable_scope('conv2') as scope:
# Conv kernel is 5x5, across all prior 64 features and we create 64 more features
conv2_kernel = truncated_normal_var(name='conv_kernel2', shape=[5, 5, 64, 64], dtype=tf.float32)
# Convolve filter across prior output with stride size of 1
conv2 = tf.nn.conv2d(norm1, conv2_kernel, [1, 1, 1, 1], padding='SAME')
# Initialize and add the bias
conv2_bias = zero_var(name='conv_bias2', shape=[64], dtype=tf.float32)
conv2_add_bias = tf.nn.bias_add(conv2, conv2_bias)
# ReLU element wise
relu_conv2 = tf.nn.relu(conv2_add_bias)
# Max Pooling
pool2 = tf.nn.max_pool(relu_conv2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool_layer2')
# Local Response Normalization (parameters from paper)
norm2 = tf.nn.lrn(pool2, depth_radius=5, bias=2.0, alpha=1e-3, beta=0.75, name='norm2')
# Reshape output into a single matrix for multiplication for the fully connected layers
reshaped_output = tf.reshape(norm2, [batch_size, -1])
reshaped_dim = reshaped_output.get_shape()[1].value
# First Fully Connected Layer
with tf.variable_scope('full1') as scope:
# Fully connected layer will have 384 outputs.
full_weight1 = truncated_normal_var(name='full_mult1', shape=[reshaped_dim, 384], dtype=tf.float32)
full_bias1 = zero_var(name='full_bias1', shape=[384], dtype=tf.float32)
full_layer1 = tf.nn.relu(tf.add(tf.matmul(reshaped_output, full_weight1), full_bias1))
# Second Fully Connected Layer
with tf.variable_scope('full2') as scope:
# Second fully connected layer has 192 outputs.
full_weight2 = truncated_normal_var(name='full_mult2', shape=[384, 192], dtype=tf.float32)
full_bias2 = zero_var(name='full_bias2', shape=[192], dtype=tf.float32)
full_layer2 = tf.nn.relu(tf.add(tf.matmul(full_layer1, full_weight2), full_bias2))
# Final Fully Connected Layer -> 10 categories for output (num_targets)
with tf.variable_scope('full3') as scope:
# Final fully connected layer has 10 (num_targets) outputs.
full_weight3 = truncated_normal_var(name='full_mult3', shape=[192, num_targets], dtype=tf.float32)
full_bias3 = zero_var(name='full_bias3', shape=[num_targets], dtype=tf.float32)
final_output = tf.add(tf.matmul(full_layer2, full_weight3), full_bias3)
return final_output
我们的本地响应标准化参数取自本文,并在本文的“另见”部分中引用。
- 现在我们将创建损失函数。我们将使用 softmax 函数,因为图片只能占用一个类别,因此输出应该是十个目标的概率分布:
def cifar_loss(logits, targets):
# Get rid of extra dimensions and cast targets into integers
targets = tf.squeeze(tf.cast(targets, tf.int32))
# Calculate cross entropy from logits and targets
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=targets)
# Take the average loss across batch size
cross_entropy_mean = tf.reduce_mean(cross_entropy)
return cross_entropy_mean
- 接下来,我们宣布我们的训练步骤。学习率将以指数阶跃函数降低:
def train_step(loss_value, generation_num):
# Our learning rate is an exponential decay (stepped down)
model_learning_rate = tf.train.exponential_decay(learning_rate, generation_num, num_gens_to_wait, lr_decay, staircase=True)
# Create optimizer
my_optimizer = tf.train.GradientDescentOptimizer(model_learning_rate)
# Initialize train step
train_step = my_optimizer.minimize(loss_value)
return train_step
- 我们还必须具有精确度函数,以计算一批图像的准确率。我们将输入对率目标向量,并输出平均精度。然后我们可以将它用于训练和测试批次:
def accuracy_of_batch(logits, targets):
# Make sure targets are integers and drop extra dimensions
targets = tf.squeeze(tf.cast(targets, tf.int32))
# Get predicted values by finding which logit is the greatest
batch_predictions = tf.cast(tf.argmax(logits, 1), tf.int32)
# Check if they are equal across the batch
predicted_correctly = tf.equal(batch_predictions, targets)
# Average the 1's and 0's (True's and False's) across the batch size
accuracy = tf.reduce_mean(tf.cast(predicted_correctly, tf.float32))
return accuracy
- 现在我们有了一个图像管道函数,我们可以初始化训练图像管道和测试图像管道:
images, targets = input_pipeline(batch_size, train_logical=True)
test_images, test_targets = input_pipeline(batch_size, train_logical=False)
- 接下来,我们将初始化训练输出和测试输出的模型。值得注意的是,我们必须在创建训练模型后声明
scope.reuse_variables(),这样,当我们为测试网络声明模型时,它将使用相同的模型参数:
with tf.variable_scope('model_definition') as scope:
# Declare the training network model
model_output = cifar_cnn_model(images, batch_size)
# Use same variables within scope
scope.reuse_variables()
# Declare test model output
test_output = cifar_cnn_model(test_images, batch_size)
- 我们现在可以初始化我们的损耗和测试精度函数。然后我们将声明
generation变量。此变量需要声明为不可训练,并传递给我们的训练函数,该函数在学习率指数衰减计算中使用它:
loss = cifar_loss(model_output, targets)
accuracy = accuracy_of_batch(test_output, test_targets)
generation_num = tf.Variable(0, trainable=False)
train_op = train_step(loss, generation_num)
- 我们现在将初始化所有模型的变量,然后通过运行 TensorFlow 函数
start_queue_runners()来启动图像管道。当我们开始训练或测试模型输出时,管道将输入一批图像来代替饲料字典:
init = tf.global_variables_initializer()
sess.run(init)
tf.train.start_queue_runners(sess=sess)
- 我们现在循环训练我们的训练,节省训练损失和测试准确率:
train_loss = []
test_accuracy = []
for i in range(generations):
_, loss_value = sess.run([train_op, loss])
if (i+1) % output_every == 0:
train_loss.append(loss_value)
output = 'Generation {}: Loss = {:.5f}'.format((i+1), loss_value)
print(output)
if (i+1) % eval_every == 0:
[temp_accuracy] = sess.run([accuracy])
test_accuracy.append(temp_accuracy)
acc_output = ' --- Test Accuracy= {:.2f}%.'.format(100\. * temp_accuracy)
print(acc_output)
- 这产生以下输出:
...
Generation 19500: Loss = 0.04461
--- Test Accuracy = 80.47%.
Generation 19550: Loss = 0.01171
Generation 19600: Loss = 0.06911
Generation 19650: Loss = 0.08629
Generation 19700: Loss = 0.05296
Generation 19750: Loss = 0.03462
Generation 19800: Loss = 0.03182
Generation 19850: Loss = 0.07092
Generation 19900: Loss = 0.11342
Generation 19950: Loss = 0.08751
Generation 20000: Loss = 0.02228
--- Test Accuracy = 83.59%.
- 最后,这里有一些
matplotlib代码将绘制在训练过程中的损失和测试准确率:
eval_indices = range(0, generations, eval_every)
output_indices = range(0, generations, output_every)
# Plot loss over time
plt.plot(output_indices, train_loss, 'k-')
plt.title('Softmax Loss per Generation')
plt.xlabel('Generation')
plt.ylabel('Softmax Loss')
plt.show()
# Plot accuracy over time
plt.plot(eval_indices, test_accuracy, 'k-')
plt.title('Test Accuracy')
plt.xlabel('Generation')
plt.ylabel('Accuracy')
plt.show()
我们得到以下秘籍的以下绘图:

图 5:训练损失在左侧,测试精度在右侧。对于 CIFAR-10 图像识别 CNN,我们能够实现在测试集上达到约 75% 准确率的模型
工作原理
在我们下载了 CIFAR-10 数据之后,我们建立了一个图像管道而不是使用源字典。有关图像管道的更多信息,请参阅 TensorFlow CIFAR-10 官方教程。我们使用此训练和测试管道来尝试预测图像的正确类别。最后,该模型在测试集上达到了约 75% 的准确率。
另见
- 有关 CIFAR-10 数据集的更多信息,请参阅学习 Tiny Images 的多个特征层,Alex Krizhevsky,2009
- 要查看原始的 TensorFlow 代码,请参阅此链接
- 有关局部响应归一化的更多信息,请参阅使用深度卷积神经网络的 ImageNet 分类,Krizhevsky,A. 等人,2012
重新训练现有的 CNN 模型
从头开始训练新的图像识别需要大量的时间和计算能力。如果我们可以采用先前训练的网络并使用我们的图像重新训练它,它可以节省我们的计算时间。对于此秘籍,我们将展示如何使用预先训练的 TensorFlow 图像识别模型并对其进行微调以处理不同的图像集。
准备
其思想是从卷积层重用先前模型的权重和结构,并重新训练网络顶部的完全连接层。
TensorFlow 在现有 CNN 模型的基础上创建了一个关于训练的教程(请参阅下一节中的第一个要点)。在本文中,我们将说明如何对 CIFAR-10 使用相同的方法。我们将采用的 CNN 网络使用一种非常流行的架构,称为 Inception。 Inception CNN 模型由 Google 创建,在许多图像识别基准测试中表现非常出色。有关详细信息,请参阅“另见”部分的第二个要点中的纸张参考。
我们将介绍的主要 Python 脚本显示如何下载 CIFAR-10 图像数据并自动分离,标记和保存图像到每个训练和测试文件夹中的十个类。之后,我们将重申如何在我们的图像上训练网络。
操作步骤
执行以下步骤:
- 我们首先加载必要的库来下载,解压缩和保存 CIFAR-10 图像:
import os
import tarfile
import _pickle as cPickle
import numpy as np
import urllib.request
import scipy.misc
from imageio import imwrite
- 我们现在声明 CIFAR-10 数据链接并创建我们将存储数据的临时目录。我们还将在以后保存图像时声明要引用的十个类别:
cifar_link = 'https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz'
data_dir = 'temp'
if not os.path.isdir(data_dir):
os.makedirs(data_dir)
objects = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
- 现在我们将下载 CIFAR-10
.tar数据文件,并解压该文件:
target_file = os.path.join(data_dir, 'cifar-10-python.tar.gz')
if not os.path.isfile(target_file):
print('CIFAR-10 file not found. Downloading CIFAR data (Size = 163MB)')
print('This may take a few minutes, please wait.')
filename, headers = urllib.request.urlretrieve(cifar_link, target_file)
# Extract into memory
tar = tarfile.open(target_file)
tar.extractall(path=data_dir)
tar.close()
- 我们现在为训练创建必要的文件夹结构。临时目录将有两个文件夹,
train_dir和validation_dir。在每个文件夹中,我们将为每个类别创建 10 个子文件夹:
# Create train image folders
train_folder = 'train_dir'
if not os.path.isdir(os.path.join(data_dir, train_folder)):
for i in range(10):
folder = os.path.join(data_dir, train_folder, objects[i])
os.makedirs(folder)
# Create test image folders
test_folder = 'validation_dir'
if not os.path.isdir(os.path.join(data_dir, test_folder)):
for i in range(10):
folder = os.path.join(data_dir, test_folder, objects[i])
os.makedirs(folder)
- 为了保存图像,我们将创建一个从内存加载它们并将它们存储在图像字典中的函数:
def load_batch_from_file(file):
file_conn = open(file, 'rb')
image_dictionary = cPickle.load(file_conn, encoding='latin1')
file_conn.close()
return(image_dictionary)
- 使用前面的字典,我们将使用以下函数将每个文件保存在正确的位置:
def save_images_from_dict(image_dict, folder='data_dir'):
for ix, label in enumerate(image_dict['labels']):
folder_path = os.path.join(data_dir, folder, objects[label])
filename = image_dict['filenames'][ix]
#Transform image data
image_array = image_dict['data'][ix]
image_array.resize([3, 32, 32])
# Save image
output_location = os.path.join(folder_path, filename)
imwrite(output_location,image_array.transpose())
- 使用上述函数,我们可以遍历下载的数据文件并将每个图像保存到正确的位置:
data_location = os.path.join(data_dir, 'cifar-10-batches-py')
train_names = ['data_batch_' + str(x) for x in range(1,6)]
test_names = ['test_batch']
# Sort train images
for file in train_names:
print('Saving images from file: {}'.format(file))
file_location = os.path.join(data_dir, 'cifar-10-batches-py', file)
image_dict = load_batch_from_file(file_location)
save_images_from_dict(image_dict, folder=train_folder)
# Sort test images
for file in test_names:
print('Saving images from file: {}'.format(file))
file_location = os.path.join(data_dir, 'cifar-10-batches-py', file)
image_dict = load_batch_from_file(file_location)
save_images_from_dict(image_dict, folder=test_folder)
- 我们脚本的最后一部分创建了图像标签文件,这是我们需要的最后一条信息。这个文件让我们将输出解释为标签而不是数字索引:
cifar_labels_file = os.path.join(data_dir,'cifar10_labels.txt')
print('Writing labels file, {}'.format(cifar_labels_file))
with open(cifar_labels_file, 'w') as labels_file:
for item in objects:
labels_file.write("{}n".format(item))
- 当前面的脚本运行时,它将下载图像并将它们分类到 TensorFlow 再训练教程所期望的正确文件夹结构中。完成后,我们只需按照教程进行操作即可。首先,我们应该克隆教程仓库:
git clone https://github.com/tensorflow/models/tree/master/research/inception
- 为了使用先前训练的模型,我们必须下载网络权重并将其应用于我们的模型。为此,您必须访问该站点,并按照说明下载并安装 cifar10 模型架构和权重。您还将最终下载包含下面描述的构建,训练和测试脚本的数据目录。
对于此步骤,我们导航到
research/inception/inception目录,然后执行以下命令,--train_directory,--validation_directory,--output_directory和--labels_file的路径指向相对路径或完整路径创建的目录结构。
- 现在我们将图像放在正确的文件夹结构中,我们必须将它们变成
TFRecords对象。我们通过运行以下命令来完成此操作:
me@computer:~$ python3 data/build_image_data.py
--train_directory="temp/train_dir/"
--validation_directory="temp/validation_dir"
--output_directory="temp/" --labels_file="temp/cifar10_labels.txt"
- 现在我们将使用
bazel训练模型,将参数设置为true。该脚本每 10 代输出一次损失。我们可以随时终止此过程,模型输出将在temp/training_results文件夹中。我们可以从此文件夹加载模型以进行评估:
me@computer:~$ bazel-bin/inception/flowers_train
--train_dir="temp/training_results" --data_dir="temp/data_dir"
--pretrained_model_checkpoint_path="model.ckpt-157585"
--fine_tune=True --initial_learning_rate=0.001
--input_queue_memory_factor=1
- 这应该使输出类似于以下内容:
2018-06-02 11:10:10.557012: step 1290, loss = 2.02 (1.2 examples/sec; 23.771 sec/batch)
...
工作原理
关于预训练 CNN 上的训练的 TensorFlow 官方教程需要设置一个文件夹;我们从 CIFAR-10 数据创建的设置。然后我们将数据转换为所需的TFRecords格式并开始训练模型。请记住,我们正在微调模型并重新训练顶部的完全连接的层以适合我们的 10 类数据。
另见
应用 StyleNet 和 NeuralStyle 项目
一旦我们对 CNN 进行了图像识别训练,我们就可以将网络本身用于一些有趣的数据和图像处理。 Stylenet 是一种尝试从一张图片中学习图像样式并将其应用于第二张图片同时保持第二图像结构(或内容)完整的过程。如果我们能够找到与样式强烈相关的中间 CNN 节点,这可能是可能的,与图像的内容分开。
准备
Stylenet 是一个过程,它接收两个图像并将一个图像的样式应用于第二个图像的内容。它基于 2015 年的着名论文“艺术风格的神经算法”(参见下一节的第一个要点)。作者在一些 CNN 中找到了一个属性,其中存在中间层,它们似乎编码图片的样式,有些编码图片的内容。为此,如果我们训练样式图片上的样式层和原始图像上的内容层,并反向传播那些计算的损失,我们可以将原始图像更改为更像样式图像。
为了实现这一目标,我们将下载本文推荐的网络;叫做 imagenet-vgg-19。还有一个 imagenet-vgg-16 网络也可以使用,但是本文推荐使用 imagenet-vgg-19。
操作步骤
执行以下步骤:
- 首先,我们将以
mat格式下载预先训练好的网络。mat格式是matlab对象,Python 中的scipy包有一个可以读取它的方法。下载mat对象的链接在这里。我们将此模型保存在 Python 脚本所在的同一文件夹中,以供参考:
http://www.vlfeat.org/matconvnet/models/beta16/imagenet-vgg-verydeep-19.mat
- 我们将通过加载必要的库来启动我们的 Python 脚本:
import os
import scipy.io
import scipy.misc
import imageio
from skimage.transform import resize
from operator import mul
from functools import reduce
import numpy as np
import tensorflow as tf
from tensorflow.python.framework import ops
ops.reset_default_graph()
- 然后我们可以声明两个图像的位置:原始图像和样式图像。出于我们的目的,我们将使用本书的封面图片作为原始图像;对于风格形象,我们将使用文森特·梵高的星夜。随意使用您想要的任何两张图片。如果您选择使用这些图片,可以在本书的 GitHub 网站上找到(导航到 Styelnet 部分):
original_image_file = 'temp/book_cover.jpg'
style_image_file = 'temp/starry_night.jpg'
- 我们将为我们的模型设置一些参数:
mat文件的位置,权重,学习率,代数以及输出中间图像的频率。对于权重,有助于在原始图像上高度加权样式图像。应根据所需结果的变化调整这些超参数:
vgg_path = 'imagenet-vgg-verydeep-19.mat'
original_image_weight = 5.0
style_image_weight = 500.0
regularization_weight = 100
learning_rate = 10
generations = 100
output_generations = 25
beta1 = 0.9
beta2 = 0.999
- 现在我们将使用
scipy加载两个图像并更改样式图像以适合原始图像大小:
original_image = imageio.imread(original_image_file)
style_image = imageio.imread(style_image_file)
# Get shape of target and make the style image the same
target_shape = original_image.shape
style_image = resize(style_image, target_shape)
- 从论文中,我们可以按照它们出现的顺序定义层。我们将使用作者的命名约定:
vgg_layers = ['conv1_1', 'relu1_1',
'conv1_2', 'relu1_2', 'pool1',
'conv2_1', 'relu2_1',
'conv2_2', 'relu2_2', 'pool2',
'conv3_1', 'relu3_1',
'conv3_2', 'relu3_2',
'conv3_3', 'relu3_3',
'conv3_4', 'relu3_4', 'pool3',
'conv4_1', 'relu4_1',
'conv4_2', 'relu4_2',
'conv4_3', 'relu4_3',
'conv4_4', 'relu4_4', 'pool4',
'conv5_1', 'relu5_1',
'conv5_2', 'relu5_2',
'conv5_3', 'relu5_3',
'conv5_4', 'relu5_4']
- 现在我们将定义一个从
mat文件中提取参数的函数:
def extract_net_info(path_to_params):
vgg_data = scipy.io.loadmat(path_to_params)
normalization_matrix = vgg_data['normalization'][0][0][0]
mat_mean = np.mean(normalization_matrix, axis=(0,1))
network_weights = vgg_data['layers'][0]
return mat_mean, network_weights
- 根据加载的权重和
layer定义,我们可以使用以下函数在 TensorFlow 中重新创建网络。我们将遍历每一层并使用适当的weights和biases分配相应的函数,如果适用:
def vgg_network(network_weights, init_image):
network = {}
image = init_image
for i, layer in enumerate(vgg_layers):
if layer[1] == 'c':
weights, bias = network_weights[i][0][0][0][0]
weights = np.transpose(weights, (1, 0, 2, 3))
bias = bias.reshape(-1)
conv_layer = tf.nn.conv2d(image, tf.constant(weights), (1, 1, 1, 1), 'SAME')
image = tf.nn.bias_add(conv_layer, bias)
elif layer[1] == 'r':
image = tf.nn.relu(image)
else:
image = tf.nn.max_pool(image, (1, 2, 2, 1), (1, 2, 2, 1), 'SAME')
network[layer] = image
return(network)
- 本文推荐了一些策略,用于将中间层分配给原始图像和样式图像。虽然我们应该为原始图像保留
relu4_2,但我们可以为样式图像尝试其他reluX_1层输出的不同组合:
original_layer = ['relu4_2']
style_layers = ['relu1_1', 'relu2_1', 'relu3_1', 'relu4_1', 'relu5_1']
- 接下来,我们将运行前面的函数来获取权重和均值。我们还需要均匀设置 VGG19 样式层权重。如果您愿意,可以通过更改权重进行实验。现在,我们假设它们对于两个层都是 0.5:
# Get network parameters
normalization_mean, network_weights = extract_net_info(vgg_path)
shape = (1,) + original_image.shape
style_shape = (1,) + style_image.shape
original_features = {}
style_features = {}
# Set style weights
style_weights = {l: 1./(len(style_layers)) for l in style_layers}
- 为了忠实于原始图片外观,我们希望添加一个损失值,将内容/原始特征与原始内容特征进行比较。为此,我们加载 VGG19 模型并计算原始内容特征的内容/原始特征:
g_original = tf.Graph()
with g_original.as_default(), tf.Session() as sess1:
image = tf.placeholder('float', shape=shape)
vgg_net = vgg_network(network_weights, image)
original_minus_mean = original_image - normalization_mean
original_norm = np.array([original_minus_mean])
for layer in original_layers:
original_features[layer] = vgg_net[layer].eval(feed_dict={image: original_norm})
- 与步骤 11 类似,我们希望将原始图像的样式特征更改为样式图片的样式特征。为此,我们将为损失函数添加样式损失值。此损失值需要查看我们预先确定的样式层中样式图像的值。我们还将通过单独的图运行此操作。我们按如下方式计算这些样式特征:
# Get style image network
g_style = tf.Graph()
with g_style.as_default(), tf.Session() as sess2:
image = tf.placeholder('float', shape=style_shape)
vgg_net = vgg_network(network_weights, image)
style_minus_mean = style_image - normalization_mean
style_norm = np.array([style_minus_mean])
for layer in style_layers:
features = vgg_net[layer].eval(feed_dict={image: style_norm})
features = np.reshape(features, (-1, features.shape[3]))
gram = np.matmul(features.T, features) / features.size
style_features[layer] = gram
- 我们启动默认图来计算损失和训练步骤。首先,我们首先将随机图像初始化为 TensorFlow 变量:
# Make Combined Image via loss function
with tf.Graph().as_default():
# Get network parameters
initial = tf.random_normal(shape) * 0.256
init_image = tf.Variable(initial)
vgg_net = vgg_network(network_weights, init_image)
- 接下来,我们计算原始内容损失(将其缩进到默认图下)。这个损失部分将尽可能保持原始图像的结构完整:
# Loss from Original Image
original_layers_w = {'relu4_2': 0.5, 'relu5_2': 0.5}
original_loss = 0
for o_layer in original_layers:
temp_original_loss = original_layers_w[o_layer] * original_image_weight *\
(2 * tf.nn.l2_loss(vgg_net[o_layer] - original_features[o_layer]))
original_loss += (temp_original_loss / original_features[o_layer].size)
- 仍然在默认图缩进下,我们创建第二个损失项,即样式损失。此损失将比较我们预先计算的样式特征与输入图像的样式特征(随机初始化):
# Loss from Style Image
style_loss = 0
style_losses = []
for style_layer in style_layers:
layer = vgg_net[style_layer]
feats, height, width, channels = [x.value for x in layer.get_shape()]
size = height * width * channels
features = tf.reshape(layer, (-1, channels))
style_gram_matrix = tf.matmul(tf.transpose(features), features) / size
style_expected = style_features[style_layer]
style_losses.append(style_weights[style_layer] * 2 *
tf.nn.l2_loss(style_gram_matrix - style_expected) /
style_expected.size)
style_loss += style_image_weight * tf.reduce_sum(style_losses)
- 第三个也是最后一个损失条款将有助于平滑图像。我们在这里使用总变差损失来惩罚相邻像素的剧烈变化,如下所示:
total_var_x = reduce(mul, init_image[:, 1:, :, :].get_shape().as_list(), 1)
total_var_y = reduce(mul, init_image[:, :, 1:, :].get_shape().as_list(), 1)
first_term = regularization_weight * 2
second_term_numerator = tf.nn.l2_loss(init_image[:, 1:, :, :] - init_image[:, :shape[1]-1, :, :])
second_term = second_term_numerator / total_var_y
third_term = (tf.nn.l2_loss(init_image[:, :, 1:, :] - init_image[:, :, :shape[2]-1, :]) / total_var_x)
total_variation_loss = first_term * (second_term + third_term)
- 接下来,我们结合损失项并创建优化函数和训练步骤,如下所示:
# Combined Loss
loss = original_loss + style_loss + total_variation_loss
# Declare Optimization Algorithm
optimizer = tf.train.AdamOptimizer(learning_rate, beta1, beta2)
train_step = optimizer.minimize(loss)
- 现在我们运行训练步骤,保存中间图像,并保存最终输出图像,如下所示:
# Initialize variables and start training
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(generations):
train_step.run()
# Print update and save temporary output
if (i+1) % output_generations == 0:
print('Generation {} out of {}, loss: {}'.format(i + 1, generations, sess.run(loss)))
image_eval = init_image.eval()
best_image_add_mean = image_eval.reshape(shape[1:]) + normalization_mean
output_file = 'temp_output_{}.jpg'.format(i)
imageio.imwrite(output_file, best_image_add_mean.astype(np.uint8))
# Save final image
image_eval = init_image.eval()
best_image_add_mean = image_eval.reshape(shape[1:]) + normalization_mean
output_file = 'final_output.jpg'
scipy.misc.imsave(output_file, best_image_add_mean)

图 6:使用 Stylenet 算法将书籍封面图像与星夜相结合。请注意,可以通过更改脚本开头的权重来使用不同的样式重点
工作原理
我们首先加载两个图像,然后将预先训练的网络权重和指定的层加载到原始图像和样式图像。我们计算了三种损失函数:原始图像损失,样式损失和总变差损失。然后我们训练随机噪声图片以使用样式图像的样式和原始图像的内容。
损失函数受 GitHub 神经风格项目的影响很大。我们还强烈建议读者查看这些项目中的代码以获得改进,更多细节,以及通常更强大的算法,可以提供更好的结果。
另见
- Gatys,Ecker,Bethge 的艺术风格神经算法,2015
- Leon Gatys 在 CVPR 2016(计算机视觉和模式识别)上的一个很好的推荐视频
实现 DeepDream
受过训练的 CNN 的另一个用途是利用一些中间节点检测标签特征(例如,猫的耳朵或鸟的羽毛)的事实。利用这一事实,我们可以找到转换任何图像的方法,以反映我们选择的任何节点的节点特征。对于这个秘籍,我们将在 TensorFlow 的网站上浏览 DeepDream 教程,但我们将更详细地介绍基本部分。希望我们可以让读者准备好使用 DeepDream 算法来探索 CNN 及其中创建的特征。
准备
TensorFlow 的官方教程展示了如何通过脚本实现 DeepDream(请参阅下一节中的第一个要点)。这个方法的目的是通过他们提供的脚本并解释每一行。虽然教程很棒,但有些部分可以跳过,有些部分可以使用更多解释。我们希望提供更详细的逐行说明。我们还将在必要时使代码符合 Python3 标准。
操作步骤
执行以下步骤:
- 为了开始使用 DeepDream,我们需要下载在 CIFAR-1000 上接受过 CNN 训练的 GoogleNet:
me@computer:~$ wget https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip
me@computer:~$ unzip inception5h.zip
- 我们首先加载必要的库并启动图会话:
import os
import matplotlib.pyplot as plt
import numpy as np
import PIL.Image
import tensorflow as tf
from io import BytesIO
graph = tf.Graph()
sess = tf.InteractiveSession(graph=graph)
- 我们现在声明解压缩模型参数的位置(从步骤 1 开始)并将参数加载到 TensorFlow 图中:
# Model location
model_fn = 'tensorflow_inception_graph.pb'
# Load graph parameters
with tf.gfile.FastGFile(model_fn, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
- 我们为输入创建一个占位符,保存 imagenet 平均值 117.0,然后使用正则化占位符导入图定义:
# Create placeholder for input
t_input = tf.placeholder(np.float32, name='input')
# Imagenet average bias to subtract off images
imagenet_mean = 117.0
t_preprocessed = tf.expand_dims(t_input-imagenet_mean, 0)
tf.import_graph_def(graph_def, {'input':t_preprocessed})
- 接下来,我们将导入卷积层,以便在以后可视化并使用它们进行 DeepDream 处理:
# Create a list of layers that we can refer to later
layers = [op.name for op in graph.get_operations() if op.type=='Conv2D' and 'import/' in op.name]
# Count how many outputs for each layer
feature_nums = [int(graph.get_tensor_by_name(name+':0').get_shape()[-1]) for name in layers]
- 现在我们将选择一个可视化的层。我们也可以通过名字选择其他人。我们选择查看特征号
139。图像以随机噪声开始:
layer = 'mixed4d_3x3_bottleneck_pre_relu'
channel = 139
img_noise = np.random.uniform(size=(224,224,3)) + 100.0
- 我们声明了一个绘制图像数组的函数:
def showarray(a, fmt='jpeg'):
# First make sure everything is between 0 and 255
a = np.uint8(np.clip(a, 0, 1)*255)
# Pick an in-memory format for image display
f = BytesIO()
# Create the in memory image
PIL.Image.fromarray(a).save(f, fmt)
# Show image
plt.imshow(a)
- 我们将通过创建一个从图中按名称检索层的函数来缩短一些重复代码:
def T(layer): #Helper for getting layer output tensor return graph.get_tensor_by_name("import/%s:0"%layer)
- 我们将创建的下一个函数是一个包装函数,用于根据我们指定的参数创建占位符:
# The following function returns a function wrapper that will create the placeholder
# inputs of a specified dtype
def tffunc(*argtypes):
'''Helper that transforms TF-graph generating function into a regular one.
See "resize" function below.
'''
placeholders = list(map(tf.placeholder, argtypes))
def wrap(f):
out = f(*placeholders)
def wrapper(*args, **kw):
return out.eval(dict(zip(placeholders, args)), session=kw.get('session'))
return wrapper
return wrap
- 我们还需要一个将图像大小调整为大小规格的函数。我们使用 TensorFlow 的内置图像线性插值函数:
tf.image.resize.bilinear()
# Helper function that uses TF to resize an image
def resize(img, size):
img = tf.expand_dims(img, 0)
# Change 'img' size by linear interpolation
return tf.image.resize_bilinear(img, size)[0,:,:,:]
- 现在我们需要一种方法来更新源图像,使其更像我们使用的特征。我们通过指定如何计算图像上的梯度来完成此操作。我们定义了一个函数,用于计算图像上子区域(图块)的梯度,以加快计算速度。为了防止平铺输出,我们将在
x和y方向上随机移动或滚动图像,这将平滑平铺效果:
def calc_grad_tiled(img, t_grad, tile_size=512):
'''Compute the value of tensor t_grad over the image in a tiled way.
Random shifts are applied to the image to blur tile boundaries over
multiple iterations.'''
# Pick a subregion square size
sz = tile_size
# Get the image height and width
h, w = img.shape[:2]
# Get a random shift amount in the x and y direction
sx, sy = np.random.randint(sz, size=2)
# Randomly shift the image (roll image) in the x and y directions
img_shift = np.roll(np.roll(img, sx, 1), sy, 0)
# Initialize the while image gradient as zeros
grad = np.zeros_like(img)
# Now we loop through all the sub-tiles in the image
for y in range(0, max(h-sz//2, sz),sz):
for x in range(0, max(w-sz//2, sz),sz):
# Select the sub image tile
sub = img_shift[y:y+sz,x:x+sz]
# Calculate the gradient for the tile
g = sess.run(t_grad, {t_input:sub})
# Apply the gradient of the tile to the whole image gradient
grad[y:y+sz,x:x+sz] = g
# Return the gradient, undoing the roll operation
return np.roll(np.roll(grad, -sx, 1), -sy, 0)
- 现在我们可以声明 DeepDream 函数。我们算法的目标是我们选择的特征的平均值。损耗在梯度上运行,这取决于输入图像和所选特征之间的距离。策略是将图像分成高频和低频,并计算低频部分的梯度。将得到的高频图像再次分开并重复该过程。原始图像和低频图像的集合称为
octaves。对于每次传递,我们计算梯度并将它们应用于图像:
def render_deepdream(t_obj, img0=img_noise,
iter_n=10, step=1.5, octave_n=4, octave_scale=1.4):
# defining the optimization objective, the objective is the mean of the feature
t_score = tf.reduce_mean(t_obj)
# Our gradients will be defined as changing the t_input to get closer to the values of t_score. Here, t_score is the mean of the feature we select.
# t_input will be the image octave (starting with the last)
t_grad = tf.gradients(t_score, t_input)[0] # behold the power of automatic differentiation!
# Store the image
img = img0
# Initialize the image octave list
octaves = []
# Since we stored the image, we need to only calculate n-1 octaves
for i in range(octave_n-1):
# Extract the image shape
hw = img.shape[:2]
# Resize the image, scale by the octave_scale (resize by linear interpolation)
lo = resize(img, np.int32(np.float32(hw)/octave_scale))
# Residual is hi. Where residual = image - (Resize lo to be hw-shape)
hi = img-resize(lo, hw)
# Save the lo image for re-iterating
img = lo
# Save the extracted hi-image
octaves.append(hi)
# generate details octave by octave
for octave in range(octave_n):
if octave>0:
# Start with the last octave
hi = octaves[-octave]
#
img = resize(img, hi.shape[:2])+hi
for i in range(iter_n):
# Calculate gradient of the image.
g = calc_grad_tiled(img, t_grad)
# Ideally, we would just add the gradient, g, but
# we want do a forward step size of it ('step'),
# and divide it by the avg. norm of the gradient, so
# we are adding a gradient of a certain size each step.
# Also, to make sure we aren't dividing by zero, we add 1e-7\.
img += g*(step / (np.abs(g).mean()+1e-7))
print('.',end = ' ')
showarray(img/255.0)
- 通过我们所做的所有特征设置,我们现在可以运行 DeepDream 算法:
# Run Deep Dream
if __name__=="__main__":
# Create resize function that has a wrapper that creates specified placeholder types
resize = tffunc(np.float32, np.int32)(resize)
# Open image
img0 = PIL.Image.open('book_cover.jpg')
img0 = np.float32(img0)
# Show Original Image
showarray(img0/255.0)
# Create deep dream
render_deepdream(T(layer)[:,:,:,139], img0, iter_n=15)
sess.close()
输出如下:

图 7:本书的封面,贯穿 DeepDream 算法,其特征层编号为 50,110,100 和 139
更多
我们敦促读者使用 DeepDream 官方教程作为进一步信息的来源,并访问 DeepDream 上的原始 Google 研究博客文章(请参阅下面的第二个要点参见另见部分)。
另见
九、循环神经网络
在本章中,我们将介绍循环神经网络(RNN)以及如何在 TensorFlow 中实现它们。我们将首先演示如何使用 RNN 来预测垃圾邮件。然后,我们将介绍一种用于创建莎士比亚文本的 RNN 变体。我们将通过创建 RNN 序列到序列模型来完成从英语到德语的翻译:
- 实现 RNN 以进行垃圾邮件预测
- 实现 LSTM 模型
- 堆叠多个 LSTM 层
- 创建序列到序列模型
- 训练 Siamese 相似性度量
本章的所有代码都可以在 Github 和 Packt 在线仓库。
介绍
在迄今为止我们考虑过的所有机器学习算法中,没有人将数据视为序列。为了考虑序列数据,我们扩展了存储先前迭代输出的神经网络。这种类型的神经网络称为 RNN。考虑完全连接的网络秘籍:

这里,权重由A乘以输入层x给出,然后通过激活函数σ,给出输出层y。
如果我们有一系列输入数据x[1], x[2], x[3], ...,我们可以调整完全连接的层以考虑先前的输入,如下所示:

在此循环迭代之上获取下一个输入,我们希望得到概率分布输出,如下所示:

一旦我们有一个完整的序列输出{S[1], S[2], S[3], ...},我们可以通过考虑最后的输出将目标视为数字或类别。有关通用架构的工作原理,请参见下图:

图 1:为了预测单个数字或类别,我们采用一系列输入(标记)并将最终输出视为预测输出
我们还可以将序列输出视为序列到序列模型中的输入:

图 2:为了预测序列,我们还可以将输出反馈到模型中以生成多个输出
对于任意长序列,使用反向传播算法进行训练会产生长时间相关的梯度。因此,存在消失或爆炸的梯度问题。在本章的后面,我们将通过将 RNN 单元扩展为所谓的长短期记忆(LSTM)单元来探索该问题的解决方案。主要思想是 LSTM 单元引入另一个操作,称为门,它控制通过序列的信息流。我们将在后面的章节中详细介绍。
在处理 NLP 的 RNN 模型时,编码是用于描述将数据(NLP 中的字或字符)转换为数字 RNN 特征的过程的术语。术语解码是将 RNN 数字特征转换为输出字或字符的过程。
为垃圾邮件预测实现 RNN
首先,我们将应用标准 RNN 单元来预测奇异数值输出,即垃圾邮件概率。
准备
在此秘籍中,我们将在 TensorFlow 中实现标准 RNN,以预测短信是垃圾邮件还是非垃圾邮件。我们将使用 UCI 的 ML 仓库中的 SMS 垃圾邮件收集数据集。我们将用于预测的架构将是来自嵌入文本的输入 RNN 序列,我们将最后的 RNN 输出作为垃圾邮件或非垃圾邮件(1 或 0)的预测。
操作步骤
- 我们首先加载此脚本所需的库:
import os
import re
import io
import requests
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from zipfile import ZipFile
- 接下来,我们启动图会话并设置 RNN 模型参数。我们将通过
20周期以250的批量大小运行数据。我们将考虑的每个文本的最大长度是25字;我们将更长的文本剪切为25或零填充短文本。 RNN 将是10单元。我们只考虑在词汇表中出现至少 10 次的单词,并且每个单词都将嵌入到可训练的大小50中。丢弃率将是我们可以在训练期间0.5或评估期间1.0设置的占位符:
sess = tf.Session()
epochs = 20
batch_size = 250
max_sequence_length = 25
rnn_size = 10
embedding_size = 50
min_word_frequency = 10
learning_rate = 0.0005
dropout_keep_prob = tf.placeholder(tf.float32)
- 现在我们获取 SMS 文本数据。首先,我们检查它是否已经下载,如果是,请在文件中读取。否则,我们下载数据并保存:
data_dir = 'temp'
data_file = 'text_data.txt'
if not os.path.exists(data_dir):
os.makedirs(data_dir)
if not os.path.isfile(os.path.join(data_dir, data_file)):
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')
# Format Data
text_data = file.decode()
text_data = text_data.encode('ascii',errors='ignore')
text_data = text_data.decode().split('\n')
# Save data to text file
with open(os.path.join(data_dir, data_file), 'w') as file_conn:
for text in text_data:
file_conn.write("{}\n".format(text))
else:
# Open data from text file
text_data = []
with open(os.path.join(data_dir, data_file), 'r') as file_conn:
for row in file_conn:
text_data.append(row)
text_data = text_data[:-1]
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)]
- 为了减少我们的词汇量,我们将通过删除特殊字符和额外的空格来清理输入文本,并将所有内容放在小写中:
def clean_text(text_string):
text_string = re.sub(r'([^sw]|_|[0-9])+', '', text_string)
text_string = " ".join(text_string.split())
text_string = text_string.lower()
return text_string
# Clean texts
text_data_train = [clean_text(x) for x in text_data_train]
请注意,我们的清洁步骤会删除特殊字符作为替代方案,我们也可以用空格替换它们。理想情况下,这取决于数据集的格式。
- 现在我们使用 TensorFlow 的内置词汇处理器函数处理文本。这会将文本转换为适当的索引列表:
vocab_processor = tf.contrib.learn.preprocessing.VocabularyProcessor(max_sequence_length, min_frequency=min_word_frequency)
text_processed = np.array(list(vocab_processor.fit_transform(text_data_train)))
请注意,
contrib.learn.preprocessing中的函数目前已弃用(使用当前的 TensorFlow 版本,1.10)。目前的替换建议 TensorFlow 预处理包仅在 Python2 中运行。将 TensorFlow 预处理移至 Python3 的工作目前正在进行中,并将取代前两行。请记住,所有当前和最新的代码都可以在这个 GitHub 页面,和 Packt 仓库找到。
- 接下来,我们打乱数据以使其随机化:
text_processed = np.array(text_processed)
text_data_target = np.array([1 if x=='ham' else 0 for x in text_data_target])
shuffled_ix = np.random.permutation(np.arange(len(text_data_target)))
x_shuffled = text_processed[shuffled_ix]
y_shuffled = text_data_target[shuffled_ix]
- 我们还将数据拆分为 80-20 训练测试数据集:
ix_cutoff = int(len(y_shuffled)*0.80)
x_train, x_test = x_shuffled[:ix_cutoff], x_shuffled[ix_cutoff:]
y_train, y_test = y_shuffled[:ix_cutoff], y_shuffled[ix_cutoff:]
vocab_size = len(vocab_processor.vocabulary_)
print("Vocabulary Size: {:d}".format(vocab_size))
print("80-20 Train Test split: {:d} -- {:d}".format(len(y_train), len(y_test)))
对于这个秘籍,我们不会进行任何超参数调整。如果读者朝这个方向前进,请记住在继续之前将数据集拆分为训练测试验证集。一个很好的选择是 Scikit-learn 函数
model_selection.train_test_split()。
- 接下来,我们声明图占位符。
x输入将是一个大小为[None, max_sequence_length]的占位符,它将是根据文本消息允许的最大字长的批量大小。对于非垃圾邮件或垃圾邮件,y_output占位符只是一个 0 或 1 的整数:
x_data = tf.placeholder(tf.int32, [None, max_sequence_length])
y_output = tf.placeholder(tf.int32, [None])
- 我们现在为
x输入数据创建嵌入矩阵和嵌入查找操作:
embedding_mat = tf.Variable(tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0))
embedding_output = tf.nn.embedding_lookup(embedding_mat, x_data)
- 我们将模型声明如下。首先,我们初始化一种要使用的 RNN 单元(RNN 大小为 10)。然后我们通过使其成为动态 RNN 来创建 RNN 序列。然后我们将退出添加到 RNN:
cell = tf.nn.rnn_cell.BasicRNNCell(num_units = rnn_size)
output, state = tf.nn.dynamic_rnn(cell, embedding_output, dtype=tf.float32)
output = tf.nn.dropout(output, dropout_keep_prob)
注意,动态 RNN 允许可变长度序列。即使我们在这个例子中使用固定的序列长度,通常最好在 TensorFlow 中使用
dynamic_rnn有两个主要原因。一个原因是,在实践中,动态 RNN 实际上运行速度更快;第二个是,如果我们选择,我们可以通过 RNN 运行不同长度的序列。
- 现在要得到我们的预测,我们必须重新安排 RNN 并切掉最后一个输出:
output = tf.transpose(output, [1, 0, 2])
last = tf.gather(output, int(output.get_shape()[0]) - 1)
- 为了完成 RNN 预测,我们通过完全连接的网络层将
rnn_size输出转换为两个类别输出:
weight = tf.Variable(tf.truncated_normal([rnn_size, 2], stddev=0.1))
bias = tf.Variable(tf.constant(0.1, shape=[2]))
logits_out = tf.nn.softmax(tf.matmul(last, weight) + bias)
- 我们接下来宣布我们的损失函数。请记住,当使用 TensorFlow 中的
sparse_softmax函数时,目标必须是整数索引(类型为int),并且对率必须是浮点数:
losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits_out, labels=y_output)
loss = tf.reduce_mean(losses)
- 我们还需要一个精确度函数,以便我们可以比较测试和训练集上的算法:
accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(logits_out, 1), tf.cast(y_output, tf.int64)), tf.float32))
- 接下来,我们创建优化函数并初始化模型变量:
optimizer = tf.train.RMSPropOptimizer(learning_rate)
train_step = optimizer.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
- 现在我们可以开始循环遍历数据并训练模型。在多次循环数据时,最好在每个周期对数据进行洗牌以防止过度训练:
train_loss = []
test_loss = []
train_accuracy = []
test_accuracy = []
# Start training
for epoch in range(epochs):
# Shuffle training data
shuffled_ix = np.random.permutation(np.arange(len(x_train)))
x_train = x_train[shuffled_ix]
y_train = y_train[shuffled_ix]
num_batches = int(len(x_train)/batch_size) + 1
for i in range(num_batches):
# Select train data
min_ix = i * batch_size
max_ix = np.min([len(x_train), ((i+1) * batch_size)])
x_train_batch = x_train[min_ix:max_ix]
y_train_batch = y_train[min_ix:max_ix]
# Run train step
train_dict = {x_data: x_train_batch, y_output: y_train_batch, dropout_keep_prob:0.5}
sess.run(train_step, feed_dict=train_dict)
# Run loss and accuracy for training
temp_train_loss, temp_train_acc = sess.run([loss, accuracy], feed_dict=train_dict)
train_loss.append(temp_train_loss)
train_accuracy.append(temp_train_acc)
# Run Eval Step
test_dict = {x_data: x_test, y_output: y_test, dropout_keep_prob:1.0}
temp_test_loss, temp_test_acc = sess.run([loss, accuracy], feed_dict=test_dict)
test_loss.append(temp_test_loss)
test_accuracy.append(temp_test_acc)
print('Epoch: {}, Test Loss: {:.2}, Test Acc: {:.2}'.format(epoch+1, temp_test_loss, temp_test_acc))
- 这产生以下输出:
Vocabulary Size: 933
80-20 Train Test split: 4459 -- 1115
Epoch: 1, Test Loss: 0.59, Test Acc: 0.83
Epoch: 2, Test Loss: 0.58, Test Acc: 0.83
...
Epoch: 19, Test Loss: 0.46, Test Acc: 0.86
Epoch: 20, Test Loss: 0.46, Test Acc: 0.86
- 以下是绘制训练/测试损失和准确率的代码:
epoch_seq = np.arange(1, epochs+1)
plt.plot(epoch_seq, train_loss, 'k--', label='Train Set')
plt.plot(epoch_seq, test_loss, 'r-', label='Test Set')
plt.title('Softmax Loss')
plt.xlabel('Epochs')
plt.ylabel('Softmax Loss')
plt.legend(loc='upper left')
plt.show()
# Plot accuracy over time
plt.plot(epoch_seq, train_accuracy, 'k--', label='Train Set')
plt.plot(epoch_seq, test_accuracy, 'r-', label='Test Set')
plt.title('Test Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend(loc='upper left')
plt.show()
工作原理
在这个秘籍中,我们创建了一个 RNN 到类别的模型来预测 SMS 文本是垃圾邮件还是非垃圾邮件。我们在测试装置上实现了大约 86% 的准确率。以下是测试和训练集的准确率和损失图:

图 3:训练和测试集的准确率(左)和损失(右)
更多
强烈建议您多次浏览训练数据集以获取顺序数据(这也建议用于非顺序数据)。每次传递数据都称为周期。此外,在每个周期之前对数据进行混洗是非常常见的(并且强烈推荐),以最小化数据顺序对训练的影响。
实现 LSTM 模型
我们将扩展我们的 RNN 模型,以便通过在此秘籍中引入 LSTM 单元来使用更长的序列。
准备
长短期记忆(LSTM)是传统 RNN 的变体。 LSTM 是一种解决可变长度 RNN 所具有的消失/爆炸梯度问题的方法。为了解决这个问题,LSTM 单元引入了一个内部遗忘门,它可以修改从一个单元到下一个单元的信息流。为了概念化它的工作原理,我们将逐步介绍一个无偏置的 LSTM 方程式。第一步与常规 RNN 相同:

为了确定我们想要忘记或通过的值,我们将如下评估候选值。这些值通常称为存储单元:

现在我们用一个遗忘矩阵修改候选存储单元,其计算方法如下:

我们现在将遗忘存储器与先前的存储器步骤相结合,并将其添加到候选存储器单元以获得新的存储器值:

现在我们将所有内容组合起来以获取单元格的输出:

然后,对于下一次迭代,我们更新h如下:

LSTM 的想法是通过基于输入到单元的信息可以忘记或修改的单元具有自我调节的信息流。
在这里使用 TensorFlow 的一个好处是我们不必跟踪这些操作及其相应的反向传播属性。 TensorFlow 将跟踪这些并根据我们的损失函数,优化器和学习率指定的梯度自动更新模型变量。
对于这个秘籍,我们将使用具有 LSTM 单元的序列 RNN 来尝试预测接下来的单词,对莎士比亚的作品进行训练。为了测试我们的工作方式,我们将提供模型候选短语,例如thou art more,并查看模型是否可以找出短语后面应该包含的单词。
操作步骤
- 首先,我们为脚本加载必要的库:
import os
import re
import string
import requests
import numpy as np
import collections
import random
import pickle
import matplotlib.pyplot as plt
import tensorflow as tf
- 接下来,我们启动图会话并设置 RNN 参数:
sess = tf.Session()
# Set RNN Parameters
min_word_freq = 5
rnn_size = 128
epochs = 10
batch_size = 100
learning_rate = 0.001
training_seq_len = 50
embedding_size = rnn_size
save_every = 500
eval_every = 50
prime_texts = ['thou art more', 'to be or not to', 'wherefore art thou']
- 我们设置数据和模型文件夹和文件名,同时声明要删除的标点符号。我们希望保留连字符和撇号,因为莎士比亚经常使用它们来组合单词和音节:
data_dir = 'temp'
data_file = 'shakespeare.txt'
model_path = 'shakespeare_model'
full_model_dir = os.path.join(data_dir, model_path)
# Declare punctuation to remove, everything except hyphens and apostrophe's
punctuation = string.punctuation
punctuation = ''.join([x for x in punctuation if x not in ['-', "'"]])
- 接下来,我们获取数据。如果数据文件不存在,我们下载并保存莎士比亚文本。如果确实存在,我们加载数据:
if not os.path.exists(full_model_dir):
os.makedirs(full_model_dir)
# Make data directory
if not os.path.exists(data_dir):
os.makedirs(data_dir)
print('Loading Shakespeare Data')
# Check if file is downloaded.
if not os.path.isfile(os.path.join(data_dir, data_file)):
print('Not found, downloading Shakespeare texts from www.gutenberg.org')
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
s_text = shakespeare_file.decode('utf-8')
# Drop first few descriptive paragraphs.
s_text = s_text[7675:]
# Remove newlines
s_text = s_text.replace('\r\n', '')
s_text = s_text.replace('\n', '')
# Write to file
with open(os.path.join(data_dir, data_file), 'w') as out_conn:
out_conn.write(s_text)
else:
# If file has been saved, load from that file
with open(os.path.join(data_dir, data_file), 'r') as file_conn:
s_text = file_conn.read().replace('\n', '')
- 我们通过删除标点符号和额外的空格来清理莎士比亚的文本:
s_text = re.sub(r'[{}]'.format(punctuation), ' ', s_text)
s_text = re.sub('s+', ' ', s_text ).strip().lower()
- 我们现在处理创建要使用的莎士比亚词汇。我们创建一个函数,它将返回两个字典(单词到索引和索引到单词),其中的单词出现的频率超过指定的频率:
def build_vocab(text, min_word_freq):
word_counts = collections.Counter(text.split(' '))
# limit word counts to those more frequent than cutoff
word_counts = {key:val for key, val in word_counts.items() if val>min_word_freq}
# Create vocab --> index mapping
words = word_counts.keys()
vocab_to_ix_dict = {key:(ix+1) for ix, key in enumerate(words)}
# Add unknown key --> 0 index
vocab_to_ix_dict['unknown']=0
# Create index --> vocab mapping
ix_to_vocab_dict = {val:key for key,val in vocab_to_ix_dict.items()}
return ix_to_vocab_dict, vocab_to_ix_dict
ix2vocab, vocab2ix = build_vocab(s_text, min_word_freq)
vocab_size = len(ix2vocab) + 1
请注意,在处理文本时,我们必须小心索引值为零的单词。我们应该保存填充的零值,也可能保存未知单词。
- 现在我们有了词汇量,我们将莎士比亚的文本变成了一系列索引:
s_text_words = s_text.split(' ')
s_text_ix = []
for ix, x in enumerate(s_text_words):
try:
s_text_ix.append(vocab2ix[x])
except:
s_text_ix.append(0)
s_text_ix = np.array(s_text_ix)
- 在本文中,我们将展示如何在类对象中创建模型。这对我们很有帮助,因为我们希望使用相同的模型(具有相同的权重)来批量训练并从示例文本生成文本。如果没有采用内部抽样方法的类,这将很难做到。理想情况下,此类代码应位于单独的 Python 文件中,我们可以在此脚本的开头导入该文件:
class LSTM_Model():
def __init__(self, rnn_size, batch_size, learning_rate,
training_seq_len, vocab_size, infer =False):
self.rnn_size = rnn_size
self.vocab_size = vocab_size
self.infer = infer
self.learning_rate = learning_rate
if infer:
self.batch_size = 1
self.training_seq_len = 1
else:
self.batch_size = batch_size
self.training_seq_len = training_seq_len
self.lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(rnn_size)
self.initial_state = self.lstm_cell.zero_state(self.batch_size, tf.float32)
self.x_data = tf.placeholder(tf.int32, [self.batch_size, self.training_seq_len])
self.y_output = tf.placeholder(tf.int32, [self.batch_size, self.training_seq_len])
with tf.variable_scope('lstm_vars'):
# Softmax Output Weights
W = tf.get_variable('W', [self.rnn_size, self.vocab_size], tf.float32, tf.random_normal_initializer())
b = tf.get_variable('b', [self.vocab_size], tf.float32, tf.constant_initializer(0.0))
# Define Embedding
embedding_mat = tf.get_variable('embedding_mat', [self.vocab_size, self.rnn_size], tf.float32, tf.random_normal_initializer())
embedding_output = tf.nn.embedding_lookup(embedding_mat, self.x_data)
rnn_inputs = tf.split(embedding_output, num_or_size_splits=self.training_seq_len, axis=1)
rnn_inputs_trimmed = [tf.squeeze(x, [1]) for x in rnn_inputs]
# If we are inferring (generating text), we add a 'loop' function
# Define how to get the i+1 th input from the i th output
def inferred_loop(prev, count):
prev_transformed = tf.matmul(prev, W) + b
prev_symbol = tf.stop_gradient(tf.argmax(prev_transformed, 1))
output = tf.nn.embedding_lookup(embedding_mat, prev_symbol)
return output
decoder = tf.nn.seq2seq.rnn_decoder
outputs, last_state = decoder(rnn_inputs_trimmed,
self.initial_state,
self.lstm_cell,
loop_function=inferred_loop if infer else None)
# Non inferred outputs
output = tf.reshape(tf.concat(1, outputs), [-1, self.rnn_size])
# Logits and output
self.logit_output = tf.matmul(output, W) + b
self.model_output = tf.nn.softmax(self.logit_output)
loss_fun = tf.contrib.legacy_seq2seq.sequence_loss_by_example
loss = loss_fun([self.logit_output],[tf.reshape(self.y_output, [-1])],
[tf.ones([self.batch_size * self.training_seq_len])],
self.vocab_size)
self.cost = tf.reduce_sum(loss) / (self.batch_size * self.training_seq_len)
self.final_state = last_state
gradients, _ = tf.clip_by_global_norm(tf.gradients(self.cost, tf.trainable_variables()), 4.5)
optimizer = tf.train.AdamOptimizer(self.learning_rate)
self.train_op = optimizer.apply_gradients(zip(gradients, tf.trainable_variables()))
def sample(self, sess, words=ix2vocab, vocab=vocab2ix, num=10, prime_text='thou art'):
state = sess.run(self.lstm_cell.zero_state(1, tf.float32))
word_list = prime_text.split()
for word in word_list[:-1]:
x = np.zeros((1, 1))
x[0, 0] = vocab[word]
feed_dict = {self.x_data: x, self.initial_state:state}
[state] = sess.run([self.final_state], feed_dict=feed_dict)
out_sentence = prime_text
word = word_list[-1]
for n in range(num):
x = np.zeros((1, 1))
x[0, 0] = vocab[word]
feed_dict = {self.x_data: x, self.initial_state:state}
[model_output, state] = sess.run([self.model_output, self.final_state], feed_dict=feed_dict)
sample = np.argmax(model_output[0])
if sample == 0:
break
word = words[sample]
out_sentence = out_sentence + ' ' + word
return out_sentence
- 现在我们将声明 LSTM 模型以及测试模型。我们将在变量范围内执行此操作,并告诉范围我们将重用测试 LSTM 模型的变量:
with tf.variable_scope('lstm_model', reuse=tf.AUTO_REUSE) as scope:
# Define LSTM Model
lstm_model = LSTM_Model(rnn_size, batch_size, learning_rate,
training_seq_len, vocab_size)
scope.reuse_variables()
test_lstm_model = LSTM_Model(rnn_size, batch_size, learning_rate,
training_seq_len, vocab_size, infer=True)
- 我们创建一个保存操作,并将输入文本拆分为相等的批量大小的块。然后我们初始化模型的变量:
saver = tf.train.Saver()
# Create batches for each epoch
num_batches = int(len(s_text_ix)/(batch_size * training_seq_len)) + 1
# Split up text indices into subarrays, of equal size
batches = np.array_split(s_text_ix, num_batches)
# Reshape each split into [batch_size, training_seq_len]
batches = [np.resize(x, [batch_size, training_seq_len]) for x in batches]
# Initialize all variables
init = tf.global_variables_initializer()
sess.run(init)
- 我们现在可以遍历我们的周期,在每个周期开始之前对数据进行混洗。我们数据的目标只是相同的数据,但是移动了 1(使用
numpy.roll()函数):
train_loss = []
iteration_count = 1
for epoch in range(epochs):
# Shuffle word indices
random.shuffle(batches)
# Create targets from shuffled batches
targets = [np.roll(x, -1, axis=1) for x in batches]
# Run a through one epoch
print('Starting Epoch #{} of {}.'.format(epoch+1, epochs))
# Reset initial LSTM state every epoch
state = sess.run(lstm_model.initial_state)
for ix, batch in enumerate(batches):
training_dict = {lstm_model.x_data: batch, lstm_model.y_output: targets[ix]}
c, h = lstm_model.initial_state
training_dict[c] = state.c
training_dict[h] = state.h
temp_loss, state, _ = sess.run([lstm_model.cost, lstm_model.final_state, lstm_model.train_op], feed_dict=training_dict)
train_loss.append(temp_loss)
# Print status every 10 gens
if iteration_count % 10 == 0:
summary_nums = (iteration_count, epoch+1, ix+1, num_batches+1, temp_loss)
print('Iteration: {}, Epoch: {}, Batch: {} out of {}, Loss: {:.2f}'.format(*summary_nums))
# Save the model and the vocab
if iteration_count % save_every == 0:
# Save model
model_file_name = os.path.join(full_model_dir, 'model')
saver.save(sess, model_file_name, global_step = iteration_count)
print('Model Saved To: {}'.format(model_file_name))
# Save vocabulary
dictionary_file = os.path.join(full_model_dir, 'vocab.pkl')
with open(dictionary_file, 'wb') as dict_file_conn:
pickle.dump([vocab2ix, ix2vocab], dict_file_conn)
if iteration_count % eval_every == 0:
for sample in prime_texts:
print(test_lstm_model.sample(sess, ix2vocab, vocab2ix, num=10, prime_text=sample))
iteration_count += 1
- 这产生以下输出:
Loading Shakespeare Data
Cleaning Text
Building Shakespeare Vocab
Vocabulary Length = 8009
Starting Epoch #1 of 10\.
Iteration: 10, Epoch: 1, Batch: 10 out of 182, Loss: 10.37
Iteration: 20, Epoch: 1, Batch: 20 out of 182, Loss: 9.54
...
Iteration: 1790, Epoch: 10, Batch: 161 out of 182, Loss: 5.68
Iteration: 1800, Epoch: 10, Batch: 171 out of 182, Loss: 6.05
thou art more than i am a
to be or not to the man i have
wherefore art thou art of the long
Iteration: 1810, Epoch: 10, Batch: 181 out of 182, Loss: 5.99
- 最后,以下是我们如何绘制历史上的训练损失:
plt.plot(train_loss, 'k-')
plt.title('Sequence to Sequence Loss')
plt.xlabel('Generation')
plt.ylabel('Loss')
plt.show()
This results in the following plot of our loss values:

图 4:模型所有代的序列到序列损失
工作原理
在这个例子中,我们基于莎士比亚词汇构建了一个带有 LSTM 单元的 RNN 模型来预测下一个单词。可以采取一些措施来改进模型,可能会增加序列大小,具有衰减的学习率,或者训练模型以获得更多的周期。
更多
为了抽样,我们实现了一个贪婪的采样器。贪婪的采样器可能会一遍又一遍地重复相同的短语;例如,他们可能会卡住for the for the for the....为了防止这种情况,我们还可以实现一种更随机的采样方式,可能是根据输出的对数或概率分布制作加权采样器。
堆叠多个 LSTM 层
正如我们可以增加神经网络或 CNN 的深度,我们可以增加 RNN 网络的深度。在这个秘籍中,我们应用了一个三层深度的 LSTM 来改进我们的莎士比亚语言生成。
准备
我们可以通过将它们叠加在一起来增加循环神经网络的深度。从本质上讲,我们将获取目标输出并将其输入另一个网络。
要了解这对于两层的工作原理,请参见下图:

图 5:在上图中,我们扩展了单层 RNN,使它们具有两层。对于原始的单层版本,请参阅上一章简介中的绘图。左侧架构说明了使用多层 RNN 预测输出序列中的一个输出的方法。正确的架构显示了使用多层 RNN 预测输出序列的方法,该输出序列使用输出作为输入
TensorFlow 允许使用MultiRNNCell()函数轻松实现多个层,该函数接受 RNN 单元列表。有了这种行为,很容易用MultiRNNCell([rnn_cell(num_units) for n in num_layers])单元格从 Python 中的一个单元格创建多层 RNN。
对于这个秘籍,我们将执行我们在之前的秘籍中执行的相同的莎士比亚预测。将有两个变化:第一个变化将是具有三个堆叠的 LSTM 模型而不是仅一个层,第二个变化将是进行字符级预测而不是单词。进行字符级预测会将我们潜在的词汇量大大减少到只有 40 个字符(26 个字母,10 个数字,1 个空格和 3 个特殊字符)。
操作步骤
我们将说明本节中的代码与上一节的不同之处,而不是重新使用所有相同的代码。有关完整代码,请参阅 GitHub 仓库或 Packt 仓库。
- 我们首先需要设置模型的层数。我们将此作为参数放在脚本的开头,并使用其他模型参数:
num_layers = 3
min_word_freq = 5
rnn_size = 128
epochs = 10
- 第一个主要变化是我们将按字符加载,处理和提供文本,而不是按字词加载。为了实现这一点,在清理文本之后,我们可以使用 Python 的
list()命令逐个字符地分隔整个文本:
s_text = re.sub(r'[{}]'.format(punctuation), ' ', s_text)
s_text = re.sub('s+', ' ', s_text ).strip().lower()
# Split up by characters
char_list = list(s_text)
- 我们现在需要更改 LSTM 模型,使其具有多个层。我们接受
num_layers变量并使用 TensorFlow 的MultiRNNCell()函数创建一个多层 RNN 模型,如下所示:
class LSTM_Model():
def __init__(self, rnn_size, num_layers, batch_size, learning_rate,
training_seq_len, vocab_size, infer_sample=False):
self.rnn_size = rnn_size
self.num_layers = num_layers
self.vocab_size = vocab_size
self.infer_sample = infer_sample
self.learning_rate = learning_rate
...
self.lstm_cell = tf.contrib.rnn.BasicLSTMCell(rnn_size)
self.lstm_cell = tf.contrib.rnn.MultiRNNCell([self.lstm_cell for _ in range(self.num_layers)])
self.initial_state = self.lstm_cell.zero_state(self.batch_size, tf.float32)
self.x_data = tf.placeholder(tf.int32, [self.batch_size, self.training_seq_len])
self.y_output = tf.placeholder(tf.int32, [self.batch_size, self.training_seq_len])
请注意,TensorFlow 的
MultiRNNCell()函数接受 RNN 单元列表。在这个项目中,RNN 层都是相同的,但您可以列出您希望堆叠在一起的任何 RNN 层。
- 其他一切基本相同。在这里,我们可以看到一些训练输出:
Building Shakespeare Vocab by Characters
Vocabulary Length = 40
Starting Epoch #1 of 10
Iteration: 9430, Epoch: 10, Batch: 889 out of 950, Loss: 1.54
Iteration: 9440, Epoch: 10, Batch: 899 out of 950, Loss: 1.46
Iteration: 9450, Epoch: 10, Batch: 909 out of 950, Loss: 1.49
thou art more than the
to be or not to the serva
wherefore art thou dost thou
Iteration: 9460, Epoch: 10, Batch: 919 out of 950, Loss: 1.41
Iteration: 9470, Epoch: 10, Batch: 929 out of 950, Loss: 1.45
Iteration: 9480, Epoch: 10, Batch: 939 out of 950, Loss: 1.59
Iteration: 9490, Epoch: 10, Batch: 949 out of 950, Loss: 1.42
- 以下是最终文本输出的示例:
thou art more fancy with to be or not to be for be wherefore art thou art thou
- 最后,以下是我们如何绘制几代的训练损失:
plt.plot(train_loss, 'k-')
plt.title('Sequence to Sequence Loss')
plt.xlabel('Generation')
plt.ylabel('Loss')
plt.show()

图 6:多层 LSTM 莎士比亚模型的训练损失与世代的关系图
工作原理
TensorFlow 只需一个 RNN 单元列表即可轻松将 RNN 层扩展到多个层。对于这个秘籍,我们使用与上一个秘籍相同的莎士比亚数据,但是用字符而不是单词处理它。我们通过三层 LSTM 模型来生成莎士比亚文本。我们可以看到,在仅仅 10 个周期之后,我们就能够以文字的形式产生古老的英语。
创建序列到序列模型
由于我们使用的每个 RNN 单元也都有输出,我们可以训练 RNN 序列来预测其他可变长度的序列。对于这个秘籍,我们将利用这一事实创建一个英语到德语的翻译模型。
准备
对于这个秘籍,我们将尝试构建一个语言翻译模型,以便从英语翻译成德语。
TensorFlow 具有用于序列到序列训练的内置模型类。我们将说明如何在下载的英语 - 德语句子上训练和使用它。我们将使用的数据来自 www.manythings.org 的编译 zip 文件,该文件汇编了 Tatoeba 项目 的数据。这些数据是制表符分隔的英语 - 德语句子翻译;例如,一行可能包含句子hello. /t hallo。该数据包含数千种不同长度的句子。
此部分的代码已升级为使用 TensorFlow 官方仓库提供的神经机器翻译模型。
该项目将向您展示如何下载数据,使用,修改和添加到超参数,以及配置您自己的数据以使用项目文件。
虽然官方教程向您展示了如何通过命令行执行此操作,但本教程将向您展示如何使用提供的内部代码从头开始训练您自己的模型。
操作步骤
- 我们首先加载必要的库:
import os
import re
import sys
import json
import math
import time
import string
import requests
import io
import numpy as np
import collections
import random
import pickle
import string
import matplotlib.pyplot as plt
import tensorflow as tf
from zipfile import ZipFile
from collections import Counter
from tensorflow.python.ops import lookup_ops
from tensorflow.python.framework import ops
ops.reset_default_graph()
local_repository = 'temp/seq2seq'
- 以下代码块将整个 NMT 模型仓库导入
temp文件夹:
if not os.path.exists(local_repository):
from git import Repo
tf_model_repository = 'https://github.com/tensorflow/nmt/'
Repo.clone_from(tf_model_repository, local_repository)
sys.path.insert(0, 'temp/seq2seq/nmt/')
# May also try to use 'attention model' by importing the attention model:
# from temp.seq2seq.nmt import attention_model as attention_model
from temp.seq2seq.nmt import model as model
from temp.seq2seq.nmt.utils import vocab_utils as vocab_utils
import temp.seq2seq.nmt.model_helper as model_helper
import temp.seq2seq.nmt.utils.iterator_utils as iterator_utils
import temp.seq2seq.nmt.utils.misc_utils as utils
import temp.seq2seq.nmt.train as train
- 接下来,我们设置一些关于词汇量大小,我们将删除的标点符号以及数据存储位置的参数:
# Model Parameters
vocab_size = 10000
punct = string.punctuation
# Data Parameters
data_dir = 'temp'
data_file = 'eng_ger.txt'
model_path = 'seq2seq_model'
full_model_dir = os.path.join(data_dir, model_path)
- 我们将使用 TensorFlow 提供的超参数格式。这种类型的参数存储(在外部
json或xml文件中)允许我们以编程方式迭代不同类型的架构(在不同的文件中)。对于本演示,我们将使用提供给我们的wmt16.json并进行一些更改:
# Load hyper-parameters for translation model. (Good defaults are provided in Repository).
hparams = tf.contrib.training.HParams()
param_file = 'temp/seq2seq/nmt/standard_hparams/wmt16.json'
# Can also try: (For different architectures)
# 'temp/seq2seq/nmt/standard_hparams/iwslt15.json'
# 'temp/seq2seq/nmt/standard_hparams/wmt16_gnmt_4_layer.json',
# 'temp/seq2seq/nmt/standard_hparams/wmt16_gnmt_8_layer.json',
with open(param_file, "r") as f:
params_json = json.loads(f.read())
for key, value in params_json.items():
hparams.add_hparam(key, value)
hparams.add_hparam('num_gpus', 0)
hparams.add_hparam('num_encoder_layers', hparams.num_layers)
hparams.add_hparam('num_decoder_layers', hparams.num_layers)
hparams.add_hparam('num_encoder_residual_layers', 0)
hparams.add_hparam('num_decoder_residual_layers', 0)
hparams.add_hparam('init_op', 'uniform')
hparams.add_hparam('random_seed', None)
hparams.add_hparam('num_embeddings_partitions', 0)
hparams.add_hparam('warmup_steps', 0)
hparams.add_hparam('length_penalty_weight', 0)
hparams.add_hparam('sampling_temperature', 0.0)
hparams.add_hparam('num_translations_per_input', 1)
hparams.add_hparam('warmup_scheme', 't2t')
hparams.add_hparam('epoch_step', 0)
hparams.num_train_steps = 5000
# Not use any pretrained embeddings
hparams.add_hparam('src_embed_file', '')
hparams.add_hparam('tgt_embed_file', '')
hparams.add_hparam('num_keep_ckpts', 5)
hparams.add_hparam('avg_ckpts', False)
# Remove attention
hparams.attention = None
- 如果模型和数据目录尚不存在,请创建它们:
# Make Model Directory
if not os.path.exists(full_model_dir):
os.makedirs(full_model_dir)
# Make data directory
if not os.path.exists(data_dir):
os.makedirs(data_dir)
- 现在我们删除标点符号并将翻译数据拆分为英语和德语句子的单词列表:
print('Loading English-German Data')
# Check for data, if it doesn't exist, download it and save it
if not os.path.isfile(os.path.join(data_dir, data_file)):
print('Data not found, downloading Eng-Ger sentences from www.manythings.org')
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('utf-8')
eng_ger_data = eng_ger_data.encode('ascii', errors='ignore')
eng_ger_data = eng_ger_data.decode().split('\n')
# Write to file
with open(os.path.join(data_dir, data_file), 'w') as out_conn:
for sentence in eng_ger_data:
out_conn.write(sentence + '\n')
else:
eng_ger_data = []
with open(os.path.join(data_dir, data_file), 'r') as in_conn:
for row in in_conn:
eng_ger_data.append(row[:-1])
print('Done!')
- 现在我们删除英语和德语句子的标点符号:
# Remove punctuation
eng_ger_data = [''.join(char for char in sent if char not in punct) for sent in eng_ger_data]
# Split each sentence by tabs
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)]
english_sentence = [x.lower().split() for x in english_sentence]
german_sentence = [x.lower().split() for x in german_sentence]
- 为了使用 TensorFlow 中更快的数据管道函数,我们需要以适当的格式将格式化的数据写入磁盘。翻译模型期望的格式如下:
train_prefix.source_suffix = train.en
train_prefix.target_suffix = train.de
后缀将决定语言(en = English,de = deutsch),前缀决定数据集的类型(训练或测试):
# We need to write them to separate text files for the text-line-dataset operations.
train_prefix = 'train'
src_suffix = 'en' # English
tgt_suffix = 'de' # Deutsch (German)
source_txt_file = train_prefix + '.' + src_suffix
hparams.add_hparam('src_file', source_txt_file)
target_txt_file = train_prefix + '.' + tgt_suffix
hparams.add_hparam('tgt_file', target_txt_file)
with open(source_txt_file, 'w') as f:
for sent in english_sentence:
f.write(' '.join(sent) + '\n')
with open(target_txt_file, 'w') as f:
for sent in german_sentence:
f.write(' '.join(sent) + '\n')
- 接下来,我们需要解析一些(~100)测试句子翻译。我们任意选择大约 100 个句子。然后我们也将它们写入适当的文件:
# Partition some sentences off for testing files
test_prefix = 'test_sent'
hparams.add_hparam('dev_prefix', test_prefix)
hparams.add_hparam('train_prefix', train_prefix)
hparams.add_hparam('test_prefix', test_prefix)
hparams.add_hparam('src', src_suffix)
hparams.add_hparam('tgt', tgt_suffix)
num_sample = 100
total_samples = len(english_sentence)
# Get around 'num_sample's every so often in the src/tgt sentences
ix_sample = [x for x in range(total_samples) if x % (total_samples // num_sample) == 0]
test_src = [' '.join(english_sentence[x]) for x in ix_sample]
test_tgt = [' '.join(german_sentence[x]) for x in ix_sample]
# Write test sentences to file
with open(test_prefix + '.' + src_suffix, 'w') as f:
for eng_test in test_src:
f.write(eng_test + '\n')
with open(test_prefix + '.' + tgt_suffix, 'w') as f:
for ger_test in test_src:
f.write(ger_test + '\n')
- 接下来,我们处理英语和德语句子的词汇表。然后我们将词汇表列表保存到适当的文件中:
print('Processing the vocabularies.')
# Process the English Vocabulary
all_english_words = [word for sentence in english_sentence for word in sentence]
all_english_counts = Counter(all_english_words)
eng_word_keys = [x[0] for x in all_english_counts.most_common(vocab_size-3)] # -3 because UNK, S, /S is also in there
eng_vocab2ix = dict(zip(eng_word_keys, range(1, vocab_size)))
eng_ix2vocab = {val: key for key, val in eng_vocab2ix.items()}
english_processed = []
for sent in english_sentence:
temp_sentence = []
for word in sent:
try:
temp_sentence.append(eng_vocab2ix[word])
except KeyError:
temp_sentence.append(0)
english_processed.append(temp_sentence)
# Process the German Vocabulary
all_german_words = [word for sentence in german_sentence for word in sentence]
all_german_counts = Counter(all_german_words)
ger_word_keys = [x[0] for x in all_german_counts.most_common(vocab_size-3)]
# -3 because UNK, S, /S is also in there
ger_vocab2ix = dict(zip(ger_word_keys, range(1, vocab_size)))
ger_ix2vocab = {val: key for key, val in ger_vocab2ix.items()}
german_processed = []
for sent in german_sentence:
temp_sentence = []
for word in sent:
try:
temp_sentence.append(ger_vocab2ix[word])
except KeyError:
temp_sentence.append(0)
german_processed.append(temp_sentence)
# Save vocab files for data processing
source_vocab_file = 'vocab' + '.' + src_suffix
hparams.add_hparam('src_vocab_file', source_vocab_file)
eng_word_keys = ['<unk>', '<s>', '</s>'] + eng_word_keys
target_vocab_file = 'vocab' + '.' + tgt_suffix
hparams.add_hparam('tgt_vocab_file', target_vocab_file)
ger_word_keys = ['<unk>', '<s>', '</s>'] + ger_word_keys
# Write out all unique english words
with open(source_vocab_file, 'w') as f:
for eng_word in eng_word_keys:
f.write(eng_word + '\n')
# Write out all unique german words
with open(target_vocab_file, 'w') as f:
for ger_word in ger_word_keys:
f.write(ger_word + '\n')
# Add vocab size to hyper parameters
hparams.add_hparam('src_vocab_size', vocab_size)
hparams.add_hparam('tgt_vocab_size', vocab_size)
# Add out-directory
out_dir = 'temp/seq2seq/nmt_out'
hparams.add_hparam('out_dir', out_dir)
if not tf.gfile.Exists(out_dir):
tf.gfile.MakeDirs(out_dir)
- 接下来,我们将分别创建训练,推断和评估图。首先,我们创建训练图。我们用一个类来做这个并将参数设为
namedtuple。此代码来自 NMT TensorFlow 仓库。有关更多信息,请参阅名为model_helper.py的仓库中的文件:
class TrainGraph(collections.namedtuple("TrainGraph", ("graph", "model", "iterator", "skip_count_placeholder"))):
pass
def create_train_graph(scope=None):
graph = tf.Graph()
with graph.as_default():
src_vocab_table, tgt_vocab_table = vocab_utils.create_vocab_tables(hparams.src_vocab_file, hparams.tgt_vocab_file,share_vocab=False)
src_dataset = tf.data.TextLineDataset(hparams.src_file)
tgt_dataset = tf.data.TextLineDataset(hparams.tgt_file)
skip_count_placeholder = tf.placeholder(shape=(), dtype=tf.int64)
iterator = iterator_utils.get_iterator(src_dataset, tgt_dataset, src_vocab_table, tgt_vocab_table, batch_size=hparams.batch_size, sos=hparams.sos, eos=hparams.eos, random_seed=None, num_buckets=hparams.num_buckets, src_max_len=hparams.src_max_len, tgt_max_len=hparams.tgt_max_len, skip_count=skip_count_placeholder)
final_model = model.Model(hparams, iterator=iterator, mode=tf.contrib.learn.ModeKeys.TRAIN, source_vocab_table=src_vocab_table, target_vocab_table=tgt_vocab_table, scope=scope)
return TrainGraph(graph=graph, model=final_model, iterator=iterator, skip_count_placeholder=skip_count_placeholder)
train_graph = create_train_graph()
- 我们现在创建评估图:
# Create the evaluation graph
class EvalGraph(collections.namedtuple("EvalGraph", ("graph", "model", "src_file_placeholder", "tgt_file_placeholder","iterator"))):
pass
def create_eval_graph(scope=None):
graph = tf.Graph()
with graph.as_default():
src_vocab_table, tgt_vocab_table = vocab_utils.create_vocab_tables(
hparams.src_vocab_file, hparams.tgt_vocab_file, hparams.share_vocab)
src_file_placeholder = tf.placeholder(shape=(), dtype=tf.string)
tgt_file_placeholder = tf.placeholder(shape=(), dtype=tf.string)
src_dataset = tf.data.TextLineDataset(src_file_placeholder)
tgt_dataset = tf.data.TextLineDataset(tgt_file_placeholder)
iterator = iterator_utils.get_iterator(
src_dataset,
tgt_dataset,
src_vocab_table,
tgt_vocab_table,
hparams.batch_size,
sos=hparams.sos,
eos=hparams.eos,
random_seed=hparams.random_seed,
num_buckets=hparams.num_buckets,
src_max_len=hparams.src_max_len_infer,
tgt_max_len=hparams.tgt_max_len_infer)
final_model = model.Model(hparams,
iterator=iterator,
mode=tf.contrib.learn.ModeKeys.EVAL,
source_vocab_table=src_vocab_table,
target_vocab_table=tgt_vocab_table,
scope=scope)
return EvalGraph(graph=graph,
model=final_model,
src_file_placeholder=src_file_placeholder,
tgt_file_placeholder=tgt_file_placeholder,
iterator=iterator)
eval_graph = create_eval_graph()
- 现在我们对推理图做同样的事情:
# Inference graph
class InferGraph(collections.namedtuple("InferGraph", ("graph","model","src_placeholder", "batch_size_placeholder","iterator"))):
pass
def create_infer_graph(scope=None):
graph = tf.Graph()
with graph.as_default():
src_vocab_table, tgt_vocab_table = vocab_utils.create_vocab_tables(hparams.src_vocab_file,hparams.tgt_vocab_file, hparams.share_vocab)
reverse_tgt_vocab_table = lookup_ops.index_to_string_table_from_file(hparams.tgt_vocab_file, default_value=vocab_utils.UNK)
src_placeholder = tf.placeholder(shape=[None], dtype=tf.string)
batch_size_placeholder = tf.placeholder(shape=[], dtype=tf.int64)
src_dataset = tf.data.Dataset.from_tensor_slices(src_placeholder)
iterator = iterator_utils.get_infer_iterator(src_dataset,
src_vocab_table,
batch_size=batch_size_placeholder,
eos=hparams.eos,
src_max_len=hparams.src_max_len_infer)
final_model = model.Model(hparams,
iterator=iterator,
mode=tf.contrib.learn.ModeKeys.INFER,
source_vocab_table=src_vocab_table,
target_vocab_table=tgt_vocab_table,
reverse_target_vocab_table=reverse_tgt_vocab_table,
scope=scope)
return InferGraph(graph=graph,
model=final_model,
src_placeholder=src_placeholder,
batch_size_placeholder=batch_size_placeholder,
iterator=iterator)
infer_graph = create_infer_graph()
- 为了在训练期间提供更多说明性输出,我们提供了在训练迭代期间输出的任意源/目标翻译的简短列表:
# Create sample data for evaluation
sample_ix = [25, 125, 240, 450]
sample_src_data = [' '.join(english_sentence[x]) for x in sample_ix]
sample_tgt_data = [' '.join(german_sentence[x]) for x in sample_ix]
print([x for x in zip(sample_src_data, sample_tgt_data)])
- 接下来,我们加载训练图:
config_proto = utils.get_config_proto()
train_sess = tf.Session(config=config_proto, graph=train_graph.graph)
eval_sess = tf.Session(config=config_proto, graph=eval_graph.graph)
infer_sess = tf.Session(config=config_proto, graph=infer_graph.graph)
# Load the training graph
with train_graph.graph.as_default():
loaded_train_model, global_step = model_helper.create_or_load_model(train_graph.model,
hparams.out_dir,
train_sess,
"train")
summary_writer = tf.summary.FileWriter(os.path.join(hparams.out_dir, 'Training'), train_graph.graph)
- 现在我们将评估操作添加到图中:
for metric in hparams.metrics:
hparams.add_hparam("best_" + metric, 0)
best_metric_dir = os.path.join(hparams.out_dir, "best_" + metric)
hparams.add_hparam("best_" + metric + "_dir", best_metric_dir)
tf.gfile.MakeDirs(best_metric_dir)
eval_output = train.run_full_eval(hparams.out_dir, infer_graph, infer_sess, eval_graph, eval_sess, hparams, summary_writer, sample_src_data, sample_tgt_data)
eval_results, _, acc_blue_scores = eval_output
- 现在我们创建初始化操作并初始化图;我们还初始化了一些将更新每次迭代的参数(时间,全局步骤和周期步骤):
# Training Initialization
last_stats_step = global_step
last_eval_step = global_step
last_external_eval_step = global_step
steps_per_eval = 10 * hparams.steps_per_stats
steps_per_external_eval = 5 * steps_per_eval
avg_step_time = 0.0
step_time, checkpoint_loss, checkpoint_predict_count = 0.0, 0.0, 0.0
checkpoint_total_count = 0.0
speed, train_ppl = 0.0, 0.0
utils.print_out("# Start step %d, lr %g, %s" %
(global_step, loaded_train_model.learning_rate.eval(session=train_sess),
time.ctime()))
skip_count = hparams.batch_size * hparams.epoch_step
utils.print_out("# Init train iterator, skipping %d elements" % skip_count)
train_sess.run(train_graph.iterator.initializer,
feed_dict={train_graph.skip_count_placeholder: skip_count})
请注意,默认情况下,训练将每 1,000 次迭代保存模型。如果需要,您可以在超参数中更改此设置。目前,训练此模型并保存最新的五个模型占用大约 2 GB 的硬盘空间。
- 以下代码将开始模型的训练和评估。训练的重要部分是在循环的最开始(前三分之一)。其余代码专门用于评估,从样本推断和保存模型,如下所示:
# Run training
while global_step < hparams.num_train_steps:
start_time = time.time()
try:
step_result = loaded_train_model.train(train_sess)
(_, step_loss, step_predict_count, step_summary, global_step, step_word_count,
batch_size, __, ___) = step_result
hparams.epoch_step += 1
except tf.errors.OutOfRangeError:
# Next Epoch
hparams.epoch_step = 0
utils.print_out("# Finished an epoch, step %d. Perform external evaluation" % global_step)
train.run_sample_decode(infer_graph,
infer_sess,
hparams.out_dir,
hparams,
summary_writer,
sample_src_data,
sample_tgt_data)
dev_scores, test_scores, _ = train.run_external_eval(infer_graph,
infer_sess,
hparams.out_dir,
hparams,
summary_writer)
train_sess.run(train_graph.iterator.initializer, feed_dict={train_graph.skip_count_placeholder: 0})
continue
summary_writer.add_summary(step_summary, global_step)
# Statistics
step_time += (time.time() - start_time)
checkpoint_loss += (step_loss * batch_size)
checkpoint_predict_count += step_predict_count
checkpoint_total_count += float(step_word_count)
# print statistics
if global_step - last_stats_step >= hparams.steps_per_stats:
last_stats_step = global_step
avg_step_time = step_time / hparams.steps_per_stats
train_ppl = utils.safe_exp(checkpoint_loss / checkpoint_predict_count)
speed = checkpoint_total_count / (1000 * step_time)
utils.print_out(" global step %d lr %g "
"step-time %.2fs wps %.2fK ppl %.2f %s" %
(global_step,
loaded_train_model.learning_rate.eval(session=train_sess),
avg_step_time, speed, train_ppl, train._get_best_results(hparams)))
if math.isnan(train_ppl):
break
# Reset timer and loss.
step_time, checkpoint_loss, checkpoint_predict_count = 0.0, 0.0, 0.0
checkpoint_total_count = 0.0
if global_step - last_eval_step >= steps_per_eval:
last_eval_step = global_step
utils.print_out("# Save eval, global step %d" % global_step)
utils.add_summary(summary_writer, global_step, "train_ppl", train_ppl)
# Save checkpoint
loaded_train_model.saver.save(train_sess, os.path.join(hparams.out_dir, "translate.ckpt"), global_step=global_step)
# Evaluate on dev/test
train.run_sample_decode(infer_graph,
infer_sess,
out_dir,
hparams,
summary_writer,
sample_src_data,
sample_tgt_data)
dev_ppl, test_ppl = train.run_internal_eval(eval_graph,
eval_sess,
out_dir,
hparams,
summary_writer)
if global_step - last_external_eval_step >= steps_per_external_eval:
last_external_eval_step = global_step
# Save checkpoint
loaded_train_model.saver.save(train_sess, os.path.join(hparams.out_dir, "translate.ckpt"), global_step=global_step)
train.run_sample_decode(infer_graph,
infer_sess,
out_dir,
hparams,
summary_writer,
sample_src_data,
sample_tgt_data)
dev_scores, test_scores, _ = train.run_external_eval(infer_graph,
infer_sess,
out_dir,
hparams,
summary_writer)
工作原理
对于这个秘籍,我们使用 TensorFlow 内置的序列到序列模型从英语翻译成德语。
由于我们没有为我们的测试句子提供完美的翻译,因此还有改进的余地。如果我们训练时间更长,并且可能组合一些桶(每个桶中有更多的训练数据),我们可能能够改进我们的翻译。
更多
在 ManyThings 网站上托管了其他类似的双语句子数据集。您可以随意替换任何吸引您的语言数据集。
训练 Siamese RNN 相似性度量
与许多其他模型相比,RNN 模型的一个重要特性是它们可以处理各种长度的序列。利用这一点,以及它们可以推广到之前未见过的序列这一事实,我们可以创建一种方法来衡量输入的相似序列是如何相互作用的。在这个秘籍中,我们将训练一个 Siamese 相似性 RNN 来测量地址之间的相似性以进行记录匹配。
准备
在本文中,我们将构建一个双向 RNN 模型,该模型将输入到一个完全连接的层,该层输出一个固定长度的数值向量。我们为两个输入地址创建双向 RNN 层,并将输出馈送到完全连接的层,该层输出固定长度的数字向量(长度 100)。然后我们将两个向量输出与余弦距离进行比较,余弦距离在 -1 和 1 之间。我们将输入数据表示为与目标 1 相似,并且目标为 -1。余弦距离的预测只是输出的符号(负值表示不相似,正表示相似)。我们可以使用此网络通过从查询地址获取在余弦距离上得分最高的参考地址来进行记录匹配。
请参阅以下网络架构图:

图 8:Siamese RNN 相似性模型架构
这个模型的优点还在于它接受以前没有见过的输入,并且可以将它们与 -1 到 1 的输出进行比较。我们将通过选择模型之前未见过的测试地址在代码中显示它并查看它是否可以匹配到类似的地址。
操作步骤
- 我们首先加载必要的库并启动图会话:
import os
import random
import string
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
sess = tf.Session()
- 我们现在设置模型参数如下:
batch_size = 200
n_batches = 300
max_address_len = 20
margin = 0.25
num_features = 50
dropout_keep_prob = 0.8
- 接下来,我们创建 Siamese RNN 相似性模型类,如下所示:
def snn(address1, address2, dropout_keep_prob,
vocab_size, num_features, input_length):
# Define the Siamese double RNN with a fully connected layer at the end
def Siamese_nn(input_vector, num_hidden):
cell_unit = tf.nn.rnn_cell.BasicLSTMCell
# Forward direction cell
lstm_forward_cell = cell_unit(num_hidden, forget_bias=1.0)
lstm_forward_cell = tf.nn.rnn_cell.DropoutWrapper(lstm_forward_cell, output_keep_prob=dropout_keep_prob)
# Backward direction cell
lstm_backward_cell = cell_unit(num_hidden, forget_bias=1.0)
lstm_backward_cell = tf.nn.rnn_cell.DropoutWrapper(lstm_backward_cell, output_keep_prob=dropout_keep_prob)
# Split title into a character sequence
input_embed_split = tf.split(1, input_length, input_vector)
input_embed_split = [tf.squeeze(x, squeeze_dims=[1]) for x in input_embed_split]
# Create bidirectional layer
outputs, _, _ = tf.nn.bidirectional_rnn(lstm_forward_cell,
lstm_backward_cell,
input_embed_split,
dtype=tf.float32)
# Average The output over the sequence
temporal_mean = tf.add_n(outputs) / input_length
# Fully connected layer
output_size = 10
A = tf.get_variable(name="A", shape=[2*num_hidden, output_size],
dtype=tf.float32,
initializer=tf.random_normal_initializer(stddev=0.1))
b = tf.get_variable(name="b", shape=[output_size], dtype=tf.float32,
initializer=tf.random_normal_initializer(stddev=0.1))
final_output = tf.matmul(temporal_mean, A) + b
final_output = tf.nn.dropout(final_output, dropout_keep_prob)
return(final_output)
with tf.variable_scope("Siamese") as scope:
output1 = Siamese_nn(address1, num_features)
# Declare that we will use the same variables on the second string
scope.reuse_variables()
output2 = Siamese_nn(address2, num_features)
# Unit normalize the outputs
output1 = tf.nn.l2_normalize(output1, 1)
output2 = tf.nn.l2_normalize(output2, 1)
# Return cosine distance
# in this case, the dot product of the norms is the same.
dot_prod = tf.reduce_sum(tf.mul(output1, output2), 1)
return dot_prod
请注意,使用变量范围在两个地址输入的 Siamese 网络的两个部分之间共享参数。另外,请注意,余弦距离是通过归一化向量的点积来实现的。
- 现在我们将声明我们的预测函数,它只是余弦距离的符号,如下所示:
def get_predictions(scores):
predictions = tf.sign(scores, name="predictions")
return predictions
- 现在我们将如前所述声明我们的
loss函数。请记住,我们希望为误差留下边距(类似于 SVM 模型)。我们还将有一个真正的积极和真正的消极的损失期限。使用以下代码进行损失:
def loss(scores, y_target, margin):
# Calculate the positive losses
pos_loss_term = 0.25 * tf.square(tf.sub(1., scores))
pos_mult = tf.cast(y_target, tf.float32)
# Make sure positive losses are on similar strings
positive_loss = tf.mul(pos_mult, pos_loss_term)
# Calculate negative losses, then make sure on dissimilar strings
neg_mult = tf.sub(1., tf.cast(y_target, tf.float32))
negative_loss = neg_mult*tf.square(scores)
# Combine similar and dissimilar losses
loss = tf.add(positive_loss, negative_loss)
# Create the margin term. This is when the targets are 0, and the scores are less than m, return 0\.
# Check if target is zero (dissimilar strings)
target_zero = tf.equal(tf.cast(y_target, tf.float32), 0.)
# Check if cosine outputs is smaller than margin
less_than_margin = tf.less(scores, margin)
# Check if both are true
both_logical = tf.logical_and(target_zero, less_than_margin)
both_logical = tf.cast(both_logical, tf.float32)
# If both are true, then multiply by (1-1)=0\.
multiplicative_factor = tf.cast(1\. - both_logical, tf.float32)
total_loss = tf.mul(loss, multiplicative_factor)
# Average loss over batch
avg_loss = tf.reduce_mean(total_loss)
return avg_loss
- 我们声明
accuracy函数如下:
def accuracy(scores, y_target):
predictions = get_predictions(scores)
correct_predictions = tf.equal(predictions, y_target)
accuracy = tf.reduce_mean(tf.cast(correct_predictions, tf.float32))
return accuracy
- 我们将通过在地址中创建拼写错误来创建类似的地址。我们将这些地址(参考地址和拼写错误地址)表示为类似:
def create_typo(s):
rand_ind = random.choice(range(len(s)))
s_list = list(s)
s_list[rand_ind]=random.choice(string.ascii_lowercase + '0123456789')
s = ''.join(s_list)
return s
- 我们将生成的数据将是街道号码,
street_names和街道后缀的随机组合。名称和后缀来自以下列表:
street_names = ['abbey', 'baker', 'canal', 'donner', 'elm', 'fifth', 'grandvia', 'hollywood', 'interstate', 'jay', 'kings']
street_types = ['rd', 'st', 'ln', 'pass', 'ave', 'hwy', 'cir', 'dr', 'jct']
- 我们生成测试查询和引用如下:
test_queries = ['111 abbey ln', '271 doner cicle',
'314 king avenue', 'tensorflow is fun']
test_references = ['123 abbey ln', '217 donner cir', '314 kings ave', '404 hollywood st', 'tensorflow is so fun']
请注意,最后一个查询和引用不是模型之前会看到的地址,但我们希望它们将是模型最终看到的最相似的地址。
- 我们现在将定义如何生成一批数据。我们的批量数据将是 50% 类似的地址(参考地址和拼写错误地址)和 50% 不同的地址。我们通过占用地址列表的一半并将目标移动一个位置(使用
numpy.roll()函数)来生成不同的地址:
def get_batch(n):
# Generate a list of reference addresses with similar addresses that have
# a typo.
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)]
full_streets = [str(w) + ' ' + x + ' ' + y for w,x,y in zip(numbers, streets, street_suffs)]
typo_streets = [create_typo(x) for x in full_streets]
reference = [list(x) for x in zip(full_streets, typo_streets)]
# Shuffle last half of them for training on dissimilar addresses
half_ix = int(n/2)
bottom_half = reference[half_ix:]
true_address = [x[0] for x in bottom_half]
typo_address = [x[1] for x in bottom_half]
typo_address = list(np.roll(typo_address, 1))
bottom_half = [[x,y] for x,y in zip(true_address, typo_address)]
reference[half_ix:] = bottom_half
# Get target similarities (1's for similar, -1's for non-similar)
target = [1]*(n-half_ix) + [-1]*half_ix
reference = [[x,y] for x,y in zip(reference, target)]
return reference
- 接下来,我们定义地址词汇表并指定如何将地址热编码为索引:
vocab_chars = string.ascii_lowercase + '0123456789 '
vocab2ix_dict = {char:(ix+1) for ix, char in enumerate(vocab_chars)}
vocab_length = len(vocab_chars) + 1
# Define vocab one-hot encoding
def address2onehot(address,
vocab2ix_dict = vocab2ix_dict,
max_address_len = max_address_len):
# translate address string into indices
address_ix = [vocab2ix_dict[x] for x in list(address)]
# Pad or crop to max_address_len
address_ix = (address_ix + [0]*max_address_len)[0:max_address_len]
return address_ix
- 处理完词汇后,我们将开始声明我们的模型占位符和嵌入查找。对于嵌入查找,我们将使用单一矩阵作为查找矩阵来使用单热编码嵌入。使用以下代码:
address1_ph = tf.placeholder(tf.int32, [None, max_address_len], name="address1_ph")
address2_ph = tf.placeholder(tf.int32, [None, max_address_len], name="address2_ph")
y_target_ph = tf.placeholder(tf.int32, [None], name="y_target_ph")
dropout_keep_prob_ph = tf.placeholder(tf.float32, name="dropout_keep_prob")
# Create embedding lookup
identity_mat = tf.diag(tf.ones(shape=[vocab_length]))
address1_embed = tf.nn.embedding_lookup(identity_mat, address1_ph)
address2_embed = tf.nn.embedding_lookup(identity_mat, address2_ph)
- 我们现在将声明
model,batch_accuracy,batch_loss和predictions操作如下:
# Define Model
text_snn = model.snn(address1_embed, address2_embed, dropout_keep_prob_ph,
vocab_length, num_features, max_address_len)
# Define Accuracy
batch_accuracy = model.accuracy(text_snn, y_target_ph)
# Define Loss
batch_loss = model.loss(text_snn, y_target_ph, margin)
# Define Predictions
predictions = model.get_predictions(text_snn)
- 最后,在我们开始训练之前,我们将优化和初始化操作添加到图中,如下所示:
# Declare optimizer
optimizer = tf.train.AdamOptimizer(0.01)
# Apply gradients
train_op = optimizer.minimize(batch_loss)
# Initialize Variables
init = tf.global_variables_initializer()
sess.run(init)
- 我们现在将遍历训练世代并跟踪损失和准确率:
train_loss_vec = []
train_acc_vec = []
for b in range(n_batches):
# Get a batch of data
batch_data = get_batch(batch_size)
# Shuffle data
np.random.shuffle(batch_data)
# Parse addresses and targets
input_addresses = [x[0] for x in batch_data]
target_similarity = np.array([x[1] for x in batch_data])
address1 = np.array([address2onehot(x[0]) for x in input_addresses])
address2 = np.array([address2onehot(x[1]) for x in input_addresses])
train_feed_dict = {address1_ph: address1,
address2_ph: address2,
y_target_ph: target_similarity,
dropout_keep_prob_ph: dropout_keep_prob}
_, train_loss, train_acc = sess.run([train_op, batch_loss, batch_accuracy],
feed_dict=train_feed_dict)
# Save train loss and accuracy
train_loss_vec.append(train_loss)
train_acc_vec.append(train_acc)
- 经过训练,我们现在处理测试查询和引用,以了解模型的执行方式:
test_queries_ix = np.array([address2onehot(x) for x in test_queries])
test_references_ix = np.array([address2onehot(x) for x in test_references])
num_refs = test_references_ix.shape[0]
best_fit_refs = []
for query in test_queries_ix:
test_query = np.repeat(np.array([query]), num_refs, axis=0)
test_feed_dict = {address1_ph: test_query,
address2_ph: test_references_ix,
y_target_ph: target_similarity,
dropout_keep_prob_ph: 1.0}
test_out = sess.run(text_snn, feed_dict=test_feed_dict)
best_fit = test_references[np.argmax(test_out)]
best_fit_refs.append(best_fit)
print('Query Addresses: {}'.format(test_queries))
print('Model Found Matches: {}'.format(best_fit_refs))
- 这产生以下输出:
Query Addresses: ['111 abbey ln', '271 doner cicle', '314 king avenue', 'tensorflow is fun']
Model Found Matches: ['123 abbey ln', '217 donner cir', '314 kings ave', 'tensorflow is so fun']
更多
我们可以从测试查询和参考中看到模型不仅能够识别正确的参考地址,而且还能够推广到非地址短语。我们还可以通过查看训练期间的损失和准确率来了解模型的执行情况:

图 9:训练期间 Siamese RNN 相似性模型的准确率和损失
请注意,我们没有为此练习指定测试集。这是因为我们如何生成数据。我们创建了一个批量函数,每次调用它时都会创建新的批量数据,因此模型始终可以看到新数据。因此,我们可以使用批量损失和精度作为测试损失和准确率的替代项。但是,对于一组有限的实际数据,情况永远不会如此,因为我们总是需要训练和测试集来判断模型的表现。
十、将 TensorFlow 投入生产
在本章中,我们将介绍以下主题:
- 实现单元测试
- 使用多个执行器
- 并行化 TensorFlow
- 将 TensorFlow 投入生产
- 生产环境 TensorFlow 的一个例子
- 使用 TensorFlow 服务
介绍
到目前为止,我们已经介绍了如何在 TensorFlow 中训练和评估各种模型。因此,在本章中,我们将向您展示如何编写可供生产使用的代码。生产就绪代码有各种定义,但对我们来说,生产代码将被定义为具有单元测试的代码,分离训练和评估代码,并有效地保存,并加载数据管道和图会话的各种所需部分。
本章提供的 Python 脚本应该从命令行运行。这允许运行测试,并将设备位置记录到屏幕上。
实现单元测试
测试代码可以加快原型设计速度,提高调试效率,加快更改速度,并且可以更轻松地共享代码。在 TensorFlow 中有许多简单的方法可以实现单元测试,我们将在本文中介绍它们。
准备
在编写 TensorFlow 模型时,有助于进行单元测试以检查程序的功能。这有助于我们,因为当我们想要对程序单元进行更改时,测试将确保这些更改不会以未知方式破坏模型。在这个秘籍中,我们将创建一个依赖于MNIST数据的简单 CNN 网络。有了它,我们将实现三种不同类型的单元测试来说明如何在 TensorFlow 中编写它们。
请注意,Python 有一个很棒的测试库,名为 Nose。 TensorFlow 还具有内置测试功能,我们将在其中查看,这样可以更轻松地测试 Tensor 对象的值,而无需评估会话中的值。
- 首先,我们需要加载必要的库并格式化数据,如下所示:
import sys
import numpy as np
import tensorflow as tf
from tensorflow.python.framework import ops
ops.reset_default_graph()
# Start a graph session
sess = tf.Session()
# Load data
data_dir = 'temp'
mnist = tf.keras.datasets.mnist
(train_xdata, train_labels), (test_xdata, test_labels) = mnist.load_data()
train_xdata = train_xdata / 255.0
test_xdata = test_xdata / 255.0
# Set model parameters
batch_size = 100
learning_rate = 0.005
evaluation_size = 100
image_width = train_xdata[0].shape[0]
image_height = train_xdata[0].shape[1]
target_size = max(train_labels) + 1
num_channels = 1 # greyscale = 1 channel
generations = 100
eval_every = 5
conv1_features = 25
conv2_features = 50
max_pool_size1 = 2 # NxN window for 1st max pool layer
max_pool_size2 = 2 # NxN window for 2nd max pool layer
fully_connected_size1 = 100
dropout_prob = 0.75
- 然后,我们需要声明我们的占位符,变量和模型公式,如下所示:
# Declare model placeholders
x_input_shape = (batch_size, image_width, image_height, num_channels)
x_input = tf.placeholder(tf.float32, shape=x_input_shape)
y_target = tf.placeholder(tf.int32, shape=(batch_size))
eval_input_shape = (evaluation_size, image_width, image_height, num_channels)
eval_input = tf.placeholder(tf.float32, shape=eval_input_shape)
eval_target = tf.placeholder(tf.int32, shape=(evaluation_size))
dropout = tf.placeholder(tf.float32, shape=())
# Declare model parameters
conv1_weight = tf.Variable(tf.truncated_normal([4, 4, num_channels, conv1_features],
stddev=0.1, dtype=tf.float32))
conv1_bias = tf.Variable(tf.zeros([conv1_features], dtype=tf.float32))
conv2_weight = tf.Variable(tf.truncated_normal([4, 4, conv1_features, conv2_features],
stddev=0.1, dtype=tf.float32))
conv2_bias = tf.Variable(tf.zeros([conv2_features], dtype=tf.float32))
# fully connected variables
resulting_width = image_width // (max_pool_size1 * max_pool_size2)
resulting_height = image_height // (max_pool_size1 * max_pool_size2)
full1_input_size = resulting_width * resulting_height * conv2_features
full1_weight = tf.Variable(tf.truncated_normal([full1_input_size, fully_connected_size1],
stddev=0.1, dtype=tf.float32))
full1_bias = tf.Variable(tf.truncated_normal([fully_connected_size1], stddev=0.1, dtype=tf.float32))
full2_weight = tf.Variable(tf.truncated_normal([fully_connected_size1, target_size],
stddev=0.1, dtype=tf.float32))
full2_bias = tf.Variable(tf.truncated_normal([target_size], stddev=0.1, dtype=tf.float32))
# Initialize Model Operations
def my_conv_net(input_data):
# First Conv-ReLU-MaxPool Layer
conv1 = tf.nn.conv2d(input_data, conv1_weight, strides=[1, 1, 1, 1], padding='SAME')
relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_bias))
max_pool1 = tf.nn.max_pool(relu1, ksize=[1, max_pool_size1, max_pool_size1, 1],
strides=[1, max_pool_size1, max_pool_size1, 1], padding='SAME')
# Second Conv-ReLU-MaxPool Layer
conv2 = tf.nn.conv2d(max_pool1, conv2_weight, strides=[1, 1, 1, 1], padding='SAME')
relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_bias))
max_pool2 = tf.nn.max_pool(relu2, ksize=[1, max_pool_size2, max_pool_size2, 1],
strides=[1, max_pool_size2, max_pool_size2, 1], padding='SAME')
# Transform Output into a 1xN layer for next fully connected layer
final_conv_shape = max_pool2.get_shape().as_list()
final_shape = final_conv_shape[1] * final_conv_shape[2] * final_conv_shape[3]
flat_output = tf.reshape(max_pool2, [final_conv_shape[0], final_shape])
# First Fully Connected Layer
fully_connected1 = tf.nn.relu(tf.add(tf.matmul(flat_output, full1_weight), full1_bias))
# Second Fully Connected Layer
final_model_output = tf.add(tf.matmul(fully_connected1, full2_weight), full2_bias)
# Add dropout
final_model_output = tf.nn.dropout(final_model_output, dropout)
return final_model_output
model_output = my_conv_net(x_input)
test_model_output = my_conv_net(eval_input)
- 接下来,我们创建我们的损失函数以及我们的预测和精确操作。然后,我们初始化以下模型变量:
# Declare Loss Function (softmax cross entropy)
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(model_output, y_target))
# Create a prediction function
prediction = tf.nn.softmax(model_output)
test_prediction = tf.nn.softmax(test_model_output)
# Create accuracy function
def get_accuracy(logits, targets):
batch_predictions = np.argmax(logits, axis=1)
num_correct = np.sum(np.equal(batch_predictions, targets))
return 100\. * num_correct/batch_predictions.shape[0]
# Create an optimizer
my_optimizer = tf.train.MomentumOptimizer(learning_rate, 0.9)
train_step = my_optimizer.minimize(loss)
# Initialize Variables
init = tf.global_variables_initializer()
sess.run(init)
- 对于我们的第一个单元测试,我们使用类
tf.test.TestCase并创建一种方法来测试占位符(或变量)的值。对于此测试用例,我们确保损失概率(用于保持)大于0.25,因此模型不会更改为尝试训练超过 75% 的损失,如下所示:
# Check values of tensors!
class DropOutTest(tf.test.TestCase):
# Make sure that we don't drop too much
def dropout_greaterthan(self):
with self.test_session():
self.assertGreater(dropout.eval(), 0.25)
- 接下来,我们需要测试我们的
accuracy函数是否按预期运行。为此,我们创建一个概率样本数组和我们期望的样本,然后确保测试精度返回 100% ,如下所示:
# Test accuracy function
class AccuracyTest(tf.test.TestCase):
# Make sure accuracy function behaves correctly
def accuracy_exact_test(self):
with self.test_session():
test_preds = [[0.9, 0.1],[0.01, 0.99]]
test_targets = [0, 1]
test_acc = get_accuracy(test_preds, test_targets)
self.assertEqual(test_acc.eval(), 100.)
- 我们还可以确保
Tensor对象是我们期望的形状。要通过target_size测试模型输出是batch_size的预期形状,请输入以下代码:
# Test tensorshape
class ShapeTest(tf.test.TestCase):
# Make sure our model output is size [batch_size, num_classes]
def output_shape_test(self):
with self.test_session():
numpy_array = np.ones([batch_size, target_size])
self.assertShapeEqual(numpy_array, model_output)
- 现在我们需要在脚本中使用
main()函数告诉 TensorFlow 我们正在运行哪个应用。脚本如下:
def main(argv):
# Start training loop
train_loss = []
train_acc = []
test_acc = []
for i in range(generations):
rand_index = np.random.choice(len(train_xdata), size=batch_size)
rand_x = train_xdata[rand_index]
rand_x = np.expand_dims(rand_x, 3)
rand_y = train_labels[rand_index]
train_dict = {x_input: rand_x, y_target: rand_y, dropout: dropout_prob}
sess.run(train_step, feed_dict=train_dict)
temp_train_loss, temp_train_preds = sess.run([loss, prediction], feed_dict=train_dict)
temp_train_acc = get_accuracy(temp_train_preds, rand_y)
if (i + 1) % eval_every == 0:
eval_index = np.random.choice(len(test_xdata), size=evaluation_size)
eval_x = test_xdata[eval_index]
eval_x = np.expand_dims(eval_x, 3)
eval_y = test_labels[eval_index]
test_dict = {eval_input: eval_x, eval_target: eval_y, dropout: 1.0}
test_preds = sess.run(test_prediction, feed_dict=test_dict)
temp_test_acc = get_accuracy(test_preds, eval_y)
# Record and print results
train_loss.append(temp_train_loss)
train_acc.append(temp_train_acc)
test_acc.append(temp_test_acc)
acc_and_loss = [(i + 1), temp_train_loss, temp_train_acc, temp_test_acc]
acc_and_loss = [np.round(x, 2) for x in acc_and_loss]
print('Generation # {}. Train Loss: {:.2f}. Train Acc (Test Acc): {:.2f}
({:.2f})'.format(*acc_and_loss))
- 要让我们的脚本执行测试或训练,我们需要以不同的方式从命令行调用它。以下代码段是主程序代码。如果程序收到参数
test,它将执行测试;否则,它将运行训练:
if __name__ == '__main__':
cmd_args = sys.argv
if len(cmd_args) > 1 and cmd_args[1] == 'test':
# Perform unit-tests
tf.test.main(argv=cmd_args[1:])
else:
# Run the TensorFlow app
tf.app.run(main=None, argv=cmd_args)
- 如果我们在命令行上运行程序,我们应该得到以下输出:
$ python3 implementing_unit_tests.py test
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
前面步骤中描述的完整程序可以在书籍的 GitHub 仓库和 Packt 仓库中找到。
工作原理
在本节中,我们实现了三种类型的单元测试:张量值,操作输出和张量形状。 TensorFlow 有更多类型的单元测试函数,可在此处找到 。
请记住,单元测试有助于确保代码能够按预期运行,为共享代码提供信心,并使再现性更易于访问。
使用多个执行器
您将意识到 TensorFlow 有许多功能,包括计算图,它们可以自然地并行计算。计算图可以分为不同的处理器以及处理不同的批量。我们将讨论如何在此秘籍中访问同一台机器上的不同处理器。
准备
对于此秘籍,我们将向您展示如何在同一系统上访问多个设备并对其进行训练。这是一种非常常见的情况:与 CPU 一起,机器可能具有一个或多个可以共享计算负载的 GPU。如果 TensorFlow 可以访问这些设备,它将通过贪婪的过程自动将计算分配给多个设备。但是,TensorFlow 还允许程序通过名称范围放置指定哪些设备将在哪个设备上。
要访问 GPU 设备,必须安装 GPU 版本的 TensorFlow。要安装 TensorFlow 的 GPU 版本,请访问此链接。下载,设置并按照特定系统的说明进行操作。请注意,TensorFlow 的 GPU 版本需要 CUDA 才能使用 GPU。
在本文中,我们将向您展示各种命令,允许您访问系统上的各种设备;我们还将演示如何找出 TensorFlow 正在使用的设备。
操作步骤
- 为了找出 TensorFlow 用于哪些操作的设备,我们需要在会话参数中设置
config,将log_device_placement设置为True。当我们从命令行运行脚本时,我们将看到特定的设备放置,如以下输出所示:
import tensorflow as tf
sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3], name='a')
b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2], name='b')
c = tf.matmul(a, b)
# Runs the op.
print(sess.run(c))
- 从终端,运行以下命令:
$python3 using_multiple_devices.py
Device mapping: no known devices.
I tensorflow/core/common_runtime/direct_session.cc:175] Device mapping:
MatMul: /job:localhost/replica:0/task:0/cpu:0
I tensorflow/core/common_runtime/simple_placer.cc:818] MatMul: /job:localhost/replica:0/task:0/cpu:0
b: /job:localhost/replica:0/task:0/cpu:0
I tensorflow/core/common_runtime/simple_placer.cc:818] b: /job:localhost/replica:0/task:0/cpu:0
a: /job:localhost/replica:0/task:0/cpu:0
I tensorflow/core/common_runtime/simple_placer.cc:818] a: /job:localhost/replica:0/task:0/cpu:0
[[ 22\. 28.]
[ 49\. 64.]]
- 默认情况下,TensorFlow 会自动决定如何跨计算设备(CPU 和 GPU)分配计算,有时我们需要了解这些展示位置。这在加载早期的现有模型时非常有用,该模型在我们的计算机具有不同设备时在图中分配了硬展示位置。我们可以在配置中设置软放置以解决此问题,如下所示:
config = tf.ConfigProto()
config.allow_soft_placement = True
sess_soft = tf.Session(config=config)
- 使用 GPU 时,TensorFlow 会自动占用 GPU 内存的很大一部分。虽然通常需要这样做,但我们可以采取措施更加小心 GPU 内存分配。虽然 TensorFlow 从未发布 GPU 内存,但我们可以通过设置 GPU 内存增长选项,将其分配缓慢增加到最大限制(仅在需要时),如下所示:
config.gpu_options.allow_growth = True
sess_grow = tf.Session(config=config)
- 如果我们想对 TensorFlow 使用的 GPU 内存百分比设置硬限制,我们可以使用
config设置per_process_gpu_memory_fraction,如下所示:
config.gpu_options.per_process_gpu_memory_fraction = 0.4
sess_limited = tf.Session(config=config)
- 有时我们可能需要编写可靠的代码来确定它是否在 GPU 可用的情况下运行。 TensorFlow 具有内置功能,可以测试 GPU 是否可用。当我们想要编写在可用时利用 GPU 并为其分配特定操作的代码时,这很有用。这是通过以下代码完成的:
if tf.test.is_built_with_cuda():
<Run GPU specific code here>
- 如果我们需要为 GPU 分配特定操作,请输入以下代码。这将执行简单的计算并将操作分配给主 CPU 和两个辅助 GPU:
with tf.device('/cpu:0'):
a = tf.constant([1.0, 3.0, 5.0], shape=[1, 3])
b = tf.constant([2.0, 4.0, 6.0], shape=[3, 1])
with tf.device('/gpu:0'):
c = tf.matmul(a,b)
c = tf.reshape(c, [-1])
with tf.device('/gpu:1'):
d = tf.matmul(b,a)
flat_d = tf.reshape(d, [-1])
combined = tf.multiply(c, flat_d)
print(sess.run(combined))
工作原理
当我们想在我们的机器上为 TensorFlow 操作指定特定设备时,我们需要知道 TensorFlow 如何引用这些设备。 TensorFlow 中的设备名称遵循以下约定:
| 设备 | 设备名称 |
| --- | --- | --- |
| 主 CPU | /CPU:0 |
| 第二个 CPU | /CPU:1 |
| 主 GPU | /GPU:0 |
| 第二个 GPU | /GPU:1 |
| 第三个 GPU | /GPU:2 |
更多
幸运的是,在云中运行 TensorFlow 现在比以往更容易。许多云计算服务提供商都提供 GPU 实例,其中包含主 CPU 和强大的 GPU。 Amazon Web Services(AWS)具有 G 实例和 P2 实例,允许使用功能强大的 GPU,为 TensorFlow 流程提供极快的速度。您甚至可以免费选择 AWS Machine Images(AMI),它将在安装了 TensorFlow 的 GPU 实例的情况下启动选定的实例。
并行化 TensorFlow
为了扩展 TensorFlow 并行化的范围,我们还可以以分布式方式在完全不同的机器上从我们的图执行单独的操作。这个秘籍将告诉你如何。
准备
在 TensorFlow 发布几个月后,谷歌发布了分布式 TensorFlow,它是对 TensorFlow 生态系统的一次重大升级,并且允许在不同的工作机器上设置 TensorFlow 集群,并分享训练和评估的计算任务楷模。使用分布式 TensorFlow 就像为工作器设置参数一样简单,然后为不同的工作器分配不同的工作。
在这个秘籍中,我们将建立两个本地工作器并将他们分配到不同的工作。
操作步骤
- 首先,我们加载 TensorFlow 并使用配置字典文件(端口
2222和2223)定义我们的两个本地 worker,如下所示:
import tensorflow as tf
# Cluster for 2 local workers (tasks 0 and 1):
cluster = tf.train.ClusterSpec({'local': ['localhost:2222', 'localhost:2223']})
- 现在,我们将两个工作器连接到服务器并使用以下任务编号标记它们:
server = tf.train.Server(cluster, job_name="local", task_index=0)
server = tf.train.Server(cluster, job_name="local", task_index=1)
- 现在我们将让每个工作器完成一项任务。第一个工作器将初始化两个矩阵(每个矩阵将是 25 乘 25)。第二个工作器将找到所有元素的总和。然后,我们将自动分配两个总和的总和并打印输出,如下所示:
mat_dim = 25
matrix_list = {}
with tf.device('/job:local/task:0'):
for i in range(0, 2):
m_label = 'm_{}'.format(i)
matrix_list[m_label] = tf.random_normal([mat_dim, mat_dim])
# Have each worker calculate the sums
sum_outs = {}
with tf.device('/job:local/task:1'):
for i in range(0, 2):
A = matrix_list['m_{}'.format(i)]
sum_outs['m_{}'.format(i)] = tf.reduce_sum(A)
# Sum all the sums
summed_out = tf.add_n(list(sum_outs.values()))
with tf.Session(server.target) as sess:
result = sess.run(summed_out)
print('Summed Values:{}'.format(result))
- 输入上面的代码后,我们可以在命令提示符下运行以下命令:
$ python3 parallelizing_tensorflow.py
I tensorflow/core/distributed_runtime/rpc/grpc_channel.cc:197] Initialize GrpcChannelCache for job local -> {0 -> localhost:2222, 1 -> localhost:2223}
I tensorflow/core/distributed_runtime/rpc/grpc_server_lib.cc:206] Started server with target: grpc://localhost:2222
I tensorflow/core/distributed_runtime/rpc/grpc_channel.cc:197] Initialize GrpcChannelCache for job local -> {0 -> localhost:2222, 1 -> localhost:2223}
I tensorflow/core/distributed_runtime/rpc/grpc_server_lib.cc:206] Started server with target: grpc://localhost:2223
I tensorflow/core/distributed_runtime/master_session.cc:928] Start master session 252bb6f530553002 with config:
Summed Values:-21.12611198425293
工作原理
使用分布式 TensorFlow 非常简单。您所要做的就是将工作器 IP 分配给具有名称的服务器。然后,可以手动或自动为工作器分配操作。
将 TensorFlow 投入生产
如果我们想在生产环境中使用我们的机器学习脚本,我们首先需要考虑一些要点作为最佳实践。在本节中,我们将概述其中的一些内容。
准备
在本文中,我们想总结并浓缩将 TensorFlow 投入生产的各种技巧。我们将介绍如何最好地保存和加载词汇表,图,变量和模型检查点。我们还将讨论如何使用 TensorFlow 的命令行参数解析器并更改 TensorFlow 的日志记录详细程度。
操作步骤
- 运行 TensorFlow 程序时,我们可能需要检查内存中是否已存在其他图会话,或者在调试程序后是否清除了图会话。我们可以使用以下命令行来完成此任务:
from tensorflow.python.framework import ops
ops.reset_default_graph()
- 在处理文本(或任何数据管道)时,我们需要确保我们保存处理数据的方式,以便我们可以以相同的方式处理未来的评估数据。例如,如果我们处理文本,我们需要确保我们可以保存并加载词汇表。以下代码是如何使用
JSON库保存词汇表字典的示例:
import json word_list = ['to', 'be', 'or', 'not', 'to', 'be']
vocab_list = list(set(word_list))
vocab2ix_dict = dict(zip(vocab_list, range(len(vocab_list))))
ix2vocab_dict = {val:key for key,val in vocab2ix_dict.items()}
# Save vocabulary
import json
with open('vocab2ix_dict.json', 'w') as file_conn:
json.dump(vocab2ix_dict, file_conn)
# Load vocabulary
with open('vocab2ix_dict.json', 'r') as file_conn:
vocab2ix_dict = json.load(file_conn)
在这里,我们以
JSON格式保存了词汇词典,但我们也可以将其保存在text文件,csv甚至二进制格式中。如果词汇量很大,则首选二进制文件。您还可以考虑使用 Pickle 库来创建pkl二进制文件,但请注意,Pickle 文件在库和 Python 版本之间不能很好地转换。
- 为了保存模型图和变量,我们创建了一个
Saver()操作并将其添加到图中。建议我们在训练期间定期保存模型。要保存模型,请输入以下代码:
After model declaration, add a saving operations
saver = tf.train.Saver()
# Then during training, save every so often, referencing the training generation
for i in range(generations):
...
if i%save_every == 0:
saver.save(sess, 'my_model', global_step=step)
# Can also save only specific variables:
saver = tf.train.Saver({"my_var": my_variable})
请注意,
Saver()操作也会采用其他参数。如前面的示例所示,它可以使用变量和张量字典来保存特定元素。每隔n小时也可以检查一次,定期执行保存操作。默认情况下,保存操作仅保留最后五个模型保存(出于空间考虑)。可以使用maximum_to_keep选项更改此设置。
- 在保存模型之前,请务必命名模型的重要操作。如果 TensorFlow 没有名称,则没有简单的方法来加载特定的占位符,操作或变量。 TensorFlow 中的大多数操作和函数都接受
name参数,如下例所示:
conv_weights = tf.Variable(tf.random_normal(), name='conv_weights')
loss = tf.reduce_mean(... , name='loss')
- TensorFlow 还可以使用
tf.apps.flags库在命令行上轻松执行参数解析。使用这些函数,我们可以定义字符串,浮点数,整数或布尔值的命令行参数,如下面的代码片段所示。使用这些标志定义,我们可以运行tf.app.run(),它将使用以下标志参数运行main()函数:
tf.flags.DEFINE_string("worker_locations", "", "List of worker addresses.")
tf.flags.DEFINE_float('learning_rate', 0.01, 'Initial learning rate.')
tf.flags.DEFINE_integer('generations', 1000, 'Number of training generations.')
tf.flags.DEFINE_boolean('run_unit_tests', False, 'If true, run tests.')
FLAGS = tf.flags.FLAGS
# Need to define a 'main' function for the app to run
def main(_):
worker_ips = FLAGS.worker_locations.split(",")
learning_rate = FLAGS.learning_rate
generations = FLAGS.generations
run_unit_tests = FLAGS.run_unit_tests
# Run the Tensorflow app
if __name__ == "__main__":
# The following is looking for a "main()" function to run and will pass.
tf.app.run()
# Can modify this to be more custom:
tf.app.run(main=my_main_function(), argv=my_arguments)
- TensorFlow 具有内置日志记录,我们可以为其设置级别参数。我们可以设定的水平是
DEBUG,INFO,WARN,ERROR和FATAL。默认为WARN,如下所示:
tf.logging.set_verbosity(tf.logging.WARN)
# WARN is the default value, but to see more information, you can set it to
# INFO or DEBUG
tf.logging.set_verbosity(tf.logging.DEBUG)
工作原理
在本节中,我们提供了在 TensorFlow 中创建生产级代码的提示。我们想介绍应用标志,模型保存和日志记录等概念,以便用户可以使用这些工具一致地编写代码,并了解在其他代码中看到这些工具时的含义。还有许多其他方法可以编写好的生产代码,但下面的秘籍中将显示完整的示例。
生产环境 TensorFlow 的一个例子
生产机器学习模型的一个好方法是将训练和评估程序分开。在本节中,我们将说明一个评估脚本,该脚本已经扩展到包括单元测试,模型保存和加载以及评估。
准备
在本文中,我们将向您展示如何使用上述标准实现评估脚本。代码实际上包含一个训练脚本和一个评估脚本,但是对于这个秘籍,我们只会向您展示评估脚本。提醒一下,两个脚本都可以在在线 GitHub 仓库和 Packt 官方仓库中看到。
对于即将到来的示例,我们将实现第 9 章,回归神经网络中的第一个 RNN 示例,该示例试图预测文本消息是垃圾邮件还是非垃圾邮件。我们将假设 RNN 模型与词汇一起被训练和保存。
操作步骤
- 首先,我们首先加载必要的库并声明 TensorFlow 应用标志,如下所示:
import os
import re
import numpy as np
import tensorflow as tf
from tensorflow.python.framework import ops
ops.reset_default_graph()
# Define App Flags
tf.flags.DEFINE_string("storage_folder", "temp", "Where to store model and data.")
tf.flags.DEFINE_float('learning_rate', 0.0005, 'Initial learning rate.')
tf.flags.DEFINE_float('dropout_prob', 0.5, 'Per to keep probability for dropout.')
tf.flags.DEFINE_integer('epochs', 20, 'Number of epochs for training.')
tf.flags.DEFINE_integer('batch_size', 250, 'Batch Size for training.')
tf.flags.DEFINE_integer('rnn_size', 15, 'RNN feature size.')
tf.flags.DEFINE_integer('embedding_size', 25, 'Word embedding size.')
tf.flags.DEFINE_integer('min_word_frequency', 20, 'Word frequency cutoff.')
tf.flags.DEFINE_boolean('run_unit_tests', False, 'If true, run tests.')
FLAGS = tf.flags.FLAGS
- 接下来,我们声明一个文本清理函数。这与训练脚本中使用的清洁函数相同,如下所示:
def clean_text(text_string):
text_string = re.sub(r'([^sw]|_|[0-9])+', '', text_string)
text_string = " ".join(text_string.split())
text_string = text_string.lower()
return text_string
- 现在,我们需要加载以下词汇处理函数:
def load_vocab():
vocab_path = os.path.join(FLAGS.storage_folder, "vocab")
vocab_processor = tf.contrib.learn.preprocessing.VocabularyProcessor.restore(vocab_path)
return vocab_processor
- 现在我们有了清理文本的方法,并且还有一个词汇处理器,我们可以将这些函数组合起来为给定的文本创建数据处理管道,如下所示:
def process_data(input_data, vocab_processor):
input_data = clean_text(input_data)
input_data = input_data.split()
processed_input = np.array(list(vocab_processor.transform(input_data)))
return processed_input
- 接下来,我们需要一种方法来获取要评估的数据。为此,我们将要求用户在屏幕上键入文本。然后,我们将处理文本并返回以下处理过的文本:
def get_input_data():
input_text = input("Please enter a text message to evaluate: ")
vocab_processor = load_vocab()
return process_data(input_text, vocab_processor)
对于此示例,我们通过要求用户键入来创建评估数据。虽然许多应用将通过提供的文件或 API 请求获取数据,但我们可以相应地更改此输入数据函数。
- 对于单元测试,我们需要使用以下代码确保我们的文本清理函数正常运行:
class clean_test(tf.test.TestCase):
# Make sure cleaning function behaves correctly
def clean_string_test(self):
with self.test_session():
test_input = '--Tensorflow's so Great! Dont you think so? '
test_expected = 'tensorflows so great don you think so'
test_out = clean_text(test_input)
self.assertEqual(test_expected, test_out)
- 现在我们有了模型和数据,我们可以运行
main函数。main函数将获取数据,设置图,加载变量,输入处理过的数据,然后打印输出,如下面的代码片段所示:
def main(args):
# Get flags
storage_folder = FLAGS.storage_folder
# Get user input text
x_data = get_input_data()
# Load model
graph = tf.Graph()
with graph.as_default():
sess = tf.Session()
with sess.as_default():
# Load the saved meta graph and restore variables
saver = tf.train.import_meta_graph("{}.meta".format(os.path.join(storage_folder, "model.ckpt")))
saver.restore(sess, os.path.join(storage_folder, "model.ckpt"))
# Get the placeholders from the graph by name
x_data_ph = graph.get_operation_by_name("x_data_ph").outputs[0]
dropout_keep_prob = graph.get_operation_by_name("dropout_keep_prob").outputs[0]
probability_outputs = graph.get_operation_by_name("probability_outputs").outputs[0]
# Make the prediction
eval_feed_dict = {x_data_ph: x_data, dropout_keep_prob: 1.0}
probability_prediction = sess.run(tf.reduce_mean(probability_outputs, 0), eval_feed_dict)
# Print output (Or save to file or DB connection?)
print('Probability of Spam: {:.4}'.format(probability_prediction[1]))
- 最后,要运行
main()函数或单元测试,请使用以下代码:
if __name__ == "__main__":
if FLAGS.run_unit_tests:
# Perform unit tests
tf.test.main()
else:
# Run evaluation
tf.app.run()
工作原理
为了评估模型,我们能够使用 TensorFlow 的应用标志加载命令行参数,加载模型和词汇处理器,然后通过模型运行处理过的数据并进行预测。
请记住通过命令行运行此脚本,并在创建模型和词汇表字典之前检查是否运行了训练脚本。
使用 TensorFlow 服务
在本节中,我们将向您展示如何设置 RNN 模型以预测 TensorFlow 上的垃圾邮件或非垃圾邮件文本消息。我们将首先说明如何以 protobuf 格式保存模型,然后将模型加载到本地服务器,监听端口9000以进行输入。
准备
我们通过鼓励读者阅读 TensorFlow 服务网站上的官方文档和简短教程来开始本节。
对于这个例子,我们将在第 9 章,循环神经网络中重用我们在预测垃圾邮件中使用的大部分 RNN 代码和 RNNs 秘籍。我们将更改模型保存代码,以便将 protobuf 模型保存在使用 TensorFlow 服务所需的正确文件夹结构中。
请注意,本章中的所有脚本都应该从命令行 bash 提示符执行。
有关更新的安装说明,请访问官方安装站点。正常安装就像向 Linux 源添加 gpg-key 并运行以下安装命令一样简单:
$ sudo apt install tensorflow-model-server
操作步骤
- 在这里,我们将以与以前相同的方式开始,通过加载必要的库并设置 TensorFlow 标志,如下所示:
import os
import re
import io
import sys
import requests
import numpy as np
import tensorflow as tf
from zipfile import ZipFile
from tensorflow.python.framework import ops
ops.reset_default_graph()
# Define App Flags
tf.flags.DEFINE_string("storage_folder", "temp", "Where to store model and data.")
tf.flags.DEFINE_float('learning_rate', 0.0005, 'Initial learning rate.')
tf.flags.DEFINE_float('dropout_prob', 0.5, 'Per to keep probability for dropout.')
tf.flags.DEFINE_integer('epochs', 20, 'Number of epochs for training.')
tf.flags.DEFINE_integer('batch_size', 250, 'Batch Size for training.')
tf.flags.DEFINE_integer('rnn_size', 15, 'RNN feature size.')
tf.flags.DEFINE_integer('embedding_size', 25, 'Word embedding size.')
tf.flags.DEFINE_integer('min_word_frequency', 20, 'Word frequency cutoff.')
tf.flags.DEFINE_boolean('run_unit_tests', False, 'If true, run tests.')
FLAGS = tf.flags.FLAGS
- 我们将以完全相同的方式继续完成脚本。为简洁起见,我们只会在训练脚本中包含差异,这就是我们如何保存 protobuf 模型。这是通过在训练完成后插入以下代码来完成的:
请注意此代码与教程代码的相似之处。这里的主要区别在于模型名称,版本号以及我们正在保存 RNN 而不是 CNN 的事实。
# Save the finished model for TensorFlow Serving (pb file)
# Here, it's our storage folder / version number
out_path = os.path.join(tf.compat.as_bytes(os.path.join(storage_folder, '1')))
print('Exporting finished model to : {}'.format(out_path))
builder = tf.saved_model.builder.SavedModelBuilder(out_path)
# Build the signature_def_map.
classification_inputs = tf.saved_model.utils.build_tensor_info(x_data_ph)
classification_outputs_classes = tf.saved_model.utils.build_tensor_info(rnn_model_outputs)
classification_signature = (tf.saved_model.signature_def_utils.build_signature_def(
inputs={tf.saved_model.signature_constants.CLASSIFY_INPUTS:
classification_inputs},
outputs={tf.saved_model.signature_constants.CLASSIFY_OUTPUT_CLASSES:
classification_outputs_classes},
method_name=tf.saved_model.signature_constants.CLASSIFY_METHOD_NAME))
tensor_info_x = tf.saved_model.utils.build_tensor_info(x_data_ph)
tensor_info_y = tf.saved_model.utils.build_tensor_info(y_output_ph)
prediction_signature = (
tf.saved_model.signature_def_utils.build_signature_def(
inputs={'texts': tensor_info_x},
outputs={'scores': tensor_info_y},
method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))
legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')
builder.add_meta_graph_and_variables(
sess, [tf.saved_model.tag_constants.SERVING],
signature_def_map={
'predict_spam': prediction_signature,
tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:
classification_signature,
},
legacy_init_op=legacy_init_op)
builder.save()
print('Done exporting!')
- 对我们来说,重要的是要意识到 TensorFlow Serving 需要特定的文件或文件夹结构来加载模型。该脚本将以以下格式安装文件:

A screenshot of the directory structure that TensorFlow Serving expects.
上面的屏幕截图显示了所需的目录结构。在其中,我们有我们定义的数据目录temp,然后是我们的模型版本号1。在版本号目录中,我们保存我们的 protobuf 模型和一个包含要保存的所需变量的variables文件夹。
我们应该知道,在我们的数据目录中,TensorFlow 服务将查找整数文件夹。 TensorFlow 服务将自动启动并在最大整数下获取模型。这意味着要部署新模型,我们需要将其标记为版本 2,并将其粘贴在也标记为
2的新文件夹下。然后,TensorFlow 服务将自动获取模型。
- 要启动我们的服务器,我们使用端口,
model_name和model_base_path参数调用命令tensorflow_model_server。然后,TensorFlow Serving 查找版本号文件夹并选择最大版本编号的模型。然后它将它部署到机器上,命令通过作为参数给出的端口运行。在以下示例中,我们在本地计算机(0.0.0.0)上运行,并且接受的默认端口是9000:
$ tensorflow_model_server --port=9000 --model_name=spam_ham --model_base_path=<directory of our code>/tensorflow_cookbook/10_Taking_TensorFlow_to_Production/06_Using_TensorFlow_Serving/temp/
2018-08-09 12:05:16.206712: I tensorflow_serving/model_servers/main.cc:153] Building single TensorFlow model file config: model_name: spam_ham model_base_path: .../temp/
2018-08-09 12:05:16.206874: I tensorflow_serving/model_servers/server_core.cc:459] Adding/updating models.
2018-08-09 12:05:16.206903: I tensorflow_serving/model_servers/server_core.cc:514] (Re-)adding model: spam_ham
2018-08-09 12:05:16.307681: I tensorflow_serving/core/basic_manager.cc:716] Successfully reserved resources to load servable {name: spam_ham version: 1}
2018-08-09 12:05:16.307744: I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: spam_ham version: 1}
2018-08-09 12:05:16.307773: I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: spam_ham version: 1}
2018-08-09 12:05:16.307829: I external/org_tensorflow/tensorflow/contrib/session_bundle/bundle_shim.cc:360] Attempting to load native SavedModelBundle in bundle-shim from: .../temp/1
2018-08-09 12:05:16.307867: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:242] Loading SavedModel with tags: { serve }; from: .../temp/1
2018-08-09 12:05:16.313811: I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2018-08-09 12:05:16.325866: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:161] Restoring SavedModel bundle.
2018-08-09 12:05:16.329290: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:196] Running LegacyInitOp on SavedModel bundle.
2018-08-09 12:05:16.332936: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:291] SavedModel load for tags { serve }; Status: success. Took 25074 microseconds.
2018-08-09 12:05:16.332972: I tensorflow_serving/servables/tensorflow/saved_model_warmup.cc:83] No warmup data file found at .../temp/1/assets.extra/tf_serving_warmup_requests
2018-08-09 12:05:16.333335: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: spam_ham version: 1}
2018-08-09 12:05:16.334678: I tensorflow_serving/model_servers/main.cc:323] Running ModelServer at 0.0.0.0:9000 ...
- 我们现在可以将二进制数据提交给
<host>:9000并返回显示结果的 JSON 响应。我们可以通过任何机器和任何编程语言来完成。不必依赖客户端拥有 TensorFlow 的本地副本是非常有用的。
工作原理
如果我们将早期的生产规模部分与前一部分进行比较,主要区别在于我们在主机上部署了可以响应传入请求的模型服务器。前面的部分是一个很好的设置示例,用于执行批量结果或在可以加载 TensorFlow 的机器上工作,但秘籍不是很擅长部署可用的模型,可以进行计算,并将结果返回给任何客户。在本节中,我们将了解如何处理这种架构,如下表所示:
| 第 5 节 - 批量作业 | 第 6 节 - 通过 TensorFlow 服务的作业 | |
|---|---|---|
| 优点 | 不依赖于网络连接或主机 | 结果与客户端结构无关,唯一的要求是 Numpy 数组的正确格式化的二进制文件 |
| 缺点 | 客户端必须具有 TensorFlow 和模型文件 | 依靠可用的主机 |
| 理想的用途 | 大批量数据 | 生产服务始终可用,通常是小的请求 |
当然,每种方法的优缺点都值得商榷,两者都能满足每种情况的要求。还有许多其他可用的架构可以满足不同的需求,例如 Docker,Kubernetes,Luigi,Django/Flask,Celery,AWS 和 Azure。
更多
本章未涉及的架构工具和资源的链接如下:
- 在 Docker 中使用 TensorFlow 服务
- 在 Kubernetes 中使用 TensorFlow 服务
- Luigi,批量作业的管道工具
- 在 Flask 中使用 TensorFlow
- 用于分布式任务排队的 Python 框架
- 如何在 TensorFlow 模型中使用 AWS lambdas
十一、更多 TensorFlow
在本章中,我们将介绍以下秘籍:
- 在 TensorBoard 中可视化的图
- 使用遗传算法
- 使用 K 均值聚类
- 求解常微分方程组
- 使用随机森林
- 使用 TensorFlow 和 Keras
本章中出现的所有代码均可在 Github 和 Packt 仓库中在线获取。
介绍
在本书中,我们已经看到 TensorFlow 能够实现许多模型,但 TensorFlow 可以做更多。本章将向您展示其中的一些内容。我们将首先展示如何使用 TensorBoard 的各个方面,这是 TensorFlow 附带的一项功能,它允许我们在模型训练时可视化摘要指标,图和图像。本章中的其余秘籍将展示如何使用 TensorFlow 的group()函数进行逐步更新。该函数将允许我们实现遗传算法,执行 k 均值聚类,求解 ODE 系统,甚至创建梯度提升随机森林。
可视化 TensorBoard 中的图
监视和排除机器学习算法可能是一项艰巨的任务,尤其是在您知道结果之前必须等待很长时间才能完成训练。为了解决这个问题,TensorFlow 包含一个名为 TensorBoard 的计算图可视化工具。使用 TensorBoard,即使在训练期间,我们也可以可视化和绘制重要值(损失,准确率,批次训练时间等)。
准备
为了说明我们可以使用 TensorBoard 的各种方法,我们将从第 3 章,线性回归中的线性回归方法的 TensorFlow 方法重新实现线性回归模型。我们将生成带有误差的线性数据,并使用 TensorFlow 损失和反向传播来匹配数据线。我们将展示如何监控数值,值集的直方图以及如何在 TensorBoard 中创建图像。
操作步骤
- 首先,我们将加载脚本所需的库:
import os
import io
import time
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
- 我们现在将初始化一个会话并创建一个可以将 TensorBoard 摘要写入
tensorboard文件夹的摘要编写器:
sess = tf.Session()
# Create a visualizer object
summary_writer = tf.summary.FileWriter('tensorboard', sess.graph)
- 我们需要确保
tensorboard文件夹存在,以便摘要编写者编写tensorboard日志:
if not os.path.exists('tensorboard'):
os.makedirs('tensorboard')
- 我们现在将设置模型参数并生成模型的线性数据。请注意,由于我们生成数据的方式,我们的真实斜率值是
2。我们将随着时间的推移想象出变化的斜率,并看到它接近真正的值:
batch_size = 50
generations = 100
# Create sample input data
x_data = np.arange(1000)/10\.
true_slope = 2\.
y_data = x_data * true_slope + np.random.normal(loc=0.0, scale=25, size=1000)
- 接下来,我们将数据集拆分为训练和测试集:
train_ix = np.random.choice(len(x_data), size=int(len(x_data)*0.9), replace=False)
test_ix = np.setdiff1d(np.arange(1000), train_ix)
x_data_train, y_data_train = x_data[train_ix], y_data[train_ix]
x_data_test, y_data_test = x_data[test_ix], y_data[test_ix]
- 现在我们可以创建占位符,变量,模型运算,损失和优化操作:
x_graph_input = tf.placeholder(tf.float32, [None])
y_graph_input = tf.placeholder(tf.float32, [None])
# Declare model variables
m = tf.Variable(tf.random_normal([1], dtype=tf.float32), name='Slope')
# Declare model
output = tf.multiply(m, x_graph_input, name='Batch_Multiplication')
# Declare loss function (L1)
residuals = output - y_graph_input
l2_loss = tf.reduce_mean(tf.abs(residuals), name="L2_Loss")
# Declare optimization function
my_optim = tf.train.GradientDescentOptimizer(0.01)
train_step = my_optim.minimize(l2_loss)
- 我们现在可以创建一个 TensorBoard 操作来汇总标量值。我们将总结的标量值是模型的斜率估计值:
with tf.name_scope('Slope_Estimate'):
tf.summary.scalar('Slope_Estimate', tf.squeeze(m))
- 我们可以添加到 TensorBoard 的另一个摘要是直方图摘要,它在张量中输入多个值并输出图和直方图:
with tf.name_scope('Loss_and_Residuals'):
tf.summary.histogram('Histogram_Errors', tf.squeeze(l1_loss))
tf.summary.histogram('Histogram_Residuals', tf.squeeze(residuals))
- 创建这些摘要操作后,我们需要创建一个将所有摘要组合在一起的摘要合并操作。然后我们可以初始化模型变量:
summary_op = tf.summary.merge_all()
# Initialize Variables
init = tf.global_variables_initializer()
sess.run(init)
- 现在,我们可以训练线性模型并编写每一代的摘要:
for i in range(generations):
batch_indices = np.random.choice(len(x_data_train), size=batch_size)
x_batch = x_data_train[batch_indices]
y_batch = y_data_train[batch_indices]
_, train_loss, summary = sess.run([train_step, l2_loss, summary_op],
feed_dict={x_graph_input: x_batch,
y_graph_input: y_batch})
test_loss, test_resids = sess.run([l2_loss, residuals], feed_dict={x_graph_input: x_data_test, y_graph_input: y_data_test})
if (i+1)%10==0:
print('Generation {} of {}. Train Loss: {:.3}, Test Loss: {:.3}.'.format(i+1, generations, train_loss, test_loss))
log_writer = tf.train.SummaryWriter('tensorboard')
log_writer.add_summary(summary, i)
- 为了将最终的线性拟合图与 TensorBoard 中的数据点放在一起,我们必须以
protobuf格式创建图的图像。为此,我们将创建一个输出protobuf图像的函数:
def gen_linear_plot(slope):
linear_prediction = x_data * slope
plt.plot(x_data, y_data, 'b.', label='data')
plt.plot(x_data, linear_prediction, 'r-', linewidth=3, label='predicted line')
plt.legend(loc='upper left')
buf = io.BytesIO()
plt.savefig(buf, format='png')
buf.seek(0)
return(buf)
- 现在,我们可以创建
protobuf图像并将其添加到 TensorBoard:
# Get slope value
slope = sess.run(m)
# Generate the linear plot in buffer
plot_buf = gen_linear_plot(slope[0])
# Convert PNG buffer to TF image
image = tf.image.decode_png(plot_buf.getvalue(), channels=4)
# Add the batch dimension
image = tf.expand_dims(image, 0)
# Add image summary
image_summary_op = tf.summary.image("Linear_Plot", image)
image_summary = sess.run(image_summary_op)
log_writer.add_summary(image_summary, i)
log_writer.close()
Be careful writing image summaries too often to TensorBoard. For example, if we were to write an image summary every generation for 10,000 generations, that would generate 10,000 images worth of summary data. This tends to eat up disk space very quickly.
更多
- 由于我们要从命令行运行描述的 python 脚本,我们打开命令提示符并运行以下命令:
$ python3 using_tensorboard.py
Run the command: $tensorboard --logdir="tensorboard" Then navigate to http://127.0.0.0:6006
Generation 10 of 100\. Train Loss: 20.4, Test Loss: 20.5\.
Generation 20 of 100\. Train Loss: 17.6, Test Loss: 20.5\.
Generation 90 of 100\. Train Loss: 20.1, Test Loss: 20.5\.
Generation 100 of 100\. Train Loss: 19.4, Test Loss: 20.5\.
- 然后我们将运行前面指定的命令来启动 tensorboard:
$ tensorboard --logdir="tensorboard" Starting tensorboard b'29' on port 6006 (You can navigate to http://127.0.0.1:6006)
以下是我们在 TensorBoard 中可以看到的示例:

图 1:标量值,我们的斜率估计,在张量板中可视化
在这里,我们可以看到我们的标量总结的 100 代的绘图,斜率估计。事实上,我们可以看到它确实接近2的真正值:

图 2:在这里,我们可视化模型的误差和残差的直方图
上图显示了查看直方图摘要的一种方法,可以将其视为多个折线图:

图 3:张量板中插入的图片
前面是我们以protobuf格式放入的最终拟合和数据点图,并插入到 TensorBoard 中的图像摘要中。
使用遗传算法
TensorFlow 还可用于更新我们可以在计算图中表达的任何迭代算法。一种这样的迭代算法是遗传算法,即优化过程。
准备
在本文中,我们将说明如何实现简单的遗传算法。遗传算法是优化任何参数空间(离散,连续,平滑,非平滑等)的一种方法。我们的想法是创建一组随机初始化的解决方案,并应用选择,重组和变异来生成新的(可能更好的)子解决方案。整个想法取决于我们可以通过查看个人解决问题的程度来计算个体解决方案的适用性。
通常,遗传算法的概要是从随机初始化的群体开始,根据它们的适应性对它们进行排序,然后选择最适合的个体来随机重组(或交叉)以创建新的子解决方案。然后,这些子解决方案会稍微突变,以产生新的和看不见的改进,然后再添加回父群体。在我们将子代和父代结合起来之后,我们再次重复整个过程。
停止遗传算法的标准各不相同,但出于我们的目的,我们将迭代它们一定数量的世代。当我们最适合的人达到理想的适应水平或者在这么多代之后最大适应度没有改变时,我们也可以停止。
对于这个秘籍,我们将简单地说明如何在 Tensorflow 中执行此操作。我们要解决的问题是生成一个最接近地面实况函数的个体(50 个浮点数的数组)f(x) = sin(2πx / 50)。适应度将是个体与地面事实之间的均方误差(越高越好)的负值。
操作步骤
- 我们首先加载脚本所需的库:
import os
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
- 接下来,我们将设置遗传算法的参数。在这里,我们将有
100个体,每个个体的长度为50。选择百分比为 20%(保持前 20 名个人)。突变将被设置为特征数量的倒数,这是突变开始的常见位置。这意味着我们希望每个子解决方案的一个特征发生变化。我们将为200世代运行遗传算法:
pop_size = 100
features = 50
selection = 0.2
mutation = 1./ features
generations = 200
num_parents = int(pop_size*selection)
num_children = pop_size - num_parents
- 我们将初始化图会话并创建基础事实函数,我们将使用它来快速计算适应度:
sess = tf.Session()
# Create ground truth
truth = np.sin(2*np.pi*(np.arange(features, dtype=np.float32))/features)
- 接下来,我们将
population初始化为具有随机正常输入的 TensorFlow 变量:
population = tf.Variable(np.random.randn(pop_size, features), dtype=tf.float32)
- 我们现在可以为遗传算法创建占位符。占位符是为了事实,也是为了每一代都会改变的数据。由于我们希望父代之间的交叉位置发生变化,并且变异概率/值会发生变化,因此这些将是我们模型中的占位符:
truth_ph = tf.placeholder(tf.float32, [1, features])
crossover_mat_ph = tf.placeholder(tf.float32, [num_children, features])
mutation_val_ph = tf.placeholder(tf.float32, [num_children, features])
- 现在,我们将计算人口
fitness(负均方误差),并找到表现最佳的人:
fitness = -tf.reduce_mean(tf.square(tf.subtract(population, truth_ph)), 1)
top_vals, top_ind = tf.nn.top_k(fitness, k=pop_size)
- 为了达到结果和绘图目的,我们还希望检索人群中最适合的个体:
best_val = tf.reduce_min(top_vals)
best_ind = tf.argmin(top_vals, 0)
best_individual = tf.gather(population, best_ind)
- 接下来,我们对父代群体进行排序,并切断表现最佳的个体,使其成为下一代的父代:
population_sorted = tf.gather(population, top_ind)
parents = tf.slice(population_sorted, [0, 0], [num_parents, features])
- 现在,我们将通过创建随机洗牌的两个父矩阵来创建子项。然后,我们将父矩阵乘以 1 和 0 的交叉矩阵,我们将为占位符生成每一代:
# Indices to shuffle-gather parents
rand_parent1_ix = np.random.choice(num_parents, num_children)
rand_parent2_ix = np.random.choice(num_parents, num_children)
# Gather parents by shuffled indices, expand back out to pop_size too
rand_parent1 = tf.gather(parents, rand_parent1_ix)
rand_parent2 = tf.gather(parents, rand_parent2_ix)
rand_parent1_sel = tf.multiply(rand_parent1, crossover_mat_ph)
rand_parent2_sel = tf.multiply(rand_parent2, tf.subtract(1., crossover_mat_ph))
children_after_sel = tf.add(rand_parent1_sel, rand_parent2_sel)
- 最后的步骤是改变子项,我们将通过向子矩阵中的少量条目添加随机正常量并将此矩阵连接回父族:
mutated_children = tf.add(children_after_sel, mutation_val_ph)
# Combine children and parents into new population
new_population = tf.concat(0, [parents, mutated_children])
- 我们模型的最后一步是使用 TensorFlow 的
group()操作将新种群分配给旧种群的变量:
step = tf.group(population.assign(new_population))
- 我们现在可以初始化模型变量,如下所示:
init = tf.global_variables_initializer()
sess.run(init)
- 最后,我们遍历世代,重新创建随机交叉和变异矩阵并更新每一代的人口:
for i in range(generations):
# Create cross-over matrices for plugging in.
crossover_mat = np.ones(shape=[num_children, features])
crossover_point = np.random.choice(np.arange(1, features-1, step=1), num_children)
for pop_ix in range(num_children):
crossover_mat[pop_ix,0:crossover_point[pop_ix]]=0\.
# Generate mutation probability matrices
mutation_prob_mat = np.random.uniform(size=[num_children, features])
mutation_values = np.random.normal(size=[num_children, features])
mutation_values[mutation_prob_mat >= mutation] = 0
# Run GA step
feed_dict = {truth_ph: truth.reshape([1, features]),
crossover_mat_ph: crossover_mat,
mutation_val_ph: mutation_values}
step.run(feed_dict, session=sess)
best_individual_val = sess.run(best_individual, feed_dict=feed_dict)
if i % 5 == 0:
best_fit = sess.run(best_val, feed_dict = feed_dict)
print('Generation: {}, Best Fitness (lowest MSE): {:.2}'.format(i, -best_fit))
- 这产生以下输出:
Generation: 0, Best Fitness (lowest MSE): 1.5
Generation: 5, Best Fitness (lowest MSE): 0.83
Generation: 10, Best Fitness (lowest MSE): 0.55
Generation: 185, Best Fitness (lowest MSE): 0.085
Generation: 190, Best Fitness (lowest MSE): 0.15
Generation: 195, Best Fitness (lowest MSE): 0.083
工作原理
在本文中,我们向您展示了如何使用 TensorFlow 运行简单的遗传算法。为了验证它是否有效,我们还可以在一个图上查看最合适的个体解决方案和基本事实:

图 4:200 代后的真实情况和最适合个体的绘图图。我们可以看到,最合适的个体非常接近真相
更多
遗传算法有许多变化。我们可以有两个具有两个不同适合度标准的父代群体(例如,最低 MSE 和平滑度)。我们可以对突变值施加限制,使其不大于 1 或小于 -1。我们可以进行许多不同的更改,这些更改会有很大差异,具体取决于我们要优化的问题。对于这个人为的问题,很容易计算出适应度,但对于大多数遗传算法来说,计算适应度是一项艰巨的任务。例如,如果我们想使用遗传算法来优化卷积神经网络的架构,我们可以让个体成为参数数组。参数可以代表每个卷积层的滤波器大小,步幅大小等。这种个体的适应性将是在通过数据集的固定量的迭代之后的分类的准确率。如果我们在这个人口中有 100 个人,我们将不得不为每一代评估 100 个不同的 CNN 模型。这在计算上非常强烈。
在使用遗传算法解决问题之前,明智的做法是弄清楚计算个体的fitness需要多长时间。如果此操作耗时,遗传算法可能不是最佳使用工具。
使用 K 均值聚类
TensorFlow 还可用于实现迭代聚类算法,例如 K 均值。在本文中,我们展示了在iris数据集上使用 K 均值的示例。
准备
我们在本书中探讨的几乎所有机器学习模型都是监督模型。 TensorFlow 非常适合这些类型的问题。但是,如果我们愿意,我们也可以实现无监督的模型。例如,此秘籍将实现 K 均值聚类。
我们将实现聚类的数据集是iris数据集。这是一个很好的数据集的原因之一是因为我们已经知道有三种不同的目标(三种类型的鸢尾花)。这让我们知道我们正在寻找数据中的三个不同的集群。
我们将iris数据集聚类为三组,然后将这些聚类的准确率与实际标签进行比较。
操作步骤
- 首先,我们加载必要的库。我们还从
sklearn加载了一些 PCA 工具,以便我们可以将结果数据从四维更改为二维,以实现可视化目的:
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from sklearn import datasets
from scipy.spatial import cKDTree
from sklearn.decomposition import PCA
from sklearn.preprocessing import scale
- 我们启动图会话,并加载
iris数据集:
sess = tf.Session()
iris = datasets.load_iris()
num_pts = len(iris.data)
num_feats = len(iris.data[0])
- 我们现在将设置组,代,并创建图所需的变量:
k=3
generations = 25
data_points = tf.Variable(iris.data)
cluster_labels = tf.Variable(tf.zeros([num_pts], dtype=tf.int64))
- 我们需要的下一个变量是每组的质心。我们将通过随机选择
iris数据集的三个不同点来初始化 k-means 算法的质心:
rand_starts = np.array([iris.data[np.random.choice(len(iris.data))] for _ in range(k)])
centroids = tf.Variable(rand_starts)
- 现在,我们需要计算每个数据点和每个
centroids之间的距离。我们通过将centroids扩展为矩阵来实现这一点,对数据点也是如此。然后我们将计算两个矩阵之间的欧几里德距离:
centroid_matrix = tf.reshape(tf.tile(centroids, [num_pts, 1]), [num_pts, k, num_feats])
point_matrix = tf.reshape(tf.tile(data_points, [1, k]), [num_pts, k, num_feats])
distances = tf.reduce_sum(tf.square(point_matrix - centroid_matrix), reduction_indices=2)
centroids赋值是每个数据点最接近的centroids(最小距离):
centroid_group = tf.argmin(distances, 1)
- 现在,我们必须计算组平均值以获得新的质心:
def data_group_avg(group_ids, data):
# Sum each group
sum_total = tf.unsorted_segment_sum(data, group_ids, 3)
# Count each group
num_total = tf.unsorted_segment_sum(tf.ones_like(data), group_ids, 3)
# Calculate average
avg_by_group = sum_total/num_total
return(avg_by_group)
means = data_group_avg(centroid_group, data_points)
update = tf.group(centroids.assign(means), cluster_labels.assign(centroid_group))
- 接下来,我们初始化模型变量:
init = tf.global_variables_initializer()
sess.run(init)
- 我们将遍历几代并相应地更新每个组的质心:
for i in range(generations):
print('Calculating gen {}, out of {}.'.format(i, generations))
_, centroid_group_count = sess.run([update, centroid_group])
group_count = []
for ix in range(k):
group_count.append(np.sum(centroid_group_count==ix))
print('Group counts: {}'.format(group_count))
- 这产生以下输出:
Calculating gen 0, out of 25\. Group counts: [50, 28, 72] Calculating gen 1, out of 25\. Group counts: [50, 35, 65] Calculating gen 23, out of 25\. Group counts: [50, 38, 62] Calculating gen 24, out of 25\. Group counts: [50, 38, 62]
- 为了验证我们的聚类,我们可以使用聚类进行预测。我们现在看到有多少数据点位于相同鸢尾种类的相似簇中:
[centers, assignments] = sess.run([centroids, cluster_labels])
def most_common(my_list):
return(max(set(my_list), key=my_list.count))
label0 = most_common(list(assignments[0:50]))
label1 = most_common(list(assignments[50:100]))
label2 = most_common(list(assignments[100:150]))
group0_count = np.sum(assignments[0:50]==label0)
group1_count = np.sum(assignments[50:100]==label1)
group2_count = np.sum(assignments[100:150]==label2)
accuracy = (group0_count + group1_count + group2_count)/150\.
print('Accuracy: {:.2}'.format(accuracy))
- 这产生以下输出:
Accuracy: 0.89
- 为了直观地看到我们的分组,如果它们确实已经分离出
iris物种,我们将使用 PCA 将四维转换为二维,并绘制数据点和组。在 PCA 分解之后,我们在 x-y 值网格上创建预测,以绘制颜色图:
pca_model = PCA(n_components=2)
reduced_data = pca_model.fit_transform(iris.data)
# Transform centers
reduced_centers = pca_model.transform(centers)
# Step size of mesh for plotting
h = .02
x_min, x_max = reduced_data[:, 0].min() - 1, reduced_data[:, 0].max() + 1
y_min, y_max = reduced_data[:, 1].min() - 1, reduced_data[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
# Get k-means classifications for the grid points
xx_pt = list(xx.ravel())
yy_pt = list(yy.ravel())
xy_pts = np.array([[x,y] for x,y in zip(xx_pt, yy_pt)])
mytree = cKDTree(reduced_centers)
dist, indexes = mytree.query(xy_pts)
indexes = indexes.reshape(xx.shape)
- 并且,这里是
matplotlib代码将我们的发现结合在一个绘图上。这个密码的绘图部分很大程度上改编自 scikit-learn 文档网站上的演示:
plt.clf()
plt.imshow(indexes, interpolation='nearest',
extent=(xx.min(), xx.max(), yy.min(), yy.max()),
cmap=plt.cm.Paired,
aspect='auto', origin='lower')
# Plot each of the true iris data groups
symbols = ['o', '^', 'D']
label_name = ['Setosa', 'Versicolour', 'Virginica']
for i in range(3):
temp_group = reduced_data[(i*50):(50)*(i+1)]
plt.plot(temp_group[:, 0], temp_group[:, 1], symbols[i], markersize=10, label=label_name[i])
# Plot the centroids as a white X
plt.scatter(reduced_centers[:, 0], reduced_centers[:, 1],
marker='x', s=169, linewidths=3,
color='w', zorder=10)
plt.title('K-means clustering on Iris Datasets'
'Centroids are marked with white cross')
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.legend(loc='lower right')
plt.show()
这个绘图代码将向我们展示三个类,三个类的预测空间以及每个组的质心:

图 5:显示 K 均值的无监督分类算法的屏幕截图;可以用来将三种鸢尾花种组合在一起。三个 K 均值组是三个阴影区域,三个不同的点(圆,三角形和菱形)是三个真正的物种分类
更多
对于此秘籍,我们使用 TensorFlow 将iris数据集聚类为三组。然后,我们计算了落入相似组的数据点的百分比(89%),并绘制了所得 K 均值组的图。由于 K 均值作为分类算法是局部线性的(线性分离器向上),因此很难学习杂色鸢尾和弗吉尼亚鸢尾之间的天然非线性边界。但是,一个优点是 K 均值算法根本不需要标记数据来执行。
求解常微分方程组
TensorFlow 可用于许多算法实现和过程。 TensorFlow 多功能性的一个很好的例子是实现 ODE 求解器。以数字方式求解 ODE 是一种迭代过程,可以在计算图中轻松描述。对于这个秘籍,我们将解决 Lotka-Volterra 捕食者 - 猎物系统。
准备
该秘籍将说明如何求解常微分方程(ODE)系统。我们可以使用与前两节类似的方法来更新值,因为我们迭代并解决 ODE 系统。
我们将考虑的 ODE 系统是着名的 Lotka-Volterra 捕食者 - 猎物系统。该系统显示了捕食者 - 食饵系统如何在给定特定参数的情况下振荡。
Lotka-Volterra 系统于 1920 年在一篇论文中发表(参见图 1,标量值,我们的斜率估计,在张量板中可视化)。我们将使用类似的参数来表明可以发生振荡系统。这是以数学上离散的方式表示的系统:


在这里,X是猎物,Y将成为捕食者。我们通过a,b,c和d的值来确定哪个是猎物,哪个是捕食者:对于猎物,a > 0,b < 0和捕食者,c < 0,d > 0。我们将在 TensorFlow 解决方案中将此离散版本实现到系统中。
操作步骤
- 我们首先加载库并启动图会话:
import matplotlib.pyplot as plt
import tensorflow as tf
sess = tf.Session()
- 然后我们在图中声明我们的常量和变量:
x_initial = tf.constant(1.0)
y_initial = tf.constant(1.0)
X_t1 = tf.Variable(x_initial)
Y_t1 = tf.Variable(y_initial)
# Make the placeholders
t_delta = tf.placeholder(tf.float32, shape=())
a = tf.placeholder(tf.float32, shape=())
b = tf.placeholder(tf.float32, shape=())
c = tf.placeholder(tf.float32, shape=())
d = tf.placeholder(tf.float32, shape=())
- 接下来,我们将实现先前引入的离散系统,然后更新
X和Y群体:
X_t2 = X_t1 + (a * X_t1 + b * X_t1 * Y_t1) * t_delta
Y_t2 = Y_t1 + (c * Y_t1 + d * X_t1 * Y_t1) * t_delta
# Update to New Population
step = tf.group(
X_t1.assign(X_t2),
Y_t1.assign(Y_t2))
- 我们现在初始化图并运行离散 ODE 系统,并使用特定参数来说明循环行为:
init = tf.global_variables_initializer() sess.run(init) # Run the ODE prey_values = [] predator_values = [] for i in range(1000): # Step simulation (using constants for a known cyclic solution) step.run({a: (2./3.), b: (-4./3.), c: -1.0, d: 1.0, t_delta: 0.01}, session=sess) # Store each outcome temp_prey, temp_pred = sess.run([X_t1, Y_t1]) prey_values.append(temp_prey) predator_values.append(temp_pred)
A steady state (and cyclic) solution to this specific system, the Lotka-Volterra equations, very much depends on specific parameters and population values. We encourage the reader to try different parameters and values to see what can happen.
- 现在,我们可以绘制捕食者和猎物的值:
plt.plot(prey_values, label="Prey")
plt.plot(predator_values, label="Predator")
plt.legend(loc='upper right')
plt.show()
这个绘图代码将生成一个屏幕截图,显示掠食者和猎物的振荡种群:

图 6:在这里,我们绘制 ODE 解决方案的捕食者和猎物值。事实上,我们可以看到周期确实发生了
工作原理
我们使用 TensorFlow 逐步求解 ODE 系统的离散版本。对于特定参数,我们看到捕食者 - 食饵系统确实可以具有循环解。这在我们的系统生物学上是有意义的,因为如果有太多的捕食者,猎物开始死亡,然后掠食者的食物就会减少,所以他们会死掉,等等。
另见
Lotka,A.J.,关于有机系统中某些节奏关系的分析性说明。
使用随机森林
随机森林算法建立在随机选择的观察和/或随机选择的特征上的聚合决策树上。我们不会介绍如何训练决策树,但会显示有些类型的随机森林可以使用梯度提升训练,TensorFlow 可以为我们计算。
准备
基于树的算法传统上是非平滑的,因为它们基于对数据进行分区以最小化目标输出中的方差。非光滑方法不适合基于梯度的方法。 TensorFlow 依赖于以下事实:模型中使用的函数是平滑的,并且它自动计算如何更改模型参数以最小化函数损失。 TensorFlow 绕过这个障碍的方式是对决策边界进行平滑逼近。可以使用 softmax 函数或类似形状函数来近似决策边界。
决策树将通过生成规则在数据集上提供硬拆分,例如,如果x > 0.5,则移动到树的这个分支....这告诉我们整个数据子集将组合在一起或拆分,取决于x的值。这个的平滑近似处理概率而不是整个分裂。这意味着数据集的每次观察都有可能存在于树的每个末端节点中。下图比较传统决策树和概率决策树,可以更好地说明这些差异。
下图说明了两个示例决策树之间的区别:

This diagram illustrates a standard decision tree (left) which is non-differentiable, and a smooth decision tree (right), which illustrates the usage of sigmoid functions to develop probabilities of an observation appearing in a labeled leaf or end-node.
我们选择不详细介绍函数的可微性,连续性和平滑性。本节的目的是提供关于如何通过可微分模型近似非可微分模型的直观描述。有关更多数学详细信息,我们建议读者查看本秘籍末尾的“另见”部分。
操作步骤
TensorFlow 包含了一些我们将依赖于此秘籍的默认模型估计函数。有两个主要的梯度提升模型,回归树和分类树。对于此示例,我们将使用回归树来预测波士顿房价数据集。
- 首先,我们加载必要的库:
import numpy as np
import tensorflow as tf
from keras.datasets import boston_housing
from tensorflow.python.framework import ops
ops.reset_default_graph()
- 接下来,我们从 TensorFlow 估计器库中设置我们将要使用的模型。在这里,我们将使用
BoostedTreesRegressor模型,该模型用于使用梯度提升树进行回归:
regression_classifier = tf.estimator.BoostedTreesRegressor
或者,对于二分类问题,读者可以使用估计器
BoostedTreesClassifier。目前不支持多类别分类,尽管它将来会在路线图上。
- 现在,我们可以使用 Keras 数据导入函数将波士顿住房价格数据集加载到一行中,如下所示:
(x_train, y_train), (x_test, y_test) = boston_housing.load_data()
- 在这里,我们可以设置一些模型参数;批量大小是一次训练的训练观测数量,我们将训练
500迭代,梯度提升森林将有100树,每棵树的最大深度(分裂数)为6。
# Batch size
batch_size = 32
# Number of training steps
train_steps = 500
# Number of trees in our 'forest'
n_trees = 100
# Maximum depth of any tree in forest
max_depth = 6
- TensorFlow 提供的模型估计器需要输入函数。我们将为估计器函数创建数据输入函数。但首先,我们需要将数据放入正确标记的
numpy数组格式的字典中。这些在 TensorFlow 中称为特征列。纯数字列尚不支持,因此我们将数字列放入自动存储桶中,如下所示:(a)二元特征将具有两个存储桶,(b)其他连续数字特征将被划分为 5 个存储桶。
binary_split_cols = ['CHAS', 'RAD']
col_names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT']
X_dtrain = {col: x_train[:, ix] for ix, col in enumerate(col_names)}
X_dtest = {col: x_test[:, ix] for ix, col in enumerate(col_names)}
# Create feature columns!
feature_cols = []
for ix, column in enumerate(x_train.T):
col_name = col_names[ix]
# Create binary split feature
if col_name in binary_split_cols:
# To create 2 buckets, need 1 boundary - the mean
bucket_boundaries = [column.mean()]
numeric_feature = tf.feature_column.numeric_column(col_name)
final_feature = tf.feature_column.bucketized_column(source_column=numeric_feature, boundaries=bucket_boundaries)
# Create bucketed feature
else:
# To create 5 buckets, need 4 boundaries
bucket_boundaries = list(np.linspace(column.min() * 1.1, column.max() * 0.9, 4))
numeric_feature = tf.feature_column.numeric_column(col_name)
final_feature = tf.feature_column.bucketized_column(source_column=numeric_feature, boundaries=bucket_boundaries)
# Add feature to feature_col list
feature_cols.append(final_feature)
将输入函数的
shuffle选项设置为True进行训练,False进行测试是个好主意。我们想在每个周期改变X和Y训练集,但不是在测试期间。
- 我们现在使用 TensorFlow 估计器中输入库的
numpy输入函数声明我们的数据输入函数。我们将指定我们创建的训练观察词典和一组y目标。
input_fun = tf.estimator.inputs.numpy_input_fn(X_dtrain, y=y_train, batch_size=batch_size, num_epochs=10, shuffle=True)
- 现在,我们定义我们的模型并开始训练:
model = regression_classifier(feature_columns=feature_cols,
n_trees=n_trees,
max_depth=max_depth,
learning_rate=0.25,
n_batches_per_layer=batch_size)
model.train(input_fn=input_fun, steps=train_steps)
- 在训练期间,我们应该看到类似的输出如下:
INFO:tensorflow:Using default config.
WARNING:tensorflow:Using temporary folder as model directory: /tmp/tmpqxyd62cu
INFO:tensorflow:Using config: {'_model_dir': '/tmp/tmpqxyd62cu', '_tf_random_seed': None, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_save_checkpoints_secs': 600, '_session_config': None, '_keep_checkpoint_max': 5, '_keep_checkpoint_every_n_hours': 10000, '_log_step_count_steps': 100, '_train_distribute': None, '_device_fn': None, '_service': None, '_cluster_spec': <tensorflow.python.training.server_lib.ClusterSpec object at 0x7f43129d77b8>, '_task_type': 'worker', '_task_id': 0, '_global_id_in_cluster': 0, '_master': '', '_evaluation_master': '', '_is_chief': True, '_num_ps_replicas': 0, '_num_worker_replicas': 1}
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Saving checkpoints for 0 into /tmp/tmpqxyd62cu/model.ckpt.
INFO:tensorflow:loss = 691.09814, step = 1
INFO:tensorflow:global_step/sec: 587.923
INFO:tensorflow:loss = 178.62021, step = 101 (0.171 sec)
INFO:tensorflow:Saving checkpoints for 127 into /tmp/tmpqxyd62cu/model.ckpt.
INFO:tensorflow:Loss for final step: 37.436565.
Out[190]: <tensorflow.python.estimator.canned.boosted_trees.BoostedTreesRegressor at 0x7f43129d7470>
- 为了评估我们的模型,我们为测试数据创建了另一个输入函数,并获得每个测试数据点的预测。以下是获取预测并打印平均绝对误差(MAE)的代码:
p_input_fun = tf.estimator.inputs.numpy_input_fn(X_dtest, y=y_test, batch_size=batch_size, num_epochs=1, shuffle=False)
# Get predictions
predictions = list(model.predict(input_fn=p_input_fun))
final_preds = [pred['predictions'][0] for pred in predictions]
# Get accuracy (mean absolute error, MAE)
mae = np.mean([np.abs((actual - predicted) / predicted) for actual, predicted in zip(y_test, final_preds)])
print('Mean Abs Err on test set: {}'.format(acc))
- 其中以
0.71打印出误差。请注意,由于随机播放的随机性,读者可能会得到略微不同的结果。为了提高准确率,我们可以考虑增加数字周期或引入更低的学习率甚至是某种类型的衰减学习率(指数或线性):
Mean Abs Err on test set: 0.7111111111111111
工作原理
在本文中,我们将说明如何使用 TensorFlow 估计器和 TensorFlow 提供的数据输入函数。这些函数非常强大,不仅使我们的 TensorFlow 代码更短,更易读,而且还提高了算法的效率,减少了创建和测试算法所需的开发时间。
另见
有关决策树,随机森林,梯度提升森林以及可微分性,平滑性和连续性背后的数学的更多参考,我们鼓励读者阅读以下参考文献。
- 决策树教程。来自伯克利的机器学习速成课程。
- 随机森林 python 教程,克里斯阿尔邦
- 关于凸函数的精美 PDF 演示,它们如何用于机器学习,以及平滑度,可微性和连续性之间的差异。作者为弗朗西斯巴赫。最后还有大约 6 页有用的参考文献,读者可能会觉得有用。
- 关于软决策树的文章:将神经网络提炼为软决策树,Frosst 和 Hinton,2017。
- TensorFlow 实现的一个神经树。作者:Benoit Deschamps。
使用 TensorFlow 和 Keras
TensorFlow 非常适合为程序员提供的灵活性和强大功能。这样做的一个缺点是原型模型和迭代各种测试对程序员来说可能很麻烦。 Keras 是深度学习库的包装器,可以更轻松地处理模型的各个方面并使编程更容易。在这里,我们选择在 TensorFlow 之上使用 Keras。事实上,使用带有 TensorFlow 后端的 Keras 非常受欢迎,TensorFlow 中有一个 Keras 库。对于这个秘籍,我们将使用该 TensorFlow 库在 MNIST 数据集上进行完全连接的神经网络和简单的 CNN 图像网络。
准备
对于这个秘籍,我们将使用驻留在 TensorFlow 内部的 Keras 函数。 Keras 已经是一个可以安装的独立 python 库了。如果您选择使用纯 Keras 路线,则必须为 Keras 选择后端(如 TensorFlow)。
在本文中,我们将在 MNIST 图像识别数据集上执行两个单独的模型。第一个是直接完全连接的神经网络,而第二个是从第 8 章第 2 节“实现简单的 CNN”复制我们的 CNN 网络。
操作步骤
- 我们将首先为脚本加载必要的库。
import tensorflow as tf
from sklearn.preprocessing import MultiLabelBinarizer
from keras.utils import to_categorical
from tensorflow import keras
from tensorflow.python.framework import ops
ops.reset_default_graph()
# Load MNIST data
from tensorflow.examples.tutorials.mnist import input_data
- 我们可以在 TensorFlow 中使用提供的 MNIST 数据导入函数加载库。虽然原始 MNIST 图像是 28 像素乘 28 像素,但导入的数据是它们的扁平版本,其中每个观察是 0 到 1 之间的 784 个灰度点的行。y 标签作为 0 到 9 之间的整数导入。
mnist = input_data.read_data_sets("MNIST_data/")
x_train = mnist.train.images
x_test = mnist.test.images
y_train = mnist.train.labels
y_test = mnist.test.labels
y_train = [[i] for i in y_train]
y_test = [[i] for i in y_test]
- 我们现在将使用 scikit-learn 的
MultiLabelBinarizer()函数将目标整数转换为单热编码向量,如下所示:
one_hot = MultiLabelBinarizer()
y_train = one_hot.fit_transform(y_train)
y_test = one_hot.transform(y_test)
- 我们将创建一个三层完全连接的神经网络,其中包含 32,16 和 10 个相应的隐藏节点。然后最终输出的大小为 10(每个数字一个)。我们使用以下代码创建此网络:
# We start with a 'sequential' model type (connecting layers together)
model = keras.Sequential()
# Adds a densely-connected layer with 32 units to the model, followed by an ReLU activation.
model.add(keras.layers.Dense(32, activation='relu'))
# Adds a densely-connected layer with 16 units to the model, followed by an ReLU activation.
model.add(keras.layers.Dense(16, activation='relu'))
# Add a softmax layer with 10 output units:
model.add(keras.layers.Dense(10, activation='softmax'))
- 为了训练模型,我们接下来要做的就是使用适当的参数调用
compile()方法。我们需要的参数是优化函数和损失类型。但我们也想记录模型的准确率,因此度量列表包括accuracy参数。
model.compile(optimizer=tf.train.AdamOptimizer(0.001),
loss='categorical_crossentropy',
metrics=['accuracy'])
- 这将使输出应类似于以下内容:
Epoch 1/5
64/55000 [..............................] - ETA: 1:44 - loss: 2.3504 - acc: 0.0625
3776/55000 [=>............................] - ETA: 2s - loss: 1.7904 - acc: 0.3676
...
47104/55000 [========================>.....] - ETA: 0s - loss: 0.1337 - acc: 0.9615
50880/55000 [==========================>...] - ETA: 0s - loss: 0.1336 - acc: 0.9617
55000/55000 [==============================] - 1s 13us/step - loss: 0.1335 - acc: 0.9615
Out[]: <tensorflow.python.keras.callbacks.History at 0x7f5768a40da0>
要配置均方误差损失的回归模型,我们将使用模型编译,如下所示:
model.compile(optimizer=tf.train.AdamOptimizer(0.01), loss='mse', metrics=['mae'])
- 接下来,我们将看到如何实现具有两个卷积层的 CNN 模型,其具有最大池,全部后面是完全连接的层。首先,我们必须将平面图像重塑为 2D 图像,并将 y 目标转换为 numpy 数组,如下所示:00
x_train = x_tra0in.reshape(x_train.shape[0], 28, 28, 1)
x_test = x_test.reshape(x_test.shape[0], 28, 28, 1)
input_shape = (28, 28, 1)
num_classes = 10
# Categorize y targets
y_test = to_categorical(mnist.test.labels)
y_train = to_categorical(mnist.train.labels)
- 我们将像以前一样以类似的顺序层方法创建 CNN。这次我们将使用
Conv2D(),MaxPooling2D()和Dense()Keras 函数创建我们的 CNN 模型,如下所示:
cnn_model = keras.Sequential()
# First convolution layer
cnn_model.add(keras.layers.Conv2D(25,
kernel_size=(4, 4),
strides=(1, 1),
activation='relu',
input_shape=input_shape))
# Max pooling
cnn_model.add(keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(2, 2)))
# Second convolution layer
cnn_model.add(keras.layers.Conv2D(50,
kernel_size=(5, 5),
strides=(1, 1),
activation='relu'))
# Max pooling
cnn_model.add(keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(2, 2)))
# Flatten for dense (fully connected) layer
cnn_model.add(keras.layers.Flatten())
# Add dense (fully connected) layer
cnn_model.add(keras.layers.Dense(num_classes, activation='softmax'))
- 接下来,我们将通过选择优化和损失函数来编译我们的模型。
cnn_model.compile(optimizer=tf.train.AdamOptimizer(0.001),
loss='categorical_crossentropy',
metrics=['accuracy'])
- Keras 还允许我们将函数插入到名为
Callback的训练代码中。回调是在代码中的某些时间执行的函数,可用于执行各种函数。有许多预制回调,可以保存模型,在特定标准下停止训练,记录值等等。有关各种选项的更多信息,请参阅此链接。为了说明如何制作我们自己的自定义回调并显示它们如何工作,我们将创建一个名为RecordAccuracy()的回调,它是一个 KerasCallback类,并将在每个周期的末尾存储精度,如下所示:
class RecordAccuracy(keras.callbacks.Callback):
def on_train_begin(self, logs={}):
self.acc = []
def on_epoch_end(self, batch, logs={}):
self.acc.append(logs.get('acc'))
accuracy = RecordAccuracy()
- 接下来,我们将使用
fit()方法训练我们的 CNN 模型。这里我们将提供validation_data和callbacks如下:
cnn_model.fit(x_train,
y_train,
batch_size=64,
epochs=3,
validation_data=(x_test, y_test),
callbacks=[accuracy])
print(accuracy.acc)
- 此训练将产生类似的输出,如下所示:
Train on 55000 samples, validate on 64 samples
Epoch 1/3
64/55000 [..............................] - ETA: 2:59 - loss: 2.2805 - acc: 0.0625
192/55000 [>.............................] - ETA: 1:14 - loss: 2.2729 - acc: 0.1302\
...
54848/55000 [============================>.] - ETA: 0s - loss: 0.0603 - acc: 0.9816
54976/55000 [============================>.] - ETA: 0s - loss: 0.0603 - acc: 0.9816
55000/55000 [==============================] - 26s 469us/step - loss: 0.0604 - acc: 0.9816 - val_loss: 0.0139 - val_acc: 1.0000
Out[]: <tensorflow.python.keras.callbacks.History at 0x7f69494c7780>
[0.9414363636450334, 0.9815818181731484, 0.9998980778226293]
工作原理
在这个秘籍中,我们展示了 Keras 的简洁创建和训练模型。您可以自动处理变量类型,维度和数据摄取的许多复杂细节。虽然这可以让人放心,但我们应该意识到,如果我们掩盖太多模型细节,我们可能无意中实现了错误的模型。
另见
有关 Keras 的更多信息,建议读者查看以下资源:
- Keras 官方文档
- TensorFlow Keras 教程
- [“Keras 简介”,Francois Chollet 在斯坦福大学的客座讲座(幻灯片中的 PDF 格式)]https://web.stanford.edu/class/cs20si/lectures/march9guestlecture.pdf



。然后我们宣布我们的分类损失并将这两项加在一起。使用以下代码:

,其中

浙公网安备 33010602011771号