dl-keras-ws-merge-1

78 阅读1小时+

Keras 深度学习研讨会(二)

原文:annas-archive.org/md5/b451b2e8c375a884062f55f9c0743702

译者:飞龙

协议:CC BY-NC-SA 4.0

第四章:4. 使用 Keras 包装器进行交叉验证评估模型

概述

本章将介绍如何使用 scikit-learn 构建 Keras 包装器。你将学习如何应用交叉验证来评估深度学习模型,并创建用户定义的函数来实现深度学习模型及其交叉验证。到本章结束时,你将能够构建出在新数据和训练数据上表现一致、且稳健的模型。

引言

在上一章中,我们实验了不同的神经网络架构。通过观察训练过程中损失值和准确率的变化,我们能够评估不同模型的表现。这帮助我们判断模型是否存在欠拟合或过拟合问题,并学习如何使用如提前停止等技术来防止过拟合。

在本章中,你将学习交叉验证。这是一种重采样技术,相较于我们在前几章讨论的模型评估方法,它能提供更加准确和稳健的模型性能估计。

本章从深入讨论为什么需要使用交叉验证进行模型评估开始,讲解交叉验证的基本原理、变种以及它们之间的比较。接下来,我们将对 Keras 深度学习模型进行交叉验证的实现。我们还将结合 scikit-learn 使用 Keras 包装器,使得 Keras 模型能够作为估计器在 scikit-learn 的工作流中进行处理。然后,你将学习如何在 scikit-learn 中实现交叉验证,最终将所有内容结合起来,在 scikit-learn 中对 Keras 深度学习模型进行交叉验证。

最后,你将学习如何利用交叉验证不仅仅进行模型评估,还可以利用交叉验证对模型性能进行估计,从而比较不同模型,并选择在特定数据集上表现最好的模型。你还将使用交叉验证来提高给定模型的表现,通过寻找最佳超参数集合。我们将在三项活动中实现本章所学的概念,每项活动都涉及一个真实数据集。

交叉验证

重采样技术是统计数据分析中的一组重要技术。它们涉及反复从数据集中抽取样本来创建训练集和测试集。在每次重复中,使用从数据集中抽取的样本来训练和评估模型。

使用这些技术可以为我们提供模型的信息,这是通过仅使用一个训练集和一个测试集来拟合和评估模型无法获得的。由于重采样方法涉及多次对训练数据拟合模型,因此计算开销较大。因此,在深度学习中,我们仅在数据集和网络相对较小,并且可用计算能力允许时,才会实现这些方法。

在本节中,你将学习一种非常重要的重采样方法——交叉验证。交叉验证是最重要和最常用的重采样方法之一。它计算了在给定有限数据集的情况下,模型在新未见过的样本上的最佳性能估计。我们还将探讨交叉验证的基础知识,它的两种变体以及它们之间的比较。

仅进行一次数据集划分的缺点

在上一章中,我们提到过,在用于训练模型的同一数据集上评估模型是一个方法上的错误。由于模型已经训练以减少这个特定数据集上的误差,因此它在该数据集上的表现具有很强的偏倚性。这就是为什么训练数据上的误差率总是低估新样本上的误差率的原因。我们了解到,解决这个问题的一种方法是随机地从数据中留出一部分作为测试集进行评估,并在其余数据上拟合模型,这部分数据被称为训练集。以下图展示了这种方法的示例:

图 4.1:训练集/测试集划分概述

图 4.1:训练集/测试集划分概述

正如我们之前提到的,将数据分配到训练集或测试集完全是随机的。这意味着如果我们重复此过程,每次分配给测试集和训练集的数据会有所不同。通过这种方法报告的测试误差率可能会有所不同,这取决于哪些样本被分配到测试集,哪些样本被分配到训练集。

示例

让我们来看一个例子。在这里,我们为你在活动 3.02使用神经网络进行高级纤维化诊断中的第三章使用 Keras 的深度学习部分看到的丙型肝炎数据集构建了一个单层神经网络。我们使用了训练集/测试集的方法来计算与此模型相关的测试误差。与一次性划分和训练不同,如果我们将数据划分为五个独立的数据集,并重复此过程五次,我们可能会得到五个不同的测试误差率图。以下图表展示了这五次实验的测试误差率:

图 4.2:在一个示例数据集上,使用五种不同的训练集/测试集划分绘制的测试误差率图

图 4.2:在一个示例数据集上,使用五种不同的训练集/测试集划分绘制的测试误差率图

如你所见,每次实验中的测试误差率差异很大。模型评估结果的这种变化表明,仅仅将数据集分为训练集和测试集一次的简单策略可能无法提供对模型性能的稳健且准确的估计。

总结来说,我们在上一章学习的训练集/测试集方法有一个明显的优点,那就是简单、易于实现且计算成本低。然而,它也有一些缺点,具体如下:

  • 第一个缺点是,它对模型误差率的估计在很大程度上依赖于到底是哪些数据被分配到测试集,哪些数据被分配到训练集。

  • 第二个缺点是,在这种方法中,我们只在数据的一个子集上训练模型。当使用较少数据进行训练时,机器学习模型的表现通常会更差。

由于模型的性能可以通过在整个数据集上训练来提高,我们始终在寻找将所有可用数据点纳入训练的方法。此外,我们还希望通过将所有可用数据点纳入评估来获得对模型性能的稳健估计。这些目标可以通过使用交叉验证技术来实现。以下是两种交叉验证方法:

  • K 折交叉验证

  • 留一法交叉验证

K 折交叉验证

k折交叉验证中,我们不是将数据集划分为两个子集,而是将数据集划分为k个大小相近的子集或折叠。在方法的第一次迭代中,第一个折叠被作为测试集。模型在剩余的k-1个折叠上进行训练,然后在第一个折叠上进行评估(第一个折叠用于估算测试误差率)。

这个过程会重复k次,每次迭代使用不同的折叠作为测试集,而其余的折叠作为训练集。最终,这种方法会得到k个不同的测试误差率。模型误差率的最终k折交叉验证估计是通过平均这k个测试误差率计算得出的。

以下图示说明了k折交叉验证方法中的数据集划分过程:

图 4.3:k 折交叉验证方法中的数据集划分概述

图 4.3:k 折交叉验证方法中的数据集划分概述

在实践中,我们通常执行k-fold 交叉验证,其中k=5k=10,如果你在选择适合你数据集的值时遇到困难,这些是推荐的值。决定使用多少折叠取决于数据集中的示例数量和可用的计算能力。如果k=5,模型将被训练和评估五次,而如果k=10,这个过程将重复 10 次。折叠数越高,执行 k 折交叉验证所需的时间就越长。

在 k 折交叉验证中,示例分配到每个折叠是完全随机的。然而,通过查看前面的示意图,你会发现,最终每个数据点都会同时用于训练和评估。这就是为什么如果你在相同的数据集和相同的模型上重复多次 k 折交叉验证,最终报告的测试误差率几乎是相同的。因此,与训练集/测试集方法相比,k 折交叉验证的结果不会受到高方差的影响。现在,我们来看一下交叉验证的第二种形式:留一法验证。

留一法交叉验证

留一法LOO)是交叉验证技术的一种变体,在这种方法中,数据集不会被划分为两个大小相当的子集用于训练集和测试集,而是仅使用一个数据点进行评估。如果整个数据集中有n个数据示例,在每次LOO 交叉验证迭代中,模型会在n-1个示例上进行训练,而剩下的单个示例会用于计算测试误差率。

仅使用一个示例来估计测试误差率会导致对模型性能的无偏但高方差的估计;它是无偏的,因为这个示例没有参与训练模型,它具有高方差,因为它仅基于一个数据示例来计算,并且会根据使用的具体数据示例有所变化。这个过程会重复n次,在每次迭代中,使用不同的数据示例进行评估。最终,方法会得到n个不同的测试误差率,最终的LOO 交叉验证测试误差估计是通过平均这n个误差率来计算的。

LOO 交叉验证方法中数据集划分过程的示意图如下所示:

图 4.4:LOO 交叉验证方法中数据集划分的概述

图 4.4:LOO 交叉验证方法中数据集划分的概述

在每次留一交叉验证的迭代中,几乎所有的数据示例都用于训练模型。另一方面,在训练集/测试集方法中,数据的一个相对较大的子集用于评估,而不参与训练。因此,留一交叉验证对模型性能的估计更接近于在整个数据集上训练的模型的性能,这也是留一交叉验证相对于训练集/测试集方法的主要优势。

此外,由于在每次留一交叉验证的迭代中,仅使用一个唯一的数据示例进行评估,并且每个数据示例也都用于训练,因此这种方法没有随机性。因此,如果你在相同的数据集和相同的模型上重复进行多次留一交叉验证,最终报告的测试误差率每次都会完全相同。

留一交叉验证的缺点是计算开销大。其原因是模型需要训练n次,在n较大和/或网络较大的情况下,完成训练所需的时间会很长。留一交叉验证k 折交叉验证各有其优缺点,接下来我们将进行详细比较。

比较 K 折交叉验证和留一交叉验证方法

通过比较前两个图表,可以明显看出留一交叉验证实际上是k 折交叉验证的一个特殊情况,其中k=n。然而,正如之前提到的,选择k=n在计算上非常昂贵,相比之下,选择k=5k=10更为高效。

因此,k 折交叉验证相对于留一交叉验证的第一个优势是计算开销较小。下表比较了低 k 折交叉验证高 k 折交叉验证留一交叉验证,以及无交叉验证偏差方差方面的差异。表格显示,最大的偏差出现在简单的训练集-测试集划分方法中,最大的方差出现在留一交叉验证中。k 折交叉验证位于两者之间。这也是为什么k 折交叉验证通常是大多数机器学习任务中最合适的选择:

图 4.5:比较训练集-测试集划分、k 折交叉验证和留一交叉验证

交叉验证方法

](tos-cn-i-73owjymdk6/6de2db34e7904834be38faffee6475d7)

图 4.5:比较训练集-测试集划分、k 折交叉验证和留一交叉验证方法

以下图表比较了训练集/测试集方法、k 折交叉验证留一交叉验证偏差方差方面的差异:

图 4.6:比较训练集/测试集方法、k 折交叉验证和留一交叉验证在偏差和方差方面的差异

](github.com/OpenDocCN/f…)

图 4.6:比较训练集/测试集方法、k 折交叉验证和留一交叉验证在偏差和方差方面的差异

一般来说,在机器学习和数据分析中,最理想的模型是具有最低偏差最低方差的模型。如前面的图所示,图中间标记的区域,其中偏差方差都较低,是我们关注的重点。事实证明,这个区域等同于 k-fold 交叉验证,其中 k 的值介于 510 之间。在下一节中,我们将探索如何在实践中实现不同的交叉验证方法。

深度学习模型的交叉验证

在本节中,你将学习如何使用 Keras 封装器与 scikit-learn 配合使用,这是一种有用的工具,可以让我们将 Keras 模型作为 scikit-learn 工作流的一部分。因此,像交叉验证这样的 scikit-learn 方法和函数可以轻松地应用到 Keras 模型中。

你将一步步学习如何在本节中使用 scikit-learn 实现你在上一节中学到的交叉验证方法。此外,你还将学习如何使用交叉验证评估 Keras 深度学习模型,并使用 Keras 封装器与 scikit-learn 配合使用。最后,你将通过解决一个实际数据集中的问题来实践所学的内容。

Keras 封装器与 scikit-learn

在一般的机器学习和数据分析中,scikit-learn 库比 Keras 更加丰富且易于使用。这就是为什么能够在 Keras 模型上使用 scikit-learn 方法会非常有价值的原因。

幸运的是,Keras 提供了一个有用的封装器 keras.wrappers.scikit_learn,它允许我们为深度学习模型构建 scikit-learn 接口,这些模型可以作为分类或回归估计器在 scikit-learn 中使用。封装器有两种类型:一种用于分类估计器,另一种用于回归估计器。以下代码用于定义这些 scikit-learn 接口:

keras.wrappers.scikit_learn.KerasClassifier(build_fn=None, **sk_params)
# wrappers for classification estimators
keras.wrappers.scikit_learn.KerasRegressor(build_fn=None, **sk_params)
# wrappers for regression estimators

build_fn 参数需要是一个可调用的函数,在其内部定义、编译并返回一个 Keras 顺序模型。

sk_params 参数可以接受用于构建模型的参数(如层的激活函数)和用于拟合模型的参数(如训练轮数和批量大小)。这一点将在接下来的练习中得以应用,我们将在该练习中使用 Keras 封装器解决回归问题。

注意

本章的所有活动将在 Jupyter notebook 中开发。请下载本书的 GitHub 仓库以及所有已准备好的模板,点击以下链接即可找到:

packt.live/3btnjfA

练习 4.01:为回归问题构建 Keras 封装器与 scikit-learn 配合使用

在本次练习中,你将学习如何一步一步构建 Keras 深度学习模型的包装器,使其可以在 scikit-learn 工作流中使用。首先,加载一个包含908个数据点的回归问题数据集,其中每个记录描述了化学物质的六个属性,目标是预测鱼类 Pimephales promelas 的急性毒性,即LC50

注意

注意下面字符串中的斜杠。记住,反斜杠(\)用于将代码分割为多行,而正斜杠(/)是路径的一部分。

# import data
import pandas as pd
colnames = ['CIC0', 'SM1_Dz(Z)', 'GATS1i', \
            'NdsCH', 'NdssC','MLOGP', 'LC50']
data = pd.read_csv('../data/qsar_fish_toxicity.csv', \
                   sep=';', names=colnames)
X = data.drop('LC50', axis=1)
y = data['LC50']
# Print the sizes of the dataset
print("Number of Examples in the Dataset = ", X.shape[0])
print("Number of Features for each example = ", X.shape[1])
# print output range
print("Output Range = [%f, %f]" %(min(y), max(y)))

这是预期的输出:

Number of Examples in the Dataset =  908
Number of Features for each example =  6
Output Range = [0.053000, 9.612000]

由于该数据集的输出是一个数值,因此这是一个回归问题。目标是构建一个模型,预测给定化学物质的其他属性时,鱼类的急性毒性LC50。现在,让我们一步步来看:

  1. 定义一个函数来构建并返回用于回归问题的 Keras 模型。你定义的 Keras 模型必须有一个隐藏层,大小为8,并使用ReLU 激活函数。同时,使用均方误差MSE)损失函数和Adam 优化器来编译模型:

    from keras.models import Sequential
    from keras.layers import Dense, Activation
    # Create the function that returns the keras model
    def build_model():
        # build the Keras model
        model = Sequential()
        model.add(Dense(8, input_dim=X.shape[1], \
                  activation='relu'))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer='adam')
        # return the model
        return model  
    
  2. 现在,使用 Keras 包装器和 scikit-learn 创建 scikit-learn 接口来构建你的模型。记住,你需要在这里提供epochsbatch_sizeverbose参数:

    # build the scikit-Learn interface for the keras model
    from keras.wrappers.scikit_learn import KerasRegressor
    YourModel = KerasRegressor(build_fn= build_model, \
                               epochs=100, \
                               batch_size=20, \
                               verbose=1) 
    

    现在,YourModel已经可以作为 scikit-learn 中的回归估计器使用。

在本次练习中,我们学习了如何使用模拟数据集通过 scikit-learn 构建 Keras 包装器来解决回归问题。

注意

若要访问此特定部分的源代码,请参阅packt.live/38nuqVP

你也可以在网上运行这个示例,访问packt.live/31MLgMF

我们将在本章剩余的练习中继续使用此数据集实现交叉验证。

使用 scikit-learn 进行交叉验证

在上一章中,你学会了如何在 scikit-learn 中轻松进行训练集/测试集的划分。假设你的原始数据集存储在Xy数组中。你可以使用以下命令将它们随机划分为训练集和测试集:

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split\
                                   (X, y, test_size=0.3, \
                                    random_state=0)

test_size参数可以设置为介于01之间的任何数值,具体取决于你希望测试集的大小。通过为random_state参数提供一个int类型的数字,你将能够选择随机数生成器的种子。

在 scikit-learn 中执行交叉验证的最简单方法是使用cross_val_score函数。为此,你需要首先定义你的估计器(在我们的案例中,估计器将是一个 Keras 模型)。然后,你将能够使用以下命令对估计器/模型进行交叉验证:

from sklearn.model_selection import cross_val_score
scores = cross_val_score(YourModel, X, y, cv=5)

请注意,我们将 Keras 模型和原始数据集作为参数传递给 cross_val_score 函数,并指定折数(即 cv 参数)。在这里,我们使用了 cv=5,所以 cross_val_score 函数会将数据集随机划分为五个折,并使用五个不同的训练集和测试集对模型进行五次训练和拟合。它将在每次迭代/折叠时计算默认的模型评估指标(或在定义 Keras 模型时提供的指标),并将它们存储在 scores 中。我们可以如下打印最终的交叉验证得分:

print(scores.mean())

之前我们提到过,cross_val_score 函数返回的得分是我们模型的默认指标,或者是我们在定义模型时为其确定的指标。然而,也可以通过在调用 cross_val_score 函数时提供所需的指标作为 scoring 参数,来更改交叉验证的指标。

注意

您可以在这里了解如何在 cross_val_score 函数的 scoring 参数中提供所需的评估指标:scikit-learn.org/stable/modules/model_evaluation.html#scoring-parameter

通过为 cross_val_score 函数的 cv 参数提供一个整数,我们告诉函数在数据集上执行 k-fold 交叉验证。然而,scikit-learn 中还有几种其他的迭代器可以分配给 cv,以执行数据集的其他交叉验证变种。例如,以下代码块将对数据集执行 LOO 交叉验证

from sklearn.model_selection import LeaveOneOut
loo = LeaveOneOut()
scores = cross_val_score(YourModel, X, y, cv=loo)

在下一节中,我们将探索 scikit-learn 中的 k-fold 交叉验证,并看看它如何与 Keras 模型一起使用。

scikit-learn 中的交叉验证迭代器

这里提供了 scikit-learn 中最常用的交叉验证迭代器的列表,并简要描述了它们的功能:

  • KFold(n_splits=?)

    这将把数据集划分为 k 个折或组。n_splits 参数是必需的,用于确定使用多少个折。如果 n_splits=n,它将等同于 LOO 交叉验证

  • RepeatedKFold(n_splits=?, n_repeats=?, random_state=random_state)

    这将重复执行 k-fold 交叉验证 n_repeats 次。

  • LeaveOneOut()

    这将对数据集进行 LOO 交叉验证 的划分。

  • ShuffleSplit(n_splits=?, test_size=?, random_state=random_state)

    这将生成 n_splits 个随机且独立的训练集/测试集数据集划分。可以使用 random_state 参数存储随机数生成器的种子;如果这么做,数据集的划分将是可重现的。

除了常规迭代器(例如这里提到的迭代器),还有 分层 版本。分层抽样在数据集的不同类别的样本数量不平衡时非常有用。例如,假设我们想设计一个分类器来预测某人是否会拖欠信用卡债务,其中数据集中几乎有 95% 的样本属于 负类。分层抽样确保在每个 训练集/测试集 划分中保留类别的相对频率。对于这种情况,建议使用分层版本的迭代器。

通常,在使用训练集训练和评估模型之前,我们会对其进行预处理,以便将样本缩放,使其均值为 0,标准差为 1。在 训练集/测试集 方法中,我们需要缩放训练集并存储该转换。以下代码块将为我们完成这项工作:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

这是在我们的 Xy 数据集上执行 k=5分层 K 折交叉验证 的示例:

from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5)
scores = cross_val_score(YourModel, X, y, cv=skf)

注意

你可以在这里了解更多关于 scikit-learn 中交叉验证迭代器的内容:

scikit-learn.org/stable/modules/cross_validation.html#cross-validation-iterators

现在我们了解了交叉验证迭代器,可以在练习中将它们付诸实践。

练习 4.02:使用交叉验证评估深度神经网络

在这次练习中,我们将把我们在本主题中学习到的所有交叉验证相关概念和方法结合起来。我们将再次经历所有步骤,从定义 Keras 深度学习模型到将其转移到 scikit-learn 工作流并执行交叉验证以评估其性能。从某种意义上说,这个练习是我们到目前为止所学内容的回顾,涵盖的内容对于 活动 4.01使用交叉验证评估先进的纤维化诊断分类器模型)将非常有帮助:

  1. 第一步始终是加载你想要构建模型的数据集。首先,加载回归问题的 908 个数据点的数据集,每个记录描述了一个化学物质的六个属性,目标是预测对鱼类 Pimephales promelas 的急性毒性,即 LC50

    # import data
    import pandas as pd
    colnames = ['CIC0', 'SM1_Dz(Z)', 'GATS1i', \
                'NdsCH', 'NdssC','MLOGP', 'LC50']
    data = pd.read_csv('../data/qsar_fish_toxicity.csv', \
                       sep=';', names=colnames)
    X = data.drop('LC50', axis=1)
    y = data['LC50']
    # Print the sizes of the dataset
    print("Number of Examples in the Dataset = ", X.shape[0])
    print("Number of Features for each example = ", X.shape[1])
    # print output range
    print("Output Range = [%f, %f]" %(min(y), max(y)))
    

    输出如下:

    Number of Examples in the Dataset =  908
    Number of Features for each example =  6
    Output Range = [0.053000, 9.612000]
    
  2. 定义一个函数,返回一个具有单个隐藏层(大小为 8,使用 ReLU 激活 函数)的 Keras 模型,使用 均方误差 (MSE) 损失函数和 Adam 优化器

    from keras.models import Sequential
    from keras.layers import Dense, Activation
    # Create the function that returns the keras model
    def build_model():
        # build the Keras model
        model = Sequential()
        model.add(Dense(8, input_dim=X.shape[1], \
                  activation='relu'))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer='adam')
        # return the model
        return model
    
  3. 设置seed并使用包装器构建我们在步骤 2中定义的 Keras 模型的 scikit-learn 接口:

    # build the scikit-Learn interface for the keras model
    from keras.wrappers.scikit_learn import KerasRegressor
    import numpy as np
    from tensorflow import random
    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    YourModel = KerasRegressor(build_fn= build_model, \
                               epochs=100, batch_size=20, \
                               verbose=1 , shuffle=False)
    
  4. 定义要用于交叉验证的迭代器。我们来进行 5 折交叉验证

    # define the iterator to perform 5-fold cross-validation
    from sklearn.model_selection import KFold
    kf = KFold(n_splits=5)
    
  5. 调用 cross_val_score 函数来执行交叉验证。根据可用的计算能力,这一步可能需要一些时间才能完成:

    # perform cross-validation on X, y
    from sklearn.model_selection import cross_val_score
    results = cross_val_score(YourModel, X, y, cv=kf) 
    
  6. 一旦交叉验证完成,打印最终交叉验证模型性能评估(性能的默认评估指标为测试损失):

    # print the result
    print(f"Final Cross-Validation Loss = {abs(results.mean()):.4f}")
    

    这是一个示例输出:

    Final Cross-Validation Loss = 0.9680
    

交叉验证损失表明,在该数据集上训练的 Keras 模型能够以0.9680的平均损失预测化学物质的LC50值。我们将在下一次练习中进一步研究该模型。

这些就是使用 scikit-learn 中的交叉验证评估 Keras 深度学习模型所需的所有步骤。现在,我们将在活动中将这些步骤付诸实践。

注意

要访问此特定部分的源代码,请参考 packt.live/3eRTlTM

你也可以在网上运行此示例,访问 packt.live/31IdVT0

活动 4.01:使用交叉验证评估高级纤维化诊断分类器的模型

我们在第 3.02 活动中了解了肝炎 C 数据集,第三章Keras 深度学习中的高级纤维化诊断与神经网络。该数据集包含了1385名接受肝炎 C 治疗剂量的患者的信息。每位患者都有28个不同的属性可供参考,如年龄、性别、BMI 等,以及一个类标签,该标签只能取两个值:1,表示高级纤维化;0,表示没有高级纤维化的迹象。这是一个二元/两类分类问题,输入维度为28

第三章Keras 深度学习中,我们构建了 Keras 模型来对该数据集进行分类。我们使用训练集/测试集划分训练并评估模型,并报告了测试误差率。在本活动中,我们将运用本主题中学到的知识,使用k 折交叉验证来训练和评估深度学习模型。我们将使用在前一个活动中得出的最佳测试误差率的模型。目标是将交叉验证误差率与训练集/测试集方法的误差率进行比较:

  1. 导入必要的库。从 GitHub 的Chapter04文件夹中的data子文件夹加载数据集,使用X = pd.read_csv('../data/HCV_feats.csv'), y = pd.read_csv('../data/HCV_target.csv')。打印数据集中的示例数量、可用的特征数量以及类标签的可能值。

  2. 定义一个函数来返回 Keras 模型。该 Keras 模型将是一个深度神经网络,包含两个隐藏层,其中第一个隐藏层大小为 4第二个隐藏层大小为 2,并使用tanh 激活函数进行分类。使用以下超参数值:

    optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy']

  3. 为 Keras 模型构建 scikit-learn 接口,设置epochs=100batch_size=20,并将shuffle=False。将交叉验证迭代器定义为StratifiedKFold,并设置k=5。对模型进行 k 折交叉验证,并存储分数。

  4. 打印每次迭代/折叠的准确性,以及总体交叉验证准确性和其相关标准差。

  5. 将此结果与第三章《使用 Keras 的深度学习》中的活动 3.02《使用神经网络进行高级纤维化诊断》中的结果进行比较。

完成前述步骤后,期望的输出将如下所示:

Test accuracy at fold 1 = 0.5198556184768677
Test accuracy at fold 2 = 0.4693140685558319
Test accuracy at fold 3 = 0.512635350227356
Test accuracy at fold 4 = 0.5740072131156921
Test accuracy at fold 5 = 0.5523465871810913
Final Cross Validation Test Accuracy: 0.5256317675113678
Standard Deviation of Final Test Accuracy: 0.03584760640500936

注意

本活动的解决方案可以在第 381 页找到。

第三章《使用 Keras 的深度学习》中的活动 3.02《使用神经网络进行高级纤维化诊断》中,我们通过训练集/测试集方法得到的准确率为49.819%,这低于我们在对相同深度学习模型和相同数据集进行5 折交叉验证时所取得的测试准确率,但低于其中一折的准确率。

这种差异的原因在于,通过训练集/测试集方法计算的测试错误率是通过仅将数据点的一个子集纳入模型评估得出的。另一方面,这里计算的测试错误率是通过将所有数据点纳入评估,因此,这种模型性能的估计更为准确且更具鲁棒性,在未见过的测试数据集上表现更好。

在本活动中,我们使用交叉验证对涉及真实数据集的问题进行模型评估。提高模型评估的准确性并不是使用交叉验证的唯一目的,它还可以用来为给定的问题选择最佳模型或参数。

使用交叉验证进行模型选择

交叉验证为我们提供了模型在未见过的示例上的鲁棒性估计。因此,它可以用来在特定问题中决定两个模型之间的优劣,或者决定在特定问题中使用哪一组模型参数(或超参数)。在这些情况下,我们希望找出哪个模型或哪一组模型参数/超参数会导致最低的测试错误率。因此,我们将选择该模型或该组参数/超参数作为我们问题的解决方案。

在本节中,你将练习使用交叉验证来完成这一任务。你将学习如何为深度学习模型定义一组超参数,然后编写用户定义的函数,对模型进行交叉验证,涵盖每种可能的超参数组合。然后,你将观察哪一组超参数组合导致最低的测试错误率,这组超参数将成为你最终模型的选择。

模型评估与模型选择中的交叉验证

在本节中,我们将深入探讨使用交叉验证进行模型评估与模型选择之间的区别。到目前为止,我们已经了解到,在训练集上评估模型会导致对模型在未见样本上的错误率的低估。将数据集分为训练集测试集可以更准确地估计模型的表现,但会面临较高的方差问题。

最后,交叉验证能够更稳健、准确地估计模型在未见样本上的表现。以下图示展示了这三种模型评估方法所产生的错误率估计。

以下图示展示了在训练集/测试集方法的错误率估计稍低于交叉验证估计的情况。然而,重要的是要记住,训练集/测试集的错误率也可能高于交叉验证估计的错误率,具体取决于测试集中包含的数据(因此会存在高方差问题)。另一方面,在训练集上进行评估所得到的错误率始终低于其他两种方法:

图 4.7:展示三种模型评估方法所产生的错误率估计

图 4.7:展示三种模型评估方法所产生的错误率估计

我们已经确定,交叉验证提供了模型在独立数据样本上的最佳表现估计。知道这一点后,我们可以使用交叉验证来决定针对特定问题使用哪个模型。例如,如果我们有四个不同的模型,并希望确定哪个模型最适合某一数据集,我们可以使用交叉验证训练并评估每个模型,选择交叉验证错误率最低的模型作为最终模型。以下图展示了与四个假设模型相关的交叉验证错误率。由此,我们可以得出结论,模型 1最适合该问题,而模型 4是最差的选择。这四个模型可能是深度神经网络,它们具有不同数量的隐藏层和隐藏层中不同数量的单元:

图 4.8:展示与四个假设模型相关的交叉验证错误率假设模型

图 4.8:展示与四个假设模型相关的交叉验证错误率

在确定了哪种模型最适合特定问题后,下一步是为该模型选择最佳的参数集或超参数。之前我们讨论了,在构建深度神经网络时,模型需要选择多个超参数,而且每个超参数都有多个选择。

这些超参数包括激活函数的类型、损失函数和优化器,以及训练的轮次和批次大小。我们可以定义每个超参数的可能选择集合,然后实现模型并结合交叉验证,找出最佳的超参数组合。

以下图展示了与四组不同超参数集相关的交叉验证误差率的插图。从中我们可以得出结论,超参数集 1是该模型的最佳选择,因为与超参数集 1对应的线条在交叉验证误差率上具有最低的值:

图 4.9:展示与四组不同超参数集相关的交叉验证误差率的插图,针对一个假设的深度学习模型

图 4.9:展示与四组不同超参数集相关的交叉验证误差率的插图,针对一个假设的深度学习模型

在下一个练习中,我们将学习如何遍历不同的模型架构和超参数,以找到结果最优的模型集合。

练习 4.03:编写用户定义的函数来实现带交叉验证的深度学习模型

在本次练习中,您将学习如何使用交叉验证进行模型选择。

首先,加载包含908个数据点的回归问题数据集,每条记录描述了一种化学品的六个属性,目标是其对鱼类 Pimephales promelas 的急性毒性,或LC50。目标是构建一个模型,根据化学品属性预测每种化学品的LC50

# import data
import pandas as pd
import numpy as np
from tensorflow import random
colnames = ['CIC0', 'SM1_Dz(Z)', 'GATS1i', 'NdsCH', \
            'NdssC','MLOGP', 'LC50']
data = pd.read_csv('../data/qsar_fish_toxicity.csv', \
                   sep=';', names=colnames)
X = data.drop('LC50', axis=1)
y = data['LC50']

按照以下步骤完成本次练习:

  1. 定义三个函数,返回三个 Keras 模型。第一个模型应该有一个大小为 4的隐藏层,第二个模型应该有一个大小为 8的隐藏层,第三个模型应该有两个隐藏层,第一个层的大小为 4,第二个层的大小为 2。对于所有隐藏层,使用ReLU 激活函数。目标是找出这三种模型中,哪个模型能带来最低的交叉验证误差率:

    # Define the Keras models
    from keras.models import Sequential
    from keras.layers import Dense
    def build_model_1():
        # build the Keras model_1
        model = Sequential()
        model.add(Dense(4, input_dim=X.shape[1], \
                        activation='relu'))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer='adam')
        # return the model
        return model
    def build_model_2():
        # build the Keras model_2
        model = Sequential()
        model.add(Dense(8, input_dim=X.shape[1], \
                  activation='relu'))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer='adam')
        # return the model
        return model
    def build_model_3():
        # build the Keras model_3
        model = Sequential()
        model.add(Dense(4, input_dim=X.shape[1], \
                        activation='relu'))
        model.add(Dense(2, activation='relu'))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer='adam')
        # return the model
        return model
    
  2. 编写一个循环来构建 Keras 包装器,并对三个模型执行3 折交叉验证。存储每个模型的得分:

    """
    define a seed for random number generator so the result will be reproducible
    """
    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    # perform cross-validation on each model
    from keras.wrappers.scikit_learn import KerasRegressor
    from sklearn.model_selection import KFold
    from sklearn.model_selection import cross_val_score
    results_1 = []
    models = [build_model_1, build_model_2, build_model_3]
    # loop over three models
    for m in range(len(models)):
        model = KerasRegressor(build_fn=models[m], \
                               epochs=100, batch_size=20, \
                               verbose=0, shuffle=False)
        kf = KFold(n_splits=3)
        result = cross_val_score(model, X, y, cv=kf)
        results_1.append(result)
    
  3. 打印每个模型的最终交叉验证误差率,以找出哪个模型的误差率较低:

    # print the cross-validation scores
    print("Cross-Validation Loss for Model 1 =", \
          abs(results_1[0].mean()))
    print("Cross-Validation Loss for Model 2 =", \
          abs(results_1[1].mean()))
    print("Cross-Validation Loss for Model 3 =", \
          abs(results_1[2].mean()))
    

    下面是一个输出示例:

    Cross-Validation Loss for Model 1 = 0.990475798256843
    Cross-Validation Loss for Model 2 = 0.926532513151634
    Cross-Validation Loss for Model 3 = 0.9735719371528117
    

    模型 2的错误率最低,因此我们将在接下来的步骤中使用它。

  4. 再次使用交叉验证确定导致最低交叉验证误差率的模型的轮次和批次大小。编写代码,对epochsbatch_sizeepochs=[100, 150]batch_size=[20, 15]范围内的所有可能组合执行3 折交叉验证并存储得分:

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    results_2 = []
    epochs = [100, 150]
    batches = [20, 15]
    # Loop over pairs of epochs and batch_size
    for e in range(len(epochs)):
        for b in range(len(batches)):
            model = KerasRegressor(build_fn= build_model_3, \
                                   epochs= epochs[e], \
                                   batch_size= batches[b], \
                                   verbose=0, \
                                   shuffle=False)
            kf = KFold(n_splits=3)
            result = cross_val_score(model, X, y, cv=kf)
            results_2.append(result)
    

    注意

    上面的代码块使用了两个 for 循环来执行 3 折交叉验证,针对所有可能的 epochsbatch_size 组合。由于每个参数都有两个选择,因此有四种不同的组合,所以交叉验证将进行四次。

  5. 打印每对 epochs/batch_size 的最终交叉验证错误率,以找出哪个组合的错误率最低:

    """
    Print cross-validation score for each possible pair of epochs, batch_size
    """
    c = 0
    for e in range(len(epochs)):
        for b in range(len(batches)):
            print("batch_size =", batches[b],", \
                  epochs =", epochs[e], ", Test Loss =", \
                  abs(results_2[c].mean()))
            c += 1
    

    这是一个示例输出:

    batch_size = 20 , epochs = 100 , Test Loss = 0.9359159401008821
    batch_size = 15 , epochs = 100 , Test Loss = 0.9642481369794683
    batch_size = 20 , epochs = 150 , Test Loss = 0.9561188386646661
    batch_size = 15 , epochs = 150 , Test Loss = 0.9359079093029896
    

    如你所见,epochs=150batch_size=15,以及 epochs=100batch_size=20 的性能几乎相同。因此,我们将在下一步选择 epochs=100batch_size=20 来加快这一过程。

  6. 再次使用交叉验证,以决定隐藏层的激活函数和模型的优化器,选择范围为 activations = ['relu', 'tanh']optimizers = ['sgd', 'adam', 'rmsprop']。记得使用上一步骤中最佳的 batch_sizeepochs 组合:

    # Modify build_model_2 function
    def build_model_2(activation='relu', optimizer='adam'):
        # build the Keras model_2
        model = Sequential()
        model.add(Dense(8, input_dim=X.shape[1], \
                  activation=activation))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer=optimizer)
        # return the model
        return model
    results_3 = []
    activations = ['relu', 'tanh']
    optimizers = ['sgd', 'adam', 'rmsprop']
    """
    Define a seed for the random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # Loop over pairs of activation and optimizer
    for o in range(len(optimizers)):
        for a in range(len(activations)):
            optimizer = optimizers[o]
            activation = activations[a]
            model = KerasRegressor(build_fn= build_model_3, \
                                   epochs=100, batch_size=20, \
                                   verbose=0, shuffle=False)
            kf = KFold(n_splits=3)
            result = cross_val_score(model, X, y, cv=kf)
            results_3.append(result)
    

    注意

    注意,我们必须通过将 activationoptimizer 及其默认值作为函数的参数来修改 build_model_2 函数。

  7. 打印每对 activationoptimizer 的最终交叉验证错误率,以找出哪个组合的错误率最低:

    """
    Print cross-validation score for each possible pair of optimizer, activation
    """
    c = 0
    for o in range(len(optimizers)):
        for a in range(len(activations)):
            print("activation = ", activations[a],", \
                  optimizer = ", optimizers[o], ", \
                  Test Loss = ", abs(results_3[c].mean()))
            c += 1
    

    这是输出:

    activation =  relu , optimizer =  sgd , Test Loss =  1.0123592540516995
    activation =  tanh , optimizer =  sgd , Test Loss =  3.393908379781118
    activation =  relu , optimizer =  adam , Test Loss =  0.9662686089392641
    activation =  tanh , optimizer =  adam , Test Loss =  2.1369285960222144
    activation =  relu , optimizer =  rmsprop , Test Loss =  2.1892826984214984
    activation =  tanh , optimizer =  rmsprop , Test Loss =  2.2029884275363014
    
  8. activation='relu'optimizer='adam' 的组合产生了最低的错误率。同时,activation='relu'optimizer='sgd' 的组合结果几乎一样好。因此,我们可以在最终模型中使用这两种优化器之一来预测这个数据集的水生毒性。

    注意

    要访问此特定部分的源代码,请参考 packt.live/2BYCwbg

    你也可以在网上运行这个示例,网址是 packt.live/3gofLfP

现在,你已经准备好使用交叉验证在另一个数据集上练习模型选择。在活动 4.02使用交叉验证进行模型选择以诊断高级纤维化分类器中,你将通过自己实现这些步骤,在肝炎 C 数据集的分类问题上进一步练习。

注意

练习 4.02使用交叉验证评估深度神经网络,以及 练习 4.03编写用户定义的函数实现带交叉验证的深度学习模型,涉及多次执行 k 折交叉验证,因此步骤可能需要几分钟才能完成。如果它们需要太长时间才能完成,你可以尝试通过减少折数或训练轮数(epochs)或增加批次大小来加速过程。显然,如果这样做,你将获得与预期输出不同的结果,但选择模型和超参数的原则仍然适用。

活动 4.02:使用交叉验证进行模型选择以诊断高级纤维化分类器

在本活动中,我们将通过使用交叉验证来选择模型和超参数,以改进我们的肝炎 C 数据集分类器。请按照以下步骤操作:

  1. 导入所需的包。从 GitHub 的Chapter04文件夹中的data子文件夹加载数据集,使用X = pd.read_csv('../data/HCV_feats.csv'), y = pd.read_csv('../data/HCV_target.csv')

  2. 定义三个函数,每个函数返回一个不同的 Keras 模型。第一个 Keras 模型将是一个深度神经网络,具有三个隐藏层,每个隐藏层的大小为 4,并使用ReLU 激活函数。第二个 Keras 模型将是一个深度神经网络,具有两个隐藏层,第一个隐藏层的大小为 4,第二个隐藏层的大小为 2,并使用ReLU 激活函数。第三个 Keras 模型将是一个深度神经网络,具有两个隐藏层,两个隐藏层的大小为 8,并使用ReLU 激活函数。使用以下超参数值:

    optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy']

  3. 编写代码,遍历三个模型并对每个模型执行5 折交叉验证(在此步骤中使用epochs=100batch_size=20shuffle=False)。将所有的交叉验证得分存储在一个列表中,并打印结果。哪个模型结果获得了最佳准确率?

    注意

    本活动的步骤 345涉及分别对三个、四个和六个模型执行5 折交叉验证。因此,它们可能需要一些时间来完成。

  4. 编写代码,使用epochs = [100, 200]batches = [10, 20]的值分别作为epochsbatch_size。对每一对可能的组合在 Keras 模型上进行 5 折交叉验证,该模型是在第 3 步中得到的最佳准确率。将所有的交叉验证得分存储在一个列表中,并打印结果。哪个epochsbatch_size的组合结果获得了最佳准确率?

  5. 编写代码,使用optimizers = ['rmsprop', 'adam', 'sgd']activations = ['relu', 'tanh']的值分别作为optimizeractivation。对每一对可能的组合在 Keras 模型上进行 5 折交叉验证,该模型是在第 3 步中得到的最佳准确率。使用在第 4 步中得到的最佳准确率的batch_sizeepochs值。将所有的交叉验证得分存储在一个列表中,并打印结果。哪个optimizeractivation的组合结果获得了最佳准确率?

    注意

    请注意,初始化权重和偏差以及在执行 k 折交叉验证时选择哪些示例纳入每一折中,都存在随机性。因此,如果你运行相同的代码两次,可能会得到完全不同的结果。为此,在构建和训练神经网络时,以及在执行交叉验证时,设置随机种子非常重要。通过这样做,你可以确保每次重新运行代码时,使用的是完全相同的神经网络初始化,以及完全相同的训练集和测试集。

实现这些步骤后,预期输出将如下所示:

activation =  relu , optimizer =  rmsprop , Test accuracy =  0.5234657049179077
activation =  tanh , optimizer =  rmsprop , Test accuracy =  0.49602887630462644
activation =  relu , optimizer =  adam , Test accuracy =  0.5039711117744445
activation =  tanh , optimizer =  adam , Test accuracy =  0.4989169597625732
activation =  relu , optimizer =  sgd , Test accuracy =  0.48953068256378174
activation =  tanh , optimizer =  sgd , Test accuracy =  0.5191335678100586

注意

这个活动的解决方案可以在第 384 页找到。

在本次活动中,你学习了如何使用交叉验证评估深度神经网络,以找出使分类问题的错误率最低的模型。你还学习了如何通过使用交叉验证来改进给定的分类模型,从而找到最佳的超参数集。在下一次活动中,我们将重复这个过程,并将任务转换为回归问题。

活动 4.03:使用交叉验证进行交通流量数据集的模型选择

在本次活动中,你将再次通过交叉验证进行模型选择练习。在这里,我们将使用一个模拟数据集,数据集表示一个目标变量,代表城市桥梁上每小时的交通流量,以及与交通数据相关的各种归一化特征,如一天中的时间和前一天的交通流量。我们的目标是构建一个模型,基于这些特征预测城市桥梁上的交通流量。

数据集包含10000条记录,每条记录包括10个属性/特征。目标是构建一个深度神经网络,该网络接收10个特征并预测桥上的交通流量。由于输出是一个数字,这个问题是一个回归问题。让我们开始吧:

  1. 导入所有必需的包。

  2. 打印输入和输出的大小,检查数据集中示例的数量以及每个示例的特征数量。此外,你还可以打印输出的范围(该数据集中的输出代表拥有者自住房屋的中位数,单位为千美元)。

  3. 定义三个函数,每个函数返回一个不同的 Keras 模型。第一个 Keras 模型将是一个浅层神经网络,包含一个隐藏层,隐藏层大小为10,并使用ReLU 激活函数。第二个 Keras 模型将是一个深层神经网络,包含两个隐藏层,每个隐藏层的大小为10,并使用每层的ReLU 激活函数。第三个 Keras 模型将是一个深层神经网络,包含三个隐藏层,每个隐藏层的大小为10,并使用每层的ReLU 激活函数。

    还需使用以下值:

    optimizer = 'adam', loss = 'mean_squared_error'

    注意

    步骤 456 涉及分别执行5 折交叉验证三次、四次和三次。因此,它们可能需要一些时间才能完成。

  4. 编写代码,循环遍历这三个模型,并对每个模型执行5 折交叉验证(在这一步中使用epochs=100batch_size=5,并且shuffle=False)。将所有交叉验证得分存储在一个列表中并打印结果。哪个模型得到了最低的测试错误率?

  5. 编写代码,使用epochs = [80, 100]batches = [5, 10]作为epochsbatch_size的值。对在第 4 步中获得最低测试误差率的 Keras 模型进行 5 折交叉验证。将所有交叉验证的得分存储在列表中并打印结果。哪一对epochsbatch_size值导致最低的测试误差率?

  6. 编写代码,使用optimizers = ['rmsprop', 'sgd', 'adam']并对每个可能的优化器进行5 折交叉验证,以评估在第 4 步中获得最低测试误差率的 Keras 模型。使用在第 5 步中获得最低测试误差率的batch_sizeepochs值。将所有交叉验证的得分存储在列表中并打印结果。哪种优化器导致最低的测试误差率?

实现这些步骤后,预期输出如下:

optimizer= adam  test error rate =  25.391812739372256
optimizer= sgd  test error rate =  25.140230269432067
optimizer= rmsprop  test error rate =  25.217947859764102

注意

本活动的解答可在第 391 页找到。

在这个活动中,你学习了如何使用交叉验证来评估深度神经网络,从而找到产生最低误差率的回归问题模型。此外,你还学会了如何通过交叉验证改进给定的回归模型,以便找到最佳的超参数集。

摘要

在本章中,你学习了交叉验证,这是最重要的重采样方法之一。它能够对模型在独立数据上的表现进行最佳估计。本章介绍了交叉验证的基本知识以及它的两种不同变体,留一法和 k 折交叉验证,并进行了比较。

接下来,我们介绍了带有 scikit-learn 的 Keras 封装器,这是一个非常有用的工具,它允许将执行交叉验证的 scikit-learn 方法和函数轻松应用于 Keras 模型。随后,你将学习如何按照一步步的过程实现交叉验证,以便使用带有 scikit-learn 的 Keras 封装器评估 Keras 深度学习模型。

最后,你了解到,交叉验证对模型性能的估计可以用于在不同模型之间做出选择,或者决定某个模型应使用哪些参数(或超参数)。你通过编写用户自定义函数并执行交叉验证,练习了如何使用交叉验证来选择最终模型的最佳模型或超参数组合,从而使测试误差率最小。

在下一章,你将学习到,实际上我们在这里为模型寻找最佳超参数集的过程,称为超参数调优超参数优化。此外,你将学习如何通过 scikit-learn 中的一种方法网格搜索来进行超参数调优,并且无需编写用户自定义函数来遍历可能的超参数组合。

第五章:5. 提高模型准确性

概述

本章介绍了神经网络的正则化概念。正则化的目的是防止模型在训练过程中对训练数据过拟合,并在模型在新数据上进行测试时提供更准确的结果。你将学会使用不同的正则化技术——L1 和 L2 正则化以及丢弃法正则化——来提高模型的表现。正则化是一个重要的组成部分,因为它可以防止神经网络对训练数据过拟合,帮助我们构建出在新数据上表现良好的健壮、准确的模型。通过本章的学习,你将能够在 scikit-learn 中实现网格搜索和随机搜索,找到最优的超参数。

引言

在上一章中,我们继续通过实验交叉验证,发展了使用神经网络创建准确模型的知识。交叉验证是一种无偏地测试各种超参数表现的方法。我们使用了留一法交叉验证,其中我们将一个记录从训练过程中留下,作为验证使用,并对数据集中的每一条记录重复这一过程。接着,我们介绍了 k 折交叉验证,我们将训练数据集分为k个折叠,在k-1个折叠上训练模型,并使用最后一个折叠进行验证。这些交叉验证方法允许我们使用不同的超参数训练模型,并在无偏数据上测试它们的表现。

深度学习不仅仅是构建神经网络,使用现有的数据集训练它们,并报告模型的准确性。它还涉及理解你的模型和数据集,并且通过改善许多方面,使基本模型超越。 在本章中,你将学习到两大类对提高机器学习模型(尤其是深度学习模型)非常重要的技术。这些技术分别是正则化方法和超参数调优。

本章将进一步介绍正则化方法——特别是为什么我们需要它们,以及它们如何帮助我们。接着,我们将介绍两种最重要和最常用的正则化技术。在这里,你将详细了解参数正则化及其两种变体,L1L2范数正则化。然后,你将了解一种专门为神经网络设计的正则化技术——丢弃法(Dropout Regulation)。你还将通过完成涉及真实数据集的活动,实践在 Keras 模型上实现这些技术。最后,我们将简要介绍一些其他正则化技术,这些技术在你后续的工作中可能会有所帮助。

接下来,我们将讨论超参数调优的重要性,特别是对于深度学习模型,探讨如何调整超参数的值会显著影响模型的准确性,以及在构建深度神经网络时调优多个超参数的挑战。你将学习到两个非常有用的 scikit-learn 方法,这些方法可以用来对 Keras 模型进行超参数调优,并了解每种方法的优缺点,以及如何将它们结合起来,以便从中获得最大的收益。最后,你将通过完成一个实践活动来练习使用 scikit-learn 优化器为 Keras 模型实施超参数调优。

正则化

由于深度神经网络是高度灵活的模型,过拟合是训练过程中经常会遇到的问题。因此,成为深度学习专家的一个非常重要的部分是知道如何检测过拟合,并且随后如何解决模型中的过拟合问题。如果你的模型在训练数据上表现优异,但在新、未见过的数据上表现较差,那么模型的过拟合问题就很明显了。

例如,如果你构建了一个模型来将狗和猫的图像分类到各自的类别,并且你的图像分类器在训练过程中表现出高准确率,但在新数据上表现不佳,这就表明你的模型对训练数据过拟合。正则化技术是一类重要的方法,专门用于减少机器学习模型的过拟合问题。

彻底理解正则化技术,并能够将其应用到你的深度神经网络中,是构建深度神经网络以解决现实问题的重要一步。在本节中,你将了解正则化的基本概念,这将为后续的章节奠定基础,在这些章节中,你将学习如何使用 Keras 实现各种类型的正则化方法。

正则化的必要性

机器学习的主要目标是构建在训练数据上表现良好,并且能在新的、未包含在训练中的示例上同样表现良好的模型。一个好的机器学习模型应该能找到产生训练数据的真实底层过程/函数的形式和参数,而不是捕捉到个别训练数据的噪声。这样的模型能够很好地泛化到后续由相同过程生成的新数据上。

我们之前讨论的几种方法——比如将数据集分为训练集和测试集,以及交叉验证——都是为了估计训练模型的泛化能力。事实上,用于描述测试集误差和交叉验证误差的术语是“泛化误差”。这意味着在未用于训练的样本上的误差率。再次强调,机器学习的主要目标是构建具有低泛化误差率的模型。

第三章,*《使用 Keras 进行深度学习》*中,我们讨论了机器学习模型的两个非常重要的问题:过拟合和欠拟合。我们指出,欠拟合是指估计的模型不够灵活或复杂,无法捕捉到与真实过程相关的所有关系和模式。这是一个高偏差的模型,并且在训练误差较高时会被发现。另一方面,过拟合是指用于估计真实过程的模型过于灵活或复杂。这是一个高方差的模型,并且当训练误差和泛化误差之间存在较大差距时被诊断出来。以下图像概述了二分类问题中的这些情境:

图 5.1:欠拟合

图 5.1:欠拟合

如上所示,欠拟合比过拟合是一个较少有问题的情况。事实上,欠拟合可以通过让模型变得更加灵活/复杂来轻松修复。在深度神经网络中,这意味着改变网络的架构,通过添加更多层或增加层中的单元数来使网络变大。

现在让我们来看下面的过拟合图像:

图 5.2:过拟合

图 5.2:过拟合

类似地,解决过拟合有一些简单的方案,比如让模型变得不那么灵活/复杂(同样是通过改变网络的架构)或者提供更多的训练样本。然而,让网络变得不那么复杂有时会导致偏差或训练误差率的剧烈增加。原因在于,大多数情况下,过拟合的原因不是模型的灵活性,而是训练样本太少。另一方面,为了减少过拟合,提供更多的数据样本并非总是可能的。因此,找到在保持模型复杂度和训练样本数量不变的情况下减少泛化误差的方法,既重要又具有挑战性。

现在让我们来看下面的右侧拟合图像:

图 5.3:右侧拟合

图 5.3:右侧拟合

这就是为什么在构建高度灵活的机器学习模型(如深度神经网络)时,我们需要正则化技术,以抑制模型的灵活性,避免其对单个样本进行过拟合。在接下来的章节中,我们将描述正则化方法如何减少模型在训练数据上的过拟合,从而降低模型的方差。

通过正则化减少过拟合

正则化方法试图通过修改学习算法,减少模型的方差。通过减少方差,正则化技术旨在降低泛化误差,同时不会显著增加训练误差(或者至少不会大幅增加训练误差)。

正则化方法提供了一种限制,有助于模型的稳定性。实现这一点有几种方式。对深度神经网络进行正则化的最常见方法之一是对权重施加某种惩罚项,以保持权重较小。

保持权重较小使得网络对个别数据样本中的噪声不那么敏感。在神经网络中,权重实际上是决定每个处理单元对网络最终输出影响大小的系数。如果单元的权重大,这意味着每个单元对输出的影响都会很大。将所有处理单元产生的巨大影响结合起来,最终输出将会有很多波动。

另一方面,保持权重较小减少了每个单元对最终输出的影响。实际上,通过将权重保持接近零,某些单元将几乎对输出没有影响。训练一个大型神经网络,其中每个单元对输出的影响微乎其微,相当于训练一个更简单的网络,从而减少了方差和过拟合。下图展示了正则化如何使大网络中某些单元的影响为零的示意图:

图 5.4:正则化如何使大网络中某些单元的影响为零的示意图

图 5.4:正则化如何使大网络中某些单元的影响为零的示意图

上面的图是正则化过程的示意图。顶部的网络展示了没有正则化的网络,而底部的网络则展示了一个应用了正则化的网络示例,其中白色单元代表那些由于正则化过程的惩罚几乎对输出没有影响的单元。

到目前为止,我们已经学习了正则化背后的概念。在接下来的章节中,我们将了解深度学习模型最常见的正则化方法——L1L2 和 dropout 正则化,以及如何在 Keras 中实现它们。

L1 和 L2 正则化

深度学习模型中最常见的正则化类型是保持网络权重较小的正则化。这种正则化被称为权重正则化,并且有两种不同的变体:L2 正则化L1 正则化。在本节中,您将详细了解这些正则化方法,并学习如何在 Keras 中实现它们。此外,您还将练习将它们应用于现实问题,并观察它们如何提高模型的性能。

L1 和 L2 正则化公式

在权重正则化中,一个惩罚项被添加到损失函数中。这个项通常是L1 正则化。如果使用 L2 范数,则称之为L2 正则化。在每种情况下,这个和将乘以一个超参数,称为正则化参数lambda)。

因此,对于L1 正则化,公式如下:

损失函数 = 旧损失函数 + lambda * 权重的绝对值和

对于L2 正则化,公式如下:

损失函数 = 旧损失函数 + lambda * 权重的平方和

Lambda可以取任意值,范围从01,其中lambda=0意味着没有惩罚(相当于一个没有正则化的网络),lambda=1意味着完全惩罚。

和其他超参数一样,lambda的正确值可以通过尝试不同的值并观察哪个值提供较低的泛化误差来选择。实际上,最好从没有正则化的网络开始,并观察结果。然后,您应该使用逐渐增大的lambda值进行正则化,如0.0010.010.10.5,并观察每种情况下的结果,以找出对特定问题来说,惩罚权重值的合适程度。

在每次带有正则化的优化算法迭代中,权重(w)会变得越来越小。因此,权重正则化通常被称为权重衰减

到目前为止,我们仅讨论了在深度神经网络中正则化权重。然而,您需要记住,同样的过程也可以应用于偏置。更准确地说,我们可以通过向损失函数中添加一个惩罚偏置的项来更新损失函数,从而在神经网络训练过程中保持偏置的值较小。

注意

如果通过向损失函数添加两个项(一个惩罚权重,一个惩罚偏置)来执行正则化,那么我们称之为参数正则化,而不是权重正则化。

然而,在深度学习中,正则化偏置值并不常见。原因在于,权重是神经网络中更为重要的参数。事实上,通常,添加另一个项来正则化偏置与仅正则化权重值相比,不会显著改变结果。

L2 正则化是一般机器学习中最常见的正则化技术。与L1 正则化的不同之处在于,L1会导致更稀疏的权重矩阵,意味着有更多的权重等于零,因此有更多的节点完全从网络中移除。另一方面,L2 正则化则更为微妙。它会显著减少权重,但同时让你留下更少的权重等于零。同时进行L1L2 正则化也是可能的。

现在你已经了解了L1L2 正则化的工作原理,可以继续在 Keras 中实现深度神经网络上的L1L2 正则化了。

Keras 中的 L1 和 L2 正则化实现

Keras 提供了一个正则化 API,可以用来在每一层的深度神经网络中向损失函数添加惩罚项,以对权重或偏置进行正则化。要定义惩罚项或正则化器,你需要在keras.regularizers下定义所需的正则化方法。

例如,要定义一个L1 正则化器,使用lambda=0.01,你可以这样写:

from keras.regularizers import l1
keras.regularizers.l1(0.01)

同样地,要定义一个L2 正则化器,使用lambda=0.01,你可以这样写:

from keras.regularizers import l2
keras.regularizers.l2(0.01)

最后,要同时定义L1L2 正则化器,使用lambda=0.01,你可以这样写:

from keras.regularizers import l1_l2
keras.regularizers.l1_l2(l1=0.01, l2=0.01)

每个正则化器可以应用于层中的权重和/或偏置。例如,如果我们想在具有八个节点的密集层上同时应用L2 正则化(使用lambda=0.01)到权重和偏置上,我们可以这样写:

from keras.layers import Dense
from keras.regularizers import l2
model.add(Dense(8, kernel_regularizer=l2(0.01), \
          bias_regularizer=l2(0.01)))

我们将在活动 5.01中进一步实践实现L1L2 正则化阿维拉模式分类器的权重正则化,你将为糖尿病数据集的深度学习模型应用正则化,并观察结果与先前活动的比较。

注意

本章中的所有活动将在 Jupyter 笔记本中开发。请从packt.live/2OOBjqq下载本书的 GitHub 存储库以及所有准备好的模板。

活动 5.01:阿维拉模式分类器的权重正则化

阿维拉数据集是从阿维拉圣经的 800 幅图像中提取的,这是 12 世纪的拉丁文圣经的巨大复制品。该数据集包含有关文本图像的各种特征,例如列间距和文本的边距。数据集还包含一个类标签,指示图像模式是否属于最频繁出现的类别。在这个活动中,你将构建一个 Keras 模型,根据给定的网络架构和超参数值对该数据集进行分类。目标是在模型上应用不同类型的权重正则化,并观察每种类型如何改变结果。

在此活动中,我们将使用训练集/测试集方法进行评估,原因有两个。首先,由于我们将尝试几种不同的正则化器,执行交叉验证将需要很长时间。其次,我们希望绘制训练误差和测试误差的趋势图,以便通过视觉方式理解正则化如何防止模型对数据样本过拟合。

按照以下步骤完成此活动:

  1. 从 GitHub 的Chapter05文件夹中的data子文件夹加载数据集,使用X = pd.read_csv('../data/avila-tr_feats.csv')y = pd.read_csv('../data/avila-tr_target.csv')。使用sklearn.model_selection.train_test_split方法将数据集拆分为训练集和测试集。保留20%的数据样本作为测试集。

  2. 定义一个 Keras 模型,包含三个隐藏层,第一个隐藏层的大小为 10,第二个隐藏层的大小为 6,第三个隐藏层的大小为 4,用于执行分类任务。使用以下超参数:activation='relu'loss='binary_crossentropy'optimizer='sgd'metrics=['accuracy']batch_size=20epochs=100shuffle=False

  3. 在训练集上训练模型,并使用测试集进行评估。在每次迭代中存储训练损失和测试损失。训练完成后,绘制训练误差测试误差的趋势图(将纵轴的限制调整为(0, 1),这样可以更好地观察损失的变化)。在测试集上的最小误差率是多少?

  4. 向模型的隐藏层添加L2 正则化器,其中lambda=0.01,并重复训练。训练完成后,绘制训练误差和测试误差的趋势图。在测试集上的最小误差率是多少?

  5. lambda=0.1lambda=0.005重复前面的步骤,针对每个lambda值训练模型,并报告结果。哪个lambda值对于在该深度学习模型和数据集上执行L2 正则化更为合适?

  6. 重复前面的步骤,这次使用L1 正则化器,针对lambda=0.01lambda=0.005训练模型,并报告结果。哪个lambda值对于在该深度学习模型和数据集上执行L1 正则化更为合适?

  7. 向模型的隐藏层添加L1_L2 正则化器,其中L1 lambda=0.005L2 lambda=0.005,并重复训练。训练完成后,绘制训练误差和测试误差的趋势图。在测试集上的最小误差率是多少?

完成这些步骤后,你应该得到以下预期输出:

![图 5.5:模型在训练过程中,L1 lambda 为 0.005,L2 lambda 为 0.005 时的训练误差和验证误差的趋势图]

](github.com/OpenDocCN/f…)

图 5.5:模型在训练过程中,L1 lambda 为 0.005,L2 lambda 为 0.005 时的训练误差和验证误差的趋势图

注释

该活动的解决方案可以在第 398 页找到。

在这个活动中,你实践了为实际问题实现L1L2权重正则化,并将正则化模型的结果与没有任何正则化的模型进行比较。在下一节中,我们将探讨另一种技术的正则化方法,称为 dropout 正则化。

Dropout 正则化

在本节中,你将学习 dropout 正则化的工作原理,它如何帮助减少过拟合,以及如何使用 Keras 实现它。最后,你将通过完成一个涉及实际数据集的活动,来实践你学到的关于 dropout 的知识。

Dropout 正则化的原理

Dropout 正则化通过在训练过程中随机移除神经网络中的节点来工作。更准确地说,dropout 在每个节点上设置一个概率。这个概率表示在每次学习算法的迭代中,该节点被包含在训练中的可能性。假设我们有一个大型神经网络,其中每个节点的 dropout 概率为0.5。在这种情况下,在每次迭代中,学习算法会为每个节点掷一次硬币,决定是否将该节点从网络中移除。以下图示展示了这一过程:

图 5.6:使用 dropout 正则化从深度神经网络中移除节点的示意图

图 5.6:使用 dropout 正则化从深度神经网络中移除节点的示意图

这个过程在每次迭代时都会重复;这意味着,在每次迭代中,随机选择的节点会从网络中移除,这也意味着参数更新过程将在一个不同的较小网络上进行。例如,前图底部显示的网络只会用于一次训练迭代。对于下一次迭代,另一些随机选中的节点会从顶部网络中被删除,因此从这些节点中移除后的网络将与图中的底部网络不同。

当某些节点在学习算法的某次迭代中被选择移除/忽略时,这意味着它们在该次迭代中的参数更新过程中完全不参与。更准确地说,前向传播以预测输出、损失计算和反向传播以计算导数的所有操作都将在移除一些节点的较小网络上进行。因此,参数更新将仅在该次迭代中存在于网络中的节点上进行;被移除节点的权重和偏置将不会被更新。

然而,需要牢记的是,在测试集或保留集上评估模型的性能时,始终使用原始的完整网络。如果我们用随机删除节点的网络进行评估,结果会引入噪声,这样是不可取的。

dropout 正则化中,训练始终在通过随机移除原始网络中的部分节点所得到的网络上进行。评估则始终使用原始网络进行。在接下来的部分,我们将了解为什么 dropout 正则化有助于防止过拟合。

使用 Dropout 减少过拟合

在本节中,我们将讨论 dropout 作为正则化方法背后的概念。正如我们之前讨论的,正则化技术的目标是防止模型对数据过拟合。因此,我们将研究如何通过随机移除神经网络中的一部分节点来帮助减少方差和过拟合。

删除网络中的随机节点防止过拟合的最明显解释是,通过从网络中移除节点,我们是在对比原始网络训练一个更小的网络。如你之前所学,一个更小的神经网络提供的灵活性较低,因此网络对数据过拟合的可能性较小。

还有一个原因,解释了为什么 dropout 正则化如此有效地减少过拟合。通过在深度神经网络的每一层随机移除输入,整个网络变得对单一输入不那么敏感。我们知道,在训练神经网络时,权重会以某种方式更新,使得最终的模型能够适应训练样本。通过随机移除一些权重参与训练过程,dropout 强制其他权重参与学习与训练样本相关的模式,因此最终的权重值会更好地分散。

换句话说,不是某些权重为了拟合某些输入值而过度更新,而是所有权重都参与学习这些输入值,从而导致过拟合减少。这就是为什么执行 dropout 比仅仅使用更小的网络更能产生一个更强大的模型——在新数据上表现更好。实际上,dropout 正则化在更大的网络上效果更佳。

现在你已经了解了 dropout 的基本过程及其有效性背后的原因,我们可以继续在 Keras 中实现dropout 正则化

练习 5.01:在 Keras 中实现 Dropout

Dropout 正则化作为 Keras 中的核心层提供。因此,你可以像添加其他层到网络一样,将 dropout 添加到模型中。在 Keras 中定义 dropout 层时,你需要提供 rate 超参数作为参数。rate 可以是一个介于 01 之间的值,决定了要移除或忽略的输入单元的比例。在这个练习中,你将学习如何一步步实现带有 dropout 层的 Keras 深度学习模型。

我们的模拟数据集表示了树木的各种测量数据,如树高、树枝数和树干底部的胸围。我们的目标是根据给定的测量值将记录分类为落叶树(类值为1)或针叶树(类值为0)。数据集包含10000条记录,代表两种树种的两个类别,每个数据实例有10个特征值。请按照以下步骤完成此练习:

  1. 首先,执行以下代码块以加载数据集,并将数据集拆分为训练集测试集

    # Load the data
    import pandas as pd
    X = pd.read_csv('../data/tree_class_feats.csv')
    y = pd.read_csv('../data/tree_class_target.csv')
    """
    Split the dataset into training set and test set with a 80-20 ratio
    """
    from sklearn.model_selection import train_test_split
    seed = 1
    X_train, X_test, \
    y_train, y_test = train_test_split(X, y, \
                                       test_size=0.2, \
                                       random_state=seed)
    
  2. 导入所有必要的依赖项。构建一个四层的 Keras 顺序模型,且不使用dropout 正则化。构建该网络时,第一隐藏层有 16 个单元,第二隐藏层有12个单元,第三隐藏层有8个单元,第四隐藏层有4个单元,所有层都使用ReLU 激活函数。添加一个具有sigmoid 激活函数的输出层:

    #Define your model
    from keras.models import Sequential
    from keras.layers import Dense, Activation
    import numpy as np
    from tensorflow import random
    np.random.seed(seed)
    random.set_seed(seed)
    model_1 = Sequential()
    model_1.add(Dense(16, activation='relu', input_dim=10))
    model_1.add(Dense(12, activation='relu'))
    model_1.add(Dense(8, activation='relu'))
    model_1.add(Dense(4, activation='relu'))
    model_1.add(Dense(1, activation='sigmoid'))
    
  3. 使用binary cross-entropy作为loss函数,sgd作为优化器,编译模型,并在训练集上以batch_size=50进行300个 epochs 的训练。然后,在测试集上评估训练好的模型:

    model_1.compile(optimizer='sgd', loss='binary_crossentropy')
    # train the model
    model_1.fit(X_train, y_train, epochs=300, batch_size=50, \
                verbose=0, shuffle=False)
    # evaluate on test set
    print("Test Loss =", model_1.evaluate(X_test, y_test))
    

    这是预期的输出:

    2000/2000 [==============================] - 0s 23us/step
    Test Loss = 0.1697693831920624
    

    因此,经过300个 epochs 训练后的树种预测测试误差率为16.98%

  4. 使用与之前模型相同的层数和每层相同大小重新定义模型。然而,在模型的第一隐藏层添加rate=0.1dropout 正则化,然后重复编译、训练和在测试数据上评估模型的步骤:

    """
    define the keras model with dropout in the first hidden layer
    """
    from keras.layers import Dropout
    np.random.seed(seed)
    random.set_seed(seed)
    model_2 = Sequential()
    model_2.add(Dense(16, activation='relu', input_dim=10))
    model_2.add(Dropout(0.1))
    model_2.add(Dense(12, activation='relu'))
    model_2.add(Dense(8, activation='relu'))
    model_2.add(Dense(4, activation='relu'))
    model_2.add(Dense(1, activation='sigmoid'))
    model_2.compile(optimizer='sgd', loss='binary_crossentropy')
    # train the model
    model_2.fit(X_train, y_train, \
                epochs=300, batch_size=50, \
                verbose=0, shuffle=False)
    # evaluate on test set
    print("Test Loss =", model_2.evaluate(X_test, y_test))
    

    这是预期的输出:

    2000/2000 [==============================] - 0s 29us/step
    Test Loss = 0.16891103076934816
    

    在网络的第一层添加rate=0.1的 dropout 正则化后,测试误差率从16.98%降低到了16.89%

  5. 使用与之前模型相同的层数和每层相同大小重新定义模型。然而,在第一隐藏层添加rate=0.2的 dropout 正则化,并在模型的其余层添加rate=0.1的 dropout 正则化,然后重复编译、训练和在测试数据上评估模型的步骤:

    # define the keras model with dropout in all hidden layers
    np.random.seed(seed)
    random.set_seed(seed)
    model_3 = Sequential()
    model_3.add(Dense(16, activation='relu', input_dim=10))
    model_3.add(Dropout(0.2))
    model_3.add(Dense(12, activation='relu'))
    model_3.add(Dropout(0.1))
    model_3.add(Dense(8, activation='relu'))
    model_3.add(Dropout(0.1))
    model_3.add(Dense(4, activation='relu'))
    model_3.add(Dropout(0.1))
    model_3.add(Dense(1, activation='sigmoid'))
    model_3.compile(optimizer='sgd', loss='binary_crossentropy')
    # train the model
    model_3.fit(X_train, y_train, epochs=300, \
                batch_size=50, verbose=0, shuffle=False)
    # evaluate on test set
    print("Test Loss =", model_3.evaluate(X_test, y_test))
    

    这是预期的输出:

    2000/2000 [==============================] - 0s 40us/step
    Test Loss = 0.19390961921215058
    

在第一层保持rate=0.2的 dropout 正则化的同时,添加rate=0.1的 dropout 正则化到后续层,测试误差率从16.89%上升到19.39%。像L1L2 正则化一样,添加过多的 dropout 会阻止模型学习与训练数据相关的潜在函数,从而导致比没有 dropout 正则化时更高的偏差。

如你在本次练习中看到的,你还可以根据认为在各个层上可能发生的过拟合情况,对不同层使用不同的 dropout 比例。通常,我们不建议在输入层和输出层使用 dropout。对于隐藏层,我们需要调整 rate 值,并观察结果,以便确定哪个值最适合特定问题。

注意

要查看此特定部分的源代码,请参考packt.live/3iugM7K

你也可以在线运行这个示例,网址是packt.live/31HlSYo

在接下来的活动中,你将练习在 Keras 中实现深度学习模型,并结合 Dropout 正则化来处理交通流量数据集。

活动 5.02:在交通流量数据集上应用 Dropout 正则化

活动 4.03在交通流量数据集上使用交叉验证进行模型选择第四章使用 Keras 包装器评估模型并进行交叉验证 中,你使用交通流量数据集构建了一个模型,预测给定一系列与交通数据相关的标准化特征(如一天中的时间和前一天的交通量等)时城市桥梁上的交通流量。该数据集包含 10000 条记录,每条记录包含 10 个特征。

在本次活动中,你将从 活动 4.03在交通流量数据集上使用交叉验证进行模型选择第四章使用 Keras 包装器评估模型并进行交叉验证 开始。你将使用训练集/测试集方法来训练和评估模型,绘制训练误差和泛化误差的趋势,并观察模型的过拟合情况。接下来,你将尝试通过使用 dropout 正则化来解决过拟合问题,从而提升模型性能。具体来说,你将尝试找出应该在哪些层添加 dropout 正则化,并找到最佳的 rate 值来最大程度地改进该模型。完成此活动的步骤如下:

  1. 使用 pandas 的 read_csv 函数加载数据集。数据集也存储在 Chapter05 GitHub 仓库的 data 子文件夹中。将数据集按 80-20 比例分割为训练集和测试集。

  2. 定义一个 Keras 模型,包含两个隐藏层,每个隐藏层的size10,用于预测交通流量。使用以下超参数:activation='relu'loss='mean_squared_error'optimizer='rmsprop'batch_size=50epochs=200shuffle=False

  3. 在训练集上训练模型,并在测试集上评估。在每次迭代时存储训练损失和测试损失。

  4. 训练完成后,绘制训练误差和测试误差的趋势。训练集和测试集的最低误差率是多少?

  5. 向你的模型的第一个隐藏层添加rate=0.1的 dropout 正则化,并重复训练过程(由于训练时使用了 dropout,训练时间较长,请训练200个 epoch)。训练完成后,绘制训练误差和测试误差的趋势。训练集和测试集上的最低误差率分别是多少?

  6. 重复之前的步骤,这次向你的模型的两个隐藏层添加rate=0.1的 dropout 正则化,并训练模型并报告结果。

  7. 重复之前的步骤,这次在第一个隐藏层使用rate=0.2,在第二个隐藏层使用0.1,训练模型并报告结果。

  8. 到目前为止,哪种 dropout 正则化方法在这个深度学习模型和数据集上取得了最佳性能?

实施这些步骤后,你应该得到以下预期的输出:

图 5.7:训练过程中使用 dropout 正则化的训练误差和验证误差图,第一个隐藏层的 rate=0.2,第二个隐藏层的 rate=0.1

图 5.7:训练过程中使用 dropout 正则化的训练误差和验证误差图,第一个隐藏层的 rate=0.2,第二个隐藏层的 rate=0.1

注意

这个活动的解决方案可以在第 413 页找到。

在这个活动中,你学习了如何在 Keras 中实现 dropout 正则化,并在涉及交通流量数据集的问题中进行了实践。Dropout 正则化专门用于减少神经网络中的过拟合,其原理是通过在训练过程中随机去除神经网络中的节点。这个过程导致神经网络的权重值分布更加均匀,从而减少了单个数据样本的过拟合。接下来的章节中,我们将讨论其他可以应用于防止模型在训练数据上过拟合的正则化方法。

其他正则化方法

在这一节中,你将简要了解一些常用的正则化技术,这些技术在深度学习中被证明是有效的。需要牢记的是,正则化是机器学习中一个广泛且活跃的研究领域。因此,在一章中涵盖所有可用的正则化方法是不可能的(而且大多数情况下并不必要,尤其是在一本关于应用深度学习的书中)。因此,在这一节中,我们将简要介绍另外三种正则化方法,分别是提前停止数据增强添加噪声。你将了解它们的基本原理,并获得一些如何使用它们的技巧和建议。

提前停止

本章早些时候我们讨论了机器学习的主要假设是存在一个真实的函数或过程来生成训练样本。然而,这个过程是未知的,且没有明确的方法可以找到它。不仅找不到确切的底层过程,而且选择一个具有适当灵活性或复杂度的模型来估计这个过程也很具挑战性。因此,一种好的做法是选择一个高灵活度的模型,比如深度神经网络,来建模这个过程,并仔细监控训练过程。

通过监控训练过程,我们可以在模型捕捉到过程的形式时及时停止训练,避免在模型开始对单个数据样本过拟合时继续训练。这就是早停背后的基本思想。我们在第三章使用 Keras 的深度学习模型评估部分简要讨论了早停的概念。我们提到,通过监控和观察训练过程中 训练误差测试误差 的变化,我们可以判断训练量过少或过多的界限。

下图展示了在训练高度灵活的模型时,训练误差和测试误差的变化情况。正如我们所见,训练需要在标记为合适拟合的区域停止,以避免过拟合:

图 5.8:训练模型时训练误差和测试误差的变化图

图 5.8:训练模型时训练误差和测试误差的变化图

第三章使用 Keras 的深度学习中,我们练习了存储和绘制训练误差和测试误差的变化,以识别过拟合。你学到,在训练 Keras 模型时,你可以提供验证集或测试集,并通过以下代码在每个训练周期中存储它们的指标值:

history=model.fit(X_train, y_train, validation_data=(X_test, y_test), \
                  epochs=epochs)

在本节中,你将学习如何在 Keras 中实现早停。这意味着在 Keras 模型训练时,当某个期望的指标——例如,测试误差率——不再改善时,强制停止训练。为此,你需要定义一个 EarlyStopping() 回调函数,并将其作为参数提供给 model.fit()

在定义 EarlyStopping() 回调函数时,你需要为其提供正确的参数。第一个参数是 monitor,它决定了在训练过程中监控哪个指标来执行早停。通常,monitor='val_loss' 是一个不错的选择,这意味着我们希望监控测试误差率。

此外,根据你为 monitor 选择的参数,你需要将 mode 参数设置为 'min''max'。如果指标是误差/损失,我们希望将其最小化。例如,以下代码块定义了一个 EarlyStopping() 回调函数,用于在训练过程中监控测试误差,并检测其是否不再减少:

from keras.callbacks import EarlyStopping
es_callback = EarlyStopping(monitor='val_loss', mode='min')

如果误差率波动很大或噪声较多,那么在损失开始增加时就立即停止训练可能并不是一个好主意。因此,我们可以将patience参数设置为一定的 epoch 数量,给早停方法一些时间,在停止训练过程之前,能够监控目标度量值更长时间:

es_callback = EarlyStopping(monitor='val_loss', \
                            mode='min', patience=20)

我们还可以修改EarlyStopping()回调函数,如果在过去的epoch内,monitor度量没有发生最小的改进,或者monitor度量已达到基准水平时,停止训练过程:

es_callback = EarlyStopping(monitor='val_loss', \
                            mode='min', min_delta=1)
es_callback = EarlyStopping(monitor='val_loss', \
                            mode='min', baseline=0.2)

在定义了EarlyStopping()回调函数后,可以将其作为callbacks参数传递给model.fit()并训练模型。训练将根据EarlyStopping()回调函数自动停止:

history=model.fit(X_train, y_train, validation_data=(X_test, y_test), \
                  epochs=epochs, callbacks=[es_callback])

我们将在下一个练习中探索如何在实际中实现早停。

练习 5.02:在 Keras 中实现早停

在这个练习中,你将学习如何在 Keras 深度学习模型中实现早停。我们将使用的数据集是一个模拟数据集,包含表示树木不同测量值的数据,例如树高、树枝数量和树干基部的周长。我们的目标是根据给定的测量值将记录分类为落叶树或针叶树。

首先,执行以下代码块以加载包含10000条记录的模拟数据集,这些记录包括两类,表示两种树种,其中落叶树的类值为1,针叶树的类值为0。每条记录有10个特征值。

目标是构建一个模型,以便在给定树木的测量值时预测树的种类。现在,让我们按照以下步骤操作:

  1. 使用 pandas 的read_csv函数加载数据集,并使用train_test_split函数将数据集按80-20比例拆分:

    # Load the data
    import pandas as pd
    X = pd.read_csv('../data/tree_class_feats.csv')
    y = pd.read_csv('../data/tree_class_target.csv')
    """
    Split the dataset into training set and test set with an 80-20 ratio
    """
    from sklearn.model_selection import train_test_split
    seed=1
    X_train, X_test, \
    y_train, y_test = train_test_split(X, y, test_size=0.2, \
                                       random_state=seed)
    
  2. 导入所有必要的依赖项。构建一个没有早停的三层 Keras 顺序模型。第一层将有16个单元,第二层有8个单元,第三层有4个单元,所有层均使用ReLU 激活函数。添加输出层并使用sigmoid 激活函数

    # Define your model
    from keras.models import Sequential
    from keras.layers import Dense, Activation
    import numpy as np
    from tensorflow import random
    np.random.seed(seed)
    random.set_seed(seed)
    model_1 = Sequential()
    model_1.add(Dense(16, activation='relu', \
                      input_dim=X_train.shape[1]))
    model_1.add(Dense(8, activation='relu'))
    model_1.add(Dense(4, activation='relu'))
    model_1.add(Dense(1, activation='sigmoid'))
    
  3. 使用二进制交叉熵作为loss函数,并将优化器设为SGD来编译模型。训练模型300个 epoch,batch_size=50,同时在每次迭代时记录训练误差测试误差

    model_1.compile(optimizer='sgd', loss='binary_crossentropy')
    # train the model
    history = model_1.fit(X_train, y_train, \
                          validation_data=(X_test, y_test), \
                          epochs=300, batch_size=50, \
                          verbose=0, shuffle=False)
    
  4. 导入绘图所需的包:

    import matplotlib.pyplot as plt 
    import matplotlib
    %matplotlib inline
    
  5. 绘制在拟合过程中存储的训练误差测试误差

    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0) 
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], \
               loc='upper right')
    

    这是预期的输出:

    图 5.9:训练模型时,未使用早停的训练误差和验证误差图

    图 5.9:训练模型时,未使用早停的训练误差和验证误差图

    从前面的图中可以看出,训练模型 300 个 epoch 后,训练误差验证误差之间的差距不断扩大,这表明过拟合已经开始发生。

  6. 通过创建具有相同层数和每层相同单位数的模型来重新定义模型。这确保了模型以相同的方式初始化。向训练过程中添加回调es_callback = EarlyStopping(monitor='val_loss', mode='min')。重复步骤 4以绘制训练误差和验证误差:

    #Define your model with early stopping on test error
    from keras.callbacks import EarlyStopping
    np.random.seed(seed)
    random.set_seed(seed)
    model_2 = Sequential()
    model_2.add(Dense(16, activation='relu', \
                      input_dim=X_train.shape[1]))
    model_2.add(Dense(8, activation='relu'))
    model_2.add(Dense(4, activation='relu'))
    model_2.add(Dense(1, activation='sigmoid'))
    """
    Choose the loss function to be binary cross entropy and the optimizer to be SGD for training the model
    """
    model_2.compile(optimizer='sgd', loss='binary_crossentropy')
    # define the early stopping callback
    es_callback = EarlyStopping(monitor='val_loss', \
                                mode='min')
    # train the model
    history=model_2.fit(X_train, y_train, \
                        validation_data=(X_test, y_test), \
                        epochs=300, batch_size=50, \
                        callbacks=[es_callback], verbose=0, \
                        shuffle=False)
    
  7. 现在绘制损失值:

    # plot training error and test error
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0) 
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], \
               loc='upper right')
    

    这是预期的输出:

    图 5.10:使用提前停止(patience=0)训练模型时的训练误差和验证误差图

    图 5.10:使用提前停止(patience=0)训练模型时的训练误差和验证误差图

    通过将patience=0的提前停止回调添加到模型中,训练过程将在大约39个 epoch 后自动停止。

  8. 重复步骤 5,同时将patience=10添加到你的提前停止回调中。重复步骤 3以绘制训练误差验证误差

    """
    Define your model with early stopping on test error with patience=10
    """
    from keras.callbacks import EarlyStopping
    np.random.seed(seed)
    random.set_seed(seed)
    model_3 = Sequential()
    model_3.add(Dense(16, activation='relu', \
                      input_dim=X_train.shape[1]))
    model_3.add(Dense(8, activation='relu'))
    model_3.add(Dense(4, activation='relu'))
    model_3.add(Dense(1, activation='sigmoid'))
    """
    Choose the loss function to be binary cross entropy and the optimizer to be SGD for training the model
    """
    model_3.compile(optimizer='sgd', loss='binary_crossentropy')
    # define the early stopping callback
    es_callback = EarlyStopping(monitor='val_loss', \
                                mode='min', patience=10)
    # train the model
    history=model_3.fit(X_train, y_train, \
                        validation_data=(X_test, y_test), \
                        epochs=300, batch_size=50, \
                        callbacks=[es_callback], verbose=0, \
                        shuffle=False)
    
  9. 然后再次绘制损失图:

    # plot training error and test error
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0) 
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], \
               loc='upper right')
    

    这是预期的输出:

图 5.11:使用提前停止(patience=10)训练模型时的训练误差和验证误差图

图 5.11:使用提前停止(patience=10)训练模型时的训练误差和验证误差图

通过将patience=10的提前停止回调添加到模型中,训练过程将在大约150个 epoch 后自动停止。

在这个练习中,你学会了如何停止模型训练,以防止你的 Keras 模型在训练数据上过拟合。为此,你使用了EarlyStopping回调并在训练时应用了它。我们使用这个回调在验证损失增加时停止模型,并添加了一个patience参数,它会在停止之前等待给定的 epoch 数量。我们在一个涉及交通流量数据集的问题上练习了使用这个回调来训练 Keras 模型。

注意

要访问此特定部分的源代码,请参阅packt.live/3iuM4eL

你也可以在packt.live/38AbweB上在线运行这个示例。

在下一部分中,我们将讨论其他可以应用的正则化方法,以防止过拟合。

数据增强

数据增强是一种正则化技术,试图通过以廉价的方式在更多的训练样本上训练模型来解决过拟合问题。在数据增强中,现有数据会以不同的方式进行转换,并作为新的训练数据输入到模型中。这种类型的正则化已被证明是有效的,特别是在一些特定的应用中,例如计算机视觉中的目标检测/识别和语音处理。

例如,在计算机视觉应用中,您可以通过将每个图像的镜像版本和旋转版本添加到数据集中,轻松地将训练数据集的大小加倍或加三倍。通过这些变换生成的新训练示例显然不如原始训练示例好。然而,它们已被证明能改善模型的过拟合问题。

执行数据增强时的一个挑战是选择在数据上执行的正确变换。根据数据集的类型和应用,变换需要谨慎选择。

添加噪声

通过向数据中添加噪声来对模型进行正则化的基本思想与数据增强正则化相同。在小数据集上训练深度神经网络会增加网络记住单一数据示例的概率,而不是捕捉输入与输出之间的关系。

这样会导致在新数据上的表现不佳,表明模型对训练数据进行了过拟合。相反,在大数据集上训练模型可以增加模型捕捉真实底层过程的概率,而不是记住单个数据点,从而减少过拟合的机会。

扩展训练数据并减少过拟合的一种方法是通过向现有数据中注入噪声来生成新的数据示例。这种正则化方式已被证明能够减少过拟合,其效果与权重正则化技术相当。

通过将单个示例的不同版本添加到训练数据中(每个版本通过在原始示例中加入少量噪声创建),我们可以确保模型不会过拟合数据中的噪声。此外,通过包含这些修改后的示例来增加训练数据集的大小,可以为模型提供更好的底层数据生成过程的表示,并增加模型学习真实过程的机会。

在深度学习应用中,您可以通过向隐藏层的权重或激活值、网络的梯度,甚至输出层添加噪声,或向训练示例(输入层)添加噪声来提高模型性能。决定在深度神经网络中添加噪声的位置是另一个需要通过尝试不同网络并观察结果来解决的挑战。

在 Keras 中,您可以轻松地将噪声定义为一个层并将其添加到模型中。例如,要向模型添加高斯噪声,标准差为0.1(均值为0),可以编写如下代码:

from keras.layers import GaussianNoise
model.add(GaussianNoise(0.1))

以下代码将向模型的第一个隐藏层的输出/激活值添加高斯噪声

model = Sequential()
model.add(Dense(4, input_dim=30, activation='relu'))
model.add(GaussianNoise(0.01))
model.add(Dense(4, activation='relu'))
model.add(Dense(4, activation='relu'))
model.add(Dense(1, activation='sigmoid')) 

在本节中,你学习了三种正则化方法:early stoppingdata augmentationadding noise。除了它们的基本概念和流程外,你还了解了它们如何减少过拟合,并且提供了一些使用它们的技巧和建议。在下一节中,你将学习如何使用 scikit-learn 提供的函数来调优超参数。通过这样做,我们可以将 Keras 模型整合到 scikit-learn 的工作流中。

使用 scikit-learn 进行超参数调优

超参数调优是提高深度学习模型性能的一个非常重要的技术。在第四章使用 Keras 包装器进行交叉验证评估模型中,你学习了如何使用 scikit-learn 的 Keras 包装器,这使得 Keras 模型能够在 scikit-learn 的工作流中使用。因此,scikit-learn 中可用的不同通用机器学习和数据分析工具与方法可以应用于 Keras 深度学习模型。其中包括 scikit-learn 的超参数优化器。

在上一章中,你学习了如何通过编写用户定义的函数,遍历每个超参数的可能值来进行超参数调优。在本节中,你将学习如何通过使用 scikit-learn 中可用的各种超参数优化方法,以更简单的方式进行调优。你还将通过完成涉及实际数据集的活动来实践应用这些方法。

使用 scikit-learn 进行网格搜索

到目前为止,我们已经确认,构建深度神经网络涉及对多个超参数做出决策。超参数的列表包括隐藏层的数量、每个隐藏层中单元的数量、每层的激活函数、网络的损失函数、优化器的类型及其参数、正则化器的类型及其参数、批次大小、训练的轮次等。我们还观察到,不同的超参数值会显著影响模型的性能。

因此,找到最佳超参数值是成为深度学习专家过程中最重要也是最具挑战性的部分之一。由于没有适用于每个数据集和每个问题的超参数绝对规则,因此确定超参数的值需要通过试验和错误来针对每个特定问题进行调整。这个过程——用不同的超参数训练和评估模型,并根据模型表现决定最终的超参数——被称为超参数调优超参数优化

对于我们希望调整的每个超参数,设置一组可能的取值范围可以创建一个网格,如下图所示。因此,超参数调整可以看作是一个网格搜索问题;我们希望尝试网格中的每一个单元格(每一个可能的超参数组合),并找到那个能为模型带来最佳性能的单元格:

图 5.12:通过优化器、批处理大小和 epoch 的一些值创建的超参数网格

图 5.12:通过优化器、批处理大小和 epoch 的一些值创建的超参数网格

Scikit-learn 提供了一个名为 GridSearchCV() 的参数优化器,用于执行这种穷举的网格搜索。GridSearchCV() 接收模型作为 estimator 参数,并接收包含所有可能的超参数值的字典作为 param_grid 参数。然后,它会遍历网格中的每个点,使用该点的超参数值对模型进行交叉验证,并返回最佳的交叉验证得分,以及导致该得分的超参数值。

在上一章中,你学习了为了在 scikit-learn 中使用 Keras 模型,你需要定义一个返回 Keras 模型的函数。例如,下面的代码块定义了一个 Keras 模型,我们希望在之后对其进行超参数调整:

from keras.models import Sequential
from keras.layers import Dense
def build_model():
    model = Sequential(optimizer)
    model.add(Dense(10, input_dim=X_train.shape[1], \
                    activation='relu'))
    model.add(Dense(10, activation='relu'))
    model.add(Dense(1))
    model.compile(loss='mean_squared_error', \
                  optimizer= optimizer)
    return model

下一步是定义超参数网格。例如,假设我们想要调整 optimizer=['rmsprop', 'adam', 'sgd', 'adagrad']epochs = [100, 150]batch_size = [1, 5, 10]。为了做到这一点,我们将编写如下代码:

optimizer = ['rmsprop', 'adam', 'sgd', 'adagrad']
epochs = [100, 150]
batch_size = [1, 5, 10]
param_grid = dict(optimizer=optimizer, epochs=epochs, \
                  batch_size= batch_size)

现在超参数网格已经创建完毕,我们可以创建封装器,以便构建 Keras 模型的接口,并将其用作估计器来执行网格搜索:

from keras.wrappers.scikit_learn import KerasRegressor
model = KerasRegressor(build_fn=build_model, \
                       verbose=0, shuffle=False)
from sklearn.model_selection import GridSearchCV
grid_search = GridSearchCV(estimator=model, \
                           param_grid=param_grid, cv=10)
results = grid_search.fit(X, y)

上述代码会穷举地遍历网格中的每个单元格,并使用每个单元格中的超参数值进行 10 折交叉验证(这里,它会执行 10 折交叉验证 423=24 次)。然后,它返回每个这些 24 个单元格的交叉验证得分,并返回获得最佳得分的那个。

注意

对多个可能的超参数组合执行 k 折交叉验证确实需要很长时间。因此,你可以通过将 n_jobs=-1 参数传递给 GridSearchCV() 来并行化这个过程,这样会使用所有可用的处理器来执行网格搜索。该参数的默认值是 n_jobs=1,意味着不进行并行化。

创建超参数网格只是通过超参数迭代找到最优选择的一种方式。另一种方法是简单地随机选择超参数,这将在下一个主题中介绍。

使用 scikit-learn 进行随机化搜索

正如你可能已经意识到的那样,穷举网格搜索可能不是调优深度学习模型超参数的最佳选择,因为它效率不高。深度学习中有许多超参数,尤其是当你想为每个超参数尝试一个大范围的值时,穷举网格搜索将需要花费过长的时间才能完成。进行超参数优化的另一种方式是,在网格上进行随机采样,并对一些随机选择的单元格进行 k 折交叉验证。Scikit-learn 提供了一个名为RandomizedSearchCV()的优化器,用于执行超参数优化的随机搜索。

例如,我们可以将前一节的代码从穷举网格搜索更改为随机搜索,如下所示:

from keras.wrappers.scikit_learn import KerasRegressor
model = KerasRegressor(build_fn=build_model, verbose=0)
from sklearn.model_selection import RandomizedSearchCV
grid_search = RandomizedSearchCV(estimator=model, \
                                 param_distributions=param_grid, \
                                 cv=10, n_iter=12)
results = grid_search.fit(X, y)

请注意,RandomizedSearchCV()需要额外的n_iter参数,它决定了必须选择多少个随机单元格。这决定了 k 折交叉验证将执行多少次。因此,通过选择较小的值,将考虑较少的超参数组合,方法完成的时间也会更短。另外,请注意,这里param_grid参数已更改为param_distributionsparam_distributions参数可以接受一个字典,字典的键是参数名称,值可以是参数的列表或每个键的分布。

可以说,RandomizedSearchCV()不如GridSearchCV()好,因为它没有考虑所有可能的超参数值及其组合,这一点是合理的。因此,一种进行深度学习模型超参数调优的聪明方法是,首先对许多超参数使用RandomizedSearchCV(),或对较少的超参数使用GridSearchCV(),并且这些超参数之间的间隔较大。

通过从许多超参数的随机搜索开始,我们可以确定哪些超参数对模型性能的影响最大。这也有助于缩小重要超参数的范围。然后,你可以通过对较少的超参数及其较小的范围执行GridSearchCV()来完成超参数调优。这种方法称为粗到精的超参数调优方法。

现在,你准备好实践使用 scikit-learn 优化器进行超参数调优了。在接下来的活动中,你将通过调优超参数来改进你的糖尿病数据集模型。

活动 5.03:在 Avila 模式分类器上进行超参数调优

Avila 数据集是从800张 Avila 圣经图像中提取的,Avila 圣经是 12 世纪的拉丁文巨型圣经副本。该数据集包含有关文本图像的各种特征,如行间距和文本边距。数据集还包含一个类别标签,指示图像的模式是否属于最常见的类别。在本活动中,您将构建一个与前几个活动类似的 Keras 模型,但这次您将为模型添加正则化方法。然后,您将使用 scikit-learn 优化器来调整模型的超参数,包括正则化器的超参数。以下是您在本活动中需要完成的步骤:

  1. 使用X = pd.read_csv('../data/avila-tr_feats.csv')y = pd.read_csv('../data/avila-tr_target.csv')从 GitHub 上的Chapter05文件夹中的data子文件夹加载数据集。

  2. 定义一个返回 Keras 模型的函数,该模型包含三个隐藏层,第一层的size 10,第二层的size 6,第三层的size 4,且均带有L2 权重正则化。使用以下值作为模型的超参数:activation='relu'loss='binary_crossentropy'optimizer='sgd'metrics=['accuracy']。另外,确保将L2 lambda超参数作为参数传递给函数,以便我们以后进行调整。

  3. 为您的 Keras 模型创建包装器,并使用cv=5对其执行GridSearchCV()。然后,在参数网格中添加以下值:lambda_parameter = [0.01, 0.5, 1]epochs = [50, 100],以及batch_size = [20]。这可能需要一些时间来处理。参数搜索完成后,打印最佳交叉验证分数的准确率和超参数。您还可以打印每个其他交叉验证分数,以及导致该分数的超参数。

  4. 重复前一步,这次使用GridSearchCV()在更窄的范围内进行搜索,参数为lambda_parameter = [0.001, 0.01, 0.05, 0.1]epochs = [400],以及batch_size = [10]。这可能需要一些时间来处理。

  5. 重复前一步,但从 Keras 模型中移除L2 正则化器,而是使用rate参数在每个隐藏层中添加 dropout 正则化。使用以下值在参数网格中执行GridSearchCV()并打印结果:rate = [0, 0.2, 0.4]epochs = [350, 400],以及batch_size = [10]

  6. 重复前一步,使用rate = [0.0, 0.05, 0.1]epochs=[400]

完成这些步骤后,您应该看到以下预期输出:

Best cross-validation score= 0.7862895488739013
Parameters for Best cross-validation score= {'batch_size': 20, 'epochs': 100, 'rate': 0.0}
Accuracy 0.786290 (std 0.013557) for params {'batch_size': 20, 'epochs': 100, 'rate': 0.0}
Accuracy 0.786098 (std 0.005184) for params {'batch_size': 20, 'epochs': 100, 'rate': 0.05}
Accuracy 0.772004 (std 0.013733) for params {'batch_size': 20, 'epochs': 100, 'rate': 0.1}

注意

本活动的解决方案可以在第 422 页找到。

在这个活动中,我们学习了如何在 Keras 模型上实现超参数调优,并结合正则化器使用真实数据集进行分类。我们学习了如何使用 scikit-learn 优化器对模型超参数进行调优,包括正则化器的超参数。在这一部分,我们通过创建一个超参数网格并遍历它们来实现超参数调优。这使我们能够使用 scikit-learn 工作流找到最优的超参数组合。

总结

在本章中,你学习了两组非常重要的技术,用于提高深度学习模型的准确性:正则化和超参数调优。你学习了正则化如何通过多种不同的方法解决过拟合问题,包括 L1 和 L2 范数正则化以及 dropout 正则化——这些是常用的正则化技术。你发现了超参数调优对于机器学习模型的重要性,特别是对于深度学习模型的挑战。你甚至练习了使用 scikit-learn 优化器,在 Keras 模型上执行超参数调优。

在下一章中,你将探索评估模型性能时准确性指标的局限性,以及其他指标(如 precisionsensitivityspecificityAUC-ROC score),并了解如何使用它们更好地评估模型的性能。

第六章:6. 模型评估

概述

本章将深入讨论模型评估。我们将讨论在标准技术不可行时,如何使用其他方法来评估模型的性能,特别是在类别不平衡的情况下。最后,我们将利用混淆矩阵、敏感性、特异性、精确度、假阳性率(FPR)、ROC 曲线和 AUC 分数来评估分类器的性能。在本章结束时,你将深入理解准确度和零准确度,并能够理解并应对不平衡数据集的挑战。

介绍

在上一章中,我们讨论了神经网络的 正则化 技术。正则化 是一种重要的技术,用于防止模型对训练数据过拟合,并帮助模型在新的、未见过的数据样本上表现良好。我们讨论的正则化技术之一是 L1L2 权重正则化,在其中对权重进行惩罚。我们还学习了另一种正则化技术——丢弃正则化,在每次迭代中,随机移除模型中的某些层单元,防止它们过度影响模型拟合过程。这两种正则化技术的设计目的是防止单个权重或单元被过度影响,从而使模型能够更好地泛化。

在本章中,我们将学习一些与 准确度 不同的评估技术。对于任何数据科学家来说,构建模型后的第一步是评估模型,而评估模型的最简单方法就是通过其准确度。然而,在现实世界的场景中,特别是在分类任务中,当类别高度不平衡时,比如预测飓风的发生、预测罕见疾病的出现或预测某人是否会违约时,使用准确度分数来评估模型并不是最好的评估方法。

本章探讨了核心概念,如不平衡数据集以及如何使用不同的评估技术来处理这些不平衡数据集。本章首先介绍准确度及其局限性。然后,我们将探索 零准确度不平衡数据集敏感性特异性精确度假阳性ROC 曲线AUC 分数 等概念。

准确度

为了正确理解准确度,让我们探索模型评估。模型评估是模型开发过程中的一个关键部分。一旦你构建并执行了模型,下一步就是评估你的模型。

模型是基于 训练数据集 构建的,在同一个训练数据集上评估模型的表现在数据科学中是不推荐的。一旦模型在训练数据集上训练完成,它应该在与训练数据集完全不同的数据集上进行评估。这个数据集被称为 测试数据集。目标始终是构建一个具有泛化能力的模型,也就是说,模型应该在任何数据集上都能产生类似(但不完全相同)的结果,或者是相对相似的结果。只有在使用模型未知的数据进行评估时,才能实现这一目标。

模型评估过程需要一个能够量化模型表现的指标。模型评估中最简单的指标就是准确率。准确率 是我们的模型预测正确的比例。计算 准确率 的公式如下:

准确率 = (正确预测数量)/(总预测数量)

例如,如果我们有 10 条记录,其中 7 条预测正确,那么我们可以说模型的准确率是 70%。计算方法为 7/10 = 0.770%

Null accuracy 是通过预测最频繁类别可以达到的准确率。如果我们不运行算法,而只是基于最频繁的结果预测准确率,那么基于此预测计算得到的准确率就是 null accuracy

Null accuracy = (最频繁类别的实例总数)/(实例总数)

看一下这个例子:

10 个实际结果: [1,0,0,0,0,0,0,0,1,0]。

预测: [0,0,0,0,0,0,0,0,0,0]

Null accuracy = 8/10 = 0.8 或 80%

因此,我们的 null accuracy80%,意味着我们在 80% 的情况下预测是正确的。这意味着我们在没有运行任何算法的情况下就达到了 80% 的准确率。请记住,当 null accuracy 较高时,表示响应变量的分布偏向于频繁出现的类别。

我们来做一个练习,计算数据集的 null accuracy。可以通过使用 pandas 库中的 value_count 函数来找到数据集的 null accuracy。value_count 函数返回一个包含唯一值计数的序列。

注意

本章中所有的 Jupyter Notebook 练习和活动可在 GitHub 上找到:packt.live/37jHNUR

练习 6.01:计算太平洋飓风数据集的 Null Accuracy

我们有一个数据集,记录了是否在太平洋上观察到 飓风,该数据集有两列,日期飓风日期 列表示观察的日期,而 飓风 列表示该日期是否有飓风发生。飓风 列的值为 1 表示发生了飓风,值为 0 表示没有飓风发生。通过以下步骤可以计算该数据集的 null accuracy

  1. 打开一个 Jupyter 笔记本。导入所有必要的库,并从本书的 GitHub 仓库中将pacific_hurricanes.csv文件加载到data文件夹中:

    # Import the data
    import pandas as pd
    df = pd.read_csv("../data/pacific_hurricanes.csv")
    df.head() 
    

    以下是前面代码的输出:

    图 6.1:太平洋飓风数据集的探索

    图 6.1:太平洋飓风数据集的探索

  2. 使用pandas库内置的value_count函数来获取hurricane列数据的分布。value_count函数显示了唯一值的总实例:

    df['hurricane'].value_counts()
    

    上述代码产生的输出如下:

    0 22435
    1 1842
    Name: hurricane, dtype: int64
    
  3. 使用value_count函数并将normalize参数设置为True。为了找到空值准确度,你需要索引为0pandas序列,来获取与某一天没有发生飓风相关的值的比例:

    df['hurricane'].value_counts(normalize=True).loc[0]
    

    上述代码产生的输出如下:

    0.9241257156979857
    

    计算出的数据集空值准确度92.4126%

在这里,我们可以看到数据集的空值准确度非常高,达到了92.4126%。因此,如果我们仅仅创建一个傻瓜模型,对所有结果预测多数类,那么我们的模型将会有92.4126%的准确度。

注意

要访问此特定部分的源代码,请参考packt.live/31FtQBm

你也可以在packt.live/2ArNwNT在线运行这个示例。

在本章后面,在活动 6.01中,在我们更改训练/测试拆分时计算神经网络的准确度和空值准确度,我们将看到空值准确度如何随着test/train拆分的变化而变化。

准确度的优缺点

准确度的优点如下:

  • 易于使用:准确度的计算非常简单,容易理解,因为它只是一个简单的分数公式。

  • 与其他技术相比更流行:由于它是最容易计算的度量,它也是最受欢迎的,并且被普遍接受作为评估模型的第一步。大多数数据科学入门书籍都会将准确度作为评估指标来讲解。

  • 适用于比较不同的模型:假设你正在尝试使用不同的模型解决问题。你总是可以信任那个给出最高准确度的模型。

准确度的局限性如下:

  • response/dependent变量。如果我们模型的准确度为80%,我们并不知道响应变量是如何分布的,以及数据集的空值准确度是多少。如果我们数据集的空值准确度超过70%,那么一个80%准确的模型就毫无意义。

  • 准确率同样无法提供关于模型的类型 1类型 2错误的任何信息。类型 1错误是当某一类为类时,我们将其预测为类,而类型 2错误则是当某一类为正类时,我们将其预测为负类。我们将在本章稍后研究这两种错误。在下一节中,我们将讨论不平衡数据集。对于分类不平衡数据集的模型来说,准确率分数可能会特别具有误导性,这也是为什么其他评估指标对模型评估很有用的原因。

不平衡数据集

不平衡数据集是分类问题中的一种特殊情况,其中类分布在各个类别之间存在差异。在这种数据集中,某一类别占据主导地位。换句话说,不平衡数据集的空准确率非常高。

以信用卡欺诈为例。如果我们有一个信用卡交易的数据集,那么我们会发现,在所有交易中,欺诈交易的数量非常少,而大多数交易都是正常交易。如果1代表欺诈交易,0代表正常交易,那么数据中会有许多0,几乎没有1。该数据集的空准确率可能会超过99%。这意味着多数类(在此案例中是0)远大于少数类(在此案例中是1)。这就是不平衡数据集的特点。请看下面的图表,它展示了一个一般的不平衡数据集的散点图

图 6.2:一个一般的、不平衡数据集的散点图

图 6.2:一个一般的、不平衡数据集的散点图

上述图表展示了一个不平衡数据集的广义散点图,其中星星代表少数类,圆圈代表多数类。正如我们所见,圆圈的数量远多于星星;这可能使得机器学习模型很难区分这两类。在下一节中,我们将讨论一些处理不平衡数据集的方法。

处理不平衡数据集

在机器学习中,有两种方法可以克服不平衡数据集的缺点,具体如下:

  • 90%,然后采样技术很难正确地表现数据中多数类和少数类的比例,这可能导致我们的模型过拟合。因此,最好的方法是修改我们的评估技术。

  • 修改模型评估技术:当处理高度不平衡的数据集时,最好修改模型评估技术。这是获得良好结果的最稳健的方法,意味着使用这些方法很可能会在新数据上获得好结果。除了准确率外,还有许多其他评估指标可以修改来评估模型。要了解所有这些技术,首先需要理解混淆矩阵的概念。

混淆矩阵

混淆矩阵描述了分类模型的性能。换句话说,混淆矩阵是总结分类器性能的一种方式。下表展示了混淆矩阵的基本表示,并表示模型预测结果与实际值的比较:

图 6.3:混淆矩阵的基本表示

图 6.3:混淆矩阵的基本表示

让我们回顾一下前表中使用的缩写的含义:

  • TN真负例):这是指原本为负的结果被预测为负的数量。

  • FP假正例):这是指原本为负的结果被预测为正的数量。这种错误也叫做第一类错误

  • FN假负例):这是指原本为正的结果被预测为负的数量。这种错误也叫做第二类错误

  • TP真正例):这是指原本为正的结果被预测为正的数量。

目标是最大化前表中TNTP框中的值,即真正负例和真正正例,同时最小化FNFP框中的值,即假负例和假正例。

以下代码是混淆矩阵的一个示例:

from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test,y_pred_class)
print(cm)

前面的代码会生成如下输出:

array([[89, 2],
       [13, 4]], dtype=int64)

所有机器学习和深度学习算法的目标都是最大化 TN 和 TP,最小化 FN 和 FP。以下示例代码计算了 TN、FP、FN 和 TP:

# True Negative
TN = cm[0,0]
# False Negative
FN = cm[1,0]
# False Positives
FP = cm[0,1]
# True Positives
TP = cm[1,1]

注意

准确率无法帮助我们理解第一类错误和第二类错误。

从混淆矩阵计算的指标

混淆矩阵中可以推导出以下指标:灵敏度特异性精度假阳性率ROCAUC

  • 1,除以实际为1的患者总数:

    灵敏度 = TP / (TP+FN)

    灵敏度是指当实际值为正时,预测正确的频率。在构建如预测患者再入院的模型等情况下,我们需要模型具有很高的灵敏度。我们需要将1预测为1。如果0被预测为1,是可以接受的,但如果1被预测为0,意味着本应再入院的患者被预测为没有再入院,这会对医院造成严重的惩罚。

  • 0,除以实际为0的患者总数。特异性也称为真负例率:

    特异性 = TN / (TN+FP)

    特异性指的是当实际值为负时,预测正确的频率。比如在垃圾邮件检测中,我们希望算法更具特异性。当一封邮件是垃圾邮件时,模型预测为1;当不是垃圾邮件时,预测为0。我们希望模型总是将非垃圾邮件预测为0,因为如果一个非垃圾邮件被误分类为垃圾邮件,重要邮件可能会进入垃圾邮件文件夹。这里灵敏度可能会有所妥协,因为一些垃圾邮件可能会进入收件箱,但非垃圾邮件绝对不应该进入垃圾邮件文件夹。

    注意

    正如我们之前讨论的,模型应该偏向灵敏度还是特异性完全取决于业务问题。

  • 精确度:这是正确预测的正样本数量除以总的正样本预测数量。精确度指的是当预测值为正时,我们预测正确的频率:

    精确度 = TP / (TP+FP)

  • FPR的计算方法是将假阳性事件的数量与实际负样本总数相除。FPR指的是当实际值为负时,我们预测错误的频率。FPR也等于1 - 特异性:

    假阳性率 = FP / (FP+TN)

  • ROC 曲线ROC 曲线是一个图形,表示真实正例率(灵敏度)和FPR1 - 特异性)之间的关系。以下图展示了一个ROC 曲线的示例:图 6.4:ROC 曲线的示例

图 6.4:ROC 曲线的示例

为了决定在多个曲线中哪个ROC 曲线最好,我们需要查看曲线左上方的空白区域——空白区域越小,结果越好。以下图展示了多个ROC 曲线的示例:

图 6.5:多个 ROC 曲线的示例

图 6.5:多个 ROC 曲线的示例

注意

红色曲线比蓝色曲线更好,因为它在左上角留下的空白更少。

模型的ROC 曲线告诉我们灵敏度特异性之间的关系。

  • ROC 曲线。有时,AUC也写作AUROC,表示ROC 曲线下的面积。基本上,AUC是一个数值,表示ROC 曲线下的面积。ROC下的面积越大越好,AUC 分数越大越好。前面的图展示了一个AUC的示例。

    在前面的图中,红色曲线的AUC大于蓝色曲线的AUC,这意味着红色曲线的AUC优于蓝色曲线的AUCAUC 分数没有标准规则,但以下是一些普遍可接受的值及其与模型质量的关系:

图 6.6:一般可接受的 AUC 分数

图 6.6:一般可接受的 AUC 分数

现在我们理解了各种度量背后的理论,让我们通过一些活动和练习来实施所学的内容。

练习 6.02:使用 Scania 卡车数据计算准确率和空值准确率

我们将在本练习中使用的数据集来自于重型斯堪尼亚卡车在日常使用中的故障数据。关注的系统是 空气压力系统APS),该系统生成的加压空气用于卡车的各种功能,如刹车和换挡。数据集中的正类表示 APS 中某个特定部件的故障,而负类表示与 APS 无关的部件的故障。

本练习的目标是预测哪些卡车由于 APS 系统发生故障,以便修理和维护人员能够在检查卡车故障原因时获得有用的信息,并了解卡车需要检查的具体部件。

注意

本练习的数据集可以从本书的 GitHub 仓库下载,地址为 packt.live/2SGEEsH

在整个练习过程中,由于内部数学运算的随机性,你可能会获得略微不同的结果。

数据预处理和探索性数据分析

  1. 导入所需的库。使用 pandas 的 read_csv 函数加载数据集,并查看数据集的前 five 行:

    #import the libraries
    import numpy as np
    import pandas as pd
    # Load the Data
    X = pd.read_csv("../data/aps_failure_training_feats.csv")
    y = pd.read_csv("../data/aps_failure_training_target.csv")
    # use the head function view the first 5 rows of the data
    X.head()
    

    下表展示了前述代码的输出:

    图 6.7:患者重新入院数据集的前五行

    图 6.7:患者重新入院数据集的前五行

  2. 使用 describe 方法描述数据集中的特征值:

    # Summary of Numerical Data
    X.describe()
    

    下表展示了前述代码的输出:

    图 6.8:患者重新入院数据集的数值元数据

    图 6.8:患者重新入院数据集的数值元数据

    注意

    自变量也被称为解释变量,而因变量则被称为 响应变量。另外,请记住,Python 中的索引是从 0 开始的。

  3. 使用 head 函数查看 y 的内容:

    y.head()
    

    下表展示了前述代码的输出:

    图 6.9:患者重新入院数据集 y 变量的前五行

    图 6.9:患者重新入院数据集 y 变量的前五行

  4. 使用 scikit-learn 库中的 train_test_split 函数将数据拆分为测试集和训练集。为了确保我们获得相同的结果,设置 random_state 参数为 42。数据按 80:20 比例拆分,即 80%训练数据,剩余的 20%测试数据

    from sklearn.model_selection import train_test_split
    seed = 42
    X_train, X_test, \
    y_train, y_test= train_test_split(X, y, test_size=0.20, \
                                      random_state=seed)
    
  5. 使用 StandardScaler 函数对训练数据进行缩放,并使用该缩放器对 test data 进行缩放:

    # Initialize StandardScaler
    from sklearn.preprocessing import StandardScaler
    sc = StandardScaler()
    # Transform the training data
    X_train = sc.fit_transform(X_train)
    X_train = pd.DataFrame(X_train,columns=X_test.columns)
    # Transform the testing data
    X_test = sc.transform(X_test)
    X_test = pd.DataFrame(X_test,columns=X_train.columns)
    

    注意

    sc.fit_transform() 函数对数据进行转换,数据会被转换成 NumPy 数组。我们可能需要在数据框对象中进行进一步分析,因此 pd.DataFrame() 函数将数据重新转换为数据框。

    这完成了本次练习的数据预处理部分。现在,我们需要构建神经网络并计算准确率

  6. 导入创建神经网络架构所需的库:

    # Import the relevant Keras libraries
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from tensorflow import random
    
  7. 初始化Sequential类:

    # Initiate the Model with Sequential Class
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    
  8. 添加Dense类的隐藏层,并在每个隐藏层后面加上Dropout。构建第一个隐藏层,大小为64,丢弃率为0.5。第二个隐藏层的大小为32,丢弃率为0.4。第三个隐藏层的大小为16,丢弃率为0.3。第四个隐藏层的大小为8,丢弃率为0.2。最后一个隐藏层的大小为4,丢弃率为0.1。每个隐藏层使用ReLU 激活函数,并且权重初始化器设置为uniform

    # Add the hidden dense layers and with dropout Layer
    model.add(Dense(units=64, activation='relu', \
                    kernel_initializer='uniform', \
                    input_dim=X_train.shape[1]))
    model.add(Dropout(rate=0.5))
    model.add(Dense(units=32, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.4))
    model.add(Dense(units=16, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.3))
    model.add(Dense(units=8, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.2))
    model.add(Dense(units=4, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.1))
    
  9. 添加一个输出层,使用sigmoid激活函数:

    # Add Output Dense Layer
    model.add(Dense(units=1, activation='sigmoid', \
                    kernel_initializer='uniform'))
    

    注意

    由于输出是二分类,我们使用sigmoid函数。如果输出是多分类(即超过两个类别),则应该使用softmax函数。

  10. 编译网络并拟合模型。在训练过程中,通过设置metrics=['accuracy']来计算准确率:

    # Compile the model
    model.compile(optimizer='adam', \
                  loss='binary_crossentropy', \
                  metrics=['accuracy'])
    
  11. 使用100个训练周期(epochs),批处理大小为20,验证集比例为20%来训练模型:

    #Fit the Model
    model.fit(X_train, y_train, epochs=100, \
              batch_size=20, verbose=1, \
              validation_split=0.2, shuffle=False)
    
  12. test数据集上评估模型:

    test_loss, test_acc = model.evaluate(X_test, y_test)
    print(f'The loss on the test set is {test_loss:.4f} \
    and the accuracy is {test_acc*100:.4f}%')
    

    上述代码生成了以下输出:

    12000/12000 [==============================] - 0s 20us/step
    The loss on the test set is 0.0802 and the accuracy is 98.9917%
    

    模型返回的准确率为98.9917%。但是,这个结果够好吗?我们只能通过与空准确率进行比较来回答这个问题。

    计算空准确率:

  13. 空准确率可以通过使用pandas库的value_count函数来计算,我们在本章的练习 6.01中使用了它,在太平洋飓风数据集上计算空准确率

    """
    Use the value_count function to calculate distinct class values
    """
    y_test['class'].value_counts()
    

    上述代码生成了以下输出:

    0    11788
    1      212
    Name: class, dtype: int64
    
  14. 计算准确率:

    # Calculate the null accuracy 
    y_test['class'].value_counts(normalize=True).loc[0]
    

    上述代码生成了以下输出:

    0.9823333333333333
    

    在这里,我们得到了模型的空准确率。随着我们结束这个练习,以下几点必须注意:我们模型的准确率大约是98.9917%。在理想情况下,98.9917%的准确率是非常的准确率,但这里,空准确率非常高,这有助于我们将模型的表现放到一个合理的视角下。我们模型的空准确率98.2333%。由于模型的空准确率如此之高,98.9917%的准确率并不具有显著意义,但肯定是值得尊敬的,而在这种情况下,准确率并不是评估算法的正确指标。

    注意

    要访问该部分的源代码,请参阅packt.live/31FUb2d

    你也可以在线运行这个示例:packt.live/3goL0ax

现在,让我们通过活动来计算神经网络模型在我们改变训练/测试数据集划分时的准确率和空准确率。

活动 6.01:计算神经网络在我们改变训练/测试数据集划分时的准确率和空准确率

训练/测试划分是一种随机采样技术。在此活动中,我们将看到,通过改变train/test划分,我们的零准确度和准确度会受到影响。要实现这一点,必须更改定义train/test划分的代码部分。我们将使用在练习 6.02中使用的相同数据集,计算斯堪尼亚卡车数据的准确度和零准确度。按照以下步骤完成此活动:

  1. 导入所有必要的依赖项并加载数据集。

  2. test_sizerandom_state分别从0.20更改为0.30,从42更改为13

  3. 使用StandardScaler函数对数据进行缩放。

  4. 导入构建神经网络架构所需的库,并初始化Sequential类。

  5. 添加带有DropoutDense层。设置第一个隐藏层,使其大小为64,丢弃率为0.5,第二个隐藏层设置为32,丢弃率为0.4,第三个隐藏层设置为16,丢弃率为0.3,第四个隐藏层设置为8,丢弃率为0.2,最后一个隐藏层设置为4,丢弃率为0.1。将所有激活函数设置为ReLU

  6. 添加一个输出Dense层,并使用sigmoid激活函数。

  7. 编译网络并使用准确度拟合模型。用 100 个 epoch 和 20 的批量大小拟合模型。

  8. 将模型拟合到训练数据,并保存拟合过程中的结果。

  9. 在测试数据集上评估模型。

  10. 统计测试目标数据集中每个类别的值的数量。

  11. 使用 pandas 的value_count函数计算零准确度。

    注意

    在此活动中,由于内部数学操作的随机性质,您可能会得到略有不同的结果。

在这里,我们可以看到,随着train/test划分的变化,准确度和零准确度也会发生变化。由于我们的数据集极度不平衡,并且采样技术不会带来有效的结果,本章不涉及任何采样技术。

注意

本活动的解决方案可以在第 430 页找到。

让我们继续进行下一个练习,并计算基于混淆矩阵得出的指标。

练习 6.03:基于混淆矩阵推导并计算指标

我们在本次练习中使用的数据集包含来自重型斯堪尼亚卡车的日常使用数据,这些卡车在某些方面发生了故障。焦点系统是空气压力系统APS),它生成的加压空气被用于卡车的各种功能,如刹车和换档。数据集中的正类表示APS中某个特定组件的故障,而负类表示与APS无关的组件故障。

本练习的目标是预测哪些卡车由于 APS 发生故障,就像我们在前一个练习中所做的那样。我们将推导出神经网络模型的灵敏度、特异性、精确度和假阳性率,以评估其性能。最后,我们将调整阈值并重新计算灵敏度和特异性。按照以下步骤完成此练习:

注意

本练习的数据集可以从本书的 GitHub 仓库下载:packt.live/2SGEEsH

由于内部数学运算的随机性,您可能会得到略微不同的结果。

  1. 导入必要的库并使用 pandas 的read_csv函数加载数据:

    # Import the libraries
    import numpy as np
    import pandas as pd
    # Load the Data
    X = pd.read_csv("../data/aps_failure_training_feats.csv")
    y = pd.read_csv("../data/aps_failure_training_target.csv")
    
  2. 接下来,使用train_test_split函数将数据分割为训练集和测试集:

    from sklearn.model_selection import train_test_split
    seed = 42
    X_train, X_test, \
    y_train, y_test = train_test_split(X, y, \
                      test_size=0.20, random_state=seed)
    
  3. 接下来,使用StandardScaler函数对特征数据进行缩放,使其具有0均值1标准差。将缩放器拟合到训练数据并将其应用于测试数据

    from sklearn.preprocessing import StandardScaler
    sc = StandardScaler()
    # Transform the training data
    X_train = sc.fit_transform(X_train)
    X_train = pd.DataFrame(X_train,columns=X_test.columns)
    # Transform the testing data
    X_test = sc.transform(X_test)
    X_test = pd.DataFrame(X_test,columns=X_train.columns)
    
  4. 接下来,导入创建模型所需的Keras库。实例化一个KerasSequential类模型,并向模型中添加五个隐藏层,每个层都包括 dropout。第一个隐藏层的大小应为64,dropout 率为0.5。第二个隐藏层的大小应为32,dropout 率为0.4。第三个隐藏层的大小应为16,dropout 率为0.3。第四个隐藏层的大小应为8,dropout 率为0.2。最后一个隐藏层的大小应为4,dropout 率为0.1。所有隐藏层都应使用ReLU 激活函数,并且kernel_initializer = 'uniform'。为模型添加一个最终的输出层,并使用sigmoid 激活函数。通过在训练过程中计算准确性指标来编译模型:

    # Import the relevant Keras libraries
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from tensorflow import random
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    # Add the hidden dense layers and with dropout Layer
    model.add(Dense(units=64, activation='relu', \
                    kernel_initializer='uniform', \
                    input_dim=X_train.shape[1]))
    model.add(Dropout(rate=0.5))
    model.add(Dense(units=32, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.4))
    model.add(Dense(units=16, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.3))
    model.add(Dense(units=8, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.2))
    model.add(Dense(units=4, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.1))
    # Add Output Dense Layer
    model.add(Dense(units=1, activation='sigmoid', \
                    kernel_initializer='uniform'))
    # Compile the Model
    model.compile(optimizer='adam', \
                  loss='binary_crossentropy', \
                  metrics=['accuracy'])
    
  5. 接下来,通过训练100个周期,batch_size=20,并设置validation_split=0.2来拟合模型:

    model.fit(X_train, y_train, epochs=100, \
              batch_size=20, verbose=1, \
              validation_split=0.2, shuffle=False)
    
  6. 一旦模型完成对训练数据的拟合,创建一个变量,该变量是使用模型的predictpredict_proba方法对测试数据进行预测的结果:

    y_pred = model.predict(X_test)
    y_pred_prob = model.predict_proba(X_test)
    
  7. 接下来,通过设置测试集上预测值大于0.5时的预测值为1,小于0.5时为0来计算预测类别。使用scikit-learnconfusion_matrix函数计算混淆矩阵

    from sklearn.metrics import confusion_matrix
    y_pred_class1 = y_pred > 0.5
    cm = confusion_matrix(y_test, y_pred_class1)
    print(cm)
    

    上述代码会产生以下输出:

    [[11730  58]
     [   69 143]]
    

    始终将y_test作为第一个参数,将y_pred_class1作为第二个参数,以确保始终获得正确的结果。

  8. 计算真正例(TN)、假负例(FN)、假阳性(FP)和真正阳性(TP):

    # True Negative
    TN = cm[0,0]
    # False Negative
    FN = cm[1,0]
    # False Positives
    FP = cm[0,1]
    # True Positives
    TP = cm[1,1]
    

    注意

    按照y_testy_pred_class1的顺序使用是必要的,因为如果顺序颠倒,矩阵仍然会被计算,但结果将不正确。

  9. 计算灵敏度

    # Calculating Sensitivity
    Sensitivity = TP / (TP + FN)
    print(f'Sensitivity: {Sensitivity:.4f}')
    

    上述代码生成了以下输出:

    Sensitivity: 0.6745
    
  10. 计算特异性

    # Calculating Specificity
    Specificity = TN / (TN + FP)
    print(f'Specificity: {Specificity:.4f}')
    

    上述代码生成了以下输出:

    Specificity: 0.9951
    
  11. 计算精确度

    # Precision
    Precision = TP / (TP + FP)
    print(f'Precision: {Precision:.4f}')
    

    上述代码生成了以下输出:

    Precision: 0.7114
    
  12. 计算假阳性率

    # Calculate False positive rate
    False_Positive_rate = FP / (FP + TN)
    print(f'False positive rate: \
          {False_Positive_rate:.4f}')
    

    上述代码生成了以下输出:

    False positive rate: 0.0049
    

    以下图显示了值的输出:

    图 6.10:指标汇总

    ](tos-cn-i-73owjymdk6/cacf2b634cde4add9c3665f65c8bcd9c)

    图 6.10:指标汇总

    注意

    灵敏度与特异性成反比。

    正如我们之前讨论的那样,我们的模型应该更敏感,但它看起来更具特异性,且灵敏度较低。那么,我们该如何解决这个问题呢?答案在于阈值概率。通过调整分类依赖变量为10的阈值,可以提高模型的灵敏度。回想一下,最初我们将y_pred_class1的值设置为大于0.5。让我们将阈值更改为0.3,并重新运行代码来检查结果。

  13. 转到步骤 7,将阈值从0.5改为0.3,并重新运行代码:

    y_pred_class2 = y_pred > 0.3
    
  14. 现在,创建一个混淆矩阵并计算特异性灵敏度

    from sklearn.metrics import confusion_matrix
    cm = confusion_matrix(y_test,y_pred_class2)
    print(cm)
    

    上述代码生成了以下输出:

    [[11700  88]
     [   58 154]]
    

    为了对比,以下是阈值为0.5的之前的混淆矩阵

    [[11730  58]
     [   69 143]]
    

    注意

    始终记得,y_test的原始值应该作为第一个参数传递,而y_pred作为第二个参数。

  15. 计算混淆矩阵的各个组件:

    # True Negative
    TN = cm[0,0]
    # False Negative
    FN = cm[1,0]
    # False Positives
    FP = cm[0,1]
    # True Positives
    TP = cm[1,1]
    
  16. 计算新的灵敏度

    # Calculating Sensitivity
    Sensitivity = TP / (TP + FN)
    print(f'Sensitivity: {Sensitivity:.4f}')
    

    上述代码生成了以下输出:

    Sensitivity: 0.7264
    
  17. 计算特异性

    # Calculating Specificity
    Specificity = TN / (TN + FP)
    print(f'Specificity: {Specificity:.4f}')
    

    上述代码生成了以下输出:

    Specificity: 0.9925
    

    降低阈值后,灵敏度特异性明显增加:

    图 6.11:灵敏度与特异性对比

    ](github.com/OpenDocCN/f…)

    图 6.11:灵敏度与特异性对比

    所以,显然,降低阈值会增加灵敏度。

  18. 可视化数据分布。为了理解为什么降低阈值会增加灵敏度,我们需要查看预测概率的直方图。回想一下,我们创建了y_pred_prob变量来预测分类器的概率:

    import matplotlib.pyplot as plt
    %matplotlib inline
    # histogram of class distribution
    plt.hist(y_pred_prob, bins=100)
    plt.title("Histogram of Predicted Probabilities")
    plt.xlabel("Predicted Probabilities of APS failure")
    plt.ylabel("Frequency")
    plt.show()
    

    以下图显示了上述代码的输出:

图 6.12:数据集中患者再次入院的概率直方图

](tos-cn-i-73owjymdk6/f640c9470b5047949913df6c75d83eca)

图 6.12:数据集中患者再次入院的概率直方图

该直方图清楚地显示,大多数预测分类器的概率位于0.00.1的范围内,确实非常低。除非我们将阈值设置得非常低,否则无法提高模型的灵敏度。还要注意,灵敏度与特异性成反比,因此当灵敏度增加时,特异性会减少,反之亦然。

注意

要访问此特定部分的源代码,请参考packt.live/31E6v32

你还可以在packt.live/3gquh6y在线运行这个例子。

阈值没有一个普遍适用的值,尽管0.5的值通常作为默认值。选择阈值的一种方法是绘制直方图,然后手动选择阈值。在我们的案例中,任何在0.10.7之间的阈值都可以使用,因为在这些值之间的预测较少,正如在前一个练习结束时生成的直方图所示。

选择阈值的另一种方法是绘制ROC 曲线,该曲线将真正例率与假正例率进行比较。根据你对每个值的容忍度,可以选择合适的阈值。如果我们希望评估模型的性能,绘制ROC 曲线也是一个很好的方法,因为ROC 曲线下的面积是评估模型性能的直接指标。在接下来的活动中,我们将使用ROC 曲线AUC 分数来探索我们模型的性能。

活动 6.02:计算 ROC 曲线和 AUC 分数

ROC 曲线AUC 分数是有效且简便的二分类器性能评估方法。在本次活动中,我们将绘制ROC 曲线并计算模型的AUC 分数。我们将使用与练习 6.03中相同的数据集和模型,该练习是基于混淆矩阵推导与计算指标。使用 APS 故障数据,计算ROC 曲线AUC 分数。按照以下步骤完成此活动:

  1. 导入所有必要的依赖项并加载数据集。

  2. 使用train_test_split函数将数据集分割为训练集和测试集。

  3. 使用StandardScaler函数对训练数据和测试数据进行缩放。

  4. 导入构建神经网络架构所需的库,并初始化Sequential类。添加五个Dense层和Dropout层。设置第一个隐藏层的大小为64,并且丢弃率为0.5;第二个隐藏层的大小为32,丢弃率为0.4;第三个隐藏层的大小为16,丢弃率为0.3;第四个隐藏层的大小为8,丢弃率为0.2;最后一个隐藏层的大小为4,丢弃率为0.1。将所有激活函数设置为ReLU

  5. 添加一个输出Dense层,使用sigmoid激活函数。编译网络并使用准确度来拟合模型。用100个 epochs 和20的批次大小来训练模型。

  6. 将模型拟合到训练数据,并保存拟合过程中的结果。

  7. 创建一个变量,表示测试数据集的预测类别。

  8. 使用 sklearn.metrics 中的 roc_curve 函数计算假阳性率和真阳性率。假阳性率和真阳性率是三个返回变量中的第一和第二个。将真实值和预测值传递给函数。

  9. 绘制 ROC 曲线,这是真正阳性率作为假阳性率的函数。

  10. 使用 sklearn.metrics 中的 roc_auc_score 计算 AUC 分数,同时传递模型的真实值和预测值。

实施这些步骤后,您应该获得以下输出:

0.944787151628455

注意

此活动的解决方案可在第 434 页找到。

在此活动中,我们学习了如何使用 APS 失效数据集计算 ROCAUC 分数。我们还学习了特异性和敏感性如何随不同阈值变化。

总结

在本章中,我们深入讨论了模型评估和准确性。当数据集不平衡时,我们学到了准确性不是评估的最合适技术。我们还学习了如何使用 scikit-learn 计算混淆矩阵,以及如何推导其他指标,如敏感性、特异性、精确度和假阳性率。

最后,我们了解了如何使用阈值值来调整指标,以及 ROC 曲线AUC 分数 如何帮助我们评估模型。在现实生活中处理不平衡数据集非常常见。信用卡欺诈检测、疾病预测和垃圾邮件检测等问题都有不同比例的不平衡数据。

在下一章中,我们将学习一种不同类型的神经网络架构(卷积神经网络),它在图像分类任务上表现良好。我们将通过将图像分类为两类并尝试不同的架构和激活函数来测试性能。