tf-ml-cookbook-2e-zh-merge-0

48 阅读1小时+

TensorFlow 机器学习秘籍中文第二版(一)

原文:TensorFlow Machine Learning Cookbook

协议:CC BY-NC-SA 4.0

一、TensorFlow 入门

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

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

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

介绍

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

TensorFlow 如何工作

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

准备

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

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

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

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

操作步骤

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

  1. 导入或生成数据集:我们所有的机器学习算法都依赖于数据集。在本书中,我们将生成数据或使用外部数据集源。有时,最好依赖生成的数据,因为我们只想知道预期的结果。大多数情况下,我们将访问给定秘籍的公共数据集。有关访问这些数据集的详细信息,请参见本章第 8 节的其他资源。
  2. 转换和正则化数据:通常,输入数据集不会出现在图片中。 TensorFlow 期望我们需要转换 TensorFlow,以便它们获得可接受的形状。数据通常不在我们的算法所期望的正确维度或类型中。在我们使用之前,我们必须转换数据。大多数算法也期望归一化数据,我们也会在这里看一下。 TensorFlow 具有内置函数,可以为您正则化数据,如下所示:
import tensorflow as tf
data = tf.nn.batch_norm_with_global_normalization(...) 
  1. 将数据集划分为训练集,测试集和验证集:我们通常希望在我们训练过的不同集上测试我们的算法。此外,许多算法需要超参数调整,因此我们留出一个验证集来确定最佳的超参数集。
  2. 设置算法参数(超参数):我们的算法通常有一组参数,我们在整个过程中保持不变。例如,这可以是我们选择的迭代次数,学习率或其他固定参数。将这些一起初始化是一种良好的做法,以便读者或用户可以轻松找到它们,如下所示:
learning_rate = 0.01 
batch_size = 100 
iterations = 1000
  1. 初始化变量和占位符:TensorFlow 依赖于知道它能够和不能修改的内容。 TensorFlow 将在优化期间修改/调整变量(模型权重/偏差)以最小化损失函数。为此,我们通过占位符提供数据。我们需要初始化变量和占位符的大小和类型,以便 TensorFlow 知道会发生什么。 TensorFlow 还需要知道期望的数据类型。对于本书的大部分内容,我们将使用float32。 TensorFlow 还提供float64float16。请注意,用于精度的更多字节会导致算法速度变慢,但使用较少会导致精度降低。请参阅以下代码:
a_var = tf.constant(42) 
x_input = tf.placeholder(tf.float32, [None, input_size]) 
y_input = tf.placeholder(tf.float32, [None, num_classes]) 
  1. 定义模型结构:在获得数据并初始化变量和占位符之后,我们必须定义模型。这是通过构建计算图来完成的。我们将在第 2 章,TensorFlow 方法中更详细地讨论计算图 TensorFlow 秘籍中的运算中的计算图。此示例的模型将是线性模型(y = mx + b):
y_pred = tf.add(tf.mul(x_input, weight_matrix), b_matrix) 
  1. 声明损失函数:定义模型后,我们必须能够评估输出。这是我们宣布损失函数的地方。损失函数非常重要,因为它告诉我们预测距实际值有多远。在第 2 章,TensorFlow 方法中的实现反向传播秘籍中更详细地探讨了不同类型的损失函数。在这里,我们实现了 n 点的均方误差,即:

loss = tf.reduce_mean(tf.square(y_actual - y_pred)) 
  1. 初始化和训练模型:现在我们已经完成了所有工作,我们需要创建图实例,通过占位符输入数据,让 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(...) 
  1. 评估模型:一旦我们构建并训练了模型,我们应该通过查看通过某些指定标准对新数据的处理程度来评估模型。我们对训练和测试装置进行评估,这些评估将使我们能够看到模型是否过拟合。我们将在后面的秘籍中解决这个问题。
  2. 调整超参数:大多数情况下,我们希望根据模型的表现返回并更改一些超参数。然后,我们使用不同的超参数重复前面的步骤,并在验证集上评估模型。
  3. 部署/预测新结果:了解如何对新数据和未见数据进行预测也很重要。一旦我们对他们进行了训练,我们就可以对所有模型进行此操作。

工作原理

在 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()的行为。

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

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

  1. 序列张量: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]。请注意,此函数不包括限制值。

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

注意,这种随机均匀分布来自包含minval但不包括maxvalminval >= 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() 

操作步骤

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

  1. 创建矩阵:我们可以从 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),我们将重新初始化随机变量并最终得到不同的随机值。

  1. 加法,减法和乘法:要添加,减去或相乘相同维度的矩阵,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()函数具有参数,用于指定是否在乘法之前转置参数或每个矩阵是否稀疏。

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

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

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

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

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

  1. 分解:对于 Cholesky 分解,请使用以下内容:
print(sess.run(tf.cholesky(identity_matrix))) 
[[ 1\.  0\.  1.] 
 [ 0\.  1\.  0.] 
 [ 0\.  0\.  1.]] 
  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()。请注意,除非另有说明,否则本节中的所有操作都将按元素评估输入:

  1. TensorFlow 提供了div()和相关函数的一些变体。
  2. 值得一提的是div()返回与输入相同的类型。这意味着如果输入是整数,它确实返回了分区的底线(类似于 Python2)。要返回 Python3 版本,它在分割之前将整数转换为浮点数并始终返回浮点数,TensorFlow 提供truediv()函数,如下所示:
print(sess.run(tf.div(3, 4))) 
0 
print(sess.run(tf.truediv(3, 4))) 
0.75 
  1. 如果我们有浮点数并想要整数除法,我们可以使用floordiv()函数。请注意,这仍然会返回一个浮点数,但它会向下舍入到最接近的整数。这个函数如下:
print(sess.run(tf.floordiv(3.0,4.0))) 
0.0 
  1. 另一个重要函数是mod()。此函数返回除法后的余数。它如下:
print(sess.run(tf.mod(22.0, 5.0))) 
2.0 
  1. 两个张量之间的交叉积通过cross()函数实现。请记住,交叉乘积仅针对两个三维向量定义,因此它只接受两个三维张量。以下代码说明了这种用法:
print(sess.run(tf.cross([1., 0., 0.], [0., 1., 0.]))) 
[ 0\.  0\.  1.0]
  1. 这是一个更常见的数学函数的紧凑列表。所有这些函数都以元素方式运行:
abs()输入张量的绝对值
ceil()输入张量的向上取整函数
cos()输入张量的余弦函数
exp()输入张量的基于e指数
floor()输入张量的向下取整函数
inv()输入张量的乘法逆(1 / x
log()输入张量的自然对数
maximum()两个张量的逐元素最大值
minimum()两个张量的逐元素最小值
neg()输入张量的反转
pow()第一个张量元素的第二个张量元素次幂
round()输入张量的舍入
rsqrt()输入张量的平方根倒数
sign()返回 -1,0 或 1,具体取决于张量的符号
sin()输入张量的正弦函数
sqrt()输入张量的平方根
square()输入张量的平方
  1. 专业数学函数:有一些特殊的数学函数可以在机器学习中使用,值得一提,TensorFlow 为它们提供了内置函数。同样,除非另有说明,否则这些函数在元素方面运行:
digamma()Psi 函数,lgamma()函数的导数
erf()张量的逐元素高斯误差函数
erfc()张量的互补误差函数
igamma()较低正则化的不完全伽玛函数
igammac()较高正则化的不完全伽马函数
lbeta()Beta 函数绝对值的自然对数
lgamma()伽玛函数绝对值的自然对数
squared_difference()两个张量之间差异的平方

工作原理

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

# 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。在这里,我们选择明确每个函数调用:

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

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

  1. 另一个平滑激活函数是超切线。超正切函数与 sigmoid 非常相似,除了它的范围在 0 和 1 之间,它的范围在 -1 和 1 之间。该函数具有双曲正弦与双曲余弦的比率的形式。写这个的另一种方法是(exp(x) - exp(-x)) / (exp(x) + exp(-x))。此激活函数如下:
print(sess.run(tf.nn.tanh([-1., 0., 1.]))) 
[-0.76159418  0\.         0.76159418 ] 
  1. softsign函数也可用作激活函数。该函数的形式是x / (|x| + 1)softsign函数应该是符号函数的连续(但不是平滑)近似。请参阅以下代码:
print(sess.run(tf.nn.softsign([-1., 0., -1.]))) 
[-0.5  0\.   0.5] 
  1. 另一个函数是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。

  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 脚本下载,一些将通过互联网手动下载。几乎所有这些数据集都需要有效的互联网连接,以便您可以检索它们。

操作步骤

  1. 鸢尾数据:该数据集可以说是机器学习中使用的最经典的数据集,也可能是所有统计数据。它是一个数据集,可以测量三种不同类型鸢尾花的萼片长度,萼片宽度,花瓣长度和花瓣宽度:山鸢尾,弗吉尼亚和杂色鸢尾。总共有 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} 
  1. 出生体重数据:该数据最初来自 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
  1. 波士顿住房数据:卡内基梅隆大学在其 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 
  1. 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.] 
  1. 垃圾邮件文本数据。 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... 
  1. 电影评论数据:来自康奈尔大学的 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 . 
  1. CIFAR-10 图像数据:加拿大高级研究院发布了一个图像集,其中包含 8000 万个带标签的彩色图像(每个图像缩放为32 x 32像素)。有 10 种不同的目标类别(飞机,汽车,鸟类等)。 CIFAR-10 是包含 60,000 张图像的子集。训练集中有 50,000 个图像,测试集中有 10,000 个。由于我们将以多种方式使用此数据集,并且因为它是我们较大的数据集之一,因此我们不会在每次需要时运行脚本。要获取此数据集,请导航至此链接并下载 CIFAR-10 数据集。我们将在相应的章节中介绍如何使用此数据集。
  2. 莎士比亚文本数据的作品:古登堡项目(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
  1. 英语 - 德语句子翻译数据: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!'] 

工作原理

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

另见

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

其他资源

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

准备

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

操作步骤

以下是 TensorFlow 资源列表:

  • 本书的代码可在 Packt 仓库在线获取。

  • TensorFlow Python API 官方文档位于这里。这里有 TensorFlow 中所有函数,对象和方法的文档和示例。

  • TensorFlow 的官方教程非常详尽。它们位于这里。他们开始覆盖图像识别模型,并通过 Word2Vec,RNN 模型和序列到序列模型进行工作。他们还有额外的教程来生成分形和解决 PDE 系统。请注意,他们不断向此集合添加更多教程和示例。

  • TensorFlow 的 GitHub 官方仓库可通过此链接获得。在这里,您可以查看开源代码,甚至可以根据需要分叉或克隆最新版本的代码。如果导航到 issues 目录,您还可以查看当前提交的问题。

  • Dockerhub 的此链接提供了一个由 TensorFlow 保持最新的公共 Docker 容器。

  • Stack Overflow 是社区帮助的重要来源。 TensorFlow 有一个标签。随着 TensorFlow 越来越受欢迎,这个标签似乎越来越受关注。要查看此标记上的活动,请访问此链接

  • 虽然 TensorFlow 非常灵活且可以用于很多事情,但 TensorFlow 最常见的用途是深度学习。为了理解深度学习的基础,基础数学如何运作,以及在深度学习方面发展更多直觉,谷歌创建了一个在 Udacity 上可用的在线课程。要注册并参加视频讲座课程,请访问此链接

  • TensorFlow 还建立了一个网站,您可以在视觉上探索训练神经网络,同时更改参数和数据集。访问此链接,探讨不同的设置如何影响神经网络的训练。

  • Geoffrey Hinton 通过 Coursera 教授一个名为神经网络的机器学习在线课程

  • 斯坦福大学有一个在线教学大纲和详细的视觉识别卷积神经网络课程笔记

二、TensorFlow 的方式

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

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

介绍

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

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

计算图中的操作

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

准备

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

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矩阵,如下所示:

  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)) 
  1. 接下来,我们创建将用于矩阵乘法和加法的常量:
m1 = tf.constant([[1.], [0.], [-1.], [2.], [4.]]) 
m2 = tf.constant([[2.]]) 
a1 = tf.constant([[10.]]) 
  1. 现在,我们声明操作并将它们添加到图中:
prod1 = tf.matmul(x_data, m1) 
prod2 = tf.matmul(prod1, m2) 
add1 = tf.add(prod2, a1)
  1. 最后,我们为图提供数据:
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 图像上执行小的移动窗口平均值,然后第二层将是自定义操作层。

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

import tensorflow as tf 
import numpy as np 
sess = tf.Session() 

操作步骤

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

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

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

  1. 现在,我们定义一个自定义层,它将在移动窗口平均值的2 x 2输出上运行。自定义函数将首先将输入乘以另一个2 x 2矩阵张量,然后为每个条目添加 1。在此之后,我们取每个元素的 sigmoid 并返回2 x 2矩阵。由于矩阵乘法仅对二维矩阵进行操作,因此我们需要删除大小为1的图像的额外维度。 TensorFlow 可以使用内置的squeeze()函数执行此操作。在这里,我们定义新层:
    def custom_layer(input_matrix): 
        input_matrix_sqeezed = tf.squeeze(input_matrix) 
        A = tf.constant([[1., 2.], [-1., 3.]]) 
        b = tf.constant(1., shape=[2, 2]) 
        temp1 = tf.matmul(A, input_matrix_sqeezed) 
        temp = tf.add(temp1, b) # Ax + b 
        return tf.sigmoid(temp) 
  1. 现在,我们必须将新层放在图上。我们将使用命名范围执行此操作,以便它在计算图上可识别和可折叠/可扩展,如下所示:
with tf.name_scope('Custom_Layer') as scope: 
    custom_layer1 = custom_layer(mov_avg_layer) 
  1. 现在,我们只需输入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 和 1 之间输出 500 个值的结果。有关输出的绘图,请参阅“工作原理”部分。使用以下代码:
x_vals = tf.linspace(-1., 1., 500) 
target = tf.constant(0.) 
  1. 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。

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

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

  1. 首先,我们需要重新定义我们的预测(x_vals)和target。我们将保存输出并在下一节中绘制它们。使用以下内容:
x_vals = tf.linspace(-3., 5., 500) 
target = tf.constant(1.) 
targets = tf.fill([500,], 1.)
  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) 
  1. 二元情形的交叉熵损失有时也称为逻辑损失函数。它是在我们预测两个 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) 
  1. 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) 
  1. 加权交叉熵损失是 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) 
  1. 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] 
  1. 稀疏 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。我们得到这个数字是因为如果m1m2是两个正常函数的平均值,那么加到它们以将它们等距离转换为零的值将是 - (m1 + m2) / 2。我们将在第二个例子中看到 TensorFlow 如何达到该数字。

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

操作步骤

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

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

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

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

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

  1. 首先,我们重置图并重新初始化图会话:
from tensorflow.python.framework import ops 
ops.reset_default_graph() 
sess = tf.Session() 
  1. 接下来,我们从两个不同的正态分布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。

  1. 接下来,我们将转换操作添加到图中。请记住,我们不必将它包装在 sigmoid 函数中,因为损失函数将为我们执行此操作:
my_output = tf.add(x_data, A)
  1. 由于特定损失函数需要具有与之关联的额外维度的批量数据(添加的维度,即批次编号),因此我们将使用expand_dims()函数为输出添加额外维度。在下一节中,我们将讨论如何在训练中使用可变大小的批次。现在,我们将再次使用一个随机数据点:
my_output_expanded = tf.expand_dims(my_output, 0) 
y_target_expanded = tf.expand_dims(y_target, 0) 
  1. 接下来,我们将初始化我们的一个变量A
init = tf.initialize_all_variables() 
sess.run(init) 
  1. 现在,我们宣布我们的损失函数。我们将使用带有未缩放的对率的交叉熵,它使用 sigmoid 函数对它们进行转换。在名为nn.sigmoid_cross_entropy_with_logits()的神经网络包中,TensorFlow 为我们提供了这一函数。如前所述,它希望参数具有特定的维度,因此我们必须相应地使用扩展的输出和目标:
xentropy = tf.nn.sigmoid_cross_entropy_with_logits( my_output_expanded, y_target_expanded) 
  1. 与回归示例一样,我们需要向图中添加优化器函数,以便 TensorFlow 知道如何更新图中的偏差变量:
my_opt = tf.train.GradientDescentOptimizer(0.05) 
train_step = my_opt.minimize(xentropy) 
  1. 最后,我们循环遍历随机选择的数据点数百次并相应地更新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]] 

工作原理

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

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

更多

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

学习率大小优点缺点用途
较小的学习率收敛速度较慢但结果更准确如果解决方案不稳定,请先尝试降低学习率
学习率更高不太准确,但收敛速度更快对于某些问题,有助于防止解决方案停滞不前

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

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

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

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

另见

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

使用批量和随机训练

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

准备

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

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

import matplotlib as plt 
import numpy as np 
import tensorflow as tf 
sess = tf.Session() 

操作步骤

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

  1. 我们将从声明批量大小开始。这将是我们将同时通过计算图提供多少数据观察:
batch_size = 20
  1. 接下来,我们在模型中声明数据,占位符和变量。我们在这里做的改变是我们改变了占位符的形状。它们现在是两个维度,第一个维度是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])) 
  1. 现在,我们将操作添加到图中,现在将是矩阵乘法而不是常规乘法。请记住,矩阵乘法不是可交换的,因此我们必须在matmul()函数中以正确的顺序输入矩阵:
my_output = tf.matmul(x_data, A) 
  1. 我们的loss函数会发生变化,因为我们必须采用批次中每个数据点的所有 L2 损失的平均值。我们通过将先前的损失输出包装在 TensorFlow 的reduce_mean()函数中来实现:
loss = tf.reduce_mean(tf.square(my_output - y_target)) 
  1. 我们像以前一样声明我们的优化器并初始化我们的模型变量,如下所示:
my_opt = tf.train.GradientDescentOptimizer(0.02) 
train_step = my_opt.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
  1. 最后,我们将循环并迭代训练步骤以优化算法。这部分与以前不同,因为我们希望能够绘制随时间的损失并比较批次与随机训练的收敛。因此,我们初始化一个列表,每隔五个时间间隔存储一次损失函数:
loss_batch = [] 
for i in range(100): 
    rand_index = np.random.choice(100, size=batch_size) 
    rand_x = np.transpose([x_vals[rand_index]]) 
    rand_y = np.transpose([y_vals[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    if (i + 1) % 5 == 0: 
        print('Step #' + str(i+1) + ' A = ' + str(sess.run(A))) 
        temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
        print('Loss = ' + str(temp_loss)) 
        loss_batch.append(temp_loss) 
  1. 这是 100 次迭代的最终输出。请注意,A的值有一个额外的维度,因为它现在必须是一个 2D 矩阵:
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 入门中使用数据源秘籍中有更详细的描述。我们将加载这些数据并制作一个简单的二元分类器来预测花是否是山鸢尾的种类。需要说明的是,这个数据集有三个种类,但我们只能预测一种花是单一种,是否是一种花,给我们一个二元分类器。我们将首先加载库和数据,然后相应地转换目标。

操作步骤

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

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

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

  1. 在这里,我们定义线性模型。模型将采用x2 = x1 * A + b的形式,如果我们想要找到该行上方或下方的点,我们会在插入等式x2 - x1 * A - b时看到它们是高于还是低于零。我们将通过取该方程的 sigmoid 并从该方程预测 1 或 0 来实现。请记住,TensorFlow 具有内置 sigmoid 的loss函数,因此我们只需要在 sigmoid 函数之前定义模型的输出:
my_mult = tf.matmul(x2_data, A) 
my_add = tf.add(my_mult, b) 
my_output = tf.sub(x1_data, my_add) 
  1. 现在,我们使用 TensorFlow 的内置sigmoid_cross_entropy_with_logits()函数添加 sigmoid 交叉熵损失函数:
xentropy = tf.nn.sigmoid_cross_entropy_with_logits(my_output, y_target) 
  1. 我们还必须告诉 TensorFlow 如何通过声明优化方法来优化我们的计算图。我们希望最大限度地减少交叉熵损失。我们还会选择0.05作为我们的学习率:
my_opt = tf.train.GradientDescentOptimizer(0.05) 
train_step = my_opt.minimize(xentropy) 
  1. 现在,我们创建一个变量初始化操作并告诉 TensorFlow 执行它:
init = tf.global_variables_initializer() 
sess.run(init) 
  1. 现在,我们将训练我们的线性模型 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]] 
  1. 下一组命令提取模型变量并在绘图上绘制线条。结果绘图在它的工作原理...部分:
[[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,如下所示:

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

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

  1. 在同一个脚本中,我们可以重新加载图并创建数据,变量和占位符。请记住,我们还需要将数据和目标分成训练和测试集。使用以下代码:
from tensorflow.python.framework import ops 
ops.reset_default_graph() 
sess = tf.Session() 
batch_size = 25 
x_vals = np.concatenate((np.random.normal(-1, 1, 50), np.random.normal(2, 1, 50))) 
y_vals = np.concatenate((np.repeat(0., 50), np.repeat(1., 50))) 
x_data = tf.placeholder(shape=[1, None], dtype=tf.float32) 
y_target = tf.placeholder(shape=[1, None], dtype=tf.float32) 
train_indices = np.random.choice(len(x_vals), round(len(x_vals)*0.8), replace=False) 
test_indices = np.array(list(set(range(len(x_vals))) - set(train_indices))) 
x_vals_train = x_vals[train_indices] 
x_vals_test = x_vals[test_indices] 
y_vals_train = y_vals[train_indices] 
y_vals_test = y_vals[test_indices] 
A = tf.Variable(tf.random_normal(mean=10, shape=[1])) 
  1. 我们现在将模型和损失函数添加到图中,初始化变量,并创建优化过程,如下所示:
my_output = tf.add(x_data, A) 
init = tf.initialize_all_variables() 
sess.run(init) 
xentropy = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(my_output, y_target)) 
my_opt = tf.train.GradientDescentOptimizer(0.05) 
train_step = my_opt.minimize(xentropy) 
  1. 现在,我们运行我们的训练循环,如下所示:
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 
  1. 为了评估模型,我们将创建自己的预测操作。我们将预测操作包装在挤压函数中,因为我们希望使预测和目标形成相同的形状。然后,我们用相等的函数测试相等性。在那之后,我们留下了一个真值和假值的张量,我们将其转换为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 
  1. 通常,查看模型结果(准确率,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 范例中最好地实现它。

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

使用矩阵逆方法

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

准备

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

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

操作步骤

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

  1. 首先,我们加载必要的库,初始化图并创建数据。请参阅以下代码:
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
sess = tf.Session() 
x_vals = np.linspace(0, 10, 100) 
y_vals = x_vals + np.random.normal(0, 1, 100) 
  1. 接下来,我们创建要在逆方法中使用的矩阵。我们首先创建A矩阵,它将是x数据列和全 1 的列。然后,我们从y数据创建b矩阵。使用以下代码:
x_vals_column = np.transpose(np.matrix(x_vals)) 
ones_column = np.transpose(np.matrix(np.repeat(1, 100))) 
A = np.column_stack((x_vals_column, ones_column)) 
b = np.transpose(np.matrix(y_vals)) 
  1. 然后我们将Ab矩阵转换为张量,如下所示:
A_tensor = tf.constant(A) 
b_tensor = tf.constant(b)
  1. 现在我们已经设置了矩阵,我们可以使用 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) 
  1. 我们现在使用以下代码从解,斜率和 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 分解将矩阵分解为下三角矩阵和上三角矩阵,比如LL',使得这些矩阵是彼此的转置。有关此分解属性的更多信息,有许多可用资源来描述它以及如何到达它。在这里,我们将通过将其写为LL'x = b来解决系统Ax = b。我们首先解决Ly = by,然后求解L'x = y得到我们的系数矩阵x

操作步骤

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

  1. 我们将以与上一个秘籍完全相同的方式设置系统。我们将导入库,初始化图并创建数据。然后,我们将以之前的方式获得我们的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) 
  1. 接下来,我们找到方阵的 Cholesky 分解,A^T 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()仅返回分解的下对角线部分。这很好,因为上对角矩阵只是下对角矩阵的转置。

  1. 现在我们有了解决方案,我们提取系数:
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 损失函数。

操作步骤

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

  1. 我们首先加载必要的库,创建图并加载数据:
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
from tensorflow.python.framework import ops 
ops.reset_default_graph() 
sess = tf.Session() 
iris = datasets.load_iris() 
x_vals = np.array([x[3] for x in iris.data]) 
y_vals = np.array([y[0] for y in iris.data])
  1. 然后我们声明我们的学习率,批量大小,占位符和模型变量:
learning_rate = 0.05 
batch_size = 25 
x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[1,1])) 
b = tf.Variable(tf.random_normal(shape=[1,1])) 
  1. 接下来,我们编写线性模型的公式y = Ax + b
model_output = tf.add(tf.matmul(x_data, A), b)
  1. 然后,我们声明我们的 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)
  1. 我们现在可以在随机选择的批次上循环并训练模型。我们将运行 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
  1. 接下来,我们将提取我们找到的系数并创建一个最合适的线以放入图中:
[slope] = sess.run(A) 
[y_intercept] = sess.run(b) 
best_fit = [] 
for i in x_vals: 
    best_fit.append(slope*i+y_intercept)
  1. 在这里,我们将创建两个图。第一个是覆盖拟合线的数据。第二个是 100 次迭代中的 L2 损失函数。这是生成两个图的代码。
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 损失函数如何影响线性回归中的收敛。

准备

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

操作步骤

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

  1. 程序的开始与上一个秘籍相同,直到我们达到我们的损失函数。我们加载必要的库,启动会话,加载数据,创建占位符,并定义我们的变量和模型。需要注意的一点是,我们正在提取学习率和模型迭代。我们这样做是因为我们希望显示快速更改这些参数的效果。使用以下代码:
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
sess = tf.Session() 
iris = datasets.load_iris() 
x_vals = np.array([x[3] for x in iris.data]) 
y_vals = np.array([y[0] for y in iris.data]) 
batch_size = 25 
learning_rate = 0.1 # Will not converge with learning rate at 0.4 
iterations = 50 
x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32) 
A = tf.Variable(tf.random_normal(shape=[1,1])) 
b = tf.Variable(tf.random_normal(shape=[1,1])) 
model_output = tf.add(tf.matmul(x_data, A), b)
  1. 我们的损失函数将变为 L1 损失(loss_l1),如下所示:
loss_l1 = tf.reduce_mean(tf.abs(y_target - model_output)) 
  1. 现在,我们通过初始化变量,声明我们的优化器以及通过训练循环迭代数据来恢复。请注意,我们也在节省每一代的损失来衡量收敛。使用以下代码:
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)和最短距离回归。

准备

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

请参阅下图进行比较:

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

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

操作步骤

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

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

deming_numerator = tf.abs(tf.sub(y_target, tf.add(tf.matmul(x_data, A), b))) 
deming_denominator = tf.sqrt(tf.add(tf.square(A),1)) 
loss = tf.reduce_mean(tf.truediv(deming_numerator, deming_denominator)) 
  1. 我们现在初始化变量,声明我们的优化器,并循环遍历训练集以获得我们的参数,如下所示:
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)) 
  1. 我们可以使用以下代码绘制输出:
[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:对鸢尾数据集进行戴明回归的解决方案

工作原理

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

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

实现套索和岭回归

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

准备

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

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

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

操作步骤

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

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

准备

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

操作步骤

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

  1. 首先,我们加载必要的库并初始化图,如下所示:
import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
sess = tf.Session()
  1. 现在,我们加载数据。这次,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]) 
  1. 接下来,我们声明批量大小,占位符,变量和模型输出。这里唯一的区别是我们更改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) 
  1. 对于弹性网络,损失函数具有部分斜率的 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)
  1. 现在,我们可以初始化变量,声明我们的优化函数,运行训练循环,并拟合我们的系数,如下所示:
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)) 
  1. 这是代码的输出:
Step #250 A = [[ 0.42095602] 
 [ 0.1055888 ] 
 [ 1.77064979]] b = [[ 1.76164341]] 
Loss = [ 2.87764359] 
Step #500 A = [[ 0.62762028] 
 [ 0.06065864] 
 [ 1.36294949]] b = [[ 1.87629771]] 
Loss = [ 1.8032167] 
Step #750 A = [[ 0.67953539] 
 [ 0.102514 ] 
 [ 1.06914485]] b = [[ 1.95604002]] 
Loss = [ 1.33256555] 
Step #1000 A = [[ 0.6777274 ] 
 [ 0.16535147] 
 [ 0.8403284 ]] b = [[ 2.02246833]] 
Loss = [ 1.21458709]
  1. 现在,我们可以观察训练迭代的损失,以确保算法收敛,如下所示:
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 仓库获得的低出生体重数据。我们将从其他几个因素预测低出生体重。

操作步骤

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

  1. 我们首先加载库,包括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()
  1. 接下来,我们通过请求模块加载数据并指定我们要使用的特征。我们必须具体,因为一个特征是实际出生体重,我们不想用它来预测出生体重是大于还是小于特定量。我们也不想将 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])
  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]
  1. 当特征在 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))

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

  1. 接下来,我们声明批量大小,占位符,变量和逻辑模型。我们不将输出包装在 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)
  1. 现在,我们声明我们的损失函数,它具有 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)
  1. 在记录损失函数的同时,我们还希望在训练和测试集上记录分类准确率。因此,我们将创建一个预测函数,返回任何大小的批量的准确率:
prediction = tf.round(tf.sigmoid(model_output)) 
predictions_correct = tf.cast(tf.equal(prediction, y_target), tf.float32) 
accuracy = tf.reduce_mean(predictions_correct)
  1. 现在,我们可以开始我们的训练循环并记录损失和准确率:
loss_vec = [] 
train_acc = [] 
test_acc = [] 
for i in range(1500): 
    rand_index = np.random.choice(len(x_vals_train), size=batch_size) 
    rand_x = x_vals_train[rand_index] 
    rand_y = np.transpose([y_vals_train[rand_index]]) 
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) 
    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) 
    loss_vec.append(temp_loss) 
    temp_acc_train = sess.run(accuracy, feed_dict={x_data: x_vals_train, y_target: np.transpose([y_vals_train])}) 
    train_acc.append(temp_acc_train) 
    temp_acc_test = sess.run(accuracy, feed_dict={x_data: x_vals_test, y_target: np.transpose([y_vals_test])}) 
    test_acc.append(temp_acc_test)
  1. 以下是查看损失和准确率图的代码:
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