应用人工智能研讨会(三)
原文:
annas-archive.org/md5/0323474b1674bee4360c291447287161译者:飞龙
第六章:6. 神经网络与深度学习
概述
在本章中,你将了解神经网络和深度学习的最终主题。你将学习 TensorFlow、卷积神经网络(CNNs)和递归神经网络(RNNs)。你将利用深度学习的核心概念来评估个人的信用 worthiness 并预测社区的房价。稍后,你还将使用学到的技能实现一个图像分类程序。到本章结束时,你将牢牢掌握神经网络和深度学习的概念。
介绍
在上一章,我们学习了聚类问题的概念,并了解了几种算法,如 k-means,它们可以自动将数据点分组。在本章中,我们将学习神经网络和深度学习网络。
神经网络与深度学习网络的区别在于网络的复杂性和深度。传统的神经网络只有一个隐藏层,而深度学习网络则有多个隐藏层。
尽管我们将使用神经网络和深度学习进行监督学习,但请注意,神经网络也可以建模无监督学习技术。这种模型在 1980 年代其实非常流行,但由于当时计算能力有限,直到最近,这种模型才被广泛采用。随着图形处理单元(GPU)和云计算的普及,我们现在可以获得巨大的计算能力。这正是神经网络,尤其是深度学习,重新成为热门话题的主要原因。
深度学习能够建模比传统神经网络更复杂的模式,因此如今在计算机视觉(如面部检测和图像识别)和自然语言处理(如聊天机器人和文本生成)中得到了广泛应用。
人工神经元
人工神经网络(ANNs),顾名思义,试图模仿人类大脑的工作方式,特别是神经元的工作方式。
神经元是大脑中的一种细胞,通过电信号与其他细胞进行通信。神经元能够响应声音、光线、触摸等刺激。它们也能引发动作,比如肌肉收缩。平均而言,人脑包含约 100 亿到 200 亿个神经元。这是一个相当庞大的网络,对吧?这也是人类能够实现如此多惊人事物的原因。这也是为什么研究人员试图模仿大脑的运作,并由此创造了人工神经网络。
人工神经网络由多个相互连接的人工神经元组成,形成一个网络。人工神经元简单来说是一个处理单元,它对一些输入(x1、x2、……、xn)进行数学运算,并将最终结果(y)返回给下一个单元,如下图所示:
图 6.1:人工神经元的表示
我们将在接下来的章节中更详细地了解人工神经元的工作原理。
TensorFlow 中的神经元
TensorFlow 目前是最流行的神经网络和深度学习框架。它由 Google 创建并维护。TensorFlow 用于语音识别和语音搜索,也是 translate.google.com 背后的大脑。稍后在本章中,我们将使用 TensorFlow 来识别书写字符。
TensorFlow API 支持多种编程语言,包括 Python、JavaScript、Java 和 C。TensorFlow 与 张量 一起工作。你可以将张量视为一个容器,它由一个矩阵(通常是高维的)和与其将执行的操作相关的附加信息组成(如权重和偏差,你将在本章稍后看到)。没有维度(即无秩)的张量是标量。秩为 1 的张量是向量,秩为 2 的张量是矩阵,秩为 3 的张量是三维矩阵。秩表示张量的维度。在本章中,我们将讨论秩为 2 和 3 的张量。
注意
数学家使用矩阵和维度的术语,而深度学习程序员则使用张量和秩。
TensorFlow 还提供了数学函数来变换张量,例子包括以下内容:
-
add和multiply -
exp和log -
greater、less和equal -
concat、slice和split -
matrix_inverse、matrix_determinant和matmul -
sigmoid、relu和softmax
我们将在本章稍后详细讲解这些内容。
在下一个练习中,我们将使用 TensorFlow 来计算一个人工神经元。
练习 6.01:使用基本操作和 TensorFlow 常量
在本练习中,我们将使用 TensorFlow 中的算术操作,通过执行矩阵乘法和加法,并应用非线性函数 sigmoid 来模拟一个人工神经元。
以下步骤将帮助你完成练习:
-
打开一个新的 Jupyter Notebook 文件。
-
导入
tensorflow包并将其命名为tf:import tensorflow as tf -
创建一个名为
W的形状为[1,6](即 1 行 6 列)的张量,使用tf.constant(),并使其包含矩阵[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]。打印其值:W = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[1, 6]) W预期输出如下:
<tf.Tensor: shape=(1, 6), dtype=float32, numpy=array([[1., 2., 3., 4., 5., 6.]], dtype=float32)> -
创建一个名为
X的形状为[6,1](即 6 行 1 列)的张量,使用tf.constant(),并使其包含[7.0, 8.0, 9.0, 10.0, 11.0, 12.0]。打印其值:X = tf.constant([7.0, 8.0, 9.0, 10.0, 11.0, 12.0], \ shape=[6, 1]) X预期输出如下:
<tf.Tensor: shape=(6, 1), dtype=float32, numpy= array([[ 7.], [ 8.], [ 9.], [10.], [11.], [12.]], dtype=float32)> -
现在,创建一个名为
b的张量,使用tf.constant(),并使其包含-88。打印其值:b = tf.constant(-88.0) b预期输出如下:
<tf.Tensor: shape=(), dtype=float32, numpy=-88.0> -
在
W和X之间执行矩阵乘法,使用tf.matmul,将其结果保存在mult变量中,并打印其值:mult = tf.matmul(W, X) mult预期输出如下:
<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[217.]], dtype=float32)> -
在
mult和b之间执行矩阵加法,并将结果保存在名为Z的变量中,打印其值:Z = mult + b Z预期输出如下:
<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[129.]], dtype=float32)> -
使用
tf.math.sigmoid对Z应用sigmoid函数,将其结果保存在名为a的变量中,并打印其值。sigmoid函数将任何数值转化到 0 到 1 的范围内(我们将在后续章节中深入了解这一点):a = tf.math.sigmoid(Z) a预期的输出是这样的:
<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[1.]], dtype=float32)>
sigmoid 函数将 Z 的原始值 129 转换为 1。
注意
若要访问本节的源代码,请参考 packt.live/31ekGLM。
你也可以在 packt.live/3evuKnC 在线运行此示例。你必须执行整个笔记本才能获得预期的结果。
在本次练习中,你成功地使用 TensorFlow 实现了一个人工神经元。这是任何神经网络模型的基础。
在接下来的章节中,我们将深入研究神经网络的架构。
神经网络架构
神经网络是人工智能(AI)最新的分支。神经网络的灵感来源于人类大脑的工作方式。它们由 Warren McCulloch 和 Walter Pitts 在 1940 年代发明。神经网络是一个数学模型,用于描述人类大脑如何解决问题。
当我们谈论人类大脑时,使用 ANN 来指代数学模型和生物神经网络。
神经网络的学习方式比其他分类或回归模型更为复杂。神经网络模型具有许多内部变量,输入和输出变量之间的关系可能涉及多个内部层次。神经网络的准确性通常高于其他监督学习算法。
注意
精通使用 TensorFlow 构建神经网络是一个复杂的过程。本节的目的是为你提供一个入门资源,帮助你开始学习。
在本章中,我们将使用的主要示例是从图像中识别数字。我们采用这种格式是因为每张图像较小,并且我们有约 70,000 张图像可供使用。处理这些图像所需的计算能力类似于普通计算机的能力。
神经网络(ANN)工作原理与人类大脑相似。人类大脑中的树突与细胞核相连,细胞核与轴突相连。在神经网络中,输入相当于树突,计算发生的地方是细胞核,输出则是轴突。
人工神经元被设计成模拟细胞核的工作方式。它将通过矩阵乘法计算和激活函数来转化输入信号。如果激活函数判定神经元必须激发,那么一个信号将出现在输出端。这个信号可以是网络中其他神经元的输入:
图 6.2:显示神经网络如何工作的图
通过以 n=4 为例,我们可以进一步理解前面的图形。在这种情况下,适用的公式为:
-
X是输入矩阵,由x1、x2、x3和x4组成。 -
W,权重矩阵,将由w1、w2、w3和w4组成。 -
b是偏置。 -
f是激活函数。
我们将首先通过矩阵乘法和偏置计算 Z(神经元的左侧):
Z = W * X + b = x1*w1 + x2*w2 + x3*w3 + x4*w4 + b
然后,输出 y 将通过应用函数 f 来计算:
y = f(Z) = f(x1*w1 + x2*w2 + x3*w3 + x4*w4 + b)
很好——这就是人工神经元在幕后工作的方式。它是两个矩阵运算,先乘积后求和,再经过函数变换。
现在我们进入下一部分——权重。
权重
y。
一个单一的神经元是加权和与激活函数的组合,可以称之为隐藏层。具有一个隐藏层的神经网络称为常规神经网络:
](tos-cn-i-73owjymdk6/2ed04ca642944803957ece57a01b6f02)
图 6.3:神经元 1、2 和 3 构成了这个示例网络的隐藏层
在连接输入和输出时,我们可能会有多个隐藏层。具有多个层的神经网络称为深度神经网络。
“深度学习”一词来自于多个层次的存在。在创建人工神经网络(ANN)时,我们可以指定隐藏层的数量。
偏置
之前,我们看到神经元的方程如下:
y = f(x1*w1 + x2*w2 + x3*w3 + x4*w4)
这个方程的问题在于,缺少一个依赖于输入 x1、x2、x3 和 x4 的常数因子。前面的方程可以表示任何经过原点的线性函数:如果所有 w 值都等于 0,那么 y 也会等于 0。但对于那些不经过原点的其他函数呢?例如,假设我们正在预测某员工的流失概率,按其在职月份来算。即使他们还没有工作满一个月,流失概率也不可能为零。
为了适应这种情况,我们需要引入一个新参数 b,它可以等于 0.5,因此新雇员在第一个月的流失概率将是 50%。
因此,我们在方程中加入偏置:
y = f(x1*w1 + x2*w2 + x3*w3 + x4*w4 + b)
y = f(x w + b)
第一个方程是详细形式,描述了每个坐标、权重系数和偏置的作用。第二个方程是向量形式,其中 x = (x1, x2, x3, x4) 和 w = (w1, w2, w3, w4)。向量之间的点运算符表示这两个向量的点积或标量积。这两个方程是等效的。我们在实践中将使用第二种形式,因为使用 TensorFlow 定义变量向量比逐一定义每个变量更为简便。
类似地,对于 w1、w2、w3 和 w4,偏置 b 是一个变量,意味着它的值在学习过程中可以发生变化。
通过在每个神经元中内置这个常数因子,神经网络模型变得更加灵活,能够更好地拟合特定的训练数据集。
注意
由于某些负权重的存在,可能会发生产品p = x1*w1 + x2*w2 + x3*w3 + x4*w4为负的情况。我们可能仍然希望给予模型一定的灵活性,使其在值大于某个负数时能够激活(或触发)神经元。因此,添加一个常数偏置,例如b = 5,可以确保神经元在-5到0之间的值也能触发。
TensorFlow 提供了Dense()类来建模神经网络的隐藏层(也叫做全连接层):
from tensorflow.keras import layers
layer1 = layers.Dense(units=128, input_shape=[200])
在这个示例中,我们创建了一个包含128个神经元的全连接层,该层的输入为形状为200的张量。
注意
你可以在www.tensorflow.org/api_docs/python/tf/keras/layers/Dense找到有关此 TensorFlow 类的更多信息。
Dense()类要求输入是一个展平的数组(只有一行)。例如,如果输入的形状是28×28,你需要先使用Flatten()类将其展平,才能得到一个包含 784 个神经元(28 * 28)的单行数据:
from tensorflow.keras import layers
input_layer = layers.Flatten(input_shape=(28, 28))
layer1 = layers.Dense(units=128)
注意
你可以在www.tensorflow.org/api_docs/python/tf/keras/layers/Flatten找到有关此 TensorFlow 类的更多信息。
在接下来的部分中,我们将学习如何使用额外的参数扩展这一神经元层。
人工神经网络(ANNs)的应用场景
人工神经网络(ANNs)在监督学习技术中占有一席之地。它们可以处理分类和回归问题。分类器神经网络试图找到特征与标签之间的关系。特征是输入变量,而分类器可以选择作为返回值的每个类别是一个独立的输出。在回归的情况下,输入变量是特征,而只有一个输出:预测值。尽管传统的分类和回归技术在人工智能中有其应用场景,但人工神经网络通常更擅长发现输入与输出之间复杂的关系。
在下一节中,我们将探讨激活函数及其不同类型。
激活函数
如前所述,一个神经元需要通过应用激活函数来执行变换。神经网络中可以使用不同的激活函数。如果没有这些函数,神经网络将仅仅是一个线性模型,可以通过矩阵乘法轻松描述。
神经网络的激活函数提供了非线性,因此能够建模更复杂的模式。两种非常常见的激活函数是sigmoid和tanh(双曲正切函数)。
Sigmoid
sigmoid的公式如下:
图 6.4:Sigmoid 公式
sigmoid函数的输出值范围是0到1。这种激活函数通常用于神经网络的最后一层,用于二分类问题。
Tanh
双曲正切的公式如下:
图 6.5:tanh 公式
tanh 激活函数与 sigmoid 函数非常相似,并且直到最近都非常流行。它通常用于神经网络的隐藏层。它的值范围在 -1 和 1 之间。
ReLU
另一个重要的激活函数是 relu。ReLU 代表 Rectified Linear Unit,目前是最广泛使用的隐藏层激活函数。其公式如下:
图 6.6:ReLU 公式
目前有不同版本的 relu 函数,例如 leaky ReLU 和 PReLU。
Softmax
该函数将列表中的值缩放到 softmax 函数如下所示的范围:
图 6.7:softmax 公式
softmax 函数通常作为神经网络的最后一层,用于多类分类问题,因为它能够为每个不同的输出类别生成概率。
请记住,在 TensorFlow 中,我们可以通过激活函数扩展 Dense() 层;只需要设置 activation 参数即可。在以下示例中,我们将添加 relu 激活函数:
from tensorflow.keras import layers
layer1 = layers.Dense(units=128, input_shape=[200], \
activation='relu')
让我们使用这些不同的激活函数,观察它们如何通过解决以下练习来抑制加权输入。
练习 6.02:激活函数
在本次练习中,我们将使用 numpy 包实现以下激活函数:sigmoid、tanh、relu 和 softmax。
以下步骤将帮助你完成练习:
-
打开一个新的 Jupyter Notebook 文件。
-
导入
numpy包并命名为np:import numpy as np -
创建一个
sigmoid函数,如下所示的代码片段,使用np.exp()方法实现 sigmoid 公式(如前所述):def sigmoid(x): return 1 / (1 + np.exp(-x)) -
计算
sigmoid函数在值-1上的结果:sigmoid(-1)预期输出如下:
0.2689414213699951这是对值
-1执行 sigmoid 变换后的结果。 -
导入
matplotlib.pyplot包并命名为plt:import matplotlib.pyplot as plt -
创建一个名为
x的numpy数组,包含从-10到10的均匀间隔的值,增量为0.1,使用np.arange()方法。打印其值:x = np.arange(-10, 10, 0.1) x预期输出如下:
array(-1.00000000e+01, -9.90000000e+00, -9.80000000e+00, -9.70000000e+00, -9.60000000e+00, -9.50000000e+00, -9.40000000e+00, -9.30000000e+00, -9.20000000e+00, -9.10000000e+00, -9.00000000e+00, -8.90000000e+00, -8.80000000e+00, -8.70000000e+00, -8.60000000e+00, -8.50000000e+00, -8.40000000e+00, -8.30000000e+00, -8.20000000e+00, -8.10000000e+00, -8.00000000e+00, -7.90000000e+00, -7.80000000e+00, -7.70000000e+00, -7.60000000e+00, -7.50000000e+00, -7.40000000e+00, -7.30000000e+00, -7.20000000e+00, -7.10000000e+00, -7.00000000e+00, -6.90000000e+00,很好——我们生成了一个包含从
-10到10的值的numpy数组。注意
前面的输出已被截断。
-
使用
plt.plot()和plt.show()绘制x和sigmoid(x)的折线图:plt.plot(x, sigmoid(x)) plt.show()预期输出如下:
![图 6.8:使用 sigmoid 函数的折线图 图 6.8:使用 sigmoid 函数的折线图 我们可以看到,
sigmoid函数的输出范围在0和1之间。对于接近0的值,斜率非常陡峭。1. 创建一个tanh()函数,使用np.exp()方法实现 Tanh 公式(如前所述):py def tanh(x): return 2 / (1 + np.exp(-2*x)) - 11. 使用plt.plot()和plt.show()绘制x和tanh(x)的折线图:py plt.plot(x, tanh(x)) plt.show()预期输出是这样的:图 6.9:使用 tanh 函数的折线图
tanh函数的形状与sigmoid非常相似,但在接近0的值时,它的斜率更陡。记住,它的值域介于**-1和1**之间。 -
创建一个
relu函数,使用np.maximum()方法实现 ReLU 公式(如上一节所示):def relu(x): return np.maximum(0, x) -
使用
plt.plot()和plt.show()绘制x和relu(x)的折线图:plt.plot(x, relu(x)) plt.show()预期输出是这样的:
图 6.10:使用 relu 函数的折线图
当值为负数时,ReLU 函数等于
0,当值为正数时,ReLU 函数等于恒等函数f(x)=x。 -
创建一个
softmax函数,使用np.exp()方法实现 softmax 公式(如上一节所示):def softmax(list): return np.exp(list) / np.sum(np.exp(list)) -
计算列表
[0, 1, 168, 8, 2]上softmax的输出:result = softmax( [0, 1, 168, 8, 2]) result预期输出是这样的:
array([1.09276566e-73, 2.97044505e-73, 1.00000000e+00, 3.25748853e-70, 8.07450679e-73])
如预期的那样,第三个位置的项具有最高的 softmax 概率,因为它的原始值是最高的。
注意
要访问此特定部分的源代码,请参阅 packt.live/3fJzoOU。
你也可以在网上运行这个示例,网址是 packt.live/3188pZi。你必须执行整个 Notebook,才能获得预期的结果。
完成此练习后,我们实现了神经网络中一些最重要的激活函数。
前向传播和损失函数
到目前为止,我们已经看到一个神经元如何接收输入并对其进行一些数学运算以得到输出。我们了解到,神经网络是由多个神经元层组合而成的。
将神经网络的输入转化为结果的过程称为前向传播(或前向传递)。我们要求神经网络做的事情是,通过将多个神经元应用于输入数据,来进行预测(神经网络的最终输出):
图 6.11:展示前向传播的图示
神经网络依赖于每个神经元的权重矩阵、偏置和激活函数来计算预测的输出值,。目前,我们假设权重矩阵和偏置的值已预设。激活函数在设计神经网络架构时定义。
对于任何监督式机器学习算法,目标都是做出准确的预测。这意味着我们需要评估预测与真实值之间的准确度。对于传统的机器学习算法,我们使用诸如均方误差、准确率或 F1 分数等评分指标。这同样适用于神经网络,但唯一的区别是,这些分数有两种不同的使用方式:
-
数据科学家使用它们来评估模型在训练集和测试集上的表现,然后根据需要调整超参数。这同样适用于神经网络,所以这里没有什么新鲜的东西。
-
神经网络利用它们自动从错误中学习,并更新权重矩阵和偏差。这个过程将在下一节详细解释,那个部分讲的是反向传播。因此,神经网络将使用一个度量标准(也叫做损失函数)来比较预测值与真实标签(y),并学习如何自动进行更好的预测。
损失函数对神经网络学习做出良好预测至关重要。这是一个超参数,需要数据科学家在设计神经网络架构时定义。选择使用哪种损失函数是完全任意的,取决于你想解决的数据集或问题,你会选择一种或另一种。幸运的是,有一些基本的经验法则在大多数情况下都有效:
-
如果你正在处理回归问题,可以使用均方误差。
-
如果是二分类问题,损失函数应该使用二元交叉熵。
-
如果是多类分类问题,那么你应该选择类别交叉熵作为损失函数。
最后需要注意的是,损失函数的选择也将决定你在神经网络最后一层使用哪种激活函数。每个损失函数都需要特定类型的数据来正确评估预测性能。
以下是根据损失函数和项目/问题类型列出的激活函数:
图 6.12:不同激活函数及其应用概述
使用 TensorFlow 时,为了构建自定义架构,你可以实例化 Sequential() 类,并按如下代码片段添加完全连接的神经元层:
import tensorflow as tf
from tensorflow.keras import layers
model = tf.keras.Sequential()
input_layer = layers.Flatten(input_shape=(28,28))
layer1 = layers.Dense(128, activation='relu')
model.add(input_layer)
model.add(layer1)
现在是时候看看神经网络是如何通过反向传播改进预测的。
反向传播
之前,我们学习了神经网络如何通过使用其神经元的权重矩阵和偏置(我们可以将它们合并成一个单一的矩阵)来进行预测。通过使用损失函数,网络可以确定预测结果的好坏。如果它能利用这些信息并相应地更新参数,那就太好了。这正是反向传播的目的:优化神经网络的参数。
训练神经网络涉及多次执行前向传播和反向传播,以便进行预测并根据误差更新参数。在第一次传播中,我们首先初始化神经网络的所有权重。然后,进行前向传播,接着进行反向传播,后者会更新权重。
我们多次应用这个过程,神经网络会逐步优化其参数。你可以通过设置神经网络遍历整个数据集的最大次数(也叫做 epoch)来决定是否停止这个学习过程,或者如果神经网络在几个 epoch 后得分不再提升,则定义一个提前停止的阈值。
优化器和学习率
在前一节中,我们看到神经网络遵循一个迭代过程,以找到任何输入数据集的最佳解决方案。它的学习过程是一个优化过程。你可以使用不同的优化算法(也叫做Adam、SGD和RMSprop)。
神经网络优化器的一个重要参数是学习率。这个值定义了神经网络更新权重的速度。设置过低的学习率会减慢学习过程,神经网络需要很长时间才能找到合适的参数。另一方面,学习率过高会导致神经网络无法学习到合适的解决方案,因为它在每次更新时都会做出比需要更大的权重变化。一个好的做法是从一个不是很小的学习率开始(例如0.01或0.001),然后当神经网络的得分开始平稳或变差时停止训练,接着降低学习率(例如降低一个数量级)并继续训练网络。
使用 TensorFlow,你可以从tf.keras.optimizers实例化一个优化器。例如,下面的代码片段展示了如何创建一个学习率为0.001的Adam优化器,然后通过指定损失函数('sparse_categorical_crossentropy')和要显示的指标('accuracy')来编译我们的神经网络:
import tensorflow as tf
optimizer = tf.keras.optimizers.Adam(0.001)
model.compile(loss='sparse_categorical_crossentropy', \
optimizer=optimizer, metrics=['accuracy'])
一旦模型编译完成,我们就可以像这样使用.fit()方法训练神经网络:
model.fit(features_train, label_train, epochs=5)
在这里,我们对训练集进行了5个 epoch 的神经网络训练。训练完成后,我们可以使用该模型在测试集上进行评估,并通过.evaluate()方法评估其性能:
model.evaluate(features_test, label_test)
注意
你可以在www.tensorflow.org/api_docs/python/tf/keras/optimizers上找到更多关于 TensorFlow 优化器的信息。
在下一个练习中,我们将基于数据集训练一个神经网络。
练习 6.03:分类信用批准
在本练习中,我们将使用德国信用批准数据集,并训练一个神经网络来分类个人是否具备信用。
注意
数据集文件也可以在我们的 GitHub 仓库中找到:
以下步骤将帮助你完成练习:
-
打开一个新的 Jupyter Notebook 文件。
-
从
numpy中导入loadtxt方法:from numpy import loadtxt -
创建一个名为
file_url的变量,包含指向原始数据集的链接:file_url = 'https://raw.githubusercontent.com/'\ 'PacktWorkshops/'\ 'The-Applied-Artificial-Intelligence-Workshop'\ '/master/Datasets/german_scaled.csv' -
使用
loadtxt()加载数据到一个名为data的变量中,并指定delimiter=','参数。打印其内容:data = loadtxt(file_url, delimiter=',') data期望的输出是这样的:
array([[0\. , 0.33333333, 0.02941176, ..., 0\. , 1\. , 1\. ], [1\. , 0\. , 0.64705882, ..., 0\. , 0\. , 1\. ], [0\. , 1\. , 0.11764706, ..., 1\. , 0\. , 1\. ], ..., [0\. , 1\. , 0.11764706, ..., 0\. , 0\. , 1\. ], [1\. , 0.33333333, 0.60294118, ..., 0\. , 1\. , 1\. ], [0\. , 0\. , 0.60294118, ..., 0\. , 0\. , 1\. ]]) -
创建一个名为
label的变量,包含来自第一列的数据(这将是我们的响应变量):label = data[:, 0] -
创建一个名为
features的变量,包含除第一列外的所有数据(第一列对应于响应变量):features = data[:, 1:] -
从
sklearn.model_selection中导入train_test_split方法:from sklearn.model_selection import train_test_split -
将数据拆分为训练集和测试集,并将结果保存到四个变量中,分别为
features_train,features_test,label_train,label_test。使用 20%的数据作为测试集,并指定random_state=7:features_train, features_test, \ label_train, label_test = train_test_split(features, \ label, \ test_size=0.2, \ random_state=7) -
导入
numpy为np,tensorflow为tf,并从tensorflow.keras中导入layers:import numpy as np import tensorflow as tf from tensorflow.keras import layers -
使用
np.random_seed()和tf.random.set_seed()分别将1设置为numpy和tensorflow的种子:np.random.seed(1) tf.random.set_seed(1) -
实例化一个
tf.keras.Sequential()类,并将其保存到一个名为model的变量中:model = tf.keras.Sequential() -
实例化一个
layers.Dense()类,包含16个神经元,activation='relu',input_shape=[19],然后将其保存到一个名为layer1的变量中:layer1 = layers.Dense(16, activation='relu', \ input_shape=[19]) -
实例化第二个
layers.Dense()类,包含1个神经元,activation='sigmoid',然后将其保存到一个名为final_layer的变量中:final_layer = layers.Dense(1, activation='sigmoid') -
使用
.add()将你刚才定义的两个层添加到模型中:model.add(layer1) model.add(final_layer) -
实例化一个
tf.keras.optimizers.Adam()类,学习率为0.001,并将其保存到名为optimizer的变量中:optimizer = tf.keras.optimizers.Adam(0.001) -
使用
.compile()编译神经网络,loss='binary_crossentropy',optimizer=optimizer,metrics=['accuracy'],如下代码片段所示:model.compile(loss='binary_crossentropy', \ optimizer=optimizer, metrics=['accuracy']) -
使用
.summary()打印模型的摘要:model.summary()期望的输出是这样的:
](tos-cn-i-73owjymdk6/ba8b2fea203f427ea7bfa3798935e5f2)
图 6.13:顺序模型的摘要
这个输出总结了我们神经网络的架构。我们可以看到它由三层组成,符合预期,并且知道每一层的输出大小和参数数量,这些参数对应于权重和偏置。例如,第一层有
16个神经元和320个需要学习的参数(权重和偏置)。 -
接下来,使用训练集拟合神经网络并指定
epochs=10:model.fit(features_train, label_train, epochs=10)期望的输出是这样的:
图 6.14:使用训练集拟合神经网络
输出提供了大量关于神经网络训练的信息。第一行告诉我们训练集由800个观测值组成。然后我们可以看到每一轮训练的结果:
总处理时间(秒)
每个数据样本的处理时间(微秒/样本)
损失值和准确度得分
这个神经网络的最终结果是最后一轮训练(epoch=10),我们在此达到了0.6888的准确度得分。但我们可以看到趋势在持续改善:每一轮训练后准确度得分都在增加。因此,如果我们通过增加训练轮数或降低学习率来延长训练时间,可能会得到更好的结果。
注意
要访问此特定部分的源代码,请参考 packt.live/3fMhyLk。
你也可以在 packt.live/2Njghza 在线运行这个示例。你必须执行整个 Notebook,才能得到预期的结果。
完成这个练习后,你就训练了你的第一个分类器。在传统的机器学习算法中,你需要编写更多的代码才能实现这一点,因为你需要定义神经网络的整个架构。在这里,神经网络在10轮训练后达到了0.6888,但如果让它训练得更长一些,性能仍然有提升空间。你可以尝试自己做一下。
接下来,我们将探讨正则化。
正则化
与任何机器学习算法一样,神经网络在学习仅对训练集相关的模式时,可能会面临过拟合的问题。在这种情况下,模型无法对未见过的数据进行泛化。
幸运的是,有多种技术可以帮助减少过拟合的风险:
-
L1 正则化,它向损失函数中添加了一个惩罚参数(权重的绝对值)
-
L2 正则化,它向损失函数中添加了一个惩罚参数(权重的平方值)
-
早停法,它会在验证集的误差增加而训练集的误差减少时停止训练
-
Dropout,它会在训练过程中随机移除一些神经元
所有这些技术都可以添加到我们创建的神经网络的每一层。在下一个练习中,我们将会深入探讨这一点。
练习 6.04:使用正则化预测波士顿房价
在这个练习中,你将构建一个神经网络,用来预测波士顿某个郊区的房价中位数,并了解如何为网络添加正则化方法。
注意
数据集文件也可以在我们的 GitHub 仓库中找到:packt.live/2V9kRUU。
引用:数据最初由Harrison, D. 和 Rubinfeld, D.L. 'Hedonic prices and the demand for clean air', J. Environ. Economics & Management, vol.5, 81-102, 1978发布。
数据集包含12个不同的特征,提供有关郊区的信息和一个目标变量(MEDV)。目标变量是数字型,表示以$1,000 为单位的自有住房的中位数价值。
以下步骤将帮助你完成练习:
-
打开一个新的 Jupyter Notebook 文件。
-
将
pandas包导入为pd:import pandas as pd -
创建一个包含原始数据集链接的
file_url变量:file_url = 'https://raw.githubusercontent.com/'\ 'PacktWorkshops/'\ 'The-Applied-Artificial-Intelligence-Workshop'\ '/master/Datasets/boston_house_price.csv' -
使用
pd.read_csv()将数据集加载到一个名为df的变量中:df = pd.read_csv(file_url) -
使用
.head()显示前五行:df.head()预期输出是这样的:
图 6.15:输出显示数据集的前五行
-
使用
.pop()提取目标变量,并将其保存到一个名为label的变量中:label = df.pop('MEDV') -
从
sklearn.preprocessing导入scale函数:from sklearn.preprocessing import scale -
对 DataFrame
df进行缩放,并将结果保存到一个名为scaled_features的变量中。打印其内容:scaled_features = scale(df) scaled_features预期输出是这样的:
array([[-0.41978194, 0.28482986, -1.2879095 , ..., -0.66660821, -1.45900038, -1.0755623 ], [-0.41733926, -0.48772236, -0.59338101, ..., -0.98732948, -0.30309415, -0.49243937], [-0.41734159, -0.48772236, -0.59338101, ..., -0.98732948, -0.30309415, -1.2087274 ], ..., [-0.41344658, -0.48772236, 0.11573841, ..., -0.80321172, 1.17646583, -0.98304761], [-0.40776407, -0.48772236, 0.11573841, ..., -0.80321172, 1.17646583, -0.86530163], [-0.41500016, -0.48772236, 0.11573841, ..., -0.80321172,从输出中可以看到,我们的所有特征现在已经标准化。
-
从
sklearn.model_selection导入train_test_split:from sklearn.model_selection import train_test_split -
将数据拆分为训练集和测试集,并将结果保存到四个变量中,分别为
features_train,features_test,label_train和label_test。使用 10%的数据作为测试集,并指定random_state=8:features_train, features_test, \ label_train, label_test = train_test_split(scaled_features, \ label, \ test_size=0.1, \ random_state=8) -
将
numpy导入为np,tensorflow导入为tf,并从tensorflow.keras导入layers:import numpy as np import tensorflow as tf from tensorflow.keras import layers -
使用
np.random_seed()和tf.random.set_seed()设置 NumPy 和 TensorFlow 的种子为8:np.random.seed(8) tf.random.set_seed(8) -
实例化一个
tf.keras.Sequential()类,并将其保存到一个名为model的变量中:model = tf.keras.Sequential() -
接下来,使用
tf.keras.regularizers.l1_l2创建一个结合了l1和l2正则化器,l1=0.01和l2=0.01。将其保存到一个名为regularizer的变量中:regularizer = tf.keras.regularizers.l1_l2(l1=0.1, l2=0.01) -
实例化一个
layers.Dense()类,使用10个神经元,activation='relu',input_shape=[12],以及kernel_regularizer=regularizer,并将其保存到一个名为layer1的变量中:layer1 = layers.Dense(10, activation='relu', \ input_shape=[12], kernel_regularizer=regularizer) -
实例化第二个
layers.Dense()类,使用1个神经元,并将其保存到一个名为final_layer的变量中:final_layer = layers.Dense(1) -
使用
.add()将刚定义的两个层添加到模型中,并在它们之间添加一个layers.Dropout(0.25)层:model.add(layer1) model.add(layers.Dropout(0.25)) model.add(final_layer)我们在每个全连接层之间添加了一个 dropout 层,它会随机移除 25%的神经元。
-
实例化一个
tf.keras.optimizers.SGD()类,学习率为0.001,并将其保存到一个名为optimizer的变量中:optimizer = tf.keras.optimizers.SGD(0.001) -
使用
.compile()编译神经网络,配置loss='mse',optimizer=optimizer,metrics=['mse']:model.compile(loss='mse', optimizer=optimizer, \ metrics=['mse']) -
使用
.summary()打印模型的摘要:model.summary()预期输出是这样的:
图 6.16:模型摘要
此输出总结了我们神经网络的架构。我们可以看到它由三层组成,其中有两层密集层和一层丢弃层。
-
实例化一个
tf.keras.callbacks.EarlyStopping()类,设置monitor='val_loss'和patience=2作为学习率,并将其保存到名为callback的变量中:callback = tf.keras.callbacks.EarlyStopping(monitor='val_loss', \ patience=2)我们刚刚定义了一个回调,声明神经网络将在验证损失(
monitor='val_loss')在2个 epochs(patience=2)内未改善时停止训练。 -
使用训练集拟合神经网络,并指定
epochs=50,validation_split=0.2,callbacks=[callback]和verbose=2:model.fit(features_train, label_train, \ epochs=50, validation_split = 0.2, \ callbacks=[callback], verbose=2)预期输出如下:
图 6.17:使用训练集拟合神经网络
在输出中,我们看到神经网络在第 22 个 epoch 后停止了训练。它在最大 epochs 数50之前停止了训练。这是因为我们之前设置的回调:如果验证损失在两个 epoch 后没有改善,训练应该停止。
注意
要访问此特定部分的源代码,请参见packt.live/2Yobbba。
你也可以在packt.live/37SVSu6在线运行此示例。你必须执行整个 Notebook 才能获得预期的结果。
你刚刚应用了多种正则化技术,并训练了一个神经网络来预测波士顿郊区的住房中位数值。
活动 6.01:为数字数据集找到最佳准确度得分
在本活动中,你将训练和评估一个神经网络,该网络将识别由 MNIST 数据集提供的手写数字图像。你将专注于实现最佳的准确度得分。
注意
你可以在 TensorFlow 官网上阅读更多关于该数据集的信息:www.tensorflow.org/datasets/catalog/mnist。
引用:该数据集最初由Yann Lecun分享。
以下步骤将帮助你完成该活动:
-
导入 MNIST 数据集。
-
通过将数据除以
255来标准化数据。 -
创建一个具有以下层的神经网络架构:
一个扁平化的输入层,使用
layers.Flatten(input_shape=(28,28))一个完全连接的层,使用
layers.Dense(128, activation='relu')一个丢弃层,使用
layers.Dropout(0.25)一个完全连接的层,使用
layers.Dense(10, activation='softmax') -
指定一个学习率为
0.001的Adam优化器。 -
在验证损失上定义一个早停机制,
patience=5。 -
训练模型。
-
评估模型并找到准确度得分。
预期输出如下:
图 6.18:预期的准确度得分
注意
该活动的解决方案可以在第 378 页找到
在下一部分,我们将深入探讨深度学习的主题。
深度学习
现在我们已经能够构建和训练一个具有一个隐藏层的神经网络,我们可以研究更复杂的深度学习架构。
深度学习只是传统神经网络的扩展,但具有更深和更复杂的架构。深度学习可以建模非常复杂的模式,应用于检测图像中的物体、将文本翻译成不同语言等任务。
浅层与深层网络
现在我们已经能够构建和训练一个具有一个隐藏层的神经网络,我们可以研究更复杂的深度学习架构。
如前所述,我们可以在神经网络中添加更多的隐藏层。这将增加需要学习的参数数量,但有可能帮助建模更复杂的模式。这就是深度学习的核心:增加神经网络的深度,以解决更复杂的问题。
例如,我们可以在前向传播和损失函数部分中呈现的神经网络中添加第二层:
图 6.19:显示神经网络中两个隐藏层的图
理论上,我们可以添加无限多个隐藏层。但深层网络有一个缺点。增加深度还会增加需要优化的参数数量。因此,神经网络需要训练更长时间。所以,作为良好的实践,最好从更简单的架构开始,然后逐步增加其深度。
计算机视觉与图像分类
深度学习在计算机视觉和自然语言处理方面取得了惊人的成果。计算机视觉是一个涉及分析数字图像的领域。数字图像是由像素组成的矩阵。每个像素的值在0到255之间,这个值表示像素的强度。一张图像可以是黑白的,并且只有一个通道。但它也可以是彩色的,在这种情况下,它将有三个通道,分别代表红色、绿色和蓝色。这种数字版本的图像可以输入到深度学习模型中。
计算机视觉有多种应用,例如图像分类(识别图像中的主要物体)、物体检测(定位图像中的不同物体)和图像分割(寻找图像中物体的边缘)。本书将只关注图像分类。
在下一节中,我们将讨论一种特定类型的架构:CNNs。
卷积神经网络(CNNs)
**卷积神经网络(CNNs)**是针对图像相关模式识别优化的人工神经网络。CNNs 基于卷积层,而不是全连接层。
卷积层用于通过滤波器检测图像中的模式。滤波器只是一个矩阵,通过卷积操作应用到输入图像的某个部分,输出将是另一张图像(也称为特征图),其中突出显示了滤波器找到的模式。例如,一个简单的滤波器可以识别花朵上的垂直线条,如下图所示:
图 6.20:卷积在图像中检测模式
这些滤波器不是事先设定好的,而是通过卷积神经网络(CNN)自动学习的。训练结束后,CNN 可以识别图像中的不同形状。这些形状可以出现在图像的任何地方,卷积操作符无论图像的精确位置和方向如何,都能识别类似的信息。
卷积操作
卷积是一种特定类型的矩阵运算。对于输入图像,大小为n*n的滤波器将遍历图像的特定区域,执行逐元素相乘和求和,并返回计算结果:
图 6.21:卷积操作
在前面的例子中,我们将滤波器应用到图像的左上部分。然后,我们执行逐元素相乘,将输入图像中的一个元素与滤波器上对应的值相乘。在这个例子中,我们计算了以下内容:
-
第 1 行,第 1 列:
5*2=10 -
第 1 行,第 2 列:
10*0=0 -
第 1 行,第 3 列:
15*(-1)=-15 -
第 2 行,第 1 列:
10*2=20 -
第 2 行,第 2 列:
20*0=0 -
第 2 行,第 3 列:
30*(-1)=-30 -
第 3 行,第 1 列:
100*2=200 -
第 3 行,第 2 列:
150*0=0 -
第 3 行,第 3 列:
200*(-1)=-200
最后,我们对这些值进行求和:10 + 0 -15 + 20 + 0 - 30 + 200 + 0 - 200 = -15。
然后,我们将通过将滤波器向右滑动一个列来执行相同的操作。我们会一直滑动滤波器,直到覆盖整个图像:
图 6.22:在不同的行和列上进行卷积操作
我们不仅可以按列滑动,也可以按两列、三列或更多列滑动。定义滑动操作长度的参数称为步幅。
你可能已经注意到,卷积操作的结果是一个尺寸比输入图像小的图像(或特征图)。如果你想保持相同的尺寸,可以在输入图像的边界周围添加额外的行和列,值为 0。这种操作称为填充。
这就是卷积操作背后的原理。卷积层就是应用这种操作并使用多个滤波器。
我们可以在 TensorFlow 中用以下代码片段声明一个卷积层:
from tensorflow.keras import layers
layers.Conv2D(32, kernel_size=(3, 3), strides=(1,1), \
padding="valid", activation="relu")
在上面的示例中,我们已经实例化了一个具有 32 个滤波器的卷积层(也叫做 (3, 3) 卷积,步幅为 1(每次滑动窗口按 1 列或 1 行移动),并且没有填充(padding="valid")。
注意
您可以在 TensorFlow 的官方网站上阅读更多关于 Conv2D 类的信息,网址为 www.tensorflow.org/api_docs/python/tf/keras/layers/Conv2D。
在 TensorFlow 中,卷积层期望输入为具有以下格式的张量:(行数,高度,宽度,通道数)。根据数据集的不同,您可能需要重塑图像以符合此要求。TensorFlow 提供了一个函数来执行此操作,如下所示:
features_train.reshape(60000, 28, 28, 1)
池化层
CNN 架构中另一个常见的层是池化层。我们之前已经看到,如果没有添加填充,卷积层会减小图像的大小。这种行为是预期的吗?为什么我们不保持输入图像的完全相同的大小?通常,在 CNN 中,我们倾向于随着不同层的推进,逐渐减少特征图的大小。这样做的主要原因是,我们希望在网络的末端有更多和更多特定的模式检测器。
在网络的前端,CNN 更倾向于具有更通用的滤波器,例如垂直或水平线检测器;但随着网络的加深,例如,如果我们训练一个 CNN 来区分猫和狗,它可能会有可以检测狗尾巴或猫胡须的滤波器;或者如果我们分类水果图像,滤波器可以检测物体的纹理。此外,较小的特征图能降低检测到错误模式的风险。
通过增加步幅,我们可以进一步减小输出特征图的大小。但还有另一种方法可以做到这一点:在卷积层后添加池化层。池化层是一个给定大小的矩阵,它会对特征图的每个区域应用聚合函数。最常见的聚合方法是找到一组像素中的最大值:
图 6.23:池化层的工作原理
在上面的示例中,我们使用了大小为 (2, 2) 且步幅为 2 的最大池化。我们查看特征图的左上角,找到像素 6、8、1 和 2 中的最大值,得到结果 8。然后,我们通过步幅 2 滑动最大池化,对像素 6、1、7 和 4 执行相同的操作。我们对底部的区域重复相同的操作,得到一个大小为 (2,2) 的新特征图。
在 TensorFlow 中,我们可以使用 MaxPool2D() 类来声明一个最大池化层:
from tensorflow.keras import layers
layers.MaxPool2D(pool_size=(2, 2), strides=2)
注意
您可以在 TensorFlow 的官方网站上阅读更多关于 Conv2D 类的信息,网址为 www.tensorflow.org/api_docs/python/tf/keras/layers/MaxPool2D。
CNN 架构
正如你之前看到的,你可以通过指定隐藏层的类型和数量、激活函数等,定义你自己的自定义 CNN 架构。但对于初学者来说,这可能有些令人畏惧。我们如何知道每一层需要添加多少个滤波器,或者适当的步幅是多少呢?我们必须尝试多种组合,看看哪种效果最好。
幸运的是,深度学习领域的许多研究人员已经做了这样的探索性工作,并公开了他们设计的架构。目前,最著名的架构包括:
-
AlexNet
-
VGG
-
ResNet
-
Inception
注意
我们不会详细介绍每种架构,因为它超出了本书的范围,但你可以在 TensorFlow 的官方网站上阅读更多关于不同 CNN 架构的信息,网址为
www.tensorflow.org/api_docs/python/tf/keras/applications。
活动 6.02:使用 CNN 评估时尚图像识别模型
在本活动中,我们将训练一个 CNN 来识别属于 Fashion MNIST 数据集中的 10 个不同类别的服装图像。我们将计算这个 CNN 模型的准确性。
注意
你可以在 TensorFlow 的网站上阅读更多关于这个数据集的信息,网址为www.tensorflow.org/datasets/catalog/fashion_mnist。
原始数据集由Han Xiao分享。
以下步骤将帮助你完成这个活动:
-
导入 Fashion MNIST 数据集。
-
重新调整训练集和测试集的形状。
-
通过对数据进行
255除法标准化。 -
创建一个具有以下层的神经网络架构:
三个卷积层,使用
Conv2D(64, (3,3), activation='relu'),后跟MaxPooling2D(2,2)一个 Flatten 层
一个全连接层,使用
Dense(128, activation=relu)一个全连接层,使用
Dense(10, activation='softmax') -
指定一个学习率为
0.001的Adam优化器。 -
训练模型。
-
在测试集上评估模型。
预期输出如下:
10000/10000 [==============================] - 1s 108us/sample - loss: 0.2746 - accuracy: 0.8976
[0.27461639745235444, 0.8976]
注意
这个活动的解决方案可以在第 382 页找到。
在接下来的部分中,我们将学习另一种深度学习架构:RNN。
循环神经网络(RNNs)
在上一部分中,我们学习了如何使用 CNN 进行计算机视觉任务,如图像分类。随着深度学习的发展,计算机现在能够实现甚至超越人类的表现。另一个正在吸引研究人员大量关注的领域是自然语言处理,这是一个 RNN 表现突出的领域。
在过去的几年里,我们见证了 RNN 技术的许多不同应用,如语音识别、聊天机器人和文本翻译应用。但 RNN 在预测时间序列模式方面也表现得相当出色,这一点在股票市场预测中得到了广泛应用。
RNN 层
所有先前提到的应用程序的共同点是它们的输入是顺序的。输入中有一个时间组件。例如,句子是单词的序列,单词的顺序很重要;股票市场数据由一系列日期和相应的股价组成。
为了适应这样的输入,我们需要神经网络能够处理输入序列,并能够保持对它们之间关系的理解。一种方法是创建记忆,让网络考虑先前的输入。这正是基本循环神经网络的工作方式:
图 6.24:单个循环神经网络概述
在前面的图中,我们可以看到一个神经网络接收称为 Xt 的输入,并进行一些转换,并给出输出结果,。到目前为止没有新东西。
但您可能已经注意到,还有一个名为 Ht-1 的附加输出,它是一个输出,但也是神经网络的输入。这就是 RNN 模拟记忆的方式 - 通过考虑其先前的结果并将其作为额外输入。因此,结果 将取决于输入 xt,但也取决于 Ht-1。现在,我们可以表示四个输入的序列,这些输入被馈送到同一个神经网络中。
图 6.25:循环神经网络概述
我们可以看到神经网络在每个时间步(t、t+1、…、t+3)中接收一个输入(x)并生成一个输出(y),同时生成另一个输出(h),该输出将传递到下一次迭代。
注意
前面的图可能有点误导性 - 这里实际上只有一个循环神经网络(中间的所有循环神经网络框形成一个神经网络),但从这种格式更容易看出顺序如何工作。
RNN 单元内部如下所示:
图 6.26:使用 tanh 的 RNN 的内部工作方式
它与简单神经元非常相似,但接受更多的输入,并使用 tanh 作为激活函数。
注意
在 RNN 单元中,您可以使用任何激活函数。在 TensorFlow 中,默认值是 tanh。
这是 RNN 的基本逻辑。在 TensorFlow 中,我们可以使用 layers.SimpleRNN 实例化一个 RNN 层:
from tensorflow.keras import layers
layers.SimpleRNN(4, activation='tanh')
在代码片段中,我们创建了一个具有 4 个输出和 tanh 激活函数(这是用于 RNN 的最常用的激活函数)的 RNN 层。
GRU 层
前一类型层的一个缺点是最终输出考虑了所有先前的输出。如果您有 1,000 个输入单元的序列,则最终输出 y 受每一个先前结果的影响。如果此序列由 1,000 个单词组成,而我们试图预测下一个单词,则必须记住所有 1,000 个单词真的太多了。可能只需查看最终输出之前的前 100 个单词。
这正是 门控循环单元(GRU)单元的作用。我们来看看它们的内部结构:
图 6.27:使用 tanh 和 sigmoid 的 RNN 内部工作原理
与简单的 RNN 单元相比,GRU 单元有更多的元素:
-
第二个激活函数,即
sigmoid -
在生成输出之前执行的乘法运算
和 Ht
通常使用的 tanh 路径仍然负责进行预测,但这次我们称之为“候选”。sigmoid 路径充当“更新”门。它将告诉 GRU 单元是否需要丢弃此候选值。记住,输出范围在 0 到 1 之间。如果接近 0,则更新门(即 sigmoid 路径)会表示我们不应考虑这个候选。
另一方面,如果它更接近 1,则我们肯定应该使用这个候选的结果。
记住,输出 Ht 与 Ht-1 相关,Ht-1 又与 Ht-2 相关,依此类推。因此,更新门还将定义我们应保留多少“记忆”。它倾向于优先考虑与当前输出更接近的先前输出。
这就是 GRU 的基本逻辑(请注意,GRU 单元还有一个额外的组件,即重置门,但为了简化,我们将不讨论它)。在 TensorFlow 中,我们可以通过 layers.GRU 实例化这样的层:
from tensorflow.keras import layers
layers.GRU(4, activation='tanh', \
recurrent_activation='sigmoid')
在代码片段中,我们创建了一个具有 4 个输出单元的 GRU 层,候选预测使用 tanh 激活函数,更新门使用 sigmoid 激活函数。
LSTM 层
还有另一种非常流行的 RNN 架构单元,叫做 LSTM 单元。LSTM 代表 长短期记忆。LSTM 比 GRU 先出现,但后者更简单,这就是我们首先介绍 GRU 的原因。LSTM 的内部结构如下:
图 6.28:LSTM 概述
一开始,这看起来非常复杂。它由几个元素组成:
-
细胞状态:这是所有先前输出的拼接。它是 LSTM 单元的“记忆”。 -
遗忘门:它负责定义是否应该保留或忘记给定的记忆。 -
输入门:它负责定义是否需要更新新的记忆候选。然后,将此新记忆候选加入到先前的记忆中。 -
输出门:它负责根据先前的输出(Ht-1)、当前输入(xt)和记忆进行预测。
LSTM 单元可以考虑先前的结果,也可以考虑过去的记忆,这就是它如此强大的原因。
在 TensorFlow 中,我们可以通过 layers.SimpleRNN 实例化这样的层:
from tensorflow.keras import layers
layers.LSTM(4, activation='tanh', \
recurrent_activation='sigmoid')
在代码片段中,我们创建了一个具有 4 个输出单元的 LSTM 层,候选预测使用 tanh 激活函数,更新门使用 sigmoid 激活函数。
注意
你可以在这里阅读更多关于 TensorFlow 中 SimpleRNN 实现的内容:www.tensorflow.org/api_docs/python/tf/keras/layers/SimpleRNN。
活动 6.03:评估基于 RNN 的 Yahoo 股票模型
在本活动中,我们将使用 LSTM 训练一个 RNN 模型,以预测基于过去 30 天数据的 Yahoo! 股票价格。我们将寻找最佳的均方误差值,并检查模型是否发生过拟合。我们将使用在第二章《回归简介》中看到的相同的 Yahoo 股票数据集。
注意
数据集文件也可以在我们的 GitHub 仓库中找到:packt.live/3fRI5Hk。
以下步骤将帮助你完成此活动:
-
导入 Yahoo 股票数据集。
-
提取
close price列。 -
对数据集进行标准化。
-
创建过去
30天的股票价格特征。 -
重塑训练集和测试集。
-
创建神经网络架构,包含以下层:
五个 LSTM 层,使用
LSTM(50, (3,3), activation='relu')后跟Dropout(0.2)一个全连接层,使用
Dense(1) -
指定学习率为
0.001的Adam优化器。 -
训练模型。
-
在测试集上评估模型。
期望的输出结果是:
1000/1000 [==============================] - 0s 279us/sample - loss: 0.0016 - mse: 0.0016
[0.00158528157370165, 0.0015852816]
注意
该活动的解决方案可以在第 387 页找到。
在接下来的部分,我们将讨论深度学习所需的硬件。
深度学习硬件
如你所见,训练深度学习模型比传统机器学习算法需要更长的时间。这是因为前向传播和反向传播过程中需要进行大量计算。在本书中,我们训练了只有少数几层的简单模型。但也有一些架构有数百层,甚至更多。那种网络的训练可能需要几天,甚至几周。
为了加速训练过程,建议使用一种名为 GPU 的特定硬件。GPU 擅长执行数学运算,因此非常适合深度学习。与中央处理单元(CPU)相比,GPU 在训练深度学习模型时速度可以快到 10 倍。你可以亲自购买 GPU 并搭建自己的深度学习计算机,只需要确保所购买的 GPU 支持 CUDA(目前只有 NVIDIA 的 GPU 支持)。
另一种选择是使用像 AWS 或 Google Cloud Platform 这样的云服务提供商,并在云端训练模型。你只需为实际使用的部分付费,且在完成后可以随时关闭它们。好处是,你可以根据项目的需求调整配置的规模,但要注意费用。如果你的实例一直开启,即使没有训练模型,也会产生费用。所以,如果不使用时,记得关闭实例。
最后,谷歌最近发布了一些专门用于深度学习的新硬件:张量处理单元(TPUs)。它们比 GPU 快得多,但成本也相当高。目前,只有谷歌云平台在其云实例中提供这种硬件。
挑战与未来趋势
像任何新技术一样,深度学习也面临着挑战。其中之一是巨大的入门门槛。要成为一名深度学习从业者,你曾经需要非常了解深度学习背后的所有数学理论,并且是一名熟练的程序员。此外,你还需要学习你选择使用的深度学习框架的具体知识(无论是 TensorFlow、PyTorch、Caffe,还是其他任何框架)。一段时间以来,深度学习无法接触到广泛的受众,主要局限于研究人员。但这种情况已经发生了变化,尽管仍不完美。例如,TensorFlow 现在有了一个更高级的 API,叫做 Keras(这就是你在本章看到的),它比核心 API 更容易使用。希望这种趋势会继续下去,让深度学习框架对任何有兴趣的人都更加易于接触。
第二个挑战是,深度学习模型需要大量的计算能力,正如前面一节所提到的。这再次成为任何想要尝试的人面临的主要障碍。尽管 GPU 的成本已经下降,深度学习仍然需要一些前期投资。幸运的是,现在我们有了一个免费的选择来使用 GPU 训练深度学习模型:Google Colab。这是谷歌推出的一项倡议,旨在通过提供免费的临时云计算来促进研究。你需要的唯一条件是一个 Google 账号。注册后,你可以创建笔记本(类似于 Jupyter 笔记本),并选择一个内核,在 CPU、GPU(每天限制 10 小时)甚至 TPU(每天限制½小时)上运行。因此,在投资购买或租赁 GPU 之前,你可以先在 Google Colab 上进行练习。
注意
你可以在colab.research.google.com/上找到更多关于 Google Colab 的信息。
更先进的深度学习模型可能非常深,训练可能需要数周。因此,基础的从业者很难使用这种架构。但幸运的是,许多研究人员已经接受了开源运动,并不仅分享了他们设计的架构,还分享了网络的权重。这意味着你现在可以访问最先进的预训练模型,并对其进行微调,以适应你自己的项目。这被称为迁移学习(此书不涉及)。它在计算机视觉领域非常流行,你可以在 ImageNet 或 MS-Coco 上找到预训练模型,例如,这些是包含大量图片的数据集。迁移学习也在自然语言处理领域发生,但其发展程度不如计算机视觉领域。
注意
你可以在www.image-net.org/和cocodataset.org/找到有关这些数据集的更多信息。
另一个与深度学习相关的重要话题是对模型结果进行解释的需求日益增加。很快,这些算法可能会受到监管,深度学习从业者将必须能够解释模型为何做出某个特定决策。目前,由于网络的复杂性,深度学习模型更像是黑盒。研究人员已经提出了一些倡议,寻找解释和理解深度神经网络的方法,例如Zeiler 和 Fergus的《Visualizing and Understanding Convolutional Networks》,ECCV 2014。然而,随着这些技术在我们日常生活中的民主化,仍然需要在该领域做更多的工作。例如,我们需要确保这些算法不会存在偏见,并且不会做出影响特定群体的、不公平的决策。
总结
我们刚刚完成了整本*《应用人工智能工作坊(第二版)》*。在这个工作坊中,我们学习了 AI 的基本原理及其应用。我们写了一个 Python 程序来玩井字游戏。我们学习了广度优先搜索和深度优先搜索等搜索技术,并了解了它们如何帮助我们解决井字游戏。
在接下来的几章中,我们学习了使用回归和分类的监督学习。这些章节包括数据预处理、训练-测试拆分以及在多个实际场景中使用的模型。线性回归、多项式回归和支持向量机在预测股票数据时都发挥了重要作用。分类是通过 k-最近邻和支持向量分类器进行的。几个活动帮助你将分类的基础应用于一个有趣的实际用例:信用评分。
在第四章,决策树简介中,你学习了决策树、随机森林和极端随机树。本章介绍了评估模型效用的不同方法。我们学习了如何计算模型的准确率、精确度、召回率和 F1 分数。我们还学会了如何创建模型的混淆矩阵。本章的模型通过对汽车数据的评估得以实践。
第五章,人工智能:聚类中介绍了无监督学习,以及 k-均值和层次聚类算法。一个有趣的方面是,这些算法在聚类过程中没有预先给定标签,而是通过聚类过程来检测标签。
本次研讨会以第六章,神经网络与深度学习为结束,其中介绍了如何使用 TensorFlow 进行神经网络和深度学习。我们运用了这些技术,在实际应用中取得了最佳准确度,例如手写数字检测、图像分类和时间序列预测。
附录
1. 人工智能简介
活动 1.01:生成井字棋游戏中的所有可能步骤序列
解决方案:
以下步骤将帮助你完成此活动:
-
打开一个新的 Jupyter Notebook 文件。
-
重用先前步骤步骤 2–9中的函数代码,练习 1.02,为井字棋游戏创建具有随机行为的 AI。
-
创建一个函数,将
all_moves_from_board_list函数映射到棋盘列表的每个元素。这样,我们将在每个深度获得决策树的所有节点:def all_moves_from_board_list(board_list, sign): move_list = [] for board in board_list: move_list.extend(all_moves_from_board(board, sign)) return move_list在前面的代码片段中,我们定义了
all_moves_from_board函数,它将列举所有棋盘上的可能移动,并将这些动作添加到一个名为move_list的列表中。 -
创建一个名为 board 的变量,包含
EMPTY_SIGN * 9的决策树,并使用all_moves_from_board_list函数对该棋盘和AI_SIGN进行调用。将其输出保存到一个名为all_moves的变量中,并打印其内容:board = EMPTY_SIGN * 9 all_moves = all_moves_from_board(board, AI_SIGN ) all_moves预期输出如下:
['X........', '.X.......', '..X......', '...X.....', '....X....', '.....X...', '......X..', '.......X.', '........X'] -
创建一个
filter_wins函数,将结束的游戏从动作列表中提取,并将它们追加到一个包含 AI 玩家和对手玩家获胜棋盘状态的数组中:def filter_wins(move_list, ai_wins, opponent_wins): for board in move_list: won_by = game_won_by(board) if won_by == AI_SIGN: ai_wins.append(board) move_list.remove(board) elif won_by == OPPONENT_SIGN: opponent_wins.append(board) move_list.remove(board)在前面的代码片段中,我们定义了一个
filter_wins函数,它将每个玩家的获胜状态添加到棋盘的列表中。 -
使用
count_possibilities函数,该函数打印并返回决策树叶子节点的数量,分别为平局、第一玩家获胜和第二玩家获胜,如下方代码片段所示:def count_possibilities(): board = EMPTY_SIGN * 9 move_list = [board] ai_wins = [] opponent_wins = [] for i in range(9): print('step ' + str(i) + '. Moves: ' \ + str(len(move_list))) sign = AI_SIGN if \ i % 2 == 0 else OPPONENT_SIGN move_list = all_moves_from_board_list\ (move_list, sign) filter_wins(move_list, ai_wins, \ opponent_wins) print('First player wins: ' + str(len(ai_wins))) print('Second player wins: ' + str(len(opponent_wins))) print('Draw', str(len(move_list))) print('Total', str(len(ai_wins) \ + len(opponent_wins) + len(move_list))) return len(ai_wins), len(opponent_wins), \ len(move_list), len(ai_wins) \ + len(opponent_wins) + len(move_list)我们在每个状态中最多有
9个步骤。在第 0、2、4、6 和 8 次迭代中,AI 玩家进行移动。在所有其他迭代中,对手进行移动。我们在所有步骤中创建所有可能的动作,并从动作列表中提取完成的游戏。 -
执行可能性数量以体验组合爆炸,并将结果保存在四个变量中,分别为
first_player、second_player、draw和total:first_player, second_player, \ draw, total = count_possibilities()预期输出如下:
step 0\. Moves: 1 step 1\. Moves: 9 step 2\. Moves: 72 step 3\. Moves: 504 step 4\. Moves: 3024 step 5\. Moves: 13680 step 6\. Moves: 49402 step 7\. Moves: 111109 step 8\. Moves: 156775 First player wins: 106279 Second player wins: 68644 Draw 91150 Total 266073
如你所见,棋盘状态的树由总共266073个叶子节点组成。count_possibilities函数本质上实现了一个 BFS 算法,用于遍历游戏的所有可能状态。请注意,我们会多次计算这些状态,因为在步骤 1中将X放置在右上角与在步骤 3中将X放置在左上角会导致与从左上角开始再将X放置在右上角类似的状态。如果我们实现了重复状态的检测,我们需要检查的节点会更少。然而,在此阶段,由于游戏深度有限,我们将省略此步骤。
然而,决策树与 count_possibilities 检查的数据结构是相同的。在决策树中,我们通过探索所有可能的未来步骤来评估每个行动的效用。在我们的示例中,我们可以通过观察固定前几步后的胜负情况来计算初始步骤的效用。
注意
树的根节点是初始状态。树的内部状态是游戏尚未结束并且仍有可能进行移动的状态。树的叶子节点包含一个游戏已结束的状态。
要访问这一特定部分的源代码,请参考 packt.live/3doxPog。
你也可以在网上运行此示例,访问 packt.live/3dpnuIz。
你必须执行整个 Notebook 才能得到期望的结果。
活动 1.02:教授代理人认识到它何时防止失败
解决方案:
以下步骤将帮助你完成此活动:
-
打开一个新的 Jupyter Notebook 文件。
-
重用之前 步骤 2–6 中的所有代码,来自 练习 1.03,教授代理人获胜。
-
创建一个名为
player_can_win的函数,该函数使用all_moves_from_board函数获取所有棋盘上的移动,并通过next_move变量进行迭代。在每次迭代中,它检查玩家是否可以获胜。
def player_can_win(board, sign): next_moves = all_moves_from_board(board, sign) for next_move in next_moves: if game_won_by(next_move) == sign: return True return False -
扩展 AI 移动,使其更倾向于进行安全移动。如果一个移动是安全的,即对手无法在下一步获胜,那么该移动就被视为安全的:
def ai_move(board): new_boards = all_moves_from_board(board, AI_SIGN) for new_board in new_boards: if game_won_by(new_board) == AI_SIGN: return new_board safe_moves = [] for new_board in new_boards: if not player_can_win(new_board, OPPONENT_SIGN): safe_moves.append(new_board) return choice(safe_moves) \ if len(safe_moves) > 0 else new_boards[0]在前面的代码片段中,我们定义了
ai_move函数,该函数通过查看所有可能的列表并选择一个玩家无法在下一步获胜的选项来告诉 AI 如何行动。如果你测试我们的新应用,你会发现 AI 已经做出了正确的决定。 -
现在,将此逻辑放入状态空间生成器中,并通过生成所有可能的游戏来检查电脑玩家的表现:
def all_moves_from_board(board, sign): move_list = [] for i, v in enumerate(board): if v == EMPTY_SIGN: new_board = board[:i] + sign + board[i+1:] move_list.append(new_board) if game_won_by(new_board) == AI_SIGN: return [new_board] if sign == AI_SIGN: safe_moves = [] for move in move_list: if not player_can_win(move, OPPONENT_SIGN): safe_moves.append(move) return safe_moves if len(safe_moves) > 0 else move_list[0:1] else: return move_list在前面的代码片段中,我们定义了一个生成所有可能移动的函数。一旦我们找到了能够让玩家获胜的下一步,我们就返回一个反制的移动。我们不关心玩家是否有多个获胜选项——我们只返回第一个可能性。如果 AI 无法阻止玩家获胜,我们就返回所有可能的移动。
让我们看看这在每一步计数所有可能性时意味着什么。
-
计算所有可能的选项:
first_player, second_player, \ draw, total = count_possibilities()预期输出是这样的:
step 0\. Moves: 1 step 1\. Moves: 9 step 2\. Moves: 72 step 3\. Moves: 504 step 4\. Moves: 3024 step 5\. Moves: 5197 step 6\. Moves: 18606 step 7\. Moves: 19592 step 8\. Moves: 30936 First player wins: 20843 Second player wins: 962 Draw 20243 Total 42048
我们现在做得比之前更好了。我们不仅再次去除了几乎 2/3 的可能游戏,而且,大多数时候,AI 玩家要么获胜,要么以平局收场。
注意
要访问这一特定部分的源代码,请参考 packt.live/2B0G9xf。
你也可以在网上运行此示例,访问 packt.live/2V7qLpO。
你必须执行整个 Notebook 才能得到期望的结果。
活动 1.03:修复 AI 的第一步和第二步,使其无敌
解决方案:
以下步骤将帮助你完成此活动:
-
打开一个新的 Jupyter Notebook 文件。
-
重用前面的步骤 2–4中的代码,活动 1.02,教会代理在防止输局时识别局势。
-
现在,计算棋盘上空白格子的数量,并在空白格子有 9 个或 7 个时做出硬编码的移动。你可以尝试不同的硬编码移动。我们发现,占据任意一个角落,然后占据对角的角落,能够保证不输。如果对手占据了对角的角落,那么在中间位置做出移动也不会失败:
def all_moves_from_board(board, sign): if sign == AI_SIGN: empty_field_count = board.count(EMPTY_SIGN) if empty_field_count == 9: return [sign + EMPTY_SIGN * 8] elif empty_field_count == 7: return [board[:8] + sign if board[8] == \ EMPTY_SIGN else board[:4] + sign + board[5:]] move_list = [] for i, v in enumerate(board): if v == EMPTY_SIGN: new_board = board[:i] + sign + board[i+1:] move_list.append(new_board) if game_won_by(new_board) == AI_SIGN: return [new_board] if sign == AI_SIGN: safe_moves = [] for move in move_list: if not player_can_win(move, OPPONENT_SIGN): safe_moves.append(move) return safe_moves if len(safe_moves) > 0 else move_list[0:1] else: return move_list -
现在,验证状态空间:
first_player, second_player, draw, total = count_possibilities()预期的输出是这样的:
step 0\. Moves: 1 step 1\. Moves: 1 step 2\. Moves: 8 step 3\. Moves: 8 step 4\. Moves: 48 step 5\. Moves: 38 step 6\. Moves: 108 step 7\. Moves: 76 step 8\. Moves: 90 First player wins: 128 Second player wins: 0 Draw 60 Total 188
修正前两个步骤后,我们只需要处理 8 种可能,而不是 504 种。我们还引导 AI 进入了一个状态,在这个状态下,硬编码规则足够使它永远不会输掉游戏。修正步骤并不重要,因为我们会给 AI 提供硬编码的步骤,但它很重要,因为它是用来评估和比较每一步的工具。修正前两个步骤后,我们只需要处理 8 种可能,而不是 504 种。我们还引导 AI 进入了一个状态,在这个状态下,硬编码规则足以让它永不输掉游戏。正如你所见,AI 现在几乎是无敌的,它只会获胜或平局。
玩家对抗这个 AI 时,最好的结果是平局。
注意
若要访问此部分的源代码,请参考packt.live/2YnUcpA。
你也可以在packt.live/318TBtq上在线运行此示例。
你必须执行整个 Notebook,以获得预期的结果。
活动 1.04:四子棋
解决方案:
-
打开一个新的 Jupyter Notebook 文件。
我们通过编写
init方法来设置TwoPlayersGame框架。 -
将棋盘定义为一维列表,就像井字游戏的示例一样。我们也可以使用二维列表,但建模的难度不会有太大变化。除了像井字游戏那样进行初始化外,我们还将进一步操作。我们将生成游戏中所有可能的胜利组合,并将其保存以供以后使用,代码如下所示:
from easyAI import TwoPlayersGame, Human_Player class ConnectFour(TwoPlayersGame): def __init__(self, players): self.players = players self.board = [0 for i in range(42)] self.nplayer = 1 def generate_winning_tuples(): tuples = [] # horizontal tuples += [list(range(row*7+column, \ row*7+column+4, 1)) \ for row in range(6) \ for column in range(4)] # vertical tuples += [list(range(row*7+column, \ row*7+column+28, 7)) \ for row in range(3) \ for column in range(7)] # diagonal forward tuples += [list(range(row*7+column, \ row*7+column+32, 8)) \ for row in range(3) \ for column in range(4)] # diagonal backward tuples += [list(range(row*7+column, \ row*7+column+24, 6)) \ for row in range(3) \ for column in range(3, 7, 1)] return tuples self.tuples = generate_winning_tuples() -
接下来,处理
possible_moves函数,这是一个简单的枚举。请注意,我们在移动名称中使用从1到7的列索引,因为在玩家界面中,从1开始列索引比从零开始更为方便。对于每一列,我们检查是否有空闲格子。如果有空位,我们就将该列设为一个可能的移动:def possible_moves(self): return [column+1 \ for column in range(7) \ if any([self.board[column+row*7] == 0 \ for row in range(6)]) ] -
执行一次移动就像
possible_moves函数一样。我们检查该移动的列,并从底部开始找到第一个空单元格。一旦找到,就占据它。你也可以阅读make_move和unmake_move两个函数的实现。在unmake_move函数中,我们从上到下检查列,并在第一个非空单元格处移除移动。请注意,我们依赖于easyAI的内部表示,以确保它不会撤销自己没有做的移动。否则,这个函数会在不检查的情况下删除对方玩家的棋子:def make_move(self, move): column = int(move) - 1 for row in range(5, -1, -1): index = column + row*7 if self.board[index] == 0: self.board[index] = self.nplayer return # optional method (speeds up the AI) def unmake_move(self, move): column = int(move) - 1 for row in range(6): index = column + row*7 if self.board[index] != 0: self.board[index] = 0 return -
由于我们已经有了必须检查的元组,我们可以大部分复用来自井字游戏示例的
lose函数:def lose(self): return any([all([(self.board[c] == self.nopponent) for c in line]) for line in self.tuples]) def is_over(self): return (self.possible_moves() == []) or self.lose() -
我们的最终任务是实现
show方法,该方法打印棋盘。我们将重用井字游戏的实现,只需更改show和scoring变量:def show(self): print('\n'+'\n'.join([ ' '.join([['.', 'O', 'X']\ [self.board[7*row+column]] \ for column in range(7)]) for row in range(6)])) def scoring(self): return -100 if self.lose() else 0 if __name__ == "__main__": from easyAI import AI_Player, Negamax ai_algo = Negamax(6) ConnectFour([Human_Player(), \ AI_Player(ai_algo)]).play() -
现在所有函数都已完成,你可以尝试示例。随时和对手玩一两局。
预期输出是这样的:
图 1.30:连接四子游戏的预期输出
完成此活动后,你会发现对手并不完美,但它的表现相当不错。如果你有一台强大的计算机,你可以增加Negamax算法的参数。我们鼓励你提出更好的启发式方法。
注意
要访问此特定部分的源代码,请参考packt.live/3esk2hI。
你也可以在packt.live/3dnkfS5上在线运行此示例。
你必须执行整个 Notebook,才能获得期望的结果。
2. 回归介绍
活动 2.01:使用 1、2 和 3 度多变量的多项式回归进行波士顿房价预测
解决方案:
-
打开一个 Jupyter Notebook。
-
导入所需的包并从
sklearn加载波士顿房价数据到 DataFrame 中:import numpy as np import pandas as pd from sklearn import preprocessing from sklearn import model_selection from sklearn import linear_model from sklearn.preprocessing import PolynomialFeatures file_url = 'https://raw.githubusercontent.com/'\ 'PacktWorkshops/'\ 'The-Applied-Artificial-Intelligence-Workshop/'\ 'master/Datasets/boston_house_price.csv' df = pd.read_csv(file_url)df的输出如下:图 2.28:显示数据集的输出
在本章前面,你学习了执行线性回归所需的大部分包都来自
sklearn。我们需要导入preprocessing模块来缩放数据,linear_model模块来训练线性回归,PolynomialFeatures模块来转换多项式回归的输入,以及model_selection模块来评估每个模型的性能。 -
通过将标签和特征转换为 NumPy 数组并缩放特征,准备预测数据集:
features = np.array(df.drop('MEDV', 1)) label = np.array(df['MEDV']) scaled_features = preprocessing.scale(features)features的输出如下:array([[-0.41978194, 0.28482986, -1.2879095 , ..., -0.66660821, -1.45900038, -1.0755623 ], [-0.41733926, -0.48772236, -0.59338101, ..., -0.98732948, -0.30309415, -0.49243937], [-0.41734159, -0.48772236, -0.59338101, ..., -0.98732948, -0.30309415, -1.2087274 ], ..., [-0.41344658, -0.48772236, 0.11573841, ..., -0.80321172, 1.17646583, -0.98304761], [-0.40776407, -0.48772236, 0.11573841, ..., -0.80321172, 1.17646583, -0.86530163], [-0.41500016, -0.48772236, 0.11573841, ..., -0.80321172, 1.17646583, -0.66905833]])如你所见,我们的特征已经被正确缩放。
由于我们没有缺失值,并且不像在练习 2.03中那样试图预测未来值(准备 Quandl 数据进行预测),我们可以直接将标签(
'MEDV')和特征转换为 NumPy 数组。然后,我们可以使用preprocessing.scale()函数对特征数组进行缩放。 -
通过将缩放特征转换为适合每个多项式回归的格式,创建三组不同的特征集:
poly_1_scaled_features = PolynomialFeatures(degree=1)\ .fit_transform(scaled_features) poly_2_scaled_features = PolynomialFeatures(degree=2)\ .fit_transform(scaled_features) poly_3_scaled_features = PolynomialFeatures(degree=3)\ .fit_transform(scaled_features)poly_1_scaled_features的输出如下:array([[ 1\. , -0.41978194, 0.28482986, ..., -0.66660821, -1.45900038, -1.0755623 ], [ 1\. , -0.41733926, -0.48772236, ..., -0.98732948, -0.30309415, -0.49243937], [ 1\. , -0.41734159, -0.48772236, ..., -0.98732948, -0.30309415, -1.2087274 ], ..., [ 1\. , -0.41344658, -0.48772236, ..., -0.80321172, 1.17646583, -0.98304761], [ 1\. , -0.40776407, -0.48772236, ..., -0.80321172, 1.17646583, -0.86530163], [ 1\. , -0.41500016, -0.48772236, ..., -0.80321172, 1.17646583, -0.66905833]])我们的
scaled_features变量已正确转换,用于度数为1的多项式回归。poly_2_scaled_features的输出如下:![图 2.31:显示 poly_2_scaled_features 输出的图像]
array([[ 1\. , -0.41978194, 0.28482986, ..., -2.28953024, -1.68782164, -1.24424733], [ 1\. , -0.41733926, -0.48772236, ..., -0.04523847, -0.07349928, -0.11941484], [ 1\. , -0.41734159, -0.48772236, ..., -0.11104103, -0.4428272 , -1.76597723], ..., [ 1\. , -0.41344658, -0.48772236, ..., -1.36060852, 1.13691611, -0.9500001 ], [ 1\. , -0.40776407, -0.48772236, ..., -1.19763962, 0.88087515, -0.64789192], [ 1\. , -0.41500016, -0.48772236, ..., -0.9260248 , 0.52663205, -0.29949664]])我们的
scaled_features变量已正确转换,用于度数为3的多项式回归。我们必须以三种不同的方式转换缩放特征,因为每个多项式回归度数需要不同的输入转换。
-
将数据分为训练集和测试集,
random state = 8:(poly_1_features_train, poly_1_features_test, \ poly_label_train, poly_label_test) = \ model_selection.train_test_split(poly_1_scaled_features, \ label, \ test_size=0.1, \ random_state=8) (poly_2_features_train, poly_2_features_test, \ poly_label_train, poly_label_test) = \ model_selection.train_test_split(poly_2_scaled_features, \ label, \ test_size=0.1, \ random_state=8) (poly_3_features_train, poly_3_features_test, \ poly_label_train, poly_label_test) = \ model_selection.train_test_split(poly_3_scaled_features, \ label, \ test_size=0.1, \ random_state=8)由于我们有三组不同的缩放转换特征,但相同的标签集,我们必须执行三次不同的拆分。通过在每次拆分中使用相同的标签集和
random_state,我们确保每次拆分都获得相同的poly_label_train和poly_label_test。 -
执行度数为 1 的多项式回归,并评估模型是否存在过拟合:
model_1 = linear_model.LinearRegression() model_1.fit(poly_1_features_train, poly_label_train) model_1_score_train = model_1.score(poly_1_features_train, \ poly_label_train) model_1_score_test = model_1.score(poly_1_features_test, \ poly_label_test)model_1_score_train的输出如下:0.7406006443486721model_1_score_test的输出如下:0.6772229017901507为了估计模型是否过拟合,我们需要比较应用于训练集和测试集的模型得分。如果训练集的得分远高于测试集,则表示过拟合。在此案例中,度数为 1 的多项式回归在训练集上的得分为
0.74,而测试集上的得分为0.68。 -
执行度数为 2 的多项式回归,并评估模型是否存在过拟合:
model_2 = linear_model.LinearRegression() model_2.fit(poly_2_features_train, poly_label_train) model_2_score_train = model_2.score(poly_2_features_train, \ poly_label_train) model_2_score_test = model_2.score(poly_2_features_test, \ poly_label_test)model_2_score_train的输出如下:0.9251199698832675model_2_score_test的输出如下:0.8253870684280571就像一次多项式回归度数为 1 的情况,我们的多项式回归度数为 2 时过拟合的情况更加严重,但最终却取得了更好的结果。
-
执行度数为 3 的多项式回归,并评估模型是否存在过拟合:
model_3 = linear_model.LinearRegression() model_3.fit(poly_3_features_train, poly_label_train) model_3_score_train = model_3.score(poly_3_features_train, \ poly_label_train) model_3_score_test = model_3.score(poly_3_features_test, \ poly_label_test)model_3_score_train的输出如下:0.9910498071894897model_3_score_test的输出如下:-8430.781888645262这些结果非常有趣,因为三次多项式回归成功地达到了接近完美的分数
0.99(1 是最高分)。这给出了一个警告信号,表明我们的模型过度拟合了训练数据。当模型应用于测试集时,我们得到了一个非常低的负分数-8430,这进一步确认了过拟合问题。提醒一下,0 分是通过将数据的均值作为预测值得到的。这意味着我们的第三个模型的预测结果比单纯使用均值还要差。 -
比较 3 个模型在测试集上的预测结果与标签:
model_1_prediction = model_1.predict(poly_1_features_test) model_2_prediction = model_2.predict(poly_2_features_test) model_3_prediction = model_3.predict(poly_3_features_test) df_prediction = pd.DataFrame(poly_label_test) df_prediction.rename(columns = {0:'label'}, inplace = True) df_prediction['model_1_prediction'] = \ pd.DataFrame(model_1_prediction) df_prediction['model_2_prediction'] = \ pd.DataFrame(model_2_prediction) df_prediction['model_3_prediction'] = \ pd.DataFrame(model_3_prediction)df_prediction的输出如下:
图 2.32:显示期望预测值的输出
在对每个模型应用predict函数,得到它们各自测试集的预测值之后,我们将它们与标签值一起转换成一个单独的df_prediction数据框。增加多项式回归的次数并不意味着模型的表现一定会比低次模型更好。实际上,增加次数会导致模型在训练数据上的过拟合。
注意
要访问此特定部分的源代码,请参考packt.live/3eD8gAY。
你也可以在packt.live/3etadjp上在线运行此示例。
你必须执行整个 Notebook 才能得到期望的结果。
在这个活动中,我们学习了如何对波士顿房价数据集进行 1 到 3 度的多项式回归,并看到增加度数会导致模型过拟合。
3. 分类简介
活动 3.01:提高信用评分的准确度
解决方案:
-
打开一个新的 Jupyter Notebook 文件,执行上一个练习Exercise 3.04中的所有步骤,Scikit-Learn 中的 K-最近邻分类。
-
从
sklearn导入neighbors:from sklearn import neighbors -
创建一个名为
fit_knn的函数,该函数接受以下参数:k、p、features_train、label_train、features_test和label_test。这个函数将使用训练集拟合KNeighborsClassifier,并打印训练集和测试集的准确度评分,如下代码片段所示:def fit_knn(k, p, features_train, label_train, \ features_test, label_test): classifier = neighbors.KNeighborsClassifier(n_neighbors=k, p=p) classifier.fit(features_train, label_train) return classifier.score(features_train, label_train), \ classifier.score(features_test, label_test) -
调用
fit_knn()函数,设置k=5和p=2,将结果保存到2个变量中并打印。这些变量是acc_train_1和acc_test_1:acc_train_1, acc_test_1 = fit_knn(5, 2, features_train, \ label_train, \ features_test, label_test) acc_train_1, acc_test_1期望的输出是这样的:
(0.78625, 0.75)使用
k=5和p=2时,KNN 取得了接近0.78的不错的准确率。但训练集和测试集的分数差异较大,这意味着模型存在过拟合问题。 -
调用
fit_knn()函数,设置k=10和p=2,将结果保存到2个变量中并打印。这些变量是acc_train_2和acc_test_2:acc_train_2, acc_test_2 = fit_knn(10, 2, features_train, \ label_train, \ features_test, label_test) acc_train_2, acc_test_2期望的输出是这样的:
(0.775, 0.785)将邻居数量增加到 10 降低了训练集的准确性,但现在它与测试集的准确性非常接近。
-
使用
k=15和p=2调用fit_knn()函数,将结果保存在2个变量中,并打印它们。这些变量是acc_train_3和acc_test_3:acc_train_3, acc_test_3 = fit_knn(15, 2, features_train, \ label_train, \ features_test, label_test) acc_train_3, acc_test_3预期的输出是这样的:
(0.76625, 0.79)使用
k=15和p=2时,训练集和测试集之间的差异增加了。 -
使用
k=25和p=2调用fit_knn()函数,将结果保存在2个变量中,并打印它们。这些变量是acc_train_4和acc_test_4:acc_train_4, acc_test_4 = fit_knn(25, 2, features_train, \ label_train, \ features_test, label_test) acc_train_4, acc_test_4预期的输出是这样的:
(0.7375, 0.77)将邻居数量增加到
25对训练集产生了显著影响,但模型仍然存在过拟合。 -
使用
k=50和p=2调用fit_knn()函数,将结果保存在2个变量中,并打印它们。这些变量是acc_train_5和acc_test_5:acc_train_5, acc_test_5 = fit_knn(50, 2, features_train, \ label_train, \ features_test, label_test) acc_train_5, acc_test_5预期的输出是这样的:
(0.70625, 0.775)将邻居数量增加到
50既没有改善模型性能,也没有解决过拟合问题。 -
使用
k=5和p=1调用fit_knn()函数,将结果保存在2个变量中,并打印它们。这些变量是acc_train_6和acc_test_6:acc_train_6, acc_test_6 = fit_knn(5, 1, features_train, \ label_train, \ features_test, label_test) acc_train_6, acc_test_6预期的输出是这样的:
(0.8, 0.735)更改为曼哈顿距离有助于提高训练集的准确性,但模型仍然存在过拟合。
-
使用
k=10和p=1调用fit_knn()函数,将结果保存在2个变量中,并打印它们。这些变量是acc_train_7和acc_test_7:acc_train_7, acc_test_7 = fit_knn(10, 1, features_train, \ label_train, \ features_test, label_test) acc_train_7, acc_test_7预期的输出是这样的:
(0.77, 0.785)使用
k=10时,训练集和测试集的准确性相差无几:大约为0.78。 -
使用
k=15和p=1调用fit_knn()函数,将结果保存在2个变量中,并打印它们。这些变量是acc_train_8和acc_test_8:acc_train_8, acc_test_8 = fit_knn(15, 1, features_train, \ label_train, \ features_test, label_test) acc_train_8, acc_test_8预期的输出是这样的:
(0.7575, 0.775)将
k增加到15时,模型的准确性有所提高,且过拟合情况不再那么严重。 -
使用
k=25和p=1调用fit_knn()函数,将结果保存在2个变量中,并打印它们。这些变量是acc_train_9和acc_test_9:acc_train_9, acc_test_9 = fit_knn(25, 1, features_train, \ label_train, \ features_test, label_test) acc_train_9, acc_test_9预期的输出是这样的:
(0.745, 0.8)使用
k=25时,训练集和测试集准确性之间的差异在增加,因此模型存在过拟合。 -
使用
k=50和p=1调用fit_knn()函数,将结果保存在2个变量中,并打印它们。这些变量是acc_train_10和acc_test_10:acc_train_10, acc_test_10 = fit_knn(50, 1, features_train, \ label_train, \ features_test, label_test) acc_train_10, acc_test_10预期的输出是这样的:
(0.70875, 0.78)使用
k=50时,模型在训练集上的表现显著下降,且模型显然出现了过拟合。
在此活动中,我们尝试了多个n_neighbors和p的超参数组合。我们发现的最佳组合是n_neighbors=10和p=2。使用这些超参数,模型几乎没有过拟合,并且在训练集和测试集上都达到了大约78%的准确性。
注意
若要访问此特定部分的源代码,请参考packt.live/2V5TOtG。
你也可以在网上运行这个例子,链接:packt.live/2Bx0yd8。
你必须执行整个 Notebook 才能获得期望的结果。
活动 3.02:在 scikit-learn 中进行支持向量机优化
解决方案:
-
打开一个新的 Jupyter Notebook 文件并执行之前提到的所有步骤,练习 3.04,scikit-learn 中的 K 最近邻分类。
-
从
sklearn导入svm:from sklearn import svm -
创建一个名为
fit_knn的函数,接受以下参数:features_train、label_train、features_test、label_test、kernel="linear"、C=1、degree=3和gamma='scale'。该函数将使用训练集拟合 SVC,并打印训练集和测试集的准确度得分:def fit_svm(features_train, label_train, \ features_test, label_test, \ kernel="linear", C=1, \ degree=3, gamma='scale'): classifier = svm.SVC(kernel=kernel, C=C, \ degree=degree, gamma=gamma) classifier.fit(features_train, label_train) return classifier.score(features_train, label_train), \ classifier.score(features_test, label_test) -
调用
fit_knn()函数,使用默认的超参数值,将结果保存在2个变量中并打印。这些变量是acc_train_1和acc_test_1:acc_train_1, \ acc_test_1 = fit_svm(features_train, \ label_train, \ features_test, \ label_test) acc_train_1, acc_test_1期望的输出是这样的:
(0.71625, 0.75)使用默认的超参数值(线性模型),模型在训练集和测试集上的表现差异较大。
-
调用
fit_knn()函数,设置kernel="poly"、C=1、degree=4和gamma=0.05,将结果保存在2个变量中并打印。这些变量是acc_train_2和acc_test_2:acc_train_2, \ acc_test_2 = fit_svm(features_train, label_train, \ features_test, label_test, \ kernel="poly", C=1, \ degree=4, gamma=0.05) acc_train_2, acc_test_2期望的输出是这样的:
(0.68875, 0.745)使用四次多项式时,模型在训练集上的表现不佳。
-
调用
fit_knn()函数,设置kernel="poly"、C=2、degree=4和gamma=0.05,将结果保存在2个变量中并打印。这些变量是acc_train_3和acc_test_3:acc_train_3, \ acc_test_3 = fit_svm(features_train, \ label_train, features_test, \ label_test, kernel="poly", \ C=2, degree=4, gamma=0.05) acc_train_3, acc_test_3期望的输出是这样的:
(0.68875, 0.745)增加正则化参数
C对模型的表现没有任何影响。 -
调用
fit_knn()函数,设置kernel="poly"、C=1、degree=4和gamma=0.25,将结果保存在2个变量中并打印。这些变量是acc_train_4和acc_test_4:acc_train_4, \ acc_test_4 = fit_svm(features_train, \ label_train, features_test, \ label_test, kernel="poly", \ C=1, degree=4, gamma=0.25) acc_train_4, acc_test_4期望的输出是这样的:
(0.84625, 0.775)将 gamma 值增加到
0.25显著提高了模型在训练集上的表现。然而,测试集的准确度较低,因此模型存在过拟合现象。 -
调用
fit_knn()函数,设置kernel="poly"、C=1、degree=4和gamma=0.5,将结果保存在2个变量中并打印。这些变量是acc_train_5和acc_test_5:acc_train_5, \ acc_test_5 = fit_svm(features_train, \ label_train, features_test, \ label_test, kernel="poly", \ C=1, degree=4, gamma=0.5) acc_train_5, acc_test_5期望的输出是这样的:
(0.9575, 0.73)将 gamma 值增加到
0.5极大地改善了模型在训练集上的表现,但它明显出现了过拟合,因为测试集上的准确度得分明显降低。 -
调用
fit_knn()函数,设置kernel="poly"、C=1、degree=4和gamma=0.16,将结果保存在2个变量中并打印。这些变量是acc_train_6和acc_test_6:acc_train_6, \ acc_test_6 = fit_svm(features_train, label_train, \ features_test, label_test, \ kernel="poly", C=1, \ degree=4, gamma=0.16) acc_train_6, acc_test_6期望的输出是这样的:
(0.76375, 0.785)使用
gamma=0.16时,模型的准确度得分比最佳的 KNN 模型更高。训练集和测试集的得分都接近0.77。 -
调用
fit_knn()函数,使用kernel="sigmoid",将结果保存在2个变量中并打印出来。这些变量是acc_train_7和acc_test_7:acc_train_7, \ acc_test_7 = fit_svm(features_train, label_train, \ features_test, label_test, \ kernel="sigmoid") acc_train_7, acc_test_7预期输出如下:
(0.635, 0.66)Sigmoid 核的准确率得分较低。
-
调用
fit_knn()函数,使用kernel="rbf"和gamma=0.15,将结果保存在2个变量中并打印出来。这些变量是acc_train_8和acc_test_8:acc_train_8, \ acc_test_8 = fit_svm(features_train, \ label_train, features_test, \ label_test, kernel="rbf", \ gamma=0.15) acc_train_8, acc_test_8预期输出如下:
(0.7175, 0.765)rbf核在gamma=0.15时获得了较好的分数。尽管如此,模型仍然有些过拟合。 -
调用
fit_knn()函数,使用kernel="rbf"和gamma=0.25,将结果保存在2个变量中并打印出来。这些变量是acc_train_9和acc_test_9:acc_train_9, \ acc_test_9 = fit_svm(features_train, \ label_train, features_test, \ label_test, kernel="rbf", \ gamma=0.25) acc_train_9, acc_test_9预期输出如下:
(0.74, 0.765)模型性能在
gamma=0.25时有所提升,但仍然存在过拟合。 -
调用
fit_knn()函数,使用kernel="rbf"和gamma=0.35,将结果保存在2个变量中并打印出来。这些变量是acc_train_10和acc_test_10:acc_train_10, \ acc_test_10 = fit_svm(features_train, label_train, \ features_test, label_test, \ kernel="rbf", gamma=0.35) acc_train_10, acc_test_10预期输出如下:
(0.78125, 0.775)
使用rbf核和gamma=0.35时,我们在训练集和测试集上获得了非常相似的结果,模型的性能高于我们在前一个活动中训练的最佳 KNN 模型。这是我们在德国信用数据集上的最佳模型。
注意
若要访问此特定部分的源代码,请参考packt.live/3fPZlMQ。
你也可以在packt.live/3hVlEm3在线运行此示例。
必须执行整个 Notebook 才能得到期望的结果。
在本次活动中,我们尝试了 SVM 分类器的不同主超参数值:kernel、gamma、C和degrees。我们观察了这些超参数如何影响模型的表现以及它们过拟合的趋势。经过反复试验,我们最终找到了最佳的超参数组合,并获得了接近 0.78 的准确率。这个过程被称为超参数调优,是任何数据科学项目的重要步骤。
4. 决策树简介
活动 4.01:汽车数据分类
解决方案:
-
打开一个新的 Jupyter Notebook 文件。
-
导入
pandas包并命名为pd:import pandas as pd -
创建一个名为
file_url的新变量,该变量将包含原始数据集的 URL:file_url = 'https://raw.githubusercontent.com/'\ 'PacktWorkshops/'\ 'The-Applied-Artificial-Intelligence-Workshop/'\ 'master/Datasets/car.csv' -
使用
pd.read_csv()方法加载数据:df = pd.read_csv(file_url) -
打印
df的前五行:df.head()输出结果如下:
图 4.13:数据集的前五行
-
导入
preprocessing模块,来自sklearn:from sklearn import preprocessing -
创建一个名为
encode()的函数,该函数接受一个 DataFrame 和列名作为参数。该函数将实例化LabelEncoder(),用列的唯一值进行拟合,并转化其数据。它将返回转换后的列:def encode(data_frame, column): label_encoder = preprocessing.LabelEncoder() label_encoder.fit(data_frame[column].unique()) return label_encoder.transform(data_frame[column]) -
创建一个
for循环,该循环将遍历df的每一列,并使用encode()函数对它们进行编码:for column in df.columns: df[column] = encode(df, column) -
现在,打印
df的前五行:df.head()输出结果如下:
](tos-cn-i-73owjymdk6/c164f194e37846d2afde58e3c95d0877)
图 4.14:数据集的前五行已更新
-
使用
.pop()从 pandas 中提取类别列,并将其保存到名为label的变量中:label = df.pop('class') -
从
sklearn导入model_selection:from sklearn import model_selection -
使用
test_size=0.1和random_state=88将数据集划分为训练集和测试集:features_train, features_test, label_train, label_test = \ model_selection.train_test_split(df, label, \ test_size=0.1, \ random_state=88) -
从
sklearn导入DecisionTreeClassifier:from sklearn.tree import DecisionTreeClassifier -
实例化
DecisionTreeClassifier()并将其保存到名为decision_tree的变量中:decision_tree = DecisionTreeClassifier() -
使用训练集拟合决策树:
decision_tree.fit(features_train, label_train)输出结果如下:
](tos-cn-i-73owjymdk6/96680cd2c8394d8285114aa46dbfbcac)
图 4.15:决策树在训练集上的拟合
-
打印决策树在测试集上的得分:
decision_tree.score( features_test, label_test )输出结果如下:
0.953757225433526决策树在我们第一次尝试时的准确率为
0.95,这非常值得注意。 -
从
sklearn.metrics导入classification_report:from sklearn.metrics import classification_report -
打印测试标签和预测的分类报告:
print(classification_report(label_test, \ decision_tree.predict(features_test)))输出结果如下:
](tos-cn-i-73owjymdk6/1fb779c14ffa4d46a819ce53ec2d6760)
图 4.16:展示预期的分类报告的输出
从这个分类报告中,我们可以看到模型在四个类别的精确度得分上表现得相当好。至于召回率,我们可以看到它在最后一个类别上的表现较差。
注意
若要访问此部分的源代码,请参阅packt.live/3hQDLtr。
你也可以在线运行此示例,packt.live/2NkEEML。
你必须执行整个 Notebook 才能获得预期的结果。
完成此活动后,你已准备好汽车数据集并训练了决策树模型。你已学会如何获取其准确率和分类报告,从而分析其精确度和召回率得分。
活动 4.02:为你的租车公司进行随机森林分类
解决方案:
-
打开一个 Jupyter Notebook。
-
重用活动 1中步骤 1 - 4的代码,汽车数据分类。
-
从
sklearn.ensemble导入RandomForestClassifier:from sklearn.ensemble import RandomForestClassifier -
实例化一个随机森林分类器,设置
n_estimators=100,max_depth=6,并设置random_state=168。将其保存到一个名为random_forest_classifier的变量中:random_forest_classifier = \ RandomForestClassifier(n_estimators=100, \ max_depth=6, random_state=168) -
用训练集拟合随机森林分类器:
random_forest_classifier.fit(features_train, label_train)输出结果如下:
](tos-cn-i-73owjymdk6/3457fd24c5a9487c82bd6bfde7ec3061)
图 4.17:展示随机森林分类器及其超参数值的日志
这些是
RandomForest分类器及其超参数值的日志。 -
使用随机森林分类器对测试集进行预测,并将其保存到名为
rf_preds_test的变量中。打印其内容:rf_preds_test = random_forest_classifier.fit(features_train, \ label_train) rf_preds_test输出结果如下:
图 4.18:显示测试集预测的输出
-
从
sklearn.metrics导入classification_report:from sklearn.metrics import classification_report -
打印带有标签和测试集预测的分类报告:
print(classification_report(label_test, rf_preds_test))输出如下:
图 4.19:显示带有标签和测试集预测的分类报告的输出
前述报告中的 F1 得分告诉我们,随机森林在类
2上的表现良好,但在类0和3上表现不佳。模型无法准确预测类1,但测试集中只有 9 个观测值。准确率为0.84,而 F1 得分为0.82。 -
从
sklearn.metrics导入confusion_matrix:from sklearn.metrics import confusion_matrix -
显示测试集的真实标签和预测标签的混淆矩阵:
confusion_matrix(label_test, rf_preds_test)输出如下:
array([[ 32, 0, 10, 0], [ 8, 0, 0, 1], [ 5, 0, 109, 0], [ 3, 0, 0, 5]])从这个混淆矩阵中,我们可以看出,
RandomForest模型在准确预测第一类时遇到困难。它错误地预测了 16 个案例(8 + 5 + 3)为这一类。 -
使用
.feature_importance_打印测试集的特征重要性得分,并将结果保存在名为rf_varimp的变量中。打印其内容:rf_varimp = random_forest_classifier.feature_importances_ rf_varimp输出如下:
array([0.12676384, 0.10366314, 0.02119621, 0.35266673, 0.05915769, 0.33655239])前述输出显示最重要的特征是第四个和第六个,分别对应
persons和safety。 -
从
sklearn.ensemble导入ExtraTreesClassifier:from sklearn.ensemble import ExtraTreesClassifier -
实例化
ExtraTreesClassifier,设置n_estimators=100、max_depth=6、random_state=168,并将其保存在名为random_forest_classifier的变量中:extra_trees_classifier = \ ExtraTreesClassifier(n_estimators=100, \ max_depth=6, random_state=168) -
使用训练集拟合
extratrees分类器:extra_trees_classifier.fit(features_train, label_train)输出如下:
图 4.20:使用训练集的 extratrees 分类器的输出
这些是
extratrees分类器及其超参数值的日志。 -
使用
extratrees分类器对测试集进行预测,并将结果保存在名为et_preds_test的变量中。打印其内容:et_preds_test = extra_trees_classifier.predict(features_test) et_preds_test输出如下:
图 4.21:使用 extratrees 对测试集进行预测
-
打印带有标签和测试集预测的分类报告:
print(classification_report(label_test, \ extra_trees_classifier.predict(features_test)))输出如下:
图 4.22:带有标签和测试集预测的分类报告
前面报告中的 F1 得分显示,随机森林在类别
2上的表现良好,但在类别0上的表现较差。模型无法准确预测类别1和3,但测试集中只有分别为9和8的观测值。准确率为0.82,F1 得分为0.78。因此,我们的RandomForest分类器在extratrees上表现得更好。 -
显示测试集的真实标签与预测标签的混淆矩阵:
confusion_matrix(label_test, et_preds_test)输出将如下所示:
array([[ 28, 0, 14, 0], [ 9, 0, 0, 0], [ 2, 0, 112, 0], [ 7, 0, 0, 1]])从这个混淆矩阵中,我们可以看出,
extratrees模型在准确预测第一和第三类别时遇到了困难。 -
使用
.feature_importance_打印测试集上的特征重要性分数,并将结果保存在一个名为et_varimp的变量中。打印其内容:et_varimp = extra_trees_classifier.feature_importances_ et_varimp输出将如下所示:
array([0.08844544, 0.0702334 , 0.01440408, 0.37662014, 0.05965896, 0.39063797])
前面的输出向我们展示了最重要的特征是第六个和第四个特征,分别对应safety和persons。有趣的是,RandomForest也有相同的两个最重要特征,但顺序不同。
注意
要访问此特定部分的源代码,请参考packt.live/2YoUY5t。
你也可以在packt.live/3eswBcW上在线运行这个示例。
必须执行整个 Notebook 才能获得所需的结果。
5. 人工智能:聚类
活动 5.01:使用 K-Means 聚类销售数据
解决方案:
-
打开一个新的 Jupyter Notebook 文件。
-
将数据集加载为 DataFrame 并检查数据:
import pandas as pd file_url = 'https://raw.githubusercontent.com/'\ 'PacktWorkshops/'\ 'The-Applied-Artificial-Intelligence-Workshop/'\ 'master/Datasets/'\ 'Sales_Transactions_Dataset_Weekly.csv' df = pd.read_csv(file_url) dfdf的输出如下所示:](tos-cn-i-73owjymdk6/0345b1456f544a42a5c6b336135850ec)
图 5.18:显示数据集内容的输出
如果你查看输出,你会注意到我们的数据集包含
811行,每一行代表一个产品。它还包含107列,第一列是产品代码,然后是52列以W开头,表示每周的销售数量,最后是52列的归一化版本,以Normalized开头。归一化列比绝对销售列W更适合用于 k-means 算法,它将帮助我们的算法更快地找到每个聚类的中心。由于我们将处理归一化列,因此可以删除每个W列和Product_Code列。我们还可以删除MIN和MAX列,因为它们对我们的聚类没有任何帮助。还要注意,周数从0到51而不是从1到52。 -
接下来,创建一个没有不必要列的新 DataFrame,如以下代码片段所示(数据集的前
55列)。你应该使用inplace参数来帮助你:df2 = df.drop(df.iloc[:, 0:55], inplace = False, axis = 1)df2的输出如下所示:](tos-cn-i-73owjymdk6/dcb20257a3ff4aac854f04072be4fcb5)
drop function of the pandas DataFrame in order to remove the first 55 columns. We also set the inplace parameter to False in order to not remove the column of our original df DataFrame. As a result, we should only have the normalized columns from 0 to 51 in df2 and df should still be unchanged. -
创建一个具有
8个聚类和random state = 8的 k-means 聚类模型:from sklearn.cluster import KMeans k_means_model = KMeans(n_clusters=8, random_state=8) k_means_model.fit(df2)我们构建了一个 k-means 模型,除了
n_clusters=8和random_state=8之外,其他参数都使用默认值,以获得8个聚类并实现可重复的结果。 -
从聚类算法中提取标签:
labels = k_means_model.labels_ labelslabels的输出将如下所示:图 5.20:标签输出数组
从这个输出中很难看出任何意义,但
labels的每个索引代表基于相似的周销售趋势,产品被分配到的聚类。现在,我们可以使用这些聚类标签将产品聚集在一起。 -
现在,从第一个 DataFrame
df中,仅保留W列,并将标签作为新列添加,如以下代码片段所示:df.drop(df.iloc[:, 53:], inplace = True, axis = 1) df.drop('Product_Code', inplace = True, axis = 1) df['label'] = labels df在前面的代码片段中,我们删除了所有不需要的列,并将
labels作为新列添加到了 DataFrame 中。df的输出将如下所示:图 5.21:包含新标签作为新列的更新 DataFrame
现在我们已经有了标签,可以对
label列进行聚合,计算每个聚类的年销售平均值。 -
执行聚合(使用 pandas 中的
groupby函数),以便获得每个聚类的年销售平均值,如以下代码片段所示:df_agg = df.groupby('label').sum() df_final = df[['label','W0']].groupby('label').count() df_final=df_final.rename(columns = {'W0':'count_product'}) df_final['total_sales'] = df_agg.sum(axis = 1) df_final['yearly_average_sales']= \ df_final['total_sales'] / df_final['count_product'] df_final.sort_values(by='yearly_average_sales', \ ascending=False, inplace = True) df_final在前面的代码片段中,我们首先使用
groupby函数和 DataFrame 的sum()方法,计算每个W列和聚类的每个产品的销售总和,并将结果存储在df_agg中。然后,我们在df的单个列(任意选择)上使用groupby函数和count()方法,计算每个聚类的产品总数(注意我们还必须在聚合后重命名W0列)。接下来的步骤是对df_agg中的所有销售列求和,以获得每个聚类的总销售额。最后,我们通过将total_sales除以count_product计算每个聚类的yearly_average_sales。我们还加入了最后一步,通过yearly_average_sales对聚类进行排序。df_final的输出将如下所示:
图 5.22:销售交易数据集的预期输出
现在,通过这个输出,我们可以看到我们的 k-means 模型成功地将表现相似的产品聚集在一起。我们可以清楚地看到,3号聚类中的115个产品是最畅销的产品,而1号聚类中的123个产品表现非常差。这对于任何企业都是非常有价值的,因为它帮助企业自动识别并将表现相似的产品聚集在一起,而不受产品名称或描述的偏见影响。
注:
若要访问此特定部分的源代码,请参阅packt.live/3fVpSbT。
你还可以在网上运行此示例:packt.live/3hW24Gk。
你必须执行整个 Notebook 才能获得期望的结果。
完成这个活动后,你已经学会了如何对多个列进行 k-means 聚类,适用于多个产品。你还学会了聚类在没有标签数据的情况下对企业有多么有用。
活动 5.02: 使用均值迁移算法和凝聚层次聚类对红酒数据进行聚类
解决方案:
-
打开一个新的 Jupyter Notebook 文件。
-
使用
sep = ";"加载数据集为 DataFrame,并检查数据:import pandas as pd import numpy as np from sklearn import preprocessing from sklearn.cluster import MeanShift from sklearn.cluster import AgglomerativeClustering from scipy.cluster.hierarchy import dendrogram import scipy.cluster.hierarchy as sch from sklearn import metrics file_url = 'https://raw.githubusercontent.com/'\ 'PacktWorkshops/'\ 'The-Applied-Artificial-Intelligence-Workshop/'\ 'master/Datasets/winequality-red.csv' df = pd.read_csv(file_url,sep=';') dfdf的输出如下:图 5.23: df 显示数据集作为输出
注意
前面截图中的输出被截断了。
我们的数据集包含
1599行,每一行代表一瓶红酒。它还包含12列,最后一列是葡萄酒的质量。我们可以看到其余的 11 列将是我们的特征,我们需要对它们进行缩放,以帮助提高我们模型的准确性和速度。 -
从初始的 DataFrame
df创建features、label和scaled_features变量:features = df.drop('quality', 1) label = df['quality'] scaled_features = preprocessing.scale(features)在前面的代码片段中,我们将标签(
quality)与特征分开。然后,我们使用来自sklearn的preprocessing.scale函数对特征进行缩放,因为这将提高我们的模型性能。 -
接下来,创建一个均值迁移聚类模型,然后获取该模型预测的标签和创建的聚类数量:
mean_shift_model = MeanShift() mean_shift_model.fit(scaled_features) n_cluster_mean_shift = len(mean_shift_model.cluster_centers_) label_mean_shift = mean_shift_model.labels_ n_cluster_mean_shiftn_cluster_mean_shift的输出如下:10我们的均值迁移模型已经创建了
10个聚类,这已经超过了我们在quality标签中所拥有的组数。这可能会影响我们的外部评分,并可能是一个早期的指标,表明具有相似物理化学性质的葡萄酒不应该属于同一质量组。label_mean_shift的输出如下:图 5.24: label_mean_shift 的输出数组
这是一个非常有趣的输出,因为它清楚地显示出我们数据集中大多数葡萄酒非常相似;聚类
0中的葡萄酒数量远多于其他聚类。 -
现在,在创建树状图并为其选择最佳聚类数量后,创建一个凝聚层次聚类模型:
dendrogram = sch.dendrogram(sch.linkage(scaled_features, \ method='ward')) agglomerative_model = \ AgglomerativeClustering(n_clusters=7, \ affinity='euclidean', \ linkage='ward') agglomerative_model.fit(scaled_features) label_agglomerative = agglomerative_model.labels_dendrogram的输出如下:图 5.25: 显示聚类树状图的输出
从这个输出中,我们可以看到七个聚类似乎是我们模型的最佳数量。我们通过在y轴上寻找最低分支和最高分支之间的最大差异来得到这个数字。在我们的案例中,对于七个聚类,最低分支的值为
29,而最高分支的值为41。label_agglomerative的输出如下:图 5.26: 显示 label_agglomerative 的数组
我们可以看到我们有一个主要的聚类,
1,但不像均值漂移模型中那样显著。 -
现在,计算以下两种模型的外部方法得分:
a. 从调整后的 Rand 指数开始:
ARI_mean=metrics.adjusted_rand_score(label, label_mean_shift) ARI_agg=metrics.adjusted_rand_score(label, label_agglomerative) ARI_meanARI_mean的输出将如下所示:0.0006771608724007207接下来,输入
ARI_agg以获取预期值:ARI_aggARI_agg的输出将如下所示:0.05358047852603172我们的聚合模型的
adjusted_rand_score比均值漂移模型高得多,但两个得分都非常接近0,这意味着两个模型在真实标签的表现上都不太理想。b. 接下来,计算调整后的互信息:
AMI_mean = metrics.adjusted_mutual_info_score(label, \ label_mean_shift) AMI_agg = metrics.adjusted_mutual_info_score(label, \ label_agglomerative) AMI_meanAMI_mean的输出将如下所示:0.004837187596124968接下来,输入
AMI_agg以获取预期值:AMI_aggAMI_agg的输出将如下所示:0.05993098663692826我们的聚合模型的
adjusted_mutual_info_score比均值漂移模型高得多,但两个得分都非常接近,V_mean将如下所示:0.021907254751144124接下来,输入
V_agg以获取预期值:V_aggV_agg的输出将如下所示:0.07549735446050691我们的聚合模型的 V-Measure 比均值漂移模型高,但两个得分都非常接近,
FM_mean将如下所示:0.5721233634622408接下来,输入
FM_agg以获取预期值:FM_aggFM_agg的输出将如下所示:0.3300681478007641这一次,我们的均值漂移模型的 Fowlkes-Mallows 得分高于聚合模型,但两个模型的得分仍然处于得分范围的较低位置,这意味着两个模型在真实标签的表现上都不太理想。
总结而言,通过外部方法评估,我们的两个模型都未能根据葡萄酒的物理化学特性找到包含相似质量的聚类。我们将通过使用内部方法评估来确认这一点,以确保我们的模型聚类已被良好定义,并且能正确地将相似的葡萄酒分组在一起。
-
现在,计算以下两种模型的内部方法得分:
a. 从轮廓系数开始:
Sil_mean = metrics.silhouette_score(scaled_features, \ label_mean_shift) Sil_agg = metrics.silhouette_score(scaled_features, \ label_agglomerative) Sil_meanSil_mean的输出将如下所示:0.32769323700400077接下来,输入
Sil_agg以获取预期值:Sil_aggSil_agg的输出将如下所示:0.1591882574407987我们的均值漂移模型的轮廓系数(Silhouette Coefficient)高于聚合模型,但两个得分都非常接近,
CH_mean将如下所示:44.62091774102674接下来,输入
CH_agg以获取预期值:CH_aggCH_agg的输出将如下所示:223.5171774491095我们的聚合模型的 Calinski-Harabasz 指数比均值漂移模型高得多,这意味着聚合模型的聚类更加密集且界限更加明确。
c. 最后,计算 Davies-Bouldin 指数:
DB_mean = metrics.davies_bouldin_score(scaled_features, \ label_mean_shift) DB_agg = metrics.davies_bouldin_score(scaled_features, \ label_agglomerative) DB_meanDB_mean的输出将如下所示:0.8106334674570222接下来,输入
DB_agg以获取预期值:DB_aggDB_agg的输出将如下所示:1.4975443816135114我们的凝聚模型的 David-Bouldin 指数高于均值迁移模型,但两者的分数都接近0,这意味着两者在定义其聚类方面表现良好。
注意:
若要访问此特定部分的源代码,请参考
packt.live/2YXMl0U。你也可以在线运行这个示例,链接:
packt.live/2Bs7sAp。你必须执行整个 Notebook 才能得到期望的结果。
总结来说,通过内在方法评估,我们的两个模型都得到了很好的定义,并且验证了我们对红酒数据集的直觉,即相似的物理化学属性与相似的质量无关。我们还看到,在大多数评分中,凝聚层次模型的表现优于均值迁移模型。
6. 神经网络与深度学习
活动 6.01:为数字数据集找到最佳准确度评分
解决方案:
-
打开一个新的 Jupyter Notebook 文件。
-
导入
tensorflow.keras.datasets.mnist为mnist:import tensorflow.keras.datasets.mnist as mnist -
使用
mnist.load_data()加载mnist数据集,并将结果保存到(features_train, label_train), (features_test, label_test):(features_train, label_train), \ (features_test, label_test) = mnist.load_data() -
打印
label_train的内容:label_train预期输出是这样的:
array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)label列包含与0到9这10个手写数字对应的数值: -
打印训练集的形状:
features_train.shape预期输出是这样的:
(60000, 28, 28)训练集由
60,000个形状为28x28的观察值组成。我们需要将输入展平以适应神经网络。 -
打印测试集的形状:
features_test.shape预期输出是这样的:
(10000, 28, 28)测试集由
10,000个形状为28x28的观察值组成。 -
通过将
features_train和features_test除以255来进行标准化:features_train = features_train / 255.0 features_test = features_test / 255.0 -
导入
numpy为np,tensorflow为tf,以及从tensorflow.keras导入layers:import numpy as np import tensorflow as tf from tensorflow.keras import layers -
使用
np.random_seed()和tf.random.set_seed()分别设置 NumPy 和 TensorFlow 的种子为8:np.random.seed(8) tf.random.set_seed(8) -
实例化一个
tf.keras.Sequential()类并将其保存到一个名为model的变量中:model = tf.keras.Sequential() -
实例化
layers.Flatten(),并设置input_shape=(28,28),然后将其保存到名为input_layer的变量中:input_layer = layers.Flatten(input_shape=(28,28)) -
实例化一个
layers.Dense()类,设置128个神经元并使用activation='relu',然后将其保存到名为layer1的变量中:layer1 = layers.Dense(128, activation='relu') -
实例化第二个
layers.Dense()类,设置1个神经元并使用activation='softmax',然后将其保存到名为final_layer的变量中:final_layer = layers.Dense(10, activation='softmax') -
使用
.add()将刚刚定义的三层添加到模型中,并在每一层之间(展平层除外)添加一个layers.Dropout(0.25)层:model.add(input_layer) model.add(layer1) model.add(layers.Dropout(0.25)) model.add(final_layer) -
实例化一个
tf.keras.optimizers.Adam()类,学习率为0.001,并将其保存到名为optimizer的变量中:optimizer = tf.keras.optimizers.Adam(0.001) -
使用
.compile()编译神经网络,参数为loss='sparse_categorical_crossentropy', optimizer=optimizer, metrics=['accuracy']:model.compile(loss='sparse_categorical_crossentropy', \ optimizer=optimizer, \ metrics=['accuracy']) -
使用
.summary()打印模型的概述:model.summary()预期输出是这样的:
图 6.29:模型总结
该输出总结了我们神经网络的架构。我们可以看到它由四层组成,包括一层展平层,两层密集层和一层 Dropout 层。
-
实例化
tf.keras.callbacks.EarlyStopping()类,使用monitor='val_loss'和patience=5作为学习率,并将其保存为名为callback的变量:callback = tf.keras.callbacks.EarlyStopping(monitor='val_loss', \ patience=5) -
使用训练集拟合神经网络,并指定
epochs=10,validation_split=0.2,callbacks=[callback]和verbose=2:model.fit(features_train, label_train, epochs=10, \ validation_split = 0.2, \ callbacks=[callback], verbose=2)期望的输出是这样的:
图 6.30:使用训练集拟合神经网络
在仅经过10个 epochs 后,我们在训练集上的准确率为0.9825,在验证集上的准确率为0.9779,这些结果非常惊人。在本节中,您学习了如何使用 TensorFlow 从头开始构建并训练神经网络以进行数字分类。
注意
要访问此特定部分的源代码,请参考 packt.live/37UWf7E。
您也可以在线运行此示例,网址是 packt.live/317R2b3。
您必须执行整个 Notebook 才能得到预期的结果。
活动 6.02:使用 CNN 评估 Fashion 图像识别模型
解决方案:
-
打开一个新的 Jupyter Notebook。
-
导入
tensorflow.keras.datasets.fashion_mnist为fashion_mnist:import tensorflow.keras.datasets.fashion_mnist as fashion_mnist -
使用
fashion_mnist.load_data()加载 Fashion MNIST 数据集,并将结果保存到(features_train, label_train), (features_test, label_test)中:(features_train, label_train), \ (features_test, label_test) = fashion_mnist.load_data() -
打印训练集的形状:
features_train.shape期望的输出是这样的:
(60000, 28, 28)训练集由
60,000张大小为28*28的图像组成。 -
打印测试集的形状:
features_test.shape期望的输出是这样的:
(10000, 28, 28)测试集由
10,000张大小为28*28的图像组成。 -
按照以下代码片段的方式,重塑训练集和测试集的维度为(
number_rows,28,28,1):features_train = features_train.reshape(60000, 28, 28, 1) features_test = features_test.reshape(10000, 28, 28, 1) -
通过将
features_train和features_test除以255来标准化它们:features_train = features_train / 255.0 features_test = features_test / 255.0 -
导入
numpy为np,tensorflow为tf,并从tensorflow.keras导入layers:import numpy as np import tensorflow as tf from tensorflow.keras import layers -
使用
np.random_seed()和tf.random.set_seed()将8设置为numpy和tensorflow的种子:np.random.seed(8) tf.random.set_seed(8) -
实例化一个
tf.keras.Sequential()类,并将其保存为名为model的变量:model = tf.keras.Sequential() -
使用
64个形状为(3,3)的卷积核和activation='relu',以及input_shape=(28,28),实例化layers.Conv2D()并将其保存为名为conv_layer1的变量:conv_layer1 = layers.Conv2D(64, (3,3), \ activation='relu', input_shape=(28, 28, 1)) -
使用
64个形状为(3,3)的卷积核和activation='relu',实例化layers.Conv2D()并将其保存为名为conv_layer2的变量:conv_layer2 = layers.Conv2D(64, (3,3), activation='relu') -
使用
128个神经元和activation='relu',实例化layers.Flatten(),然后将其保存为名为fc_layer1的变量:fc_layer1 = layers.Dense(128, activation='relu') -
使用
10个神经元和activation='softmax',实例化layers.Flatten(),然后将其保存为名为fc_layer2的变量:fc_layer2 = layers.Dense(10, activation='softmax') -
使用
.add()方法将刚才定义的四个层添加到模型中,并在每个卷积层之间添加一个大小为(2,2)的MaxPooling2D()层:model.add(conv_layer1) model.add(layers.MaxPooling2D(2, 2)) model.add(conv_layer2) model.add(layers.MaxPooling2D(2, 2)) model.add(layers.Flatten()) model.add(fc_layer1) model.add(fc_layer2) -
实例化一个
tf.keras.optimizers.Adam()类,学习率为0.001,并将其保存到名为optimizer的变量中:optimizer = tf.keras.optimizers.Adam(0.001) -
使用
.compile()编译神经网络,设置loss='sparse_categorical_crossentropy',optimizer=optimizer,metrics=['accuracy']:model.compile(loss='sparse_categorical_crossentropy', \ optimizer=optimizer, metrics=['accuracy']) -
使用
.summary()打印模型概述:model.summary()预期的输出是这样的:
图 6.31:模型概述
概述显示该模型需要优化超过
240,000个参数。 -
使用训练集拟合神经网络,并指定
epochs=5、validation_split=0.2和verbose=2:model.fit(features_train, label_train, \ epochs=5, validation_split = 0.2, verbose=2)预期的输出是这样的:
图 6.32:使用训练集拟合神经网络
经过
5个训练周期后,我们在训练集上达到了0.925的准确率,在验证集上达到了0.9042的准确率。我们的模型有些过拟合。 -
评估模型在测试集上的表现:
model.evaluate(features_test, label_test)预期的输出是这样的:
10000/10000 [==============================] - 1s 108us/sample - loss: 0.2746 - accuracy: 0.8976 [0.27461639745235444, 0.8976]
我们在测试集上预测来自 Fashion MNIST 数据集的服装图像时,达到了 0.8976 的准确率。你可以尝试自己提高这个得分并减少过拟合。
注意
要访问该特定部分的源代码,请参考 packt.live/2Nzt6pn。
你也可以在网上运行这个示例,访问 packt.live/2NlM5nd。
必须执行整个 Notebook 才能获得预期的结果。
在本次活动中,我们设计并训练了一个用于识别来自 Fashion MNIST 数据集服装图像的 CNN 架构。
活动 6.03:使用 RNN 评估雅虎股票模型
解决方案:
-
打开一个 Jupyter Notebook。
-
导入
pandas作为pd和numpy作为np:import pandas as pd import numpy as np -
创建一个名为
file_url的变量,包含指向原始数据集的链接:file_url = 'https://raw.githubusercontent.com/'\ 'PacktWorkshops/'\ 'The-Applied-Artificial-Intelligence-Workshop/'\ 'master/Datasets/yahoo_spx.csv' -
使用
pd.read_csv()加载数据集,并将其保存到名为df的新变量中:df = pd.read_csv(file_url) -
使用
.iloc和.values提取第二列的值,并将结果保存到名为stock_data的变量中:stock_data = df.iloc[:, 1:2].values -
从
sklearn.preprocessing导入MinMaxScaler:from sklearn.preprocessing import MinMaxScaler -
实例化
MinMaxScaler()并将其保存到名为sc的变量中:sc = MinMaxScaler() -
使用
.fit_transform()标准化数据,并将结果保存到名为stock_data_scaled的变量中:stock_data_scaled = sc.fit_transform(stock_data) -
创建两个空数组,命名为
X_data和y_data:X_data = [] y_data = [] -
创建一个名为
window的变量,它将包含值30:window = 30 -
创建一个从
window值开始的for循环,遍历数据集的长度。在每次迭代中,使用window将stock_data_scaled的前几行添加到X_data中,并将当前的stock_data_scaled值添加到其中:for i in range(window, len(df)): X_data.append(stock_data_scaled[i - window:i, 0]) y_data.append(stock_data_scaled[i, 0])y_data将包含每天的开盘股票价格,X_data将包含过去 30 天的股票价格。 -
将
X_data和y_data转换为 NumPy 数组:X_data = np.array(X_data) y_data = np.array(y_data) -
将
X_data调整形状为(行数,列数,1):X_data = np.reshape(X_data, (X_data.shape[0], \ X_data.shape[1], 1)) -
使用前
1,000行作为训练数据,并将它们保存到两个变量中,分别叫做features_train和label_train:features_train = X_data[:1000] label_train = y_data[:1000] -
使用第
1,000行之后的行作为测试数据,并将它们保存到两个变量中,分别叫做features_test和label_test:features_test = X_data[:1000] label_test = y_data[:1000] -
导入
numpy为np,tensorflow为tf,并从tensorflow.keras导入layers:import numpy as np import tensorflow as tf from tensorflow.keras import layers -
设置
8为 NumPy 和 TensorFlow 的seed,使用np.random_seed()和tf.random.set_seed():np.random.seed(8) tf.random.set_seed(8) -
实例化一个
tf.keras.Sequential()类,并将其保存到一个变量中,名为model:model = tf.keras.Sequential() -
实例化
layers.LSTM(),使用50个单元,return_sequences='True',并且input_shape=(X_train.shape[1], 1),然后将其保存到一个变量中,名为lstm_layer1:lstm_layer1 = layers.LSTM(units=50,return_sequences=True,\ input_shape=(features_train.shape[1], 1)) -
实例化
layers.LSTM(),使用50个单元和return_sequences='True',然后将其保存到一个变量中,名为lstm_layer2:lstm_layer2 = layers.LSTM(units=50,return_sequences=True) -
实例化
layers.LSTM(),使用50个单元和return_sequences='True',然后将其保存到一个变量中,名为lstm_layer3:lstm_layer3 = layers.LSTM(units=50,return_sequences=True) -
实例化
layers.LSTM(),使用50个单元,并将其保存到一个变量中,名为lstm_layer4:lstm_layer4 = layers.LSTM(units=50) -
实例化
layers.Dense(),使用1个神经元,并将其保存到一个变量中,名为fc_layer:fc_layer = layers.Dense(1) -
使用
.add()方法将你刚才定义的五个层添加到模型中,并在每个 LSTM 层之间添加一个Dropout(0.2)层:model.add(lstm_layer1) model.add(layers.Dropout(0.2)) model.add(lstm_layer2) model.add(layers.Dropout(0.2)) model.add(lstm_layer3) model.add(layers.Dropout(0.2)) model.add(lstm_layer4) model.add(layers.Dropout(0.2)) model.add(fc_layer) -
实例化一个
tf.keras.optimizers.Adam()类,学习率为0.001,并将其保存到一个变量中,名为optimizer:optimizer = tf.keras.optimizers.Adam(0.001) -
使用
.compile()编译神经网络,参数为loss='mean_squared_error', optimizer=optimizer, metrics=[mse]:model.compile(loss='mean_squared_error', \ optimizer=optimizer, metrics=['mse']) -
使用
.summary()打印模型的摘要:model.summary()预期的输出是这样的:
图 6.33:模型摘要
汇总显示我们有超过
71,051个参数需要优化。 -
使用训练集拟合神经网络,并指定
epochs=10, validation_split=0.2, verbose=2:model.fit(features_train, label_train, epochs=10, \ validation_split = 0.2, verbose=2)预期的输出是这样的:
图 6.34:使用训练集拟合神经网络
经过
10个训练周期后,我们在训练集上获得了0.0025的均方误差分数,在验证集上获得了0.0033,说明我们的模型有些过拟合。 -
最后,评估模型在测试集上的表现:
model.evaluate(features_test, label_test)预期的输出是这样的:
1000/1000 [==============================] - 0s 279us/sample - loss: 0.0016 - mse: 0.0016 [0.00158528157370165, 0.0015852816]
我们在测试集上获得了0.0017的均方误差分数,这意味着我们可以相当准确地使用过去 30 天的股价数据作为特征来预测雅虎的股价。
注意
要访问该特定部分的源代码,请参考packt.live/3804U8P。
你也可以在线运行此示例,访问packt.live/3hWtU5l。
你必须执行整个 Notebook 才能获得期望的结果。
在这项活动中,我们设计并训练了一个 RNN 模型,用于预测基于过去 30 天数据的 Yahoo 股票价格。