Sklearn-机器学习应用实用指南-全-

Sklearn 机器学习应用实用指南(全)

原文:Hands-on Scikit-Learn for Machine Learning Applications

协议:CC BY-NC-SA 4.0

一、Scikit-Learn 简介

Scikit-Learn 是一个 Python 库,为实现监督和非监督机器学习算法提供了简单高效的工具。该库对每个人都是可访问的,因为它是开源的,并且是商业可用的。它构建在 NumPY、SciPy 和 matplolib 库之上,这意味着它是可靠的、健壮的,并且是 Python 语言的核心。

Scikit-Learn 专注于数据建模,而不是数据加载、清理、管理或操作。它也非常容易使用,而且相对来说没有编程错误。

机器学习

机器学习是让计算机自己编程。我们使用算法来实现这一点。一个算法是一套用于计算机计算或解决问题的规则。

机器学习提倡创建、研究和应用算法来提高数据驱动任务的性能。他们通过训练机器如何学习,使用工具和技术来回答有关数据的问题。

目标是构建健壮的算法,该算法可以操纵输入数据来预测输出,同时随着新数据的出现不断更新输出。发送到计算机的任何信息或数据都被认为是输入。计算机产生的数据被认为是输出

在机器学习社区中,输入数据被称为特征集,输出数据被称为目标。该特征集也被称为特征空间。样本数据通常被称为训练数据。一旦用样本数据训练了算法,它就可以对新数据进行预测。新数据通常被称为测试数据

机器学习分为两个主要领域:监督学习和非监督学习。由于机器学习通常侧重于基于从训练数据中学习到的已知属性的预测,因此我们的重点是监督学习。

监督学习是指数据集同时包含输入(或特征集)和期望输出(或目标)。也就是说,我们知道数据的属性。目标是做出预测。这种监督算法训练的能力是机器学习变得如此流行的很大一部分原因。

为了对新数据进行分类或回归,我们必须对具有已知结果的数据进行训练。我们通过将数据组织成相关的类别来对其进行分类。我们通过找到特征集数据和目标数据之间的关系来回归数据。

有了无监督学习,数据集只包含输入,没有想要的输出(或目标)。目标是探索数据并找到一些组织数据的结构或方法。虽然这不是本书的重点,但我们将探索一些无监督学习场景。

蟒蛇

您可以使用任何 Python 安装,但是出于几个原因,我建议使用 Anaconda 安装 Python。首先,它拥有超过 1500 万用户。其次,Anaconda 允许轻松安装所需的 Python 版本。第三,它预装了许多有用的机器学习库,包括 Scikit-Learn。点击这个链接查看您的操作系统和 Python 版本的 Anaconda 包列表: https://docs.anaconda.com/anaconda/packages/pkg-docs/ 。第四,它包括几个非常受欢迎的编辑器,包括 IDLE、Spyder 和 Jupyter 笔记本。第五,Anaconda 可靠且维护良好,消除了兼容性瓶颈。

你可以通过这个链接轻松下载安装 Anaconda:https://www.anaconda.com/download/。可以用这个链接更新: https://docs.anaconda.com/anaconda/install/update-version/ 。只需打开 Anaconda 并按照说明操作。我建议更新到当前版本。

Scikit-Learn

Python 的 Scikit-Learn 是最流行的机器学习库之一。它构建在 Python 库 NumPy、SciPy 和 Matplotlib 之上。该库是文档完善的、开源的、商业可用的,并且是开始机器学习的一个很好的工具。它也非常可靠且维护良好,其大量的算法集合可以很容易地集成到您的项目中。Scikit-Learn 专注于数据建模,而不是加载、操作、可视化和汇总数据。对于这样的活动,其他的库,比如 NumPy、pandas、Matplotlib 和 seaborn 也包括在内。Scikit-Learn 库作为 sklearn 导入到 Python 脚本中。

数据集

理解机器学习应用的一个很好的方法是通过 Python 数据驱动的代码例子。我们使用 Scikit-Learn、UCI 机器学习或 seaborn 数据集进行所有示例。Scikit-Learn 数据集包嵌入了一些小数据集,用于入门,并帮助获取机器学习库中常用的较大数据集,以对来自世界各地的数据进行算法基准测试。UCI 机器学习知识库维护 468 个数据集,以服务于机器学习社区。Seaborn 在 Matplotlib 的基础上提供了一个 API,该 API 在处理绘图样式、颜色默认值和便于可视化的常见统计绘图类型的高级函数时提供了简单性。它还很好地集成了 Pandas DataFrame 功能。

我们选择数据集作为我们的示例,因为机器学习社区使用它们进行学习、探索、基准测试和验证,因此我们可以在学习如何应用机器学习算法的同时将我们的结果与其他人进行比较。

我们的数据集分为分类数据和回归数据。分类数据的复杂性从简单到相对复杂不等。简单分类数据集包括 load_iris、load_wine、bank.csv 和 load_digits。复杂分类数据集包括 fetch_20newsgroups、MNIST 和 fetch_1fw_people。回归数据集包括 tips、redwine.csv、whitewine.csv 和 load_boston。

表征数据

在使用算法之前,最好先了解数据特征。每个数据集都经过精心选择,以帮助您获得机器学习最常见方面的经验。我们首先描述每个数据集的特征,以便更好地理解其组成和用途。数据集由分类和回归数据组织。

分类数据按照复杂性进一步组织。也就是说,我们从不复杂的简单分类数据集开始,以便读者可以专注于机器学习内容而不是数据。然后我们转向更复杂的数据集。

简单分类数据

分类是一种机器学习技术,用于预测因变量所属的类别。一个是一个离散响应。在机器学习中,因变量通常被称为目标。基于数据集的独立变量来预测类别。自变量通常被称为特征集特征空间。特征空间是用于表征数据的特征的集合。

简单数据集是那些具有有限数量特征的数据集。这样的数据集被称为具有低维特征空间的数据集。

虹膜数据

我们表征的第一个数据集是 load_iris,它由 Iris flower 数据组成。Iris 是一个多变量数据集,由来自三种鸢尾(鸢尾海滨鸢尾杂色鸢尾)的每一种的 50 个样本组成。每个样本包含四个特征,即以厘米为单位的萼片和花瓣的长度和宽度。Iris 是机器学习分类的典型测试用例。它也是数据科学文献中最著名的数据集之一,这意味着您可以根据许多其他可验证的示例来测试您的结果。

清单 1-1 中显示的第一个代码示例加载 Iris 数据,显示它的键、特征集和目标的形状、特征和目标名称、来自 DESCR 键的片段以及特征重要性(从最重要到最不重要)。

from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier

if __name__ == "__main__":
    br = '\n'
    iris = datasets.load_iris()
    keys = iris.keys()
    print (keys, br)
    X = iris.data
    y = iris.target
    print ('features shape:', X.shape)
    print ('target shape:', y.shape, br)
    features = iris.feature_names
    targets = iris.target_names
    print ('feature set:')
    print (features, br)
    print ('targets:')
    print (targets, br)
    print (iris.DESCR[525:900], br)
    rnd_clf = RandomForestClassifier(random_state=0,
                                     n_estimators=100)
    rnd_clf.fit(X, y)
    rnd_name = rnd_clf.__class__.__name__
    feature_importances = rnd_clf.feature_importances_
    importance = sorted(zip(feature_importances, features),
                        reverse=True)
    print ('most important features' + ' (' + rnd_name + '):')
    [print (row) for i, row in enumerate(importance)]

Listing 1-1Characterize the Iris data set

继续执行清单 1-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。

执行清单 1-1 的输出应该如下所示:

dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])

features shape: (150, 4)
target shape: (150,)

feature set:
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

targets:
['setosa' 'versicolor' 'virginica']

    ============== ==== ==== ======= ===== ====================
                    Min  Max   Mean    SD   Class Correlation
    ============== ==== ==== ======= ===== ====================
    sepal length:   4.3  7.9   5.84   0.83    0.7826
    sepal width:    2.0  4.4   3.05   0.43   -0.4194
    petal length:   1.0  6.9   3.76   1.76    0.9490  (high!)
    petal width:

most important features (RandomForestClassifier):
(0.4604447396171521, 'petal length (cm)')
(0.4241162651271012, 'petal width (cm)')
(0.09090795402103086, 'sepal length (cm)')
(0.024531041234715754, 'sepal width (cm)')

代码从导入数据集和 RandomForestClassifier 包开始。RandomForestClassifier 是一种集成学习方法,它在训练时构建大量决策树,并输出作为类模式的类。

在这个例子中,我们使用它来返回特征重要性。主程序块首先加载数据并显示其特征。将特征集数据加载到变量 X 并将目标数据加载到变量 y 是机器学习社区的惯例。

代码通过在 pandas 数据上训练 RandomForestClassifier 得出结论,因此它可以返回特征重要性。当实际建模数据时,我们将 pandas 数据转换为 NumPy 以获得最佳性能。请记住,这些键是可用的,因为数据集嵌入在 Scikit-Learn 中。

注意,我们只从 DESCR 中提取了一小部分,其中包含了关于数据集的大量信息。我总是建议在开始任何机器学习实验之前,至少显示原始数据集的形状。

小费

RandomForestClassifier 是一种强大的机器学习算法,不仅可以对训练数据进行建模,还可以返回特征重要性。

擦除数据

我们表征的下一个数据集是 load_wine。load_wine 数据集由 178 个数据元素组成。每个元素有十三个特征,描述了三个目标类。它被认为是机器学习社区中的经典,并提供了一个简单的多分类数据集。

清单 1-2 中显示的下一个代码示例加载 wine 数据,并显示它的键、特性集和目标的形状、特性和目标名称、DESCR 键的一个片段以及特性重要性(从最重要到最不重要)。

from sklearn.datasets import load_wine
from sklearn.ensemble import RandomForestClassifier

if __name__ == "__main__":
    br = '\n'
    data = load_wine()
    keys = data.keys()
    print (keys, br)
    X, y = data.data, data.target
    print ('features:', X.shape)
    print ('targets', y.shape, br)
    print (X[0], br)
    features = data.feature_names
    targets = data.target_names
    print ('feature set:')
    print (features, br)
    print ('targets:')
    print (targets, br)
    rnd_clf = RandomForestClassifier(random_state=0,
                                     n_estimators=100)
    rnd_clf.fit(X, y)
    rnd_name = rnd_clf.__class__.__name__
    feature_importances = rnd_clf.feature_importances_
    importance = sorted(zip(feature_importances, features),
                        reverse=True)
    n = 6
    print (n, 'most important features' + ' (' + rnd_name + '):')
    [print (row) for i, row in enumerate(importance) if i < n]

Listing 1-2Characterize load_wine

在执行清单 1-2 中的代码后,您的输出应该如下所示:

dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names'])

features: (178, 13)
targets (178,)

[1.423e+01 1.710e+00 2.430e+00 1.560e+01 1.270e+02 2.800e+00 3.060e+00
 2.800e-01 2.290e+00 5.640e+00 1.040e+00 3.920e+00 1.065e+03]

feature set:
['alcohol', 'malic_acid', 'ash', 'alcalinity_of_ash', 'magnesium', 'total_phenols', 'flavanoids', 'nonflavanoid_phenols', 'proanthocyanins', 'color_intensity', 'hue', 'od280/od315_of_diluted_wines', 'proline']

targets:
['class_0' 'class_1' 'class_2']

6 most important features (RandomForestClassifier):
(0.19399882779940295, 'proline')
(0.16095401215681593, 'flavanoids')
(0.1452667364559143, 'color_intensity')
(0.11070045042456281, 'alcohol')
(0.1097465262717493, 'od280/od315_of_diluted_wines')
(0.08968972021098301, 'hue')

小费

要创建(实例化)一个机器学习算法(模型),只需将其赋给一个变量(例如 model = algorithm())。要基于模型进行训练,只需使其符合数据即可(例如,model.fit(X,y))。

代码从导入 load_wine 和 RandomForestClassifier 开始。主块显示关键点,将数据加载到 X 和 y 中,显示特征集 X 中的第一个向量,显示形状,并显示特征集和目标信息。代码以用 RandomForestClassifier 训练 X 结束,因此我们可以显示六个最重要的特性。请注意,我们显示了特性集 X 中的第一个向量,以验证所有特性都是数字的。

银行数据

清单 1-3 中显示的下一个代码示例处理银行数据。bank.csv 数据集由一家葡萄牙银行机构的直接营销活动组成。目标由客户是否会认购(是/否)定期存款(目标标签为 y)来描述。它由 41188 个数据元素组成,每个元素有 20 个特征。该网站还提供了 4119 个数据元素的 10%随机样本,用于计算成本更高的算法,如 svm 和 KNeighborsClassifier。

import pandas as pd

if __name__ == "__main__":
    br = '\n'
    f = 'data/bank.csv'
    bank = pd.read_csv(f)
    features = list(bank)
    print (features, br)
    X = bank.drop(['y'], axis=1).values
    y = bank['y'].values
    print (X.shape, y.shape, br)
    print (bank[['job', 'education', 'age', 'housing',
                 'marital', 'duration']].head())

Listing 1-3Characterize bank data

在执行清单 1-3 中的代码后,您的输出应该如下所示:

['age', 'job', 'marital', 'education', 'default', 'housing', 'loan', 'contact', 'month', 'day_of_week', 'duration', 'campaign', 'pdays', 'previous', 'poutcome', 'emp.var.rate', 'cons.price.idx', 'cons.conf.idx', 'euribor3m', 'nr.employed', 'y']

(41188, 20) (41188,)

         job    education  age housing  marital  duration
0  housemaid     basic.4y   56      no  married       261
1   services  high.school   57      no  married       149
2   services  high.school   37     yes  married       226
3     admin.     basic.6y   40      no  married       151
4   services  high.school   56      no  married       307

代码示例从导入 pandas 包开始。主程序块将银行数据从 CSV 文件加载到 Pandas 数据框架中,并显示列名(或特征)。要从 pandas 中检索列名,我们需要做的就是将数据帧做成一个列表,并将结果赋给一个变量。接下来,创建特征集 X 和目标 y。最后,会显示 X 和 y 形状以及一些选择功能。

数字数据

本小节的最后一个代码示例是 load_digits。load_digits 数据集由 1797 幅 8 × 8 的手写图像组成。每幅图像由 64 个像素(基于 8 × 8 矩阵)表示,构成了特征集。预测了十个目标,用数字 0 到 9 表示。

清单 1-4 包含表征 load_digits 的代码。

import numpy as np
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt

if __name__ == "__main__":
    br = '\n'
    digits = load_digits()
    print (digits.keys(), br)
    print ('2D shape of digits data:', digits.images.shape, br)
    X = digits.data
    y = digits.target
    print ('X shape (8x8 flattened to 64 pixels):', end=' ')
    print (X.shape)
    print ('y shape:', end=' ')
    print (y.shape, br)
    i = 500
    print ('vector (flattened matrix) of "feature" image:')
    print (X[i], br)
    print ('matrix (transformed vector) of a "feature" image:')
    X_i = np.array(X[i]).reshape(8, 8)
    print (X_i, br)
    print ('target:', y[i], br)
    print ('original "digits" image matrix:')
    print (digits.images[i])
    plt.figure(1, figsize=(3, 3))
    plt.title('reshaped flattened vector')
    plt.imshow(X_i, cmap="gray", interpolation="gaussian")
    plt.figure(2, figsize=(3, 3))
    plt.title('original images dataset')
    plt.imshow(digits.images[i], cmap="gray",
               interpolation='gaussian')
    plt.show()

Listing 1-4Characterize load_digits

在执行清单 1-4 中的代码后,您的输出应该如下所示:

dict_keys(['data', 'target', 'target_names', 'images', 'DESCR'])

2D shape of digits data: (1797, 8, 8)

X shape (8x8 flattened to 64 pixels): (1797, 64)
y shape: (1797,)

vector (flattened matrix) of "feature" image:
[ 0\.  0\.  3\. 10\. 14\.  3\.  0\.  0\.  0\.  8\. 16\. 11\. 10\. 13\.  0\.  0\.  0\.  7.
 14\.  0\.  1\. 15\.  2\.  0\.  0\.  2\. 16\.  9\. 16\. 16\.  1\.  0\.  0\.  0\. 12\. 16.
 15\. 15\.  2\.  0\.  0\.  0\. 12\. 10\.  0\.  8\.  8\.  0\.  0\.  0\.  9\. 12\.  4\.  7.
 12\.  0\.  0\.  0\.  2\. 11\. 16\. 16\.  9\.  0.]

matrix (transformed vector) of a "feature" image:
[[ 0\.  0\.  3\. 10\. 14\.  3\.  0\.  0.]
 [ 0\.  8\. 16\. 11\. 10\. 13\.  0\.  0.]
 [ 0\.  7\. 14\.  0\.  1\. 15\.  2\.  0.]
 [ 0\.  2\. 16\.  9\. 16\. 16\.  1\.  0.]
 [ 0\.  0\. 12\. 16\. 15\. 15\.  2\.  0.]
 [ 0\.  0\. 12\. 10\.  0\.  8\.  8\.  0.]
 [ 0\.  0\.  9\. 12\.  4\.  7\. 12\.  0.]
 [ 0\.  0\.  2\. 11\. 16\. 16\.  9\.  0.]]

target: 8

original "digits" image matrix:
[[ 0\.  0\.  3\. 10\. 14\.  3\.  0\.  0.]
 [ 0\.  8\. 16\. 11\. 10\. 13\.  0\.  0.]
 [ 0\.  7\. 14\.  0\.  1\. 15\.  2\.  0.]
 [ 0\.  2\. 16\.  9\. 16\. 16\.  1\.  0.]
 [ 0\.  0\. 12\. 16\. 15\. 15\.  2\.  0.]
 [ 0\.  0\. 12\. 10\.  0\.  8\.  8\.  0.]
 [ 0\.  0\.  9\. 12\.  4\.  7\. 12\.  0.]
 [ 0\.  0\.  2\. 11\. 16\. 16\.  9\.  0.]]

列表 1-4 还显示了数字 1-1 和 1-2 。图 1-1 是数据集中第 500 张图像的整形展平矢量。特征集 X 中的每个数据元素都表示为一个 64 像素的展平向量,因为 Scikit-Learn 无法识别 8 × 8 的图像矩阵,所以我们必须将第 500 个向量整形为 8 × 8 的图像矩阵才能可视化。图 1-2 是我们将数据加载到变量数字时,直接从可用的图像数据集中获取的第 500 张图像。

img/481580_1_En_1_Fig2_HTML.jpg

图 1-2

第 500 个数据元素的原始图像矩阵

img/481580_1_En_1_Fig1_HTML.jpg

图 1-1

第 500 个数据元素的整形扁平矢量

代码从导入 numpy、load_digits 和 matplotlib 包开始。主程序块将 load_digits 放入 digits 变量中,并显示其关键字:数据目标目标名称图像描述。它继续显示包含在图像中的图像的二维(2D)形状。图像中的数据由 1797 个 8 × 8 矩阵表示。接下来,特征数据(表示为向量)被放置在 X 中,目标数据被放置在 y 中。

特征向量是包含关于物体重要特征的信息的向量。数据中的数据由 1797 个 64 像素特征向量表示。图像的简单特征表示是每个像素的原始亮度值。因此,一个 8 × 8 的图像由 64 个像素表示。机器学习算法将特征数据作为向量进行处理,因此数据中的每个元素都必须是其 2D 图像矩阵的一维(1D)向量表示。

小费

特征数据必须由向量组成,以便与机器学习算法一起工作。

代码继续显示第 500 幅图像的特征向量。接下来,将第 500 个特征向量从其展平的 1D 向量形状转换成 2D 图像矩阵,并用 NumPy 整形函数显示。代码继续显示第 500 幅图像的目标值 y。接下来,通过参考图像来显示第 500 个图像矩阵。

我们将图像从其 1D 展平矢量状态转换为 2D 图像矩阵的原因是,大多数数据集不包括像 load_data 这样的 images 对象。因此,为了用机器学习算法可视化和处理数据,我们必须能够手动展平图像,并将展平的图像转换回其原始的 2D 矩阵形状。

代码以两种方式可视化第 500 张图片结束。首先,我们使用展平的矢量 X_i。其次,我们参考图像。虽然机器学习算法需要特征向量,但函数 imshow 需要 2D 图像矩阵来可视化。

复杂分类数据

现在让我们处理更复杂的数据集。复杂数据集是那些具有大量特征的数据集。这样的数据集被称为具有高维特征空间的数据集。

新闻组数据

我们描述的第一个数据集是 fetch_20newsgroups,它由 20 个主题的大约 18000 篇帖子组成。数据被分成训练测试子集。这种拆分基于特定日期前后发布的消息。

清单 1-5 包含描述 fetch_20newsgroups 的代码。

from sklearn.datasets import fetch_20newsgroups

if __name__ == "__main__":
    br = '\n'
    train = fetch_20newsgroups(subset='train')
    test = fetch_20newsgroups(subset='test')
    print ('data:')
    print (train.target.shape, 'shape of train data')
    print (test.target.shape, 'shape of test data', br)
    targets = test.target_names
    print (targets, br)
    categories = ['rec.autos', 'rec.motorcycles', 'sci.space',
                  'sci.med']
    train = fetch_20newsgroups(subset='train',
                               categories=categories)
    test = fetch_20newsgroups(subset='test',
                              categories=categories)
    print ('data subset:')
    print (train.target.shape, 'shape of train data')
    print (test.target.shape, 'shape of test data', br)
    targets = train.target_names
    print (targets)

Listing 1-5Characterize fetch_20newsgroups

在执行清单 1-5 中的代码后,您的输出应该如下所示:

data:
(11314,) shape of train data
(7532,) shape of test data

['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']

data subset:
(2379,) shape of train data
(1584,) shape of test data

['rec.autos', 'rec.motorcycles', 'sci.med', 'sci.space']

代码从导入 fetch_20newsgroups 开始。主程序块首先加载训练和测试数据,并显示它们的形状。训练数据包含 11314 个帖子,而测试数据包含 7532 个帖子。代码继续显示目标名称和类别。接下来,从类别的子集创建训练和测试数据。代码以显示子集的形状和目标名称结束。

MNIST 数据

我们表征的下一个数据集是 MNIST。MNIST(改进的国家标准和技术研究所)是一个大型的手写数字数据库,通常用于机器学习社区和其他工业图像处理应用程序的训练和测试。MNIST 包含 70000 个手写数字图像的例子,从 0 到 9,大小为 28 × 28。每个目标(或标签)存储为一个数字值。该特征集是 70000 个 28 × 28 图像的矩阵,每个图像自动展平为 784 像素。因此,70000 个数据元素中的每一个都是长度为 784 的向量。目标集是一个 70000 位数值的向量。

清单 1-6 包含了 MNIST 特有的代码。

import numpy as np
from random import randint
import matplotlib.pyplot as plt

def find_image(data, labels, d):
    for i, row in enumerate(labels):
        if d == row:
            target = row
            X_pixels = np.array(data[i])
            return (target, X_pixels)

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_mnist.npy')
    y = np.load('data/y_mnist.npy')
    target = np.load('data/mnist_targets.npy')
    print ('labels (targets):')
    print (target, br)
    print ('feature set shape:')
    print (X.shape, br)
    print ('target set shape:')
    print (y.shape, br)
    indx = randint(0, y.shape[0]-1)
    target = y[indx]
    X_pixels = np.array(X[indx])
    print ('the feature image consists of', len(X_pixels),
           'pixels')
    X_image = X_pixels.reshape(28, 28)
    plt.figure(1, figsize=(3, 3))
    title = 'image @ indx ' + str(indx) + ' is digit ' \
            + str(int(target))
    plt.title(title)
    plt.imshow(X_image, cmap="gray")
    digit = 7
    target, X_pixels = find_image(X, y, digit)
    X_image = X_pixels.reshape(28, 28)
    plt.figure(2, figsize=(3, 3))
    title = 'find first ' + str(int(target)) + ' in dataset'
    plt.title(title)
    plt.imshow(X_image, cmap="gray")
    plt.show()

Listing 1-6Characterize MNIST

在执行清单 1-6 中的代码后,您的输出应该如下所示:

labels (targets):
[0\. 1\. 2\. 3\. 4\. 5\. 6\. 7\. 8\. 9.]

feature set shape:
(70000, 784)

target set shape:
(70000,)

the feature image consists of 784 pixels

列表 1-6 还显示了数字 1-3 和 1-4 。图 1-3 是索引 6969 处数字 1 的整形图像。图 1-4 是数据集中数字 7 的第一幅图像。

img/481580_1_En_1_Fig4_HTML.jpg

图 1-4

数据集中第一个数字 7 的图像

img/481580_1_En_1_Fig3_HTML.jpg

图 1-3

索引 6969 处图像的整形展平矢量

代码从导入 randint 和其他必需的包开始。函数 find_image 定位图像的第一次出现。主块将 NumPy 文件中的数据加载到功能集 X、目标 y 和目标中。可变目标保存目标标签。它继续显示 X 和 y 的形状。特征集 X 由 70000 个 784 像素的向量组成,因此每个图像由 28 × 28 像素组成。

目标 y 由 70000 个标签组成。接下来,生成一个介于 0 和 69999 之间的随机整数,因此我们可以从数据集中显示一个随机图像。我们例子中的随机整数是 6969。索引 6969 处的图像是数字 1。显示图像的大小以验证其为 784 像素。然后,我们将 vector 6969 整形为一个 28 × 28 的矩阵,这样我们就可以用函数 imshow 来可视化。代码通过找到第一个数字 7 并显示它来结束。

面孔数据

本小节中描述的最终数据集是 fetch_1fw_people。fetch_1fw_people 数据集用于分类标记的人脸。它包含 1288 张人脸图像和 7 个目标。每幅图像由一个 50 × 37 的像素矩阵表示,因此特征集有 1850 个特征(基于一个 50 × 37 的矩阵)。总之,该数据由 1288 个标记的人脸组成,每个人脸由 1850 个像素组成,每个像素预测七个目标。

清单 1-7 包含描述 fetch_1fw_people 的代码。

import numpy as np
import matplotlib.pyplot as plt

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_faces.npy')
    y = np.load('data/y_faces.npy')
    targets = np.load('data/faces_targets.npy')
    print ('shape of feature and target data:')
    print (X.shape)
    print (y.shape, br)
    print ('target faces:')
    print (targets)
    X_i = np.array(X[0]).reshape(50, 37)
    image_name = targets[y[0]]
    fig, ax = plt.subplots()
    image = ax.imshow(X_i, cmap="bone")
    plt.title(image_name)
    plt.show()

Listing 1-7Characterize fetch_1fw_people

在执行清单 1-7 中的代码后,您的输出应该如下所示:

shape of feature and target data:
(1288, 1850)
(1288,)

target faces:
['Ariel Sharon' 'Colin Powell' 'Donald Rumsfeld' 'George W Bush'
 'Gerhard Schroeder' 'Hugo Chavez' 'Tony Blair']

列表 1-7 也显示图 1-5 。图 1-5 是数据集中第一个数据元素的整形图像。

img/481580_1_En_1_Fig5_HTML.jpg

图 1-5

数据集中第一个数据元素的整形图像

代码从导入必需的包开始。主块将数据从 NumPy 文件加载到 X、y 和 targets 中。代码继续打印 X 和 y 的形状。X 包含 1288 个 1850 像素的向量,y 包含 1288 个目标值。然后显示目标标签。代码最后将第一个特征向量整形为 50 × 37 的图像,并用函数 imshow 显示出来。

回归数据

我们现在从分类转向回归。回归是一种基于数据集的自变量(或特征集)预测数值的机器学习技术。也就是说,我们正在测量特性集对一个数字输出的影响。我们表征回归的第一个数据集是 tips。

小费数据

tips 数据集与 seaborn 库集成在一起。它由餐馆的服务员小费和相关因素组成,包括小费、饭菜价格和一天中的时间。具体来说,特征包括 total_bill(餐费)、小费(小费)、性别(男性或女性)、吸烟者(是或否)、日期(周四、周五、周六或周日)、时间(白天或晚上)以及聚会的规模。特征编码如下:总账单(美元)、小费(美元)、性别(0 =男性,1 =女性)、吸烟者(0 =否,1 =是)、日(3 =星期四,4=Fri,5=星期六,6 =星期日)。Tips 数据由 244 个元素表示,具有预测一个目标的六个特征。目标是从顾客那里得到的小费。

列表 1-8 表征 tips 数据。

import numpy as np, pandas as pd, seaborn as sns

if __name__ == "__main__":
    br = '\n'
    sns.set(color_codes=True)
    tips = sns.load_dataset('tips')
    print (tips.head(), br)
    X = tips.drop(['tip'], axis=1).values
    y = tips['tip'].values
    print (X.shape, y.shape)

Listing 1-8Characterize the tips data set

在执行清单 1-8 中的代码后,您的输出应该如下所示:

   total_bill   tip     sex smoker  day    time  size
0       16.99  1.01  Female     No  Sun  Dinner     2
1       10.34  1.66    Male     No  Sun  Dinner     3
2       21.01  3.50    Male     No  Sun  Dinner     3
3       23.68  3.31    Male     No  Sun  Dinner     2
4       24.59  3.61  Female     No  Sun  Dinner     4

(244, 6) (244,)

代码首先以 Pandas 数据帧的形式加载提示,显示前五条记录,将数据转换为 NumPy,并显示特性集和目标形状。Seaborn 数据被自动加载为 Pandas 数据帧。我们无法获取要素重要性,因为 RandomForestClassifier 需要数值数据。将数据集转换成这种形式需要大量的数据争论。我们将在后面的章节中把分类数据转换成数字。

红酒和白酒

我们表征的下两个数据集是 redwine.csv 和 whitewine.csv。数据集 redwine.csv 和 whitewine.csv 分别与红葡萄酒和白葡萄酒的质量、相关。这两种葡萄酒都是由葡萄牙 Vinho Verde 葡萄酒的变种组成。

该特性集由 11 个属性组成。输入属性基于客观测试,如 pH(物质的酸度或碱度)和酒精(体积百分比)。输出质量是基于感官数据,报告为至少三个葡萄酒专家评估的中位数。每位专家将葡萄酒质量从 0(非常差)到 10(非常好)分等级。红酒数据集有 1599 个实例,而白酒数据集有 4898 个。

清单 1-9 描述了 redwine.csv。

import pandas as pd
from sklearn.ensemble import RandomForestRegressor

if __name__ == "__main__":
    br = '\n'
    f = 'data/redwine.csv'
    red_wine = pd.read_csv(f)
    X = red_wine.drop(['quality'], axis=1)
    y = red_wine['quality']
    print (X.shape)
    print (y.shape, br)
    features = list(X)
    rfr = RandomForestRegressor(random_state=0,
                                n_estimators=100)
    rfr_name = rfr.__class__.__name__
    rfr.fit(X, y)
    feature_importances = rfr.feature_importances_
    importance = sorted(zip(feature_importances, features),
                        reverse=True)
    n = 3
    print (n, 'most important features' + ' (' + rfr_name + '):')
    [print (row) for i, row in enumerate(importance) if i < n]
    for row in importance:
        print (row)
    print ()
    print (red_wine[['alcohol', 'sulphates', 'volatile acidity',
                     'total sulfur dioxide', 'quality']]. head())

Listing 1-9Characterize redwine

在执行清单 1-9 中的代码后,您的输出应该如下所示:

(1599, 11)
(1599,)

3 most important features (RandomForestRegressor):
(0.27432500255956216, 'alcohol')
(0.13700073893077233, 'sulphates')
(0.13053941311188708, 'volatile acidity')
(0.27432500255956216, 'alcohol')
(0.13700073893077233, 'sulphates')
(0.13053941311188708, 'volatile acidity')
(0.08068199773601588, 'total sulfur dioxide')
(0.06294612644261727, 'chlorides')
(0.057730976351602854, 'pH')
(0.055499749756166, 'residual sugar')
(0.05198192402458334, 'density')
(0.05114079873500658, 'fixed acidity')
(0.049730883807319035, 'free sulfur dioxide')
(0.04842238854446754, 'citric acid')

   alcohol  sulphates  volatile acidity  total sulfur dioxide  quality
0      9.4       0.56              0.70                  34.0      5.0
1      9.8       0.68              0.88                  67.0      5.0
2      9.8       0.65              0.76                  54.0      5.0
3      9.8       0.58              0.28                  60.0      6.0
4      9.4       0.56              0.70                  34.0      5.0

代码示例从加载 pandas 和 RandomForestRegressor 包开始。主程序块将 redwine.csv 加载到 Pandas DataFrame 中。然后显示特征和目标形状。代码最后用 RandomForestRegressor 训练 pandas 数据,显示三个最重要的特性,并显示数据集中的前五条记录。RandomForestRegressor 也是一种集成算法,但它用于目标为数字或连续的情况。

小费

对于使用该参数来稳定结果的算法,总是硬编码 random_state (例如,random_state=0)。

白葡萄酒示例遵循完全相同的逻辑,但是输出在数据集大小和特性重要性方面有所不同。

列表 1-10 描述了 whitewine.csv。

import numpy as np, pandas as pd
from sklearn.ensemble import RandomForestRegressor

if __name__ == "__main__":
    br = '\n'
    f = 'data/whitewine.csv'
    white_wine = pd.read_csv(f)
    X = white_wine.drop(['quality'], axis=1)
    y = white_wine['quality']
    print (X.shape)
    print (y.shape, br)
    features = list(X)
    rfr = RandomForestRegressor(random_state=0,
                                n_estimators=100)
    rfr_name = rfr.__class__.__name__
    rfr.fit(X, y)
    feature_importances = rfr.feature_importances_
    importance = sorted(zip(feature_importances, features),
                        reverse=True)
    n = 3
    print (n, 'most important features' + ' (' + rfr_name + '):')
    [print (row) for i, row in enumerate(importance) if i < n]
    print ()
    print (white_wine[['alcohol', 'sulphates',
                       'volatile acidity',
                       'total sulfur dioxide',
                       'quality']]. head())

Listing 1-10Characterize whitewine

在执行清单 1-10 中的代码后,您的输出应该如下所示:

(4898, 11)
(4898,)

3 most important features (RandomForestRegressor):
(0.24186185906056268, 'alcohol')
(0.1251626059551235, 'volatile acidity')
(0.11524332271725685, 'free sulfur dioxide')

   alcohol  sulphates  volatile acidity  total sulfur dioxide  quality
0      8.8       0.45              0.27                 170.0      6.0
1      9.5       0.49              0.30                 132.0      6.0
2     10.1       0.44              0.28                  97.0      6.0
3      9.9       0.40              0.23                 186.0      6.0
4      9.9       0.40              0.23                 186.0      6.0

波士顿数据

我们表征的最终数据集是 load_boston。load_boston 数据集包含波士顿不同位置的房价。它由 506 条记录组成,有 13 个特征和一个目标。目标值代表业主自住房屋的中值。

清单 1-11 表征 load_boston。

from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor

if __name__ == "__main__":
    br = '\n'
    boston = load_boston()
    print (boston.keys(), br)
    print (boston.feature_names, br)
    X = boston.data
    y = boston.target
    print ('feature shape', X.shape)
    print ('target shape', y.shape, br)
    keys = boston.keys()
    rfr = RandomForestRegressor(random_state=0,
                                n_estimators=100)
    rfr.fit(X, y)
    features = boston.feature_names
    feature_importances = rfr.feature_importances_
    importance = sorted(zip(feature_importances, features),
                        reverse=True)
    [print(row) for i, row in enumerate(importance) if i < 3]

Listing 1-11Characterize load_boston

在执行清单 1-11 中的代码后,您的输出应该如下所示:

dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])

['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
 'B' 'LSTAT']

feature shape (506, 13)
target shape (506,)

(0.45730362625767496, 'RM')
(0.35008661885681375, 'LSTAT')
(0.06518862820215894, 'DIS')

代码从导入 load_boston 和 RandomForestRegressor 开始。主块显示键,将数据加载到 X 和 y 中,并显示要素和数据形状。代码继续创建 RandomForestRegressor 并用 X 和 y 对其进行训练,以便显示特征的重要性。

特征缩放

特性缩放正在标准化特性集数据。当要素集数据在量值、单位和范围方面变化很大时,要素缩放很重要。如果这样的数据没有被缩放,一些机器学习算法可能表现不佳,因为它们不能正确地考虑特征集数据差异。为了缓解这个问题,我们将方差标准化。标准化将要素重新调整为均值为零(μ = 0)和标准差为 1(σ= 1)的标准正态分布的属性。也就是说,我们通过移除平均值并缩放至单位方差来重新缩放要素。

Scikit-Learn 应用 StandardScaler,它通过移除平均值并缩放到单位方差来标准化特征。清单 1-12 中显示的代码示例演示了 StandardScaler 如何与机器学习算法一起工作。

import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.linear_model import SGDClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score

if __name__ == "__main__":
    br = '\n'
    digits = load_digits()
    X = digits.data
    y = digits.target
    X_train, X_test, y_train, y_test =\
             train_test_split(X, y, random_state=0)
    sgd = SGDClassifier(random_state=0, max_iter=1000,
                        tol=0.001)
    sgd.fit(X_train, y_train)
    sgd_name = sgd.__class__.__name__
    print ('<<' + sgd_name + '>>', br)
    y_pred = sgd.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    print ('unscaled \'test\' accuracy:', accuracy)
    scaler = StandardScaler().fit(X_train)
    X_train_std, X_test_std = scaler.transform(X_train),\
                              scaler.transform(X_test)
    sgd_std = SGDClassifier(random_state=0, max_iter=1000,
                            tol=0.001)
    sgd_std.fit(X_train_std, y_train)
    y_pred = sgd_std.predict(X_test_std)
    accuracy = accuracy_score(y_test, y_pred)
    print ('scaled \'test\' accuracy:', np.round(accuracy, 4))

Listing 1-12Scaling load_digits

在执行清单 1-12 中的代码后,您的输出应该如下所示:

<<SGDClassifier>>

unscaled 'test' accuracy: 0.92
scaled 'test' accuracy: 0.9333

代码示例首先加载 train_test_split、SGDClassifier、StandardScaler、accuracy_score 和其他必需的包。train_test_split 将数据集中的向量(数据元素)分割成随机的训练测试子集。机器学习算法使用训练子集进行训练,而测试子集用于验证。

将数据分成训练测试子集是机器学习的基础,因为模型从训练数据中学习,而测试数据被认为是模型从未见过的新数据。由于测试数据从未被模型看到,我们可以确信我们的准确度分数是有效的。所以,从来没有用测试数据进行训练!

SGDClassifier 是一种分类算法,通过随机梯度下降(SGD)学习实现正则化线性模型。每次对每个样本估计损失(或误差)的梯度,并且该模型沿着递减的强度时间表(或学习速率)进行更新。分类是预测给定数据点的目标。目标也称为类、标签或类别。分类将在接下来的两章中深入讨论。

accuracy_score 用于计算准确度。主程序块首先将 load_digits 放入特征集 X 和目标集 y。代码接着将 X 和 y 分成训练测试子集。X_train 和 y_train 用于训练。X_test 和 y_test 用于验证。接下来,创建模型并将其分配给变量 sgd。然后用 X_train 和 y_train 对模型进行训练。然后根据 X_test 进行预测,并将其分配给 y_pred。通常,预测是根据测试数据进行的,但我们可以根据训练数据进行预测,以了解我们的模型表现如何。代码继续缩放训练数据,用缩放的数据训练模型,并显示精度。注意缩放提高了精确度。

降维

降维(或特征)是通过获得一组主变量(或分量)来减少所考虑的随机变量的数量。主成分是一组线性不相关变量的值。

前提是数据包含一些冗余或不相关的特征,因此可以删除而不会丢失太多信息。然而,请记住,维度缩减总是会导致一些信息丢失。

降维可以简化模型,减少训练时间,减少过拟合,避免维数灾难。过拟合是模型对数据训练得太好。也就是说,模型完全理解数据,但也会产生噪声(或误差)。因此,不需要的噪声成为模型理解数据的一部分。当在高维空间(可能包含成百上千个维度)处理数据时,诅咒尤为突出,因为它使得分析和组织数据变得非常困难。在低维空间如 2D 或人类经验中常见的三维(3D)空间中处理数据要容易得多。

维数约简对于无监督学习是有用的。无监督学习从特征数据中进行推断,而不知道它们各自的标记响应(或目标)。无监督学习对于探索数据中隐藏的模式或分组很有用。

三种常见的 Scikit-Learn 降维技术是主成分分析(PCA)、线性判别分析(LDA)和 Isomap。PCA 和 LDA 是线性降维方法。Isomap 是一种非线性降维方法。

清单 1-13 中显示的第一个代码示例利用 Iris 上的 PCA 和 LDA 来识别聚类。

from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import\
     LinearDiscriminantAnalysis
import seaborn as sns, matplotlib.pyplot as plt

if __name__ == "__main__":
    br = '\n'
    iris = load_iris()
    X = iris.data
    y = iris.target
    pca = PCA(n_components=0.95)
    X_reduced = pca.fit_transform(X)
    components = pca.n_components_
    model = PCA(n_components=components)
    model.fit(X)
    X_2D = model.transform(X)
    iris_df = sns.load_dataset('iris')
    iris_df['PCA1'] = X_2D[:, 0]
    iris_df['PCA2'] = X_2D[:, 1]
    print (iris_df[['PCA1', 'PCA2']].head(3), br)
    sns.set(color_codes=True)
    sns.lmplot('PCA1', 'PCA2', hue="species",
               data=iris_df, fit_reg=False)
    plt.suptitle('PCA reduction')
    lda = LinearDiscriminantAnalysis(n_components=2)
    transform_lda = lda.fit_transform(X, y)
    iris_df['LDA1'] = transform_lda[:,0]
    iris_df['LDA2'] = transform_lda[:,1]
    print (iris_df[['LDA1', 'LDA2']].head(3))
    sns.lmplot('LDA1', 'LDA2', hue="species",
               data=iris_df, fit_reg=False)
    plt.suptitle('LDA reduction')
    plt.show()

Listing 1-13PCA and LDA Iris dimensionality reduction

在执行清单 1-13 中的代码后,您的输出应该如下所示:

       PCA1      PCA2
0 -2.684126  0.319397
1 -2.714142 -0.177001
2 -2.888991 -0.144949

       LDA1      LDA2
0  8.061800  0.300421
1  7.128688 -0.786660
2  7.489828 -0.265384

列表 1-13 还显示了数字 1-6 和 1-7 。图 1-6 展示了 PCA 降维如何用于聚类的无监督学习可视化。图 1-7 展示了使用 LDA 进行降维如何有助于聚类的无监督学习可视化。

img/481580_1_En_1_Fig7_HTML.jpg

图 1-7

LDA 降维

img/481580_1_En_1_Fig6_HTML.jpg

图 1-6

主成分分析降维

代码示例从导入 PCA、LinearDiscriminantAnalysis 和其他必需的包开始。PCA 降低了由许多相关变量组成的数据的维数,同时保留了其大部分变化(或信息)。线性判别分析(LinearDiscriminantAnalysis)是判别函数分析(discriminant function analysis),它是将事物分配到同一类型的组、类或类别中的行为。

主程序块将 Iris 加载到 X 和 y 中,然后创建一个信息损失为 5%的 PCA 模型,并将 X 转换到 2D 空间,这样我们就可以自动确定主成分的最佳数量。接下来,创建模型并对数据进行训练。代码继续将 Iris 加载到 Pandas 数据帧中,这样我们就可以将两个主要组件切掉以便可视化。代码继续创建 LDA 模型,并根据数据对其进行训练。

请注意,LDA 对 X 和 y 数据进行训练,而 PCA 只对 X 数据进行训练。这两种方法都很好地实现了三种鸢尾的聚类可视化。

清单 1-14 中显示的最终代码示例使用 Isomap 来识别 load_digits 上的集群。

from sklearn.datasets import load_digits
from sklearn.manifold import Isomap
import matplotlib.pyplot as plt

if __name__ == "__main__":
    br = '\n'
    digits = load_digits()
    X = digits.data
    y = digits.target
    print ('feature data shape:', X.shape)
    iso = Isomap(n_components=2)
    iso_name = iso.__class__.__name__
    iso.fit(digits.data)
    data_projected = iso.transform(X)
    print ('project data to 2D:', data_projected.shape)
    project_1, project_2 = data_projected[:, 0],\
                           data_projected[:, 1]
    plt.figure(iso_name)
    plt.scatter(project_1, project_2, c=y, edgecolor="none",
                alpha=0.5, cmap="jet")
    plt.colorbar(label='digit label', ticks=range(10))
    plt.clim(-0.5, 9.5)
    plt.show()

Listing 1-14Isomap visualization

在执行清单 1-14 中的代码后,您的输出应该如下所示:

feature data shape: (1797, 64)
project data to 2D: (1797, 2)

清单 1-14 还显示了图 1-8 ,展示了 load_digits 上的 Isomap 可视化。

img/481580_1_En_1_Fig8_HTML.jpg

图 1-8

load_digits 上聚类的 Isomap 可视化

代码示例从导入 Isomap 和其他必备包开始。主程序块将数字数据加载到 X 和 y 中,并显示特征集 X 的形状。代码继续创建 Isomap 模型,将数据投影到 2D 空间,将主要组件分割为变量 project_1 和 project_2,并进行可视化。Isomap 在识别数字群 0-9 方面表现出色。

Isomap 是一个优秀的非线性数据可视化工具。由于 load_digits 数据是非线性的,Isomap 工作得很好。

二、简单训练集的分类

分类是预测离散类别标签的问题。类也称为目标、标签或类别。通过对训练数据训练分类器算法来预测新数据如何被分类,从而应用分类。

机器学习分类数据集由特征(X)和目标(y)组成,其中输入变量 X 描述已知的离散输出变量 y 。特征数据通常被称为特征集(或特征空间)。分类被认为是监督学习,因为我们知道对应于特征集的目标。

咻!那太多了。所以,让我们看一个简单的例子来帮助你理解分类是如何工作的。假设我们有一个由四类水果组成的数据集,即“苹果”、“橘子”、“柠檬”和“酸橙”。每个数据元素(或行)通过质量、宽度、高度和颜色(特征)描述一个水果(目标)。因此,苹果和桔子可以通过不同的质量、宽度、高度和颜色来区分。

在这个例子中,类标签是水果的类型。每种水果都是独立的。也就是说,苹果很容易与其他种类的水果区分开来。目标是根据水果的质量、宽度、高度和颜色来预测水果的类型。

为了训练数据集,我们将数据分成训练测试子集。列车数据特征称为 X_train ,目标称为 y_train 。测试数据特征称为 X_test ,目标称为 y_test 。然后,我们建立一个分类模型来训练 X_train 和 y_train 数据。一旦对模型进行了训练,我们就可以从 X_test 和 y_test 数据中进行验证和预测,因为模型还没有看到测试数据。通过将测试数据排除在训练过程之外,它有效地充当了数据。

小费

千万不要在测试数据上训练,以保持数据的纯净。

典型的训练测试分割是 70%/30%,但是应该根据数据集的大小来选择该比率。如果数据集很小,30%的测试集可能不包含所有的类或足够的信息来正确验证。此外,不同类别在训练集和测试集中的分布应该等于实际数据集。确保这种分布的最好方法是随机分割训练测试子集。幸运的是,Scikit-Learn 的 train_test_split 包会自动随机化拆分,但其默认的 train-test 拆分是 75%/25%。

我推荐一些处理机器学习问题时的通用步骤。首先,总是为了训练和验证的目的分割数据。第二,尝试扩展数据以潜在地提高性能。第三,试验训练和测试规模。第四,始终从基线模型、简单算法或基于数据集先前经验的算法开始。从算法的默认超参数开始。第五,试验更复杂的模型,因为 Scikit-Learn 是高效的,并且允许容易的模型替换。当处理大数据集时,尝试抽取随机样本以减少计算开销。当处理高维数据集时,尝试使用 PCA 或 LDA 进行降维,以减少计算开销。第六,调整前面步骤中确定的最佳算法,以获得最佳性能。最后,多做一些实验。机器学习是非常耗费时间和严谨的,所以要有耐心,不要放弃。

小费

总是从算法的默认超参数开始训练。

简单数据集

我们专注于四个简单的数据集来介绍机器学习分类:葡萄酒、数字、银行和月亮。我们没有在第一章介绍人造月亮,因为它是人为的。也就是说,Scikit-Learn 为 make_moons 提供了基础,我们按照自己认为合适的方式构建它。

葡萄酒数据分类

清单 2-1 中所示的代码示例对葡萄酒数据进行分类。

from sklearn.datasets import load_wine
from sklearn.preprocessing import StandardScaler
from sklearn.discriminant_analysis import\
     LinearDiscriminantAnalysis as LDA
from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import train_test_split
from sklearn import metrics
from random import *

if __name__ == "__main__":
    br = '\n'
    data = load_wine()
    X = data.data
    y = data.target
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.30, random_state=0)
    lda = LDA().fit(X_train, y_train)
    print (lda, br)
    lda_name = lda.__class__.__name__
    y_pred = lda.predict(X_train)
    accuracy = metrics.accuracy_score(y_train, y_pred)
    accuracy = str(accuracy * 100) + '%'
    print (lda_name + ':')
    print ('train:', accuracy)
    y_pred_test = lda.predict(X_test)
    accuracy = metrics.accuracy_score(y_test, y_pred_test)
    accuracy = str(round(accuracy * 100, 2)) + '%'
    print ('test: ', accuracy, br)
    print('Confusion Matrix', lda_name)
    print(metrics.confusion_matrix(y_test, lda.predict(X_test)), br)
    std_scale = StandardScaler().fit(X_train)
    X_train = std_scale.transform(X_train)
    X_test = std_scale.transform(X_test)
    sgd = SGDClassifier(max_iter=5, random_state=0)
    print (sgd, br)
    sgd.fit(X_train, y_train)
    sgd_name = sgd.__class__.__name__
    y_pred = sgd.predict(X_train)
    y_pred_test = sgd.predict(X_test)
    print (sgd_name + ':')
    print('train: {:.2%}'.format(metrics.accuracy_score\(y_train, y_pred)))
    print('test:  {:.2%}\n'.format(metrics.accuracy_score\(y_test, y_pred_test)))
    print('Confusion Matrix', sgd_name)
    print(metrics.confusion_matrix(y_test, sgd.predict(X_test)), br)
    n, ls = 100, []

    for i, row in enumerate(range(n)):
        rs = randint(0, 100)
        sgd = SGDClassifier(max_iter=5, random_state=0)
        sgd.fit(X_train, y_train)
        y_pred = sgd.predict(X_test)
        accuracy = metrics.accuracy_score(y_test, y_pred)
        ls.append(accuracy)
    avg = sum(ls) / len(ls)
    print ('MCS (true test accuracy):', avg)

Listing 2-1Classify load_wine data

继续执行清单 2-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。

执行清单 2-1 的输出应该如下所示:

LinearDiscriminantAnalysis(n_components=None, priors=None, shrinkage=None, solver="svd", store_covariance=False, tol=0.0001)

LinearDiscriminantAnalysis:
train: 100.0%
test:  98.15%

Confusion Matrix LinearDiscriminantAnalysis
[[19  0  0]
 [ 1 21  0]
 [ 0  0 13]]

SGDClassifier(alpha=0.0001, average=False, class_weight=None,
       early_stopping=False, epsilon=0.1, eta0=0.0,
       fit_intercept=True, l1_ratio=0.15,
       learning_rate='optimal', loss="hinge", max_iter=5,
       n_iter=None, n_iter_no_change=5, n_jobs=None,
       penalty='l2', power_t=0.5, random_state=0, shuffle=True,
       tol=None, validation_fraction=0.1, verbose=0,
       warm_start=False)

SGDClassifier:
train: 100.00%
test:  100.00%

Confusion Matrix SGDClassifier
[[19  0  0]
 [ 0 22  0]
 [ 0  0 13]]

MCS (true test accuracy): 1.0

代码从导入度量、随机和必需的包开始。主程序块首先加载数据,并将其分成训练测试子集。请注意,我们将测试规模调整为 30%。接下来,创建线性判别分析(LDA)模型,并在训练集上进行训练。你可以调整测试规模,看看你的准确性是否有所提高。但是,不要搞得太大。您的模型需要训练数据来更好地理解和学习。

小费

要查看模型的超参数,只需在创建后打印保存模型的变量(例如,print (lda))。

LDA 是在第一章中介绍的,作为一种无监督的降维学习模型。LDA 是一个非常有趣的模型,因为它执行非监督维度缩减监督分类。

数据缩放并不能提高 LDA 的性能,所以模型是在未缩放的数据上训练的。然后计算训练和测试子集的准确度分数。性能准确性通常只在测试数据上报告。但是,获得训练和测试精度以查看模型与数据的拟合程度是很有用的。在这种情况下,模型非常符合数据,因为训练精度和测试精度非常相似。如果训练精度远高于测试精度,则模型会过度拟合数据。

代码继续显示混淆矩阵。一个混淆矩阵描述了一个分类模型(或分类器)对一组真实值已知的测试数据的性能。由 19、21 和 13 组成的对角线是模型正确分类的地方。该模型只对测试集中的一个数据元素进行了错误分类,这非常有意义,测试准确率超过 98%。接下来,我们缩放数据,因为众所周知 SGDClassifier 在处理缩放数据时性能更好。该模型被训练,并且训练和测试精度与混淆矩阵一起显示。有了这个模型,分类就完美了。

代码的最后一部分是可选的。它使用蒙特卡罗实验来验证 SGD 分类器在葡萄酒数据上的性能。蒙特卡罗实验利用随机性解决确定性(或监督性)问题。完美的测试准确率 100%,我们应该有点怀疑。因此,我们运行了 100 次蒙特卡罗实验来获得实际的测试性能。如你所见,我们得到 100%!

蒙特卡罗实验是一种很好的获得精确度的方法,但是计算量非常大。我们在这种情况下是安全的,因为数据集既小又简单。对于高维数据的大数据集,蒙特卡罗实验并不是很实用。

线性判别分析和 SGD 分类器不是随机选择的。通过严格的反复试验和研究,这些算法被战略性地确定为最佳表现。

小费

每个数据集都是不同的,所以通过反复试验和研究来战略性地选择算法。

分类数字

清单 2-2 中显示的第一个代码示例加载数据,并将其分成训练测试子集。接下来,使用分类器 GaussianNB、SGDClassifier 和 svm 训练数据。算法 svm 表现最好。然后代码识别并可视化错误分类。代码以可视化第一个错误分类结束。

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import SGDClassifier
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt
import seaborn as sns

def find_misses(test, pred):
    return [i for i, row in enumerate(test) if row != pred[i]]

if __name__ == "__main__":
    br = '\n'
    digits = load_digits()
    X = digits.data
    y = digits.target
    X_train, X_test, y_train, y_test = train_test_split\
                                       (X, y, random_state=0)
    gnb = GaussianNB().fit(X_train, y_train)
    gnb_name = gnb.__class__.__name__
    y_pred = gnb.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    print (gnb_name + ' \'test\' accuracy:', accuracy)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    sgd = SGDClassifier(random_state=0, max_iter=1000, tol=0.001)
    sgd_name = sgd.__class__.__name__
    sgd.fit(X_train_std, y_train)
    y_pred = sgd.predict(X_test_std)
    accuracy = accuracy_score(y_test, y_pred)
    print (sgd_name + ' \'test\' accuracy:', accuracy)
    svm = SVC(gamma='auto').fit(X_train_std, y_train)
    svm_name = svm.__class__.__name__
    y_pred = svm.predict(X_test_std)
    accuracy = accuracy_score(y_test, y_pred)
    print (svm_name + ' \'test\' accuracy:', accuracy, br)
    indx = find_misses(y_test, y_pred)
    print ('total misclassifications (' + str(svm_name) +\ '):', len(indx), br)
    print ('pred', 'actual')
    misses = [(y_pred[row], y_test[row], i)
              for i, row in enumerate(indx)]
    [print (row[0], '  ', row[1]) for row in misses]
    img_indx = misses[0][2]
    img_pred = misses[0][0]
    img_act = misses[0][1]
    text = str(img_pred)
    print(classification_report(y_test, y_pred))
    cm = confusion_matrix(y_test, y_pred)
    plt.figure(1)
    ax = plt.axes()
    sns.heatmap(cm.T, annot=True, fmt="d",
                cmap='gist_ncar_r', ax=ax)
    title = svm_name + ' confusion matrix'
    ax.set_title(title)
    plt.xlabel('true value')
    plt.ylabel('predicted value')
    test_images = X_test.reshape(-1, 8, 8)
    plt.figure(2)
    plt.title('1st misclassifcation')
    plt.imshow(test_images[img_indx], cmap="gray", interpolation="gaussian")
    plt.text(0, 0.05, text, color="r", bbox=dict(facecolor='white'))
    plt.show()

Listing 2-2Classify load_digits data

在执行清单 2-2 中的代码后,您的输出应该如下所示:

GaussianNB 'test' accuracy: 0.8333333333333334
SGDClassifier 'test' accuracy: 0.9377777777777778
SVC 'test' accuracy: 0.9822222222222222

total misclassifications (SVC): 8

pred actual
7    2
1    8
7    9
9    5
4    7
4    3
2    8
4    1
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        37
           1       0.98      0.98      0.98        43
           2       0.98      0.98      0.98        44
           3       1.00      0.98      0.99        45
           4       0.93      1.00      0.96        38
           5       1.00      0.98      0.99        48
           6       1.00      1.00      1.00        52
           7       0.96      0.98      0.97        48
           8       1.00      0.96      0.98        48
           9       0.98      0.98      0.98        47

   micro avg       0.98      0.98      0.98       450
   macro avg       0.98      0.98      0.98       450
weighted avg       0.98      0.98      0.98       450

列表 2-2 还显示了数字 2-1 和 2-2 。图 2-1 显示了最佳执行算法的混淆矩阵,即 svm。您会看到显示的 SVC ,因为我们正在实现 svm 算法的 SVC 实现。SVC 实现利用 C-支持向量分类,其被表示为 svm。Scikit 中的 SVC。图 2-2 显示了预测集中的第一个误分类,即数字 2 误分类为数字 7。如果我们查看混淆矩阵,我们可以在数字 7 的预测值行和数字 2 的真值列的交叉点上看到这种错误分类。因此,真实值(数字 2)被错误地预测(或错误分类)为数字 7。

img/481580_1_En_2_Fig2_HTML.jpg

图 2-2

预测集中的第一个错误分类

img/481580_1_En_2_Fig1_HTML.jpg

图 2-1

支持向量机的混淆矩阵。SVC 算法

代码示例首先导入 GaussianNB、confusion_matrix 和 classification_report 以及其他必需的包。高斯神经网络是一种优秀的基线算法,因为它速度快,在许多分类问题上表现良好,并且需要调整的超参数很少。

小费

如果您没有使用分类数据集的经验,GaussianNB 是一个很好的起点,因为它简单、快速、易于理解,并且需要调整的超参数很少。

函数 find _ misses 返回一个分类错误的数字列表。主块加载数据,将其分成训练测试子集,并使用 GaussianNB、SGDClassifier 和 svm 进行训练。

高斯神经网络是一种概率分类器,基于应用贝叶斯定理,具有特征之间的强独立性假设。SGDClassifier 是一个分类器,它实现了一个简单的随机梯度下降学习例程,支持不同的损失函数和分类惩罚。支持向量机(svm)建立一个模型,将新的样本分配到一个类别或另一个类别。

然后代码显示所有三个模型的测试精度。自从 svm。SVC 得分最高,我们用它来识别错误分类。然后显示错误分类总数。代码继续显示每个错误分类是如何呈现的。所以,第一个分类错误的数字是 2,它被误分类为 7。

接下来,代码创建一个分类报告(用于 svm。SVC 算法),它为每个数字提供了精确度、召回率和 f1_score 分数。准确性是报告分数的一个很好的方法,但是 f1_score 是一个值得考虑的方法,因为它是最保守的。

代码以显示一个 svm 结束。SVC 混淆矩阵和第一个误分类,其中 2 被误分类为 7。该图也很有趣,因为它显示了数字 2 的实际图像,以及它被分类为红色的数字 7 的方式。

一旦为一个数据集确定了一个很好的执行算法,我强烈推荐创建一个混淆矩阵可视化。不仅很容易理解算法对目标的分类有多好,它还允许更深入地检查算法在哪里没有按预期执行。

小费

创建混淆矩阵可视化是了解算法执行情况的一个很好的方式。

虽然我们在前面的例子中从 svm 获得了高准确度的分数,但是 Scikit-Learn 允许我们非常容易地替换分类器。因此,下一个代码示例有点疯狂,用六个额外的分类器训练葡萄酒数据。请记住,数据集是小而简单的。对于更大和更复杂的数据,替换分类器在计算上是昂贵的。

清单 2-3 中显示的下一个代码示例使用几个 Scikit-Learn 算法对葡萄酒数据进行分类,以识别有希望提高性能的数据。

import humanfriendly as hf
import time
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression,\
     LogisticRegressionCV
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier,\
     ExtraTreesClassifier, GradientBoostingClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score

def get_scores(model, Xtest, ytest, avg):
    y_pred = model.predict(Xtest)
    accuracy = accuracy_score(ytest, y_pred)
    f1 = f1_score(ytest, y_pred, average=avg)
    return (accuracy, f1)

def get_time(time):
    return hf.format_timespan(time, detailed=True)

if __name__ == "__main__":
    br = '\n'
    digits = load_digits()
    X = digits.data
    y = digits.target
    X_train, X_test, y_train, y_test = train_test_split\
                                       (X, y, random_state=0)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)

    lr = LogisticRegression(random_state=0, solver="lbfgs",
                            multi_class='auto', max_iter=4000)
    lr.fit(X_train_std, y_train)
    lr_name = lr.__class__.__name__
    acc, f1 = get_scores(lr, X_test_std, y_test, 'micro')
    print (lr_name + ' scaled \'test\':')
    print ('accuracy:', acc, ', f1_score:', f1, br)
    softmax = LogisticRegression(multi_class="multinomial",
                                 solver="lbfgs", max_iter=4000,
                                 C=10, random_state=0)
    softmax.fit(X_train_std, y_train)
    acc, f1 = get_scores(softmax, X_test_std, y_test, 'micro')
    print (lr_name + ' (softmax) scaled \'test\':')
    print ('accuracy:', acc, ', f1_score:', f1, br)
    rf = RandomForestClassifier(random_state=0, n_estimators=100)
    rf.fit(X_train_std, y_train)
    rf_name = rf.__class__.__name__
    acc, f1 = get_scores(rf, X_test_std, y_test, 'micro')
    print (rf_name + ' \'test\':')
    print ('accuracy:', acc, ', f1_score:', f1, br)
    et = ExtraTreesClassifier(random_state=0, n_estimators=100)
    et.fit(X_train, y_train)
    et_name = et.__class__.__name__

    acc, f1 = get_scores(et, X_test, y_test, 'micro')
    print (et_name + ' \'test\':')
    print ('accuracy:', acc, ', f1_score:', f1, br)
    gboost_clf = GradientBoostingClassifier(random_state=0)
    gb_name = gboost_clf.__class__.__name__
    gboost_clf.fit(X_train, y_train)
    acc, f1 = get_scores(gboost_clf, X_test, y_test, 'micro')
    print (gb_name + ' \'test\':')
    print ('accuracy:', acc, ', f1_score:', f1, br)
    knn_clf = KNeighborsClassifier().fit(X_train, y_train)
    knn_name = knn_clf.__class__.__name__
    acc, f1 = get_scores(knn_clf, X_test, y_test, 'micro')
    print (knn_name + ' \'test\':')
    print ('accuracy:', acc, ', f1_score:', f1, br)
    start = time.perf_counter()
    lr_cv = LogisticRegressionCV(random_state=0, cv=5, multi_class="auto", max_iter=4000)
    lr_cv_name = lr_cv.__class__.__name__
    lr_cv.fit(X, y)
    end = time.perf_counter()
    elapsed_ls = end - start
    timer = get_time(elapsed_ls)
    print (lr_cv_name + ' timer:', timer)
    acc, f1 = get_scores(lr_cv, X_test, y_test, 'micro')
    print (lr_cv_name + ' \'test\':')
    print ('accuracy:', acc, ', f1_score:', f1)

Listing 2-3Classifying load_digits with various algorithms

在执行清单 2-3 中的代码后,您的输出应该如下所示:

LogisticRegression scaled 'test':
accuracy: 0.9733333333333334, f1_score: 0.9733333333333334

LogisticRegression (softmax) scaled 'test':
accuracy: 0.9644444444444444, f1_score: 0.9644444444444444

RandomForestClassifier 'test':
accuracy: 0.9755555555555555, f1_score: 0.9755555555555555

ExtraTreesClassifier 'test':
accuracy: 0.9822222222222222, f1_score: 0.9822222222222222

GradientBoostingClassifier 'test':
accuracy: 0.9622222222222222, f1_score: 0.9622222222222222

KNeighborsClassifier 'test':
accuracy: 0.98, f1_score: 0.98

LogisticRegressionCV timer: 49 seconds and 38.45 milliseconds
LogisticRegressionCV 'test':
accuracy: 0.9822222222222222, f1_score: 0.9822222222222222

代码从导入 humanfriendly、time、LogisticRegression、LogisticRegressionCV、KNeighborsClassifier、GradientBoostingClassifier 和 ExtraTreesClassifier 以及其他必需的包开始。函数 get_scores 返回精度和 f1_score。函数 get_time 返回经过的时间。它有助于发现一个算法训练一个数据集需要多长时间。

LogisticRegression 是一种传统上仅限于两类分类问题的分类算法。Softmax(多项逻辑回归)分类使用逻辑回归进行多类分类。RandomForestClassifier 是一种集成学习方法,它在训练时构建大量决策树,并输出作为类模式的类。ExtraTreesClassifier 实现了一个元估计器,该估计器在数据集的各种子样本上拟合多个随机决策树(或额外的树),并使用平均来提高预测精度和控制过度拟合。GradientBoostingClassifier 以弱预测模型(通常为决策树)的形式生成预测模型。KNeighborsClassifier 实现了 k-nearest neighbors 投票,其中输入由特征空间中的 k 个最近的训练示例组成。特征空间指的是你的特征存在的 n 维空间。LogisticRegression 使用逻辑函数对数据进行建模。LogisticRegressionCV 使用逻辑回归实现交叉验证估计。

交叉验证(CV)将数据分成 n 个子集,并迭代 n 次。通过每次迭代, n 个子集中的一个被拿出来作为测试集,而其余的用于训练。每次迭代使用不同的子集。因此,精确度和误差在所有的 n 次试验中被平均。结果精度非常好,但是 CV 的计算代价很高。

GradientBoostingClassifier 和 ExtraTreesClassifier 是与 RandomForestClassifier 相似的集成方法,它们根据数据和平均结果拟合(或训练)许多决策树,以提高预测准确性。

小费

您可能需要安装这个人性化的软件包,因为 Anaconda 不会自动安装它。打开一个新的 Anaconda 提示符,如清单 2-4 所示进行安装。

pip install humanfriendly

Listing 2-4Install a new package

主程序块从加载数据并将其分成训练测试子集开始。每种算法训练数据并显示分数。请注意,LogisticRegressionCV 消耗了超过 46 秒的时间。尽管所有算法的表现都令人钦佩,但我们仍然无法达到 98.22%的准确率。

银行数据分类

清单 2-5 中显示的第一个代码示例从一个 CSV 文件中加载银行数据。接下来,教育功能被设计得更像样。

特征工程正在创建使机器学习算法工作的特征(基于数据的领域知识)。虽然特征工程是机器学习应用的基础,但它既困难又昂贵。

然后,代码将分类特征转换为数字特征,以便进行算法训练。这种转换通常被称为编码。

小费

机器学习算法只对数字数据进行运算。

最后,将显示五个最重要的要素以及数据要素和类计数。功能集和目标保存在 NumPy 文件中。

import numpy as np, pandas as pd
from sklearn.ensemble import RandomForestClassifier

if __name__ == "__main__":

    br = '\n'
    f = 'data/bank.csv'
    data = pd.read_csv(f)
    print ('original "education" categories:')
    print (data.education.unique(), br)
    data['education'] = np.where(data['education'] == 'basic.9y',
                                 'basic', data['education'])
    data['education'] = np.where(data['education'] == 'basic.6y',
                                 'basic', data['education'])
    data['education'] = np.where(data['education'] == 'basic.4y',
                                 'basic', data['education'])
    data['education'] = np.where(data['education'] == 'high.school', 'high_school', data.education)
    data['education'] = np.where(data['education'] == 'professional.course', 'professional', data['education'])
    data['education'] = np.where(data['education'] == 'university.degree', 'university', data['education'])
    print ('engineered "education" categories:')
    print (data.education.unique(), br)
    print ('target value counts:')
    print (data.y.value_counts(), br)
    data_X = data.loc[:, data.columns != 'y']
    cat_vars = ['job', 'marital', 'education', 'default', 'housing', 'loan', 'contact', 'month', 'day_of_week', 'poutcome']
    data_new = pd.get_dummies(data_X, columns=cat_vars)
    X = data_new.values
    y = data.y.values
    attributes = list(data_X)
    rf = RandomForestClassifier(random_state=0, n_estimators=100)
    rf.fit(X, y)
    rf_name = rf.__class__.__name__
    feature_importances = rf.feature_importances_
    importance = sorted(zip(feature_importances, attributes), reverse=True)
    n = 5
    print (n, 'most important features' + ' (' + rf_name + '):')
    [print (row) for i, row in enumerate(importance) if i < n]
    print ()
    features_file = 'data/features'
    np.save(features_file, attributes)
    features = np.load('data/features.npy')
    print ('features:')
    print (features, br)
    y_file = 'data/y'
    X_file = 'data/X'
    np.save(y_file, y)
    np.save(X_file, X)
    d = {}
    dvc = data.y.value_counts()
    d['no'], d['yes'] = dvc['no'], dvc['yes']
    dvc_file = 'data/value_counts'
    np.save(dvc_file, d)
    d = np.load('data/value_counts.npy')
    print ('class counts:', d)

Listing 2-5Engineering and wrangling bank data

在执行清单 2-5 中的代码后,您的输出应该如下所示:

original "education" categories:
['basic.4y' 'high.school' 'basic.6y' 'basic.9y' 'professional.course'
 'unknown' 'university.degree' 'illiterate']

engineered "education" categories:
['basic' 'high_school' 'professional' 'unknown' 'university' 'illiterate']

target value counts:
no     36548
yes     4640
Name: y, dtype: int64

5 most important features (RandomForestClassifier):
(0.28697175347986037, 'job')
(0.08761238456151103, 'month')
(0.0797624194551633, 'age')
(0.05492109153356108, 'day_of_week')
(0.04027613029914145, 'marital')

features:
['age' 'job' 'marital' 'education' 'default' 'housing' 'loan' 'contact' 'month' 'day_of_week' 'duration' 'campaign' 'pdays' 'previous' 'poutcome' 'emp.var.rate' 'cons.price.idx' 'cons.conf.idx' 'euribor3m' 'nr.employed']

class counts: {'no': 36548, 'yes': 4640}

该代码示例从导入必备包开始。主程序块读取数据并显示来自教育功能的原始值。代码继续对特征进行特征工程,并显示新值。请注意,只对单个特征进行特征工程是多么困难。接下来,分类特征由熊猫 get_dummies 函数编码为一个热编码(OHE)向量。Scikit-Learn 希望特征数据是数字,这就是为什么我们需要对它们进行编码。

OHE 向量也被称为虚拟变量。OHE 是一个很好的选择,因为它是机器学习中处理分类数据最常用的方法之一。OHE 获取每个类别值,并将其转换为大小为 I 的二进制向量(其中 I 是类别 I 中值的数量),并使除类别列之外的所有列都等于零。例如,在我们的数据集中,婚姻状况是“已婚”、“单身”或“离婚”。如果有人结婚了,OHE 编码一个[1 0 0]向量。如果是 single,OHE 编码一个[0 1 0]向量。最后,如果离婚,OHE 编码一个[0 0 1]向量。简单地说,1 位是热的,表示适合数据元素的类别。

然后,代码根据转换后的数据集创建特征集 X 和目标集 y。在 RandomForestClassifier 的帮助下显示特征重要性。接下来,X 和 y 被保存在 NumPy 文件中。最后,创建、保存和显示类计数。查看类计数有助于了解目标之间的平衡。

请注意,我们有更多的值,而不是值。所以,数据集有点不平衡。这种情况通常被称为不平衡类分布,即属于一个类的观测值数量明显低于属于其他类的观测值数量。在我们的例子中,是和不是的比例大约是 12.6%。所以,我们没有大问题。低于 5%的比率(或事件率)是一个问题,因为当这种情况发生时,机器学习算法可能产生不令人满意的分类。

现在银行数据已经准备好了,我们可以运行实验来识别高性能的分类算法,如下面的代码示例所示,如清单 2-6 所示。记住,许多小时的实验导致了这个例子中算法的选择。

import numpy as np, pandas as pd, random
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier,\
     ExtraTreesClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import f1_score
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

def get_scores(model, xtrain, ytrain, xtest, ytest, scoring):
    ypred = model.predict(xtest)
    train = model.score(xtrain, ytrain)
    test = model.score(xtest, y_test)
    f1 = f1_score(ytest, ypred, average=scoring)
    return (train, test, f1)

def prep_data(data, target):
    d = [data[i] for i, _ in enumerate(data)]
    t = [target[i] for i, _ in enumerate(target)]
    return list(zip(d, t))

def create_sample(d, n, replace="yes"):
    if replace == 'yes': s = random.sample(d, n)
    else: s = [random.choice(d) for i, _ in enumerate(d) if i < n]
    Xs = [row[0] for i, row in enumerate(s)]
    ys = [row[1] for i, row in enumerate(s)]
    return np.array(Xs), np.array(ys)

if __name__ == "__main__":

    br = '\n'
    X = np.load('data/X.npy')
    y = np.load('data/y.npy')
    print ('full data set shape for X and y:')
    print (X.shape, y.shape, br)
    X_train, X_test, y_train, y_test = train_test_split\
                                       (X, y, random_state=0)
    et = ExtraTreesClassifier(random_state=0, n_estimators=100)
    et.fit(X_train, y_train)
    et_scores = get_scores(et, X_train, y_train, X_test, y_test, 'micro')
    print (et.__class__.__name__ + '(train, test, f1_score):')
    print (et_scores, br)
    rf = RandomForestClassifier(random_state=0, n_estimators=100)
    rf.fit(X_train, y_train)
    rf_scores = get_scores(rf, X_train, y_train, X_test, y_test, 'micro')
    print (rf.__class__.__name__ + '(train, test, f1_score):')
    print (rf_scores, br)
    sample_size = 4000
    data = prep_data(X, y)
    Xs, ys = create_sample(data, sample_size, replace="no")
    print ('sample data set shape for X and y:')
    print (Xs.shape, ys.shape, br)
    X_train, X_test, y_train, y_test = train_test_split\
                                       (Xs, ys, random_state=0)
    scaler = StandardScaler().fit(X_train)
    X_train_std, X_test_std = scaler.transform(X_train),\
                              scaler.transform(X_test)
    knn = KNeighborsClassifier().fit(X_train, y_train)
    knn_scores = get_scores(knn, X_train, y_train, X_test, y_test, 'micro')
    print (knn.__class__.__name__ + '(train, test, f1_score):')
    print (knn_scores, br)
    svm = SVC(random_state=0, gamma="scale")
    svm.fit(X_train_std, y_train)
    svm_scores = get_scores(svm, X_train_std, y_train, X_test_std, y_test, 'micro')
    print (svm.__class__.__name__ + '(train, test, f1_score):')
    print (svm_scores, br)
    knn_name, svm_name = knn.__class__.__name__,\
                         svm.__class__.__name__

    y_pred_knn = knn.predict(X_test)
    cm_knn = confusion_matrix(y_test, y_pred_knn)
    cm_knn_T = cm_knn.T
    y_pred_svm = svm.predict(X_test_std)
    cm_svm = confusion_matrix(y_test, y_pred_svm)
    cm_svm_T = cm_svm.T
    plt.figure(knn.__class__.__name__)
    ax = plt.axes()
    sns.heatmap(cm_knn_T, annot=True, fmt="d", cmap="gist_ncar_r", cbar=False)
    ax.set_title(str(knn_name) + ' confusion matrix')
    plt.xlabel('true label')
    plt.ylabel('predicted label')
    plt.figure(str(svm_name) + ' confusion matrix' )
    ax = plt.axes()
    sns.heatmap(cm_svm_T, annot=True, fmt="d", cmap="gist_ncar_r", cbar=False)
    ax.set_title(svm_name)
    plt.xlabel('true label')
    plt.ylabel('predicted label')
    cnt_no, cnt_yes = 0, 0
    for i, row in enumerate(y_test):
        if row == 'no': cnt_no += 1
        elif row == 'yes': cnt_yes += 1
    cnt_no, cnt_yes = str(cnt_no), str(cnt_yes)
    print ('true =>', 'no: ' + cnt_no + ', yes: ' + cnt_yes, br)
    p_no, p_nox = cm_knn_T[0][0], cm_knn_T[0][1]
    p_yes, p_yesx = cm_knn_T[1][1], cm_knn_T[1][0]
    print ('knn classification report:')
    print ('predict \'no\':', p_no, '(' +\str(p_nox) + ' misclassifed)')
    print ('predict \'yes\':', p_yes, '(' +\str(p_yesx) + ' misclassifed)', br)
    p_no, p_nox = cm_svm_T[0][0], cm_svm_T[0][1]
    p_yes, p_yesx = cm_svm_T[1][1], cm_svm_T[1][0]
    print ('svm classification report:')
    print ('predict \'no\':', p_no, '(' +\str(p_nox) + ' misclassifed)')
    print ('predict \'yes\':', p_yes, '(' +\str(p_yesx) + ' misclassifed)')

    plt.show()

Listing 2-6Classifying bank data

在执行清单 2-6 中的代码后,您的输出应该如下所示:

full data set shape for X and y:
(41188, 61) (41188,)

ExtraTreesClassifier(train, test, f1_score):
(1.0, 0.9009420219481402, 0.9009420219481401)

RandomForestClassifier(train, test, f1_score):
(0.9999676281117478, 0.9121103233951636, 0.9121103233951636)

sample data set shape for X and y:
(4000, 61) (4000,)

KNeighborsClassifier(train, test, f1_score):
(0.9323333333333333, 0.916, 0.916)

SVC(train, test, f1_score):
(0.9376666666666666, 0.92, 0.92)

true => no: 902, yes: 98

knn classification report:
predict 'no': 869 (51 misclassifed)
predict 'yes': 47 (33 misclassifed)

svm classification report:
predict 'no': 883 (61 misclassifed)
predict 'yes': 37 (19 misclassifed)

列表 2-6 还显示了数字 2-3 和 2-4 。图 2-3 显示 KNeighborsClassifier 的混淆矩阵,图 2-4 显示 svm.SVC 的混淆矩阵。

img/481580_1_En_2_Fig4_HTML.jpg

图 2-4

svm。SVC 混淆矩阵

img/481580_1_En_2_Fig3_HTML.jpg

图 2-3

近邻分类器混淆矩阵

代码从导入必需的包开始。函数 get_scores 返回训练和测试准确度分数。函数 prep_data 将 NumPy 矩阵转换为向量列表,以便更容易地操作数据元素进行采样。函数 create_sample 构建一个随机样本,并将其作为 X 和 y NumPy 矩阵返回。

Scikit-Learn 算法只能训练表示为 NumPy 的数据。主块从上一个示例中创建的 NumPy 文件中加载 X 和 y。x 和 y 被分成训练测试子集。然后,代码使用 ExtraTreesClassifier 和 RandomForestClassifier 对数据进行定型。抽取 4000 个样本,以便我们可以使用 KNeighborsClassifier 和 svm.SVC 高效地进行训练。这两种算法是优秀的分类器,但是对于大数据集来说计算开销很大。

近邻分类器和支持向量机的混淆矩阵。然后显示 SVC,因为它们更适合数据。也就是说,这些模型的精确度更高,过度拟合更少。代码通过计算 KNeighborsClassifier 和 svm 的数据和错误分类的目标值的平衡得出结论。

值得注意的是 KNeighborsClassifier 和 svm。基于少于原始数据的 10% 的样本,SVC 比其他算法执行得更好。这其实是很可观的!

UCI 机器学习库包括从银行数据中随机选择的样本,其中有 10%的例子。为了完整起见,清单 2-7 中显示的下一个例子测试了这个样本的准确性。

import pandas as pd, numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier,\
     ExtraTreesClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import f1_score

def get_scores(model, xtrain, ytrain, xtest, ytest, scoring):
    ypred = model.predict(xtest)
    train = model.score(xtrain, ytrain)
    test = model.score(xtest, y_test)
    f1 = f1_score(ytest, ypred, average=scoring)
    return (train, test, f1)

if __name__ == "__main__":
    br = '\n'
    f = 'data/bank_sample.csv'
    data = pd.read_csv(f)
    print ('data shape:', data.shape, br)
    data['education'] =\
                      np.where(data['education'] == 'basic.9y',
                               'basic', data['education'])
    data['education'] = np.where(data['education'] == 'basic.6y',
                                 'basic', data['education'])
    data['education'] = np.where(data['education'] == 'basic.4y',
                                 'basic', data['education'])
    data['education'] = np.where(data['education'] == 'high.school', 'high_school', data.education)
    data['education'] = np.where(data['education'] == 'professional.course', 'professional', data['education'])
    data['education'] = np.where(data['education'] == 'university.degree', 'university', data['education'])
    data_X = data.loc[:, data.columns != 'y']
    cat_vars = ['job', 'marital', 'education', 'default', 'housing', 'loan', 'contact', 'month', 'day_of_week', 'poutcome']
    data_new = pd.get_dummies(data_X, columns=cat_vars)
    attributes = list(data_X)
    y = data.y.values

    X = data_new.values
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
    rf = RandomForestClassifier(random_state=0, n_estimators=100)
    rf.fit(X_train, y_train)
    rf_name = rf.__class__.__name__
    rf_scores = get_scores(rf, X_train, y_train, X_test, y_test, 'micro')
    print (rf.__class__.__name__ + '(train, test, f1_score):')
    print (rf_scores, br)
    et = ExtraTreesClassifier(random_state=0, n_estimators=100)
    et.fit(X_train, y_train)
    et_name = et.__class__.__name__
    et_scores = get_scores(et, X_train, y_train, X_test, y_test, 'micro')
    print (et.__class__.__name__ + '(train, test, f1_score):')
    print (et_scores, br)
    scaler = StandardScaler().fit(X_train)
    X_train_std, X_test_std = scaler.transform(X_train),\
                              scaler.transform(X_test)
    knn = KNeighborsClassifier().fit(X_train, y_train)
    knn_scores = get_scores(knn, X_train, y_train, X_test, y_test, 'micro')
    print (knn.__class__.__name__ + '(train, test, f1_score):')
    print (knn_scores, br)
    svm = SVC(random_state=0, gamma="scale")
    svm.fit(X_train_std, y_train)
    svm_scores = get_scores(svm, X_train_std, y_train, X_test_std, y_test, 'micro')
    print (svm.__class__.__name__ + '(train, test, f1_score):')
    print (svm_scores)

Listing 2-7Classifying UCI Irvine sample bank data

在执行清单 2-7 中的代码后,您的输出应该如下所示:

data shape: (4119, 21)

RandomForestClassifier(train, test, f1_score):
(1.0, 0.9058252427184466, 0.9058252427184466)

ExtraTreesClassifier(train, test, f1_score):
(1.0, 0.8990291262135922, 0.8990291262135922)

KNeighborsClassifier(train, test, f1_score):
(0.9323405632890903, 0.8883495145631068, 0.8883495145631068)

SVC(train, test, f1_score):
(0.9494982194885077, 0.9, 0.9)

代码从导入必需的包开始。函数 get_scores 返回准确度分数。主程序块加载样本,设计教育特征,并将分类特征编码成 OHE 形式。我们不得不为这个例子添加工程师教育的特征,因为我们没有从我们已经设计的特征的完整数据集中抽取样本。

代码继续将 NumPy 数据加载到 X 和 y 中,将其分成训练测试子集,并用 RandomForestClassifier 进行训练。然后显示精确度。其余代码使用 ExtraTreesClassifier、KNeighborsClassifier 和 svm 进行训练。SVC 并显示准确度分数。

我们创建的样本的性能至少与来自 UCI 资源库的样本一样好。我们的样本甚至更小,这意味着我们的取样技术已经足够了。

给月亮分类

Scikit-Learn make_moons 数据主要用于可视化聚类和分类算法。然而,它也是一个很好的数据集,可以帮助您了解分类算法如何尝试分离二进制目标标签(或二进制分类)。make_moons 的部署描述了 2D 空间中的两个交错圆以及相关联的数据点。

通过可视化,我们可以很容易地看到两个(或二元)标签之间的分离。如果人眼可以在 2D 空间中轻松区分这种分离,分类算法应该也能做到。我们可以用一个例子来验证这一点。

清单 2-8 中显示的第一个代码示例创建了一个包含 1000 个元素的数据集,将特征数据及其关联的目标放入 Pandas DataFrame 中,并绘制出结果。每个特征元素表示用于在 2D 空间中绘图的 x 和 y 坐标。每个目标代表要素的标签,它是 0 或 1 的二进制值。

import matplotlib.pyplot as plt, pandas as pd
from sklearn import datasets

if __name__ == "__main__":
    br = '\n'
    X, y = datasets.make_moons(n_samples=1000, shuffle=True, noise=0.2, random_state=0)
    df = pd.DataFrame(dict(x=X[:,0], y=X[:,1], label=y))
    colors = {0:'magenta', 1:'cyan'}
    fig, ax = plt.subplots()
    data = df.groupby('label')
    for key, label in data:
        label.plot(ax=ax, kind="scatter", x="x", y="y", label=key, color=colors[key])
    plt.show()

Listing 2-8Plot make_moons

在执行清单 2-8 中的代码后,您的输出应该类似于图 2-5 中所示的可视化结果:

img/481580_1_En_2_Fig5_HTML.jpg

图 2-5

随机生成的卫星数据的可视化

清单 2-9 中显示的下一个代码示例创建了一个包含 1000 个元素的 make_moons 数据集,将其分割成训练测试子集,并使用 svm 进行训练。SVC 和 KNeighborsClassifier。我特意选择了这两种算法,因为我知道它们会在二进制分类方面做得很好,因为它们会查看每个数据点。

from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier
from sklearn import svm
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

def get_scores(model, Xtrain, Xtest, ytrain, ytest):
    y_ptrain = model.predict(Xtrain)
    y_ptest = model.predict(Xtest)
    acc_train = accuracy_score(ytrain, y_ptrain)
    acc_test = accuracy_score(ytest, y_ptest)
    name = model.__class__.__name__
    return (name, acc_train, acc_test)

if __name__ == "__main__":
    br = '\n'
    X, y = datasets.make_moons(n_samples=1000, shuffle=True, noise=0.2, random_state=0)
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
    knn = KNeighborsClassifier().fit(X_train, y_train)
    accuracy = get_scores(knn, X_train, X_test, y_train, y_test)
    print ('<<' + str(accuracy[0]) + '>>')
    print ('train:', accuracy[1], 'test:', accuracy[2], br)
    svm = svm.SVC(gamma='scale', random_state=0)
    svm.fit(X_train, y_train)
    accuracy = get_scores(svm, X_train, X_test, y_train, y_test)
    print ('<<' + str(accuracy[0]) + '>>')
    print ('train:', accuracy[1], 'test:', accuracy[2])

Listing 2-9Classify make_moons

在执行清单 2-9 中的代码后,您的输出应该如下所示:

<<KNeighborsClassifier>>
train: 0.9666666666666667 test: 0.964

<<SVC>>
train: 0.9653333333333334 test: 0.96

该代码示例从导入必备包开始。函数 get_scores 返回模型名称以及训练和测试准确度分数。主程序块首先加载样本数据,并将其分成训练测试子集。它继续用 KNeighborsClassifier 和 svm 训练数据。SVC 和报告准确性分数。正如所料,两种算法都非常准确地识别了标签,基本上没有过度拟合。

清单 2-10 中显示的最终代码示例通过将数据分成训练、测试和验证子集来扩展我们的知识。KNeighborsClassifier 用于训练和启用报告。

from sklearn.datasets import make_moons
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

def get_scores(model, Xtrain, ytrain, Xtest, ytest, Xvalid, yvalid):
    y_ptrain = model.predict(Xtrain)
    y_ptest = model.predict(Xtest)
    y_pvalid = model.predict(Xvalid)
    acc_train = accuracy_score(ytrain, y_ptrain)
    acc_test = accuracy_score(ytest, y_ptest)
    acc_valid = accuracy_score(yvalid, y_pvalid)
    name = model.__class__.__name__
    return (name, acc_train, acc_test, acc_valid)

if __name__ == "__main__":
    br = '\n'
    X_train, y_train = make_moons(n_samples=1000, shuffle=True, noise=0.2, random_state=0)
    X_test, y_test = make_moons(n_samples=1000, shuffle=True, noise=0.2, random_state=0)
    X_valid, y_valid = make_moons(n_samples=10000, shuffle=True, noise=0.2, random_state=0)
    knn = KNeighborsClassifier().fit(X_train, y_train)
    accuracy = get_scores(knn, X_train, y_train, X_test, y_test, X_valid, y_valid)
    print ('train test valid split (technique 1):')
    print ('<<' + str(accuracy[0]) + '>>')
    print ('train:', accuracy[1], 'test:', accuracy[2], 'valid:', accuracy[3])
    print ('sample split:', X_train.shape, X_test.shape, X_valid.shape)
    print ()

    X, y = make_moons(n_samples=1000, shuffle=True, noise=0.2, random_state=0)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
    X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.25, random_state=0)
    knn = KNeighborsClassifier().fit(X_train, y_train)
    accuracy = get_scores(knn, X_train, y_train, X_test, y_test, X_valid, y_valid)
    print ('train test valid split (technique 2):')
    print ('<<' + str(accuracy[0]) + '>>')
    print ('train:', accuracy[1], 'test:', accuracy[2], 'valid:', accuracy[3])
    print ('sample split:', X_train.shape, X_test.shape, X_val.shape)

Listing 2-10Classify make_moons on train, validate, and test subsets

在执行清单 2-10 中的代码后,您的输出应该如下所示:

train test valid split (technique 1):
<<KNeighborsClassifier>>
train: 0.969 test: 0.969 valid: 0.9688
sample split: (1000, 2) (1000, 2) (10000, 2)

train test valid split (technique 2):
<<KNeighborsClassifier>>
train: 0.9616666666666667 test: 0.975 valid: 0.9694
sample split: (600, 2) (200, 2) (200, 2)

代码开始导入必需的包。函数 get_scores 被扩展以说明验证分数。主模块首先创建三个独立的测试、训练和验证子集。使用这种技术,我们创建了三个相同大小的数据集。虽然这种技术产生了很好的结果,但是随着数据集变得越来越大,它的计算开销也越来越大。实际上,这种技术要贵三倍,因为要创建和训练三个数据集。KNeighborsClassifier 用于训练、验证和测试。第二种技术非常常见,因为它将一个数据集分为训练、验证和测试。再次使用 KNeighborsClassifier。这两种技术的结果不相上下,并且和预期的一样优秀。

小费

只有当模型在训练和验证阶段完成训练后,才能使用测试数据,这样它才能对最终模型与训练数据的拟合程度进行无偏见的评估。

在行业中,机器学习工程师通过在训练之前将数据分成训练、测试和验证子集来试验数据问题。训练数据用于拟合(或训练)模型。模型从训练数据中观察和学习。

验证数据用于评估模型。机器学习工程师使用验证数据来微调模型的超参数。测试数据根据从拟合训练数据和用验证数据调整超参数中学到的知识,提供最终模型拟合的无偏评估。

三、复杂训练集的分类

复杂数据的分类与简单数据完全一样。数据被加载到特征集 X 和目标 y 中。X 数据由向量矩阵组成,其中每个向量代表一个数据元素,y 数据由目标向量组成。但是,复杂数据由大量的要素组成(成百上千)。这样的数据集通常被称为具有高维特征空间的数据集。文本数据也很复杂,因为每个文档都必须转换为适合机器学习算法的数值向量。

复杂数据集

我们专注于三个复杂的数据集:fetch_20newsgroups、MNIST 和 fetch_lfw_people。fetch_20newsgroups 由成千上万的新闻组帖子(文档)组成。MNIST 由数千幅 28 × 28 的图像组成,每幅图像由 784 个像素表示。fetch_lfw_people 由 1288 个 50 × 37 的图像组成,其中每个图像由 1850 个像素表示。

分类 fetch _ 新闻组

由于 Scikit-Learn 算法不接受原始文本,我们需要将其转换为可以用作输入的特征向量。TfidfVectorizer 将文本(表示为原始文档)转换为 TF-IDF 特征的矩阵‘54321’(可用作估计器输入的特征向量)。

TF-IDF(术语频率-逆文档频率)是一种数字统计,旨在反映一个单词在文档中的重要性。TF-IDF 是最受欢迎的术语加权方案之一,在数字图书馆基于文本的推荐系统中有 83%的使用。

TF-IDF 是一个非常大的话题。我们不会涉及太多细节,因为我们只是想用它来确定单词的重要性。然而,重要的是要知道单词重要性是由 TF-IDF 权重决定的,并且重要性与单词在文档中出现的次数成比例地增加。

只看词频的问题是,一些像“The”、“is”和“of”这样的词可能并不重要。因此,我们还可以查看逆文档频率,它降低了常用词的权重,增加了不常用词的权重。幸运的是,Scikit-Learn 包括了 TfidfVectorizer 包,它可以有效地结合单词和逆词频来提取有意义的信息。

将文本转换为要素数据不同于图像。图像变换包括将矩阵展平成相同长度的特征向量。对于文本,每个文档的大小通常不同。因此,我们需要一种类似 TF-IDF 的技术来将文档转换成 Scikit-Learn 算法可接受的 TF-IDF 特征矩阵。文本转换也更加复杂,因为字数会影响文档中单词的重要性。

文本分类特性与字数或频率有关,所以让我们使用一个适合这个目的的分类器。多项式贝叶斯分类器是一种朴素的贝叶斯分类器,适用于具有离散特征的多项式模型分类,如文本分类的字数。

清单 3-1 中显示的第一个代码示例对 fetch_20newsgroups 数据进行分类。第一次加载这个数据集时,您可能需要等待一段时间,所以请耐心等待。第一次加载后,您不会遇到延迟。

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
from sklearn.metrics import confusion_matrix, f1_score
import matplotlib.pyplot as plt
import seaborn as sns

def predict_category(s, m, t):
    pred = m.predict([s])
    return t[pred[0]]

if __name__ == "__main__":
    br = '\n'
    train = fetch_20newsgroups(subset='train')
    test = fetch_20newsgroups(subset='test')
    print (train.target_names, br)
    categories = ['rec.autos', 'rec.motorcycles', 'sci.space', 'sci.med']
    train = fetch_20newsgroups(subset='train', categories=categories)
    test = fetch_20newsgroups(subset='test', categories=categories)
    print ('data subset:')
    print (train.target.shape, 'shape of train data')
    print (test.target.shape, 'shape of test data', br)
    targets = train.target_names
    mnb_clf = make_pipeline(TfidfVectorizer(), MultinomialNB())

    print ('<<' + mnb_clf.__class__.__name__ + '>>', br)
    mnb_clf.fit(train.data, train.target)
    labels = mnb_clf.predict(test.data)
    f1 = f1_score(test.target, labels, average="micro")
    print ('f1_score', f1, br)
    cm = confusion_matrix(test.target, labels)
    plt.figure('confusion matrix')

    sns.heatmap(cm.T, square=True, annot=True, fmt="d", cmap="gist_ncar_r", xticklabels=train.target_names, yticklabels=train.target_names, cbar=False)
    print ('sci.med predictions:')
    print (cm.T[2][2], 'correct predictions')
    print (cm.T[2][0], 'misclassified as rec.autos')
    print (cm.T[2][3], 'misclassified as sci.space')
    plt.xlabel('true label')
    plt.ylabel('predicted label')
    plt.tight_layout()
    print ('\n***PREDICTIONS***:')
    y_pred = predict_category('payload on the mars rover', mnb_clf, targets)
    print (y_pred)
    y_pred = predict_category('car broke down on the highway', mnb_clf, targets)
    print (y_pred)
    y_pred = predict_category('dad died of cancer', mnb_clf, targets)
    print (y_pred)

Listing 3-1Classify fetch_20newsgroups data

继续执行清单 3-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。

执行清单 3-1 的输出应该如下所示:

['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']

data subset:
(2379,) shape of train data
(1584,) shape of test data

<<Pipeline>>

f1_score 0.9621212121212122

sci.med predictions:
370 correct predictions
1 misclassified as rec.autos
7 misclassified as sci.space

***PREDICTIONS***:
sci.space
rec.autos
sci.med

列表 3-1 也显示图 3-1 。图 3-1 显示了使用 TfidfVectorizer 文本转换的多项分类的混淆矩阵。

img/481580_1_En_3_Fig1_HTML.jpg

图 3-1

分类实验的混淆矩阵

代码从导入 fetch_20newsgroups、TfidfVectorizer、MultinomialNB 和其他一些熟悉的包开始。函数 predict_category 用于根据新数据预测类别标签。主程序块首先从 fetch_20newsgroups 加载训练和测试文档。接下来,它显示数据集中的目标类别。代码继续创建一个包含四个类别的训练和测试数据的子集,我选择在这个实验中使用这四个类别。请记住,您可以创建自己的子集。

子集训练和测试数据集用于建模。然后显示数据形状。接下来,使用 TfidfVectorizer 和 MultinomialNB 创建管道模型。TfidfVectorizer 提取文本并将其转换为数值向量,以便 MultinomialNB 可以对其进行训练。

小费

为了算法处理,文本数据必须转换成数字形式。

创建标签向量来保存来自测试数据子集的预测。显示准确度分数(f1_score)。 f1_score 是精度和召回分数的加权平均值。因此,这是一个更保守的估计。一个 f1 _ 以上的分数真的很好!

创建混淆矩阵是为了让我们了解我们的模型对测试数据的分类有多好。我们转置矩阵(cm。t ),使得预测标签在垂直轴上,真实(实际)标签在水平轴上。你不用转置,但我更容易解读结果。

混淆矩阵的对角线表示正确的分类。也就是说,对于 rec.autos 我们做了 389 个正确的分类,rec.motorcycles 我们做了 383 个正确的分类,sci.med 我们做了 370 个正确的预测,sci.space 我们做了 383 个正确的分类。错误分类是那些偏离对角线的分类。例如,我们将一个 sci.med 错误分类为 rec.autos,将七个错误分类为 sci.space。

代码结束时,根据训练好的模型对全新的数据进行预测。文字“火星探测车上的有效载荷”被归类为 sci.space,正确!接下来的两个文本字符串也能正确预测。由于模型准确率超过 96%,我们可以相当肯定我们的预测是可靠的。

前面的例子有一个问题。该算法能够从页眉、页脚和引号中理解文本的含义。就是我们选择的算法相当巧妙。

为了创建一个更真实的例子,我们可以从文本文档中删除页眉、页脚和引号,如清单 3-2 中的下一个例子所示。

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
from sklearn.metrics import confusion_matrix, f1_score
import matplotlib.pyplot as plt
import seaborn as sns

def predict_category(s, m, t):
    pred = m.predict([s])
    return t[pred[0]]

if __name__ == "__main__":
    br = '\n'
    train = fetch_20newsgroups(subset='train')
    test = fetch_20newsgroups(subset='test')
    categories = ['rec.autos', 'rec.motorcycles', 'sci.space', 'sci.med']
    train = fetch_20newsgroups(subset='train', categories=categories, remove=('headers', 'footers', 'quotes'))
    test = fetch_20newsgroups(subset='test', categories=categories, remove=('headers', 'footers', 'quotes'))
    targets = train.target_names
    mnb_clf = make_pipeline(TfidfVectorizer(), MultinomialNB())
    print ('<<' + mnb_clf.__class__.__name__ + '>>', br)
    mnb_clf.fit(train.data, train.target)
    labels = mnb_clf.predict(test.data)
    f1 = f1_score(test.target, labels, average="micro")
    print ('f1_score', f1, br)
    cm = confusion_matrix(test.target, labels)
    plt.figure('confusion matrix')
    sns.heatmap(cm.T, square=True, annot=True, fmt="d", cmap="gist_ncar_r", xticklabels=train.target_names, yticklabels=train.target_names, cbar=False)
    plt.xlabel('true label')
    plt.ylabel('predicted label')
    plt.tight_layout()

    print ('***PREDICTIONS***:')
    y_pred = predict_category('payload on the mars rover', mnb_clf, targets)
    print (y_pred)
    y_pred = predict_category('car broke down on the highway', mnb_clf, targets)
    print (y_pred)
    y_pred = predict_category('dad died of cancer', mnb_clf, targets)
    print (y_pred)
    plt.show()

Listing 3-2Classify fetch_20newsgroups removing identifying information

执行清单 3-2 的输出应该如下所示:

<<Pipeline>>

f1_score 0.8440656565656567

***PREDICTIONS***:
sci.space
rec.autos
sci.med

列表 3-2 也显示图 3-2 。图 3-2 显示了移除页眉、页脚和引号后的混淆矩阵。

img/481580_1_En_3_Fig2_HTML.jpg

图 3-2

没有页眉、页脚和引号的混淆矩阵

代码与前面的例子非常相似,只是我们从训练和测试子集中删除了页眉、页脚和引号。请注意,f1_score 下降到 84%多一点,这是一个相当大的下降!这是一个更现实的场景,因为文本数据可能不包括识别信息。

新数据的预测似乎是正确的,但是请注意,我预测的文本非常清楚。也就是说,我们训练的模型很容易做出正确的预测,因为文本中有直接指向正确类别的单词。例如,火星车上的有效载荷肯定是科幻空间,因为该短语包含单词火星

在这种情况下,混淆矩阵是一个很好的视觉效果,因为它显示有许多错误分类,特别是试图区分 rec.autos 和 rec.motorcycles。当然,这很有意义,因为汽车和摩托车比我们子集中的其他两个类别更相似。

错误分类也是搜索算法和数据改进的好地方。也许需要更多的数据来提高准确性。也许该算法分类错误,因为它的一些或所有超参数需要调整。

MNIST 分类

在第一章中介绍了 MNIST,它是一个大型手写数字数据库,通常用于机器学习社区和其他工业图像处理应用程序的训练和测试。回顾一下,MNIST 包含 70000 张手写数字图像,大小为 28 × 28,从 0 到 9。每个目标存储为一个数字值。该特征集是 70000 个 28 × 28 图像的矩阵,每个图像自动展平为 784 像素。

使用整个 MNIST 数据集进行训练

接下来的两个代码示例为整个 MNIST 数据集定型。由于 MNIST 数据由高维特征空间组成,我们只使用精选分类器对其进行训练,以减少训练时间。

清单 3-3 中显示的第一个代码示例使用 RandomForestClassifier 和 ExtraTreesClassifier 训练 MNIST 数据,比较准确度得分,可视化混淆矩阵,并可视化错误分类场景。

import numpy as np, humanfriendly as hf
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier,\
     ExtraTreesClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

def get_time(time):
    return hf.format_timespan(time, detailed=True)

def find_misses(test, pred):
    return [i for i, row in enumerate(test) if row != pred[i]]

if __name__ == "__main__":
    br = '\n'
    X_file = 'data/X_mnist'
    y_file = 'data/y_mnist'
    X = np.load('data/X_mnist.npy')
    y = np.load('data/y_mnist.npy')
    X = X.astype(np.float32)
    X_train, X_test, y_train, y_test = train_test_split\
                                       (X, y, random_state=0)
    rf = RandomForestClassifier(random_state=0, n_estimators=100)
    rf_name = rf.__class__.__name__
    print ('<<' + rf_name + '>>')
    start = time.perf_counter()

    rf.fit(X_train, y_train)
    end = time.perf_counter()
    elapsed_ls = end - start
    timer = get_time(elapsed_ls)
    rf_name = rf.__class__.__name__
    y_pred = rf.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    print ('\'test\' accuracy:', accuracy)
    print (rf_name + ' timer:', timer, br)
    cm = confusion_matrix(y_test, y_pred)
    plt.figure(1)
    ax = plt.axes()
    sns.heatmap(cm.T, annot=True, fmt="d", cmap="gist_ncar_r", ax=ax)
    ax.set_title(rf_name + 'confustion matrix')
    plt.xlabel('true value')
    plt.ylabel('predicted value')

    et = ExtraTreesClassifier(random_state=0, n_estimators=100)
    et_name = et.__class__.__name__
    print ('<<' + et_name + '>>')
    start = time.perf_counter()
    et.fit(X_train, y_train)
    end = time.perf_counter()
    elapsed_ls = end - start
    timer = get_time(elapsed_ls)
    y_pred = et.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    print ('\'test\' accuracy:', accuracy)
    print (et_name + ' timer:', timer, br)
    cm = confusion_matrix(y_test, y_pred)
    plt.figure(2)
    ax = plt.axes()
    sns.heatmap(cm.T, annot=True, fmt="d", cmap="gist_ncar_r", ax=ax)
    ax.set_title(et_name + 'confustion matrix')
    plt.xlabel('true value')
    plt.ylabel('predicted value')
    indx = find_misses(y_test, y_pred)
    print ('pred', 'actual')
    misses = [(y_pred[row], y_test[row], i)
              for i, row in enumerate(indx)]
    [print (row[0], '  ', row[1]) for i, row in enumerate(misses)

     if i < 5]
    print()
    img_act = y_test[indx[0]]
    img_pred = y_pred[indx[0]]
    print ('actual', img_act)
    print ('pred', img_pred)
    text = str(img_pred)
    test_images = X_test.reshape(-1, 28, 28)
    plt.figure(3)
    plt.imshow(test_images[indx[0]], cmap="gray", interpolation="gaussian")
    plt.text(0, 0.05, text, color="r", bbox=dict(facecolor='white'))
    title = str(img_act) + ' misclassified as ' + text
    plt.title(title)
    plt.show()

Listing 3-3Classify MNIST data

执行清单 3-3 的输出应该如下所示:

<<RandomForestClassifier>>
'test' accuracy: 0.9687428571428571
RandomForestClassifier timer: 29 seconds and 620.77 milliseconds

<<ExtraTreesClassifier>>
'test' accuracy: 0.9727428571428571
ExtraTreesClassifier timer: 30 seconds and 462.9 milliseconds

pred actual
3.0    9.0
7.0    3.0
4.0    9.0
2.0    3.0
3.0    9.0

actual 9.0
pred 3.0

列表 3-3 还显示了数字 3-3 、 3-4 和 3-5 。图 3-3 显示了 RandomForestClassifier 的混淆矩阵。图 3-4 显示了提取树分类器的混淆矩阵。图 3-5 显示了第一次错误分类。

img/481580_1_En_3_Fig5_HTML.jpg

图 3-5

第一次错误分类

img/481580_1_En_3_Fig4_HTML.jpg

图 3-4

外分类学混淆矩阵

img/481580_1_En_3_Fig3_HTML.jpg

图 3-3

随机应变分类器混淆矩阵

代码从导入必需的包开始。函数 get_time 返回训练所需的时间。函数 find_misses 返回一个错误分类列表。主程序块将 MNIST 从 NumPy 文件加载到 X 和 y 中,将 X 转换成浮点数以供算法使用,并将数据分割成训练测试子集。

代码继续使用 RandomForestClassifier 和 ExtraTreesClassifier 对数据进行定型。对于这两种算法,显示精度和训练时间,并创建和可视化混淆矩阵。接下来,显示前五个错误分类。最后,可视化第一个错误分类(预测值:3.0,实际值:9.0)。请注意,训练确实需要一些时间(每个算法大约需要 30 秒)。原因是 MNIST 数据是由高维特征空间组成的。

请注意,人类很容易看出数字是 9 ,但对机器来说就不那么容易了,因为预测的是数字 3 。这种可视化以及混淆矩阵非常重要,因为它可以帮助数据科学家通过调整数据、试验不同的算法或改进算法来提高预测性能。

清单 3-4 中显示的下一个代码示例手动将数据分割成训练测试子集,以增加灵活性。也就是说,我们可以精确地调整训练测试子集的大小。

import numpy as np, humanfriendly as hf
import time
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import classification_report

if __name__ == "__main__":
    br = '\n'
    X_file = 'data/X_mnist'
    y_file = 'data/y_mnist'
    X = np.load('data/X_mnist.npy')
    y = np.load('data/y_mnist.npy')
    X = X.astype(np.float32)
    X_train, X_test, y_train, y_test = X[:60000], X[60000:],\
                                       y[:60000], y[60000:]
    shuffle_index = np.random.permutation(60000)
    X_train, y_train = X_train[shuffle_index],\
                       y_train[shuffle_index]
    et = ExtraTreesClassifier(random_state=0, n_estimators=100)
    start = time.perf_counter()
    et.fit(X_train, y_train)
    end = time.perf_counter()
    elapsed_ls = end - start
    print (hf.format_timespan(elapsed_ls, detailed=True))
    et_name = et.__class__.__name__
    y_pred = et.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    print (et_name + ' \'test\':', end=' ')
    print ('accuracy:', accuracy, br)
    rpt = classification_report(y_test, y_pred)
    print (rpt)

Listing 3-4Classify MNIST with manual train-test shuffle

执行清单 3-4 的输出应该如下所示:

36 seconds and 533.76 milliseconds
ExtraTreesClassifier 'test': accuracy: 0.9706

              precision    recall  f1-score   support

         0.0       0.97      0.99      0.98       980
         1.0       0.99      0.99      0.99      1135
         2.0       0.97      0.97      0.97      1032
         3.0       0.97      0.96      0.96      1010
         4.0       0.97      0.97      0.97       982
         5.0       0.97      0.97      0.97       892
         6.0       0.98      0.98      0.98       958
         7.0       0.97      0.97      0.97      1028
         8.0       0.97      0.96      0.96       974
         9.0       0.95      0.95      0.95      1009

   micro avg       0.97      0.97      0.97     10000
   macro avg       0.97      0.97      0.97     10000
weighted avg       0.97      0.97      0.97     10000

代码从导入 classification_report 和其他必需的包开始。分类报告显示模型的精确度、召回率、F1 和支持度分数。

精度是分类器不将实际上是负面的实例标记为正面的能力。回忆是分类器找到所有肯定实例的能力。f1_ 分数是精确度和召回率的加权调和平均值,其中最好的分数是 1.0,最差的分数是 0.0。f1_ 分数通常低于精度度量,因为它们将精度和召回嵌入到计算中。 Support 是指定数据集中类的实际出现次数。

主块将 MNIST 加载到 X 和 y 中,并手动将数据混洗到训练测试子集中。在这种情况下,我们使用了更多的数据进行训练。具体来说,培训 6 万,测试 1 万。我们有更多的训练数据,如果我们有足够的数据来处理,这可能是一个很好的实验。接下来,我们用 ExtraTreesClassifier 进行训练,因为它在前面的例子中在 MNIST 上的表现比 RandomForestClassifier 好。最后,给出准确度分数和分类报告。

小费

如果您需要更大的灵活性,可以手动将数据放入训练测试子集。

训练 MNIST 样本数据

清单 3-5 中显示的第一个代码示例创建了一个来自 MNIST 的 4000 个数据元素的随机样本,以支持 svm 的高效训练。SVC 和 KNeighborsClassifier。这两种算法都是优秀的分类器,但众所周知,对于高维特征空间数据集,它们的计算开销很大。

import numpy as np, random, humanfriendly as hf
import time
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn import svm
from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt

def prep_data(data, target):
    d = [data[i] for i, _ in enumerate(data)]
    t = [target[i] for i, _ in enumerate(target)]
    return list(zip(d, t))

def create_sample(d, n, replace="yes"):
    if replace == 'yes': s = random.sample(d, n)
    else: s = [random.choice(d)
               for i, _ in enumerate(d) if i < n]
    Xs = [row[0] for i, row in enumerate(s)]
    ys = [row[1] for i, row in enumerate(s)]
    return np.array(Xs), np.array(ys)

def see_time(note):
    end = time.perf_counter()

    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

if __name__ == "__main__":
    br = '\n'
    X_file = 'data/X_mnist'
    y_file = 'data/y_mnist'
    X = np.load('data/X_mnist.npy')
    y = np.load('data/y_mnist.npy')
    X = X.astype(np.float32)
    sample_size = 4000
    data = prep_data(X, y)
    Xs, ys = create_sample(data, sample_size, replace="no")
    X_train, X_test, y_train, y_test = train_test_split(
        Xs, ys, test_size=0.10, random_state=0)
    scaler = StandardScaler().fit(X_train)
    X_train_std, X_test_std = scaler.transform(X_train),\
                              scaler.transform(X_test)
    svm = svm.SVC(random_state=0, gamma="scale")
    svm_name = svm.__class__.__name__
    print ('<<', svm_name, '>>')
    start = time.perf_counter()
    svm.fit(X_train_std, y_train)

    see_time('train:')
    start = time.perf_counter()
    y_pred = svm.predict(X_test_std)
    see_time('predict:')
    start = time.perf_counter()
    train_score = svm.score(X_train_std, y_train)
    test_score = svm.score(X_test_std, y_test)
    see_time('score:')
    print ('train score:', train_score, 'test score', test_score, br)
    knn = KNeighborsClassifier()
    knn_name = knn.__class__.__name__
    print ('<<', knn_name, '>>')
    start = time.perf_counter()
    knn.fit(X_train, y_train)
    see_time('train:')
    start = time.perf_counter()
    y_pred = knn.predict(X_test)
    see_time('predict:')
    start = time.perf_counter()
    train_score = knn.score(X_train, y_train)
    test_score = knn.score(X_test, y_test)
    see_time('score:')
    print ('train score:', train_score, 'test score:', test_score)

Listing 3-5Classify MNIST with sample data

执行清单 3-5 的输出应该如下所示:

train: 6 seconds and 538.51 milliseconds
predict: 780.46 milliseconds
score: 7 seconds and 755.28 milliseconds
train score: 0.9802777777777778 test score 0.9075

<< KNeighborsClassifier >>
train: 116.53 milliseconds
predict: 1 second and 605.23 milliseconds
score: 15 seconds and 924.84 milliseconds
train score: 0.9519444444444445 test score: 0.91

代码从导入必需的包开始。函数 prep_data 将数据转换为数据元素列表,以便于采样。函数 create_sample 接受准备好的数据并创建一个随机样本而不替换。函数 see_time 返回经过的时间。

主块将数据加载到 X 和 y 中。接下来,创建 4000 个数据元素的样本,并将其分成训练测试子集。代码继续用 svm 训练样本。SVC 和 KNeighborsClassifier。

我们需要用这些算法来抽取随机样本,因为当训练大型数据集时,特别是那些具有高维特征空间的数据集时,这些算法的计算开销很大。

对于每种算法,都报告了训练、预测和评分时间。到目前为止,我们只捕获了火车时间。但是,有趣的是,我们可以看到培训过程的每个阶段占用了多少时间。对于我们的样本,KNeighborsClassifier 报告了比 svm.SVC 更少的过拟合的可观结果。

小费

通过调整 sample_size 变量,可以很容易地试验样本大小。

清单 3-6 中显示的下一个 MNIST 示例利用 PCA 增加了 7000 个样本,而没有增加太多的计算开销。

import numpy as np, random, humanfriendly as hf
import time
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn import svm
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import f1_score
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt, seaborn as sns

def prep_data(data, target):
    d = [data[i] for i, _ in enumerate(data)]
    t = [target[i] for i, _ in enumerate(target)]
    return list(zip(d, t))

def create_sample(d, n, replace="yes"):
    if replace == 'yes': s = random.sample(d, n)
    else: s = [random.choice(d)
               for i, _ in enumerate(d) if i < n]
    Xs = [row[0] for i, row in enumerate(s)]
    ys = [row[1] for i, row in enumerate(s)]
    return np.array(Xs), np.array(ys)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_scores(model, xtrain, ytrain, xtest, ytest):
    ypred = model.predict(xtest)
    train = model.score(xtrain, ytrain)
    test = model.score(xtest, y_test)
    f1 = f1_score(ytest, ypred, average="macro")
    return (ypred, train, test, f1)

if __name__ == "__main__":
    br = '\n'
    X_file = 'data/X_mnist'
    y_file = 'data/y_mnist'
    X = np.load('data/X_mnist.npy')
    y = np.load('data/y_mnist.npy')
    X = X.astype(np.float32)
    data = prep_data(X, y)
    sample_size = 7000

    Xs, ys = create_sample(data, sample_size, replace="no")
    pca = PCA(n_components=0.95, random_state=0)
    Xs_reduced = pca.fit_transform(Xs)
    print ('sample feature shape:', Xs.shape)
    components = pca.n_components_
    print ('feature components with PCA:', components, br)
    X_train, X_test, y_train, y_test = train_test_split(
        Xs_reduced, ys, test_size=0.10, random_state=0)
    scaler = StandardScaler().fit(X_train)
    X_train_std, X_test_std = scaler.transform(X_train),\
                              scaler.transform(X_test)
    start = time.perf_counter()
    svm = svm.SVC(random_state=0).fit(X_train_std, y_train)
    svm_name = svm.__class__.__name__
    svm_scores = get_scores(svm, X_train_std, y_train, X_test_std, y_test)
    cm_svm = confusion_matrix(y_test, svm_scores[0])
    see_time(svm_name + ' total training time:')

    print (svm_name + ':', svm_scores[1], svm_scores[2], svm_scores[3], br)
    start = time.perf_counter()
    knn = KNeighborsClassifier().fit(X_train, y_train)
    knn_name = knn.__class__.__name__
    knn_scores = get_scores(knn, X_train, y_train, X_test, y_test)
    cm_knn = confusion_matrix(y_test, knn_scores[0])
    see_time(knn_name + ' total training time:')
    print (knn_name + ':', knn_scores[1], knn_scores[2], knn_scores[3])
    plt.figure(svm_name)
    ax = plt.axes()
    sns.heatmap(cm_svm.T, annot=True, fmt="d", cmap="gist_ncar_r", ax=ax)
    ax.set_title(str(svm_name) + ' confustion matrix')
    plt.xlabel('true value')
    plt.ylabel('predicted value')
    plt.figure(knn_name)
    ax = plt.axes()
    sns.heatmap(cm_knn.T, annot=True, fmt="d", cmap="gist_ncar_r", ax=ax)
    ax.set_title(str(knn_name) + ' confustion matrix')
    plt.xlabel('true value')
    plt.ylabel('predicted value')
    plt.show()

Listing 3-6Classify MNIST with sample data and PCA

执行清单 3-6 的输出应该如下所示:

sample feature shape: (7000, 784)
feature components with PCA: 150

SVC total training time: 14 seconds and 290.91 milliseconds
SVC: 0.9955555555555555 0.9428571428571428 0.9425480948692136

KNeighborsClassifier total training time: 10 seconds and 313.37 milliseconds
KNeighborsClassifier: 0.9601587301587302 0.9371428571428572 0.9358573966927535

列表 3-6 还显示了数字 3-6 和 3-7 。图 3-6 显示 svm.SVC 的混淆矩阵。图 3-7 显示 KNeighborsClassifier 的混淆矩阵。

img/481580_1_En_3_Fig7_HTML.jpg

图 3-7

近邻分类器的混淆矩阵

img/481580_1_En_3_Fig6_HTML.jpg

图 3-6

svm 的混淆矩阵。交换虚拟电路

该代码示例从导入必备包开始。函数 prep_data 创建了一个数据元素列表,以便于处理。函数 create_sample 创建 7000 个数据元素的随机样本,没有替换。函数 see_time 返回经过的时间。函数 get_scores 返回分数。

主模块首先将数据加载到 X 和 y 中,然后创建 7000 个数据元素的随机样本。PCA 被用来将 784 个特征减少到 150 个特征,并且有 5%的信息丢失。特征集是从 PCA 模型创建的。显示原始样本形状以及 PCA 中减少的特征成分。接下来,svm。SVC 和 KNeighborsClassifier 对样本进行训练。显示每个模型的总训练时间和得分。分数分别报告为训练准确度、测试准确度和测试 f1_score。代码最后为每个模型创建并显示混淆矩阵。

请注意,使用 7000 这个较大的样本对两个模型都有更好的拟合。也就是说,我们有更少的过度拟合。

随着数据集变得越来越大,采样在行业中是非常常见的做法。采样可以极大地减少计算开销,同时提供一个即使是计算开销最大的算法的预测能力的好主意。此外,降维结合采样可以进一步降低计算开销!

小费

采样降维可以显著降低计算开销。

分类 fetch_lfw_people

fetch_lfw_people 由来自野外(lfw)标记人脸的预处理图像组成,这是一个为研究无约束人脸识别而设计的数据库。LFW 包含了从网上收集的 13000 多张人脸图像。每张照片都标有照片中人的名字。要了解更多关于 LFW 的信息,请点击此链接: http://vis-www.cs.umass.edu/lfw/ 。在我们的实验中,我们只考虑数据集中至少有 70 张照片的人。图像被调整到 0.4 的纵横比。

清单 3-7 中显示的第一个代码示例用 svm 对 fetch_lfw_people 进行分类。SVC 是人脸识别中最有用的算法之一。

import numpy as np
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_faces.npy')
    y = np.load('data/y_faces.npy')
    images = np.load('data/faces_images.npy')
    targets = np.load('data/faces_targets.npy')
    _, h, w = images.shape
    n_images = X.shape[0]
    n_features = X.shape[1]
    n_classes = len(targets)
    print ('features:', n_features)
    print ('images:', n_images)
    print ('classes:', n_classes, br)
    print ('target names:')
    print (targets, br)
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
    pca = PCA(n_components=0.95, whiten=True, random_state=0)
    pca.fit(X_train)
    components = pca.n_components_
    eigenfaces = pca.components_.reshape((components, h, w))
    X_train_pca = pca.transform(X_train)
    pca_name = pca.__class__.__name__
    print ('<<' + pca_name + '>>')
    print ('features (after PCA):', components)
    print ('eigenface shape:', eigenfaces.shape, br)
    print (pca, br)
    svm = SVC(kernel='rbf', class_weight="balanced", gamma="scale", random_state=0)
    svm_name = svm.__class__.__name__
    svm.fit(X_train_pca, y_train)
    X_test_pca = pca.transform(X_test)

    y_pred = svm.predict(X_test_pca)
    cr = classification_report(y_test, y_pred)
    print ('classification report <<' + svm_name+ '>>')
    print (cr)
    ls = [np.array(eigenfaces[i].reshape(h, w))
          for i, row in enumerate(range(9))]
    fig, ax = plt.subplots(3, 3, figsize=(5, 6))
    cnt = 0
    for row in [0, 1, 2]:
        for col in [0, 1, 2]:
            ax[row, col].imshow(ls[cnt], cmap="bone", aspect="auto")
            ax[row, col].set_axis_off()
            cnt += 1
    plt.tight_layout()
    plt.show()

Listing 3-7Classify fetch_lfw_people data

执行清单 3-7 的输出应该如下所示:

features: 1850
images: 1288
classes: 7

target names:
['Ariel Sharon' 'Colin Powell' 'Donald Rumsfeld' 'George W Bush'
 'Gerhard Schroeder' 'Hugo Chavez' 'Tony Blair']

<<PCA>>
features (after PCA): 135
eigenface shape: (135, 50, 37)

PCA(copy=True, iterated_power="auto", n_components=0.95,
  random_state=0, svd_solver="auto", tol=0.0, whiten=True)

classification report <<SVC>>
              precision    recall  f1-score   support

           0       1.00      0.61      0.76        28
           1       0.63      0.94      0.76        63
           2       0.90      0.75      0.82        24
           3       0.88      0.86      0.87       132
           4       0.75      0.75      0.75        20
           5       1.00      0.59      0.74        22
           6       0.90      0.82      0.86        33

   micro avg       0.82      0.82      0.82       322
   macro avg       0.87      0.76      0.79       322
weighted avg       0.85      0.82      0.82       322

列表 3-7 也显示图 3-8 。图 3-8 显示了 PCA 创建的特征脸。

img/481580_1_En_3_Fig8_HTML.jpg

图 3-8

PCA 生成的特征脸

该代码示例从加载必备包开始。主模块将图像数据加载到特征集 X、目标集 y,将图像矩阵加载到变量图像,将目标名称加载到变量目标。代码继续将数据分割成训练测试子集。接下来,创建 PCA 模型,白化设置为。启用白化以减少输入数据(或 X_train)中的冗余。

因为每个图像由 1850 个像素组成,所以我们的特征集有 1850 个维度。因此,主成分分析允许我们将维数减少到 135。维度(或特征)越少,计算费用就越低。更少的特征也降低了模型的复杂性,这可以减轻过度拟合。

PCA 试图通过保留最重要的特征,用尽可能少的维度来表示训练数据方差。当 PCA 用于图像时,剩下的特征通常被称为特征脸。特征面表示投影到来自训练集的每个数据样本上以获得独立特征的主要图像集。也就是说,算法使用特征脸从数据中学习。

PCA 在 X_train 数据上训练,有 5%的信息丢失。接下来,确定 PCA 分量和特征面(或最佳剩余特征)。代码继续将带有 PCA 的 X_train 转换为带有 135 个特征(而不是 1850)的 X_train_pca。然后,我们用 svm 训练 X_train_pca,并创建预测集 y_pred,以便我们可以创建分类报告。代码以从特征脸的前九个数据元素创建图像结束。欲知详情,请访问: http://efavdb.com/machine-learning-for-facial-recognition-3/

小费

PCA 不仅是无监督学习实验的良好模型,它还能够对训练集进行降维,从而为监督学习实验带来更快的处理和更少的过拟合。

清单 3-8 中显示的下一个代码示例与前一个示例完全一样地训练数据,但是这一次我们可视化了第一个正确的分类和第一个错误的分类。代码继续可视化四个随机预测。代码可能看起来非常复杂,但大部分工作都与使用 Matplotlib 创建漂亮的视觉效果有关。如果你还没有弄明白,Matplotlib 是而不是非常用户友好。

import numpy as np
from random import randint
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
import matplotlib.pyplot as plt

def find_misses(test, pred):
    return [i for i, row in enumerate(test) if row != pred[i]]

def find_hit(n, ls):
    return True if n in ls else False

def build_fig(indx, pos, color, one, two):
    X_i = np.array(X_test[indx]).reshape(50, 37)
    t = targets[y_test[indx]]
    p = targets[y_pred[indx]]
    ax = fig.add_subplot(pos)
    image = ax.imshow(X_i,  cmap='bone')
    ax.set_axis_off()
    ax.set_title(t)
    ax.text(one, two, p, color=color, bbox=dict(facecolor='white'))

def chk_acc(rnds):
    logic = [1 if y_test[row] == y_pred[row] else 0 for row in rnds]
    colors = ['g' if row == 1 else 'r' for row in logic]
    return colors

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_faces.npy')
    y = np.load('data/y_faces.npy')
    images = np.load('data/faces_images.npy')
    targets = np.load('data/faces_targets.npy')
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
    pca = PCA(n_components=0.95, whiten=True, random_state=0)
    pca.fit(X_train)
    X_train_pca = pca.transform(X_train)
    pca_name = pca.__class__.__name__
    svm = SVC(kernel='rbf', class_weight="balanced", gamma="scale", random_state=0)
    svm_name = svm.__class__.__name__
    svm.fit(X_train_pca, y_train)
    X_test_pca = pca.transform(X_test)
    y_pred = svm.predict(X_test_pca)
    misses = find_misses(y_test, y_pred)
    miss = misses[0]

    hit = 1
    X_hit = np.array(X_test[hit]).reshape(50, 37)
    y_test_hit = targets[y_test[hit]]
    y_pred_hit = targets[y_pred[hit]]
    X_miss = np.array(X_test[miss]).reshape(50, 37)
    y_test_miss = targets[y_test[miss]]
    y_pred_miss = targets[y_pred[miss]]
    fig = plt.figure('1st Hit and Miss')
    fig.suptitle('Visualize 1st Hit and Miss', fontsize=18, fontweight="bold")
    build_fig(hit, 121, 'g', 0.4, 1.9)
    build_fig(miss, 122, 'r', 0.4, 1.9)
    rnd_ints = [randint(0, y_test.shape[0]-1) for row in range(4)]
    colors = chk_acc(rnd_ints)
    fig = plt.figure('Four Random Predictions')
    build_fig(rnd_ints[0], 221, colors[0], .9, 4.45)
    build_fig(rnd_ints[1], 222, colors[1], .9, 4.45)
    build_fig(rnd_ints[2], 223, colors[2], .9, 4.45)
    build_fig(rnd_ints[3], 224, colors[3], .9, 4.45)
    plt.tight_layout()
    plt.show()

Listing 3-8Classify fetch_lfw_people data and visualize

列表 3-8 显示数字 3-9 和 3-10 。图 3-9 是来自训练实验的首次命中(或正确分类)和首次未命中(错误分类)的可视化。图 3-10 是四个随机预测的可视化。

img/481580_1_En_3_Fig10_HTML.jpg

图 3-10

四个随机预测

img/481580_1_En_3_Fig9_HTML.jpg

图 3-9

训练实验的第一次失败

代码从导入必需的包开始。函数 find_misses 返回测试集中错误分类的索引。函数 find_hit 有助于发现所提供的索引是否被正确分类。

在提供的代码中没有实现函数 find_hit,但是您可以通过将索引和未命中列表输入到函数中来测试它。如果函数返回 True ,则预测是正确的;否则,预测会被错误分类。我测试了索引为 1 的函数,该函数返回 True。

函数 build_figure 使我们能够构建可视化效果。虽然代码看起来很复杂,但实际上非常细致。也就是说,正确定位文本需要时间。函数 chk_acc 为错误分类返回红色,为正确分类返回绿色。

主程序块加载数据并进行训练,与前面的例子完全一样。剩下的代码创建可视化效果。第一视觉显示测试集中的第一正确分类和第一错误分类。因此,Colin Powell 图像在索引 1 处被正确分类(测试集中的第 2 和第个数据元素)。乔治·w·布什的图像被错误地归类为科林·鲍威尔,而它恰好位于索引 0 处(测试集中的第一个数据元素)。

第二个可视化是通过生成四个随机数并使用它们作为测试集中可视化的索引来创建的。我们的可视化显示了四个正确分类中的三个。唯一的错误分类是乌戈·查韦斯被误认为科林·鲍威尔。

请记住,每次运行代码时,您都会看到不同的图像,因为我们会随机生成索引。此外,您很可能会看到四个正确的分类,因为我们的准确率是 85%。

清单 3-9 中显示的最终代码示例是为了完整性。我想告诉你如何使用 LDA 降维而不是 PCA。在这种情况下,LDA 表现不佳,但在给定不同数据集的情况下,它可能表现得更好。

import numpy as np
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import\
     LinearDiscriminantAnalysis
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report
import warnings

if __name__ == "__main__":
    br = '\n'
    warnings.filterwarnings('ignore')
    X = np.load('data/X_faces.npy')
    y = np.load('data/y_faces.npy')
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

    pca = PCA(n_components=0.95, whiten=True, random_state=0)
    pca.fit(X_train)
    components = pca.n_components_
    lda = LinearDiscriminantAnalysis(n_components=components)
    lda.fit(X_train, y_train)
    X_train_lda = lda.transform(X_train)
    svm = SVC(kernel='rbf', class_weight="balanced", gamma="scale", random_state=0)
    svm_name = svm.__class__.__name__
    svm.fit(X_train_lda, y_train)
    X_test_lda = lda.transform(X_test)
    y_pred = svm.predict(X_test_lda)
    cr = classification_report(y_test, y_pred)
    print ('classification report <<' + svm_name+ '>>')
    print (cr)

Listing 3-9Dimensionality reduction with LDA

执行清单 3-9 的输出应该如下所示:

classification report <<SVC>>
              precision    recall  f1-score   support

           0       1.00      0.21      0.35        28
           1       0.84      0.49      0.62        63
           2       0.69      0.38      0.49        24
           3       0.56      0.96      0.71       132
           4       0.50      0.15      0.23        20
           5       0.73      0.36      0.48        22
           6       0.67      0.42      0.52        33

   micro avg       0.61      0.61      0.61       322
   macro avg       0.71      0.43      0.49       322
weighted avg       0.68      0.61      0.58       322

代码很短,因为性能远低于 PCA (68%对 85%的准确率)。如果 PCA 是更好的模型,我们为什么要创建特征脸、预测和可视化?请注意,我们使用 PCA 来确定具有 5%信息丢失的最佳组件数量,然后 LDA 使用它来通过 svm.SVC 进行预测。

四、回归的预测建模

分类是预测一个实例的离散类别标签的问题,而回归预测建模(或只是回归)是学习自变量(或特征)和连续因变量(或结果)之间的关联强度的问题。一个连续输出变量是一个实值,比如一个整数或浮点值,通常被量化为数量和大小。

简单地说,回归试图了解特征和结果之间的关系有多强。从形式上讲,回归近似于从输入变量(X)到连续输出变量(y)的映射函数(f)。能够学习回归预测模型的算法称为回归算法。由于回归预测一个量,性能必须在那些预测中作为误差来测量。

回归的性能可以用许多方法来衡量,但最常见的是计算均方根误差(RMSE)。 RMSE 的一个好处是误差分数的单位与预测值相同。虽然回归预测可以使用 RMSE 进行评估,但分类预测却不能。

回归数据集

我们专注于四个数据集:小费、波士顿和葡萄酒(红和白)。小费数据由餐馆的服务员小费和相关因素组成,包括小费、餐费和时间。波士顿数据由波士顿不同地点的房价组成。葡萄酒数据由两个数据集(红色和白色)组成,这两个数据集由葡萄牙 Vinho Verde 葡萄酒的变体组成。

回归提示

清单 4-1 中所示的第一个代码示例从 CSV 文件加载 tips 数据,通过将分类特征转换为虚拟特征来实现特征工程,向现有数据集中添加新数据,估算新数据,显示特征重要性,使用线性回归算法训练数据,并进行预测。在这种情况下,我们从多元线性回归中学习,因为我们正在对多个特征和一个连续相关目标进行数据训练。

import numpy as np, pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

if __name__ == "__main__":
    br = '\n'
    tips = pd.read_csv('data/tips.csv')
    print ('original data shape:', tips.shape, br)
    target = tips['tip']
    data = tips.drop(['tip'], axis=1)
    data = pd.get_dummies(data, columns=['sex', 'smoker','day', 'time'])
    d = {'sex_Male':'male', 'sex_Female':'female',
         'smoker_Yes':'smoker', 'smoker_No':'non-smoker',
         'day_Thur':'th', 'day_Fri':'fri', 'day_Sat':'sat',
         'day_Sun':'sun', 'time_Lunch':'lunch',
         'time_Dinner':'dinner'}
    data = data.rename(index=str, columns=d)
    X = data.values

    y = target.values
    print ('X and y shapes (post conversion):')
    print (X.shape, y.shape, br)
    X_vector = np.array([30.00, 'NaN', 1, 0, 1, 0, 0, 0, 0, 1, 1, 0])
    y_vector = np.array([4.5])
    X = np.vstack([X, X_vector])
    y = np.append(y, y_vector)
    print ('new X and y data point:')
    print (X[244], y[244], br)
    X_vectors = np.array([[24.99, 'NaN',0, 1, 0, 1, 1, 0, 0, 0, 0, 1],
                         [19.99, 'NaN',1, 0, 1, 0, 0, 0, 0, 1, 1, 0]])
    y_vectors = np.array([[3.5], [2.0]])
    X = np.vstack([X, X_vectors])
    y = np.append(y, y_vectors)
    print ('new X and y data points:')
    print (X[245], y[245])
    print (X[246], y[246], br)
    imputer = SimpleImputer()
    imputer.fit(X)
    X = imputer.transform(X)
    print ('new data shape:', X.shape, br)
    print ('new records post imputation (features and targets):')

    print (X[244], y[244])
    print (X[245], y[245])
    print (X[246], y[246], br)
    rfr = RandomForestRegressor(random_state=0, n_estimators=100)
    rfr.fit(X, y)
    print ('feature importance (first 6 features):')
    feature_importances = rfr.feature_importances_
    features = list(data.columns.values)
    importance = sorted(zip(feature_importances, features), reverse=True)
    [print (row) for i, row in enumerate(importance) if i < 6]
    print ()
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)

    model = LinearRegression()
    model_name = model.__class__.__name__
    print ('<<' + model_name + '>>', br)
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    print (rmse, '(rmse)', br)
    print ('predict from new data:')
    p1 = [X[244]]
    p2 = [X[245], X[246]]
    y1, y2 = model.predict(p1), model.predict(p2)
    print (y[244], y1[0])
    print (y[245], y2[0])
    print (y[246], y2[1])
    X_file = 'data/X_tips'
    y_file = 'data/y_tips'
    np.save(X_file, X)
    np.save(y_file, y)

Listing 4-1Predicting from tips with get_dummies encoding

继续执行清单 4-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。

执行清单 4-1 的输出应该如下所示:

original data shape: (244, 7)

X and y shapes (post conversion):
(244, 12) (244,)

new X and y data point:
['30.0' 'NaN' '1' '0' '1' '0' '0' '0' '0' '1' '1' '0'] 4.5

new X and y data points:
['24.99' 'NaN' '0' '1' '0' '1' '1' '0' '0' '0' '0' '1'] 3.5
['19.99' 'NaN' '1' '0' '1' '0' '0' '0' '0' '1' '1' '0'] 2.0

new data shape: (247, 12)

new records post imputation (features and targets):
[30\.          2.569672131\.        0\.          1\.          0.
  0\.          0\.           0\.        1\.          1\.          0\.        ] 4.5
[24.99        2.569672130\.        1\.          0\.          1.
  1\.          0\.           0\.        0\.          0\.          1\.        ] 3.5
[19.99        2.569672131\.        0\.          1\.          0.
  0\.          0\.           0\.        1\.          1\.          0\.        ] 2.0

feature importance (first 6 features)

:
(0.7597845511444519, 'total_bill')
(0.0643775334380493, 'size')
(0.03663421916266647, 'non-smoker')
(0.033603977117169975, 'smoker')
(0.026410154999617023, 'sat')
(0.02186564474599064, 'sun')

<<LinearRegression>>

0.9474705746817206 (rmse)

predict from new data:
4.5 3.827512419066452
3.5 3.56649951075833
2.0 2.941595038244732

代码示例首先导入 RandomForestRegressor、SimpleImputer 和 mean_square_error 以及其他必需的包。主程序块从从 CSV 文件加载 tips 数据开始。它继续用 pandas get_dummies 函数对分类变量进行特征工程。提醒一下,特征工程正在使用数据集的领域知识来创建使机器学习算法更有效工作的特征。

熊猫得到 _dummies 一热默认编码。比如,性别要么是要么是。通过一键编码,变成了【1,0】,而【0,1】。在这种情况下必须进行特征工程,因为 Scikit-Learn 只处理数字数据。接下来,创建特征集 X 和目标 y。请注意,由于一次热编码,数据形状现在有 12 个特征。

代码的下一部分向数据集中添加了三条新记录。还要注意,我们添加了一个 NaN 特性,这意味着该特性没有可识别的值。Scikit-Learn 算法不能处理 Nan 特征,所以我们用简单估算器类估算特征平均值。插补是用替代值替换缺失数据的过程。我们接下来显示新记录。请注意,所有的 Nan 值都被替换为其特征平均值。

小费

插补是用替代数据替换缺失数据的常用技术。

代码的最后一部分首先在 RandomForestRegressor 的帮助下显示六个最重要的特性,RandomForestRegressor 是一个元估计器,适合对数据进行决策树分类,并使用平均来提高性能和减少过度拟合。接下来,数据被分成训练测试子集,并用线性回归进行训练,这样我们就可以计算 RMSE。线性回归模拟特征和目标之间的关系。最后,我们根据新数据进行预测,其中第一个 st 值是实际目标值,第二个是我们的预测值,并将 X 和 y 保存为 NumPy 文件以供将来处理。

小费

回归的目标是最小化(减少)RMSE。

清单 4-2 中显示的下一个代码示例使用 DictVectorizer 而不是 get_dummies 作为一次性编码分类数据的替代选项。

import pandas as pd, numpy as np
from sklearn.feature_extraction import DictVectorizer
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from random import randint

if __name__ == "__main__":
    br = '\n'
    tips = pd.read_csv('data/tips.csv')
    data = tips.drop(['tip'], axis=1)
    target = tips['tip']
    v = ['sex', 'smoker', 'day', 'time']
    ls = data[v].to_dict(orient='records')
    vector = DictVectorizer(sparse=False, dtype=int)
    d = vector.fit_transform(ls)
    print ('one hot encoding:')
    print (d[0:3], br)
    print ('encoding order:')
    encode_order = vector.get_feature_names()
    print (encode_order, br)
    data = data.drop(['sex', 'smoker', 'day', 'time'], axis=1)
    X = data.values
    print ('feature shape after removing categorical columns:')
    print (X.shape, br)
    Xls, dls = X.tolist(), d.tolist()
    X = [np.array(row + dls[i]) for i, row in enumerate(Xls)]
    X = np.array(X)
    y = target.values
    print ('feature shape after adding encoded data back:')
    print (X.shape, br)

    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    model = LinearRegression(fit_intercept=True)
    model_name = model.__class__.__name__
    print ('<<' + model_name  +  '>>', br)
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    print (rmse, '(rmse)', br)
    print ('predict 1st test set element (actual/prediction):')
    print (y_test[0], y_pred[0], br)
    rints = [randint(0, y.shape[0]-1) for row in range(3)]
    print ('random integers:', rints, br)
    p = [X[rints[0]], X[rints[1]], X[rints[2]]]
    y_p = model.predict(p)
    y_p = list(np.around(y_p, 2))
    print (y_p, '(predicted)')
    print ([y[rints[0]], y[rints[1]], y[rints[2]]], '(actual)')

Listing 4-2Predicting from tips with DictVectorizer encoding

执行清单 4-2 的输出应该如下所示:

One hot encoding:
[[0 0 1 0 1 0 1 0 1 0]
 [0 0 1 0 0 1 1 0 1 0]
 [0 0 1 0 0 1 1 0 1 0]]

encoding order:
['day=Fri', 'day=Sat', 'day=Sun', 'day=Thur', 'sex=Female', 'sex=Male', 'smoker=No','smoker=Yes', 'time=Dinner', 'time=Lunch']

feature shape after removing categorical columns:
(244, 2)

feature shape after adding encoded data back:
(244, 12)

<<LinearRegression>>

0.9636287548943022 (rmse)

predict 1st test set element (actual/prediction):
2.64 2.8121130438023094

random integers: [202, 13, 143]

[2.19, 3.21, 4.52] (predicted)
[2.0, 3.0, 5.0] (actual)

代码从导入 DictVectorizer 以及其他必备的包开始。主程序块加载 tips 数据,剥离特征 tip 并将剩余特征放入变量数据,并将特征 tip 放入变量目标。接下来,我们将需要编码的特征放入变量 v 中。

代码继续剥离需要编码的特性,并将结果放入变量 ls 中。然后创建一个 DictVectorizer 实例,并放入变量 vector 中。DictVectorizer 是一种 Scikit-Learn 技术,它将特征值映射列表转换为向量。

然后,向量中的数据被拟合(或训练)并转换为独热编码值,结果被放入变量 d 中。要查看编码顺序,请使用函数 get_feature_names。接下来,从变量数据中删除编码特征,这样我们就可以开始构建特征集 x 了。

代码继续创建一个基于 X 的列表和另一个基于 d 的列表,这样我们可以将 X 与包含在 d 中的独热码编码值连接起来。剩下的就是将 X 和 y 转换成 NumPy 值。代码结束时将 X 和 y 分成训练测试子集,用线性回归进行训练,计算 RMSE,并进行预测。RMSE 要高一点,因为我们没有像上一个例子那样添加新数据。

小费

在大多数情况下,使用 get_dummies 进行一键编码。

尽管 DictVectorizer 比 get_dummies 更难实现,但它具有稀疏的优势。也就是说,不存在的特征不需要被存储。此外,DictVectorizer 是一种有用的表示转换,用于在自然语言处理模型中训练序列分类器,该模型通常通过提取感兴趣的特定单词周围的特征窗口来工作。

清单 4-3 中的最后一个代码示例将工程数据(来自 NumPy 文件)加载到 X 和 y 中,并为实现正则化的几个回归算法计算 RMSE。正则化是一种通过在给定数据集上适当拟合函数(或算法)来减少误差的技术,以减轻过度拟合。

import numpy as np
from sklearn.linear_model import LinearRegression, Ridge,\
     Lasso, ElasticNet, SGDRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import StandardScaler

def get_scores(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_tips.npy')
    y = np.load('data/y_tips.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    print ('rmse:')
    lr = LinearRegression().fit(X_train, y_train)
    rmse, lr_name = get_scores(lr, X_test, y_test)
    print (rmse, '(' + lr_name + ')')
    rr = Ridge(random_state=0).fit(X_train, y_train)
    rmse, rr_name = get_scores(rr, X_test, y_test)
    print (rmse, '(' + rr_name + ')')

    lasso = Lasso(random_state=0).fit(X_train, y_train)
    rmse, lasso_name = get_scores(lasso, X_test, y_test)
    print (rmse, '(' + lasso_name + ')')
    en = ElasticNet(random_state=0).fit(X_train, y_train)
    rmse, en_name = get_scores(en, X_test, y_test)
    print (rmse, '(' + en_name + ')')
    sgdr = SGDRegressor(random_state=0, max_iter=1000, tol=0.001)
    sgdr.fit(X_train, y_train)
    rmse, sgdr_name = get_scores(sgdr, X_test, y_test)
    print (rmse, '(' + sgdr_name + ')', br)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    print ('rmse std:')
    lr_std = LinearRegression().fit(X_train_std, y_train)
    rmse, lr_name = get_scores(lr_std, X_test_std, y_test)
    print (rmse, '(' + lr_name + ')')
    rr_std = Ridge(random_state=0).fit(X_train_std, y_train)
    rmse, rr_name = get_scores(rr_std, X_test_std, y_test)
    print (rmse, '(' + rr_name + ')')
    lasso_std = Lasso(random_state=0).fit(X_train_std, y_train)
    rmse, lasso_name = get_scores(lasso_std, X_test_std, y_test)

    print (rmse, '(' + lasso_name + ')')
    en_std = ElasticNet(random_state=0)
    en_std.fit(X_train_std, y_train)
    rmse, en_name = get_scores(en_std, X_test_std, y_test)
    print (rmse, '(' + en_name + ')')
    sgdr_std = SGDRegressor(random_state=0, max_iter=1000, tol=0.001)
    sgdr_std.fit(X_train_std, y_train)
    rmse, sgdr_name = get_scores(sgdr_std, X_test_std, y_test)
    print (rmse, '(' + sgdr_name + ')')

Listing 4-3Predicting from tips with regression regularization models

执行清单 4-3 的输出应该如下所示:

rmse

:
0.9474705746817206 (LinearRegression)
0.9469115898683899 (Ridge)
0.9439950256305224 (Lasso)
0.9307377813721578 (ElasticNet)
1.7005504977258326 (SGDRegressor)

rmse std:
0.9007751177881488 (LinearRegression)
0.9014055340745654 (Ridge)
1.333812899498391 (Lasso)
1.1310151423347359 (ElasticNet)
0.9021020134681715 (SGDRegressor)

代码示例从导入 Ridge、Lasso、ElasticNet、SGDRegressor 和其他必需的包开始。函数 get_scores 返回 RMSE。主程序块首先将 NumPy 文件中的数据加载到 X 和 y 中,然后将数据分割成训练测试子集。

代码的其余部分使用 LinearRegression 和几个实现正则化的回归模型来训练数据。Ridge、Lasso、ElasticNet 和 SGDRegressor 是流行的 Scikit-Learn 回归算法,用于调整线性回归。正则化通过在给定的训练集上适当地拟合模型来减少过度拟合,从而减少误差。也就是说,正则化不鼓励学习更复杂的模型来减轻过度拟合的风险。

小费

使用正则化来减少误差并最小化回归模型的过度拟合。

回归对系数的大小施加惩罚。拉索回归推导出具有较少参数值(或稀疏模型)的解决方案,有效减少了解决方案所依赖的变量数量。

Lasso 使用 L1 正则化,Ridge 使用 L2 正则化。

L1 和 L2 的主要区别在于刑期。Ridge 将系数的平方值作为惩罚添加到损失函数中,以减轻过拟合。Lasso(最小绝对收缩和选择算子)将系数的绝对值作为损失函数的惩罚,当我们有大量的特征时,它工作得很好。

损失(成本)函数是将一个或多个特征的事件(或值)映射到表示与该事件相关联的成本的实数上的函数。两种技术的主要区别在于,Lasso 将不太重要的特征的系数缩小到零,从而有效地将它们排除在考虑范围之外。Lasso 为密集和稀疏数据提供了相同的结果,对于稀疏数据,速度得到了提高。

回归包括 L1 和 L2 的惩罚作为正则项。结合 L1 和 L2 允许学习稀疏模型,其中很少有权重像 Lasso 一样非零,同时仍然保持岭的正则化属性。ElasticNet 最擅长的是多个相关的特性。利用 Lasso 和 Ridge 之间的权衡的一个实际优势是,它允许 ElasticNet 继承 Ridge 在旋转下的一些稳定性,同时仍然可以很好地处理稀疏模型。

SGDRegressor 通过最小化带有随机梯度下降(SGD)的正则化经验损失来执行。也就是说,对每个样本估计损失的梯度,然后用递减的强度时间表(或学习速率)来更新模型。正则项是添加到损失函数中的惩罚,它使用 L1(套索)或 L2(山脊)或两者的组合(弹性网)将参数向零收缩。

因此,正则化技术的选择高度依赖于数据的性质。稀疏数据表明从套索开始。创建过于复杂的模型将意味着转向 Ridge。ElasticNet 提供了一个折中方案。SGDRegressor 试图做到这一切。对于相对较小的数据集,尝试所有这些技术没有问题。但是,大型数据集是一个不同的故事,因为需要大量的处理时间(或高计算费用)。

请注意,有时扩展(或标准化)会提高性能,有时不会。例如,缩放对 LinearRegression、Ridge 和 SGDRegressor 有帮助,但对 Lasso 和 ElasticNet 不利。

小费

如果您有时间、耐心和计算资源,实验是提高性能的一个很好的方法。

回归波士顿

清单 4-4 中显示的第一个代码示例显示了波士顿数据集中的要素重要性,使用 RandomForestRegressor 进行训练,并计算有噪声和无噪声情况下的 RMSE。

import numpy as np, pandas as pd
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error

if __name__ == "__main__":
    br = '\n'
    boston = load_boston()
    X = boston.data
    y = boston.target
    print ('feature shape', X.shape)
    print ('target shape', y.shape, br)
    keys = boston.keys()
    rfr = RandomForestRegressor(random_state=0, n_estimators=100)
    rfr.fit(X, y)
    features = boston.feature_names
    feature_importances = rfr.feature_importances_
    importance = sorted(zip(feature_importances, features), reverse=True)
    [print (row) for row in importance]
    print ()
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)

    rfr = RandomForestRegressor(random_state=0, n_estimators=100)
    rfr.fit(X_train, y_train)
    rfr_name = rfr.__class__.__name__
    y_pred = rfr.predict(X_test)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    print (rfr_name + ' (rmse):', rmse, br)
    cols = list(features) + ['target']
    data = pd.DataFrame(data=np.c_[X, y], columns=cols)
    print ('boston dataset sample:')
    print (data[['RM', 'LSTAT', 'DIS', 'CRIM', 'NOX', 'PTRATIO', 'target']].head(3), br)
    print ('data set before removing noise:', data.shape)
    noise = data.loc[data['target'] >= 50]
    data = data.drop(noise.index)
    print ('data set without noise:', data.shape, br)
    X = data.loc[:, data.columns != 'target'].values
    y = data['target'].values
    print ('cleansed feature shape:', X.shape)
    print ('cleansed target shape:', y.shape, br)
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    rfr = RandomForestRegressor(random_state=0, n_estimators=100)
    rfr.fit(X_train, y_train)

    y_pred = rfr.predict(X_test)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    print (rfr_name + ' (rmse):', rmse)
    X_file = 'data/X_boston'
    y_file = 'data/y_boston'
    np.save(X_file, X)
    np.save(y_file, y)

Listing 4-4Exploring boston data with RandomForestRegressor

执行清单 4-4 的输出应该如下所示:

feature shape (506, 13)
target shape (506,)

(0.45730362625767496, 'RM')
(0.35008661885681375, 'LSTAT')
(0.06518862820215894, 'DIS')
(0.040989617257001, 'CRIM')
(0.02024797563034355, 'NOX')
(0.015576365835498516, 'PTRATIO')
(0.015524054184831321, 'TAX')
(0.011764308556043926, 'AGE')
(0.011324966974602932, 'B')
(0.005912139937999768, 'INDUS')
(0.003916064249793193, 'RAD')
(0.0011173446269339175, 'ZN')
(0.0010482894303040916, 'CHAS')

RandomForestRegressor (rmse): 4.091149842219918

boston dataset sample:
      RM  LSTAT     DIS     CRIM    NOX  PTRATIO  target
0  6.575   4.98  4.0900  0.00632  0.538     15.3    24.0
1  6.421   9.14  4.9671  0.02731  0.469     17.8    21.6
2  7.185   4.03  4.9671  0.02729  0.469     17.8    34.7

data set before removing noise: (506, 14)
data set without noise: (490, 14)

cleansed feature shape: (490, 13)
cleansed target shape: (490,)

RandomForestRegressor (rmse): 3.37169151536684

该代码示例从导入必备包开始。主块将 sklearn.datasets 中的波士顿数据加载到 X 和 y 中,并显示形状。接下来,RandomForestRegressor 对完整数据集(X 和 y)进行训练,以创建和显示特征重要性。代码继续将数据分成训练测试子集,并用 RandomForestRegressor 进行训练(X_train,y_train)。计算并显示 RMSE。

随机森林是一种集成技术,能够通过使用多个决策树和 bagging 来执行回归和分类。Bagging 包括用替换的不同数据样本训练每个决策树。这个想法是结合多个决策树来确定结果,而不是依赖单个决策树。

代码继续将 X 和 y 读入熊猫数据帧,并显示前三条记录。然后从数据帧中去除噪声,并保存到 X 和 y 中。清理后的数据被分成训练测试子集,并使用 RandomForestRegressor 进行训练。请注意,清理后的数据的 RMSE 要低得多(误差更小)。代码最后将 X 和 y 保存为 NumPy 文件。

16 个数据点的 MEDV 值为 50.0,这可能包含缺失值或删减值,可视为噪声。所以,我们不再考虑它们。有关波士顿数据集中噪声的更多信息,请参考以下链接: https://www.ritchieng.com/machine-learning-project-boston-home-prices/

清单 4-5 中显示的本节中的最后一个代码示例加载经过清理(去除噪声)的波士顿数据,并使用线性回归、正则化模型和 RandomForestRegressor 计算 RMSE。

import numpy as np
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression, Ridge,\
     Lasso, ElasticNet, SGDRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import StandardScaler

def get_scores(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_boston.npy')

    y = np.load('data/y_boston.npy')
    print ('feature shape', X.shape)
    print ('target shape', y.shape, br)
    X_train, X_test, y_train, y_test = train_test_split( X, y, random_state=0)
    print ('rmse:')
    rfr = RandomForestRegressor(random_state=0, n_estimators=100)
    rfr.fit(X_train, y_train)
    rmse, rfr_name = get_scores(rfr, X_test, y_test)
    print (rmse, '(' + rfr_name + ')')
    lr = LinearRegression().fit(X_train, y_train)
    rmse, lr_name = get_scores(lr, X_test, y_test)
    print (rmse, '(' + lr_name + ')')
    ridge = Ridge(random_state=0).fit(X_train, y_train)
    rmse, ridge_name = get_scores(ridge, X_test, y_test)
    print (rmse, '(' + ridge_name + ')')
    lasso = Lasso(random_state=0).fit(X_train, y_train)
    rmse, lasso_name = get_scores(lasso, X_test, y_test)
    print (rmse, '(' + lasso_name + ')')
    en = ElasticNet(random_state=0).fit(X_train, y_train)
    rmse, en_name = get_scores(en, X_test, y_test)
    print (rmse, '(' + en_name + ')')
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    sgdr_std = SGDRegressor(random_state=0, max_iter=1000, tol=0.001)
    sgdr_std.fit(X_train_std, y_train)
    rmse, sgdr_name = get_scores(sgdr_std, X_test_std, y_test)
    print (rmse, '(' + sgdr_name + ' - scaled)')

Listing 4-5Exploring boston data with regression algorithms

执行清单 4-5 的输出应该如下所示:

feature shape (490, 13)
target shape (490,)

rmse:
3.37169151536684 (RandomForestRegressor)
4.236710574387242 (LinearRegression)
4.2526986026173486 (Ridge)
5.097231463859832 (Lasso)
4.88844846745213 (ElasticNet)
4.410035683951274 (SGDRegressor - scaled)

代码从导入必需的包开始。函数 get_scores 返回 RMSE 和算法名。主程序块从加载 NumPy 文件中的数据开始,并将其分成训练测试子集。然后用 RandomForestRegressor、LinearRegression、Ridge、Lasso、ElasticNet 和 SGDRegressor 训练数据。请注意,RandomForestRegressor 优于所有正则化算法,因为它的 RMSE 最低。

回归葡萄酒数据

清单 4-6 中显示的第一个代码示例从 CSV 文件加载红酒数据,显示特性重要性,并将数据保存到 NumPy 文件。

import numpy as np, pandas as pd
from sklearn.ensemble import RandomForestRegressor

if __name__ == "__main__":
    br = '\n'
    f = 'data/redwine.csv'
    red_wine = pd.read_csv(f)
    X = red_wine.drop(['quality'], axis=1)
    y = red_wine['quality']
    print (X.shape)
    print (y.shape, br)
    features = list(X)
    rfr = RandomForestRegressor(random_state=0, n_estimators=100)
    rfr.fit(X, y)

    feature_importances = rfr.feature_importances_
    importance = sorted(zip(feature_importances, features), reverse=True)
    for row in importance:
        print (row)
    print ()
    print (red_wine[['alcohol', 'sulphates', 'volatile acidity',
                     'total sulfur dioxide', 'quality']]. head())
    X_file = 'data/X_red'
    y_file = 'data/y_red'
    np.save(X_file, X)
    np.save(y_file, y)

Listing 4-6Exploring and saving red wine data

执行清单 4-6 的输出应该如下所示:

(1599, 11)
(1599,)

(0.27432500255956216, 'alcohol')
(0.13700073893077233, 'sulphates')
(0.13053941311188708, 'volatile acidity')
(0.08068199773601588, 'total sulfur dioxide')
(0.06294612644261727, 'chlorides')
(0.057730976351602854, 'pH')
(0.055499749756166, 'residual sugar')
(0.05198192402458334, 'density')
(0.05114079873500658, 'fixed acidity')
(0.049730883807319035, 'free sulfur dioxide')
(0.04842238854446754, 'citric acid')

   alcohol  sulphates  volatile acidity  total sulfur dioxide  quality
0      9.4       0.56              0.70                  34.0      5.0
1      9.8       0.68              0.88                  67.0      5.0
2      9.8       0.65              0.76                  54.0      5.0
3      9.8       0.58              0.28                  60.0      6.0
4      9.4       0.56              0.70                  34.0      5.0

代码从导入必需的包开始。主块从 CSV 文件中加载红酒数据。接下来,通过从 Pandas 数据帧中剥离目标列质量来创建特征集 X,然后从质量列中创建目标 y。然后显示 x 和 y 形状。代码最后在 RandomForestRegressor 的帮助下显示特性的重要性,并将数据保存到 NumPy 文件中。

清单 4-7 中显示的下一个代码示例使用各种回归算法对红酒数据进行实验。

import numpy as np, pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression,\
     Ridge, Lasso, ElasticNet, SGDRegressor
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
import matplotlib.pyplot as plt, seaborn as sns

def get_scores(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

if __name__ == "__main__":
    br = '\n'
    d = dict()

    X = np.load('data/X_red.npy')
    y = np.load('data/y_red.npy')
    X_train, X_test, y_train, y_test =  train_test_split(
        X, y, test_size=0.2, random_state=0)
    print ('rmse (unscaled):')
    rfr = RandomForestRegressor(random_state=0, n_estimators=100)
    rfr.fit(X_train, y_train)
    rmse, rfr_name = get_scores(rfr, X_test, y_test)
    d['rfr'] = [rmse]
    print (rmse, '(' + rfr_name + ')')
    lr = LinearRegression().fit(X_train, y_train)
    rmse, lr_name = get_scores(lr, X_test, y_test)
    d['lr'] = [rmse]
    print (rmse, '(' + lr_name + ')')
    ridge = Ridge(random_state=0).fit(X_train, y_train)
    rmse, ridge_name = get_scores(ridge, X_test, y_test)
    d['ridge'] = [rmse]
    print (rmse, '(' + ridge_name + ')')
    lasso = Lasso(random_state=0).fit(X_train, y_train)
    rmse, lasso_name = get_scores(lasso, X_test, y_test)
    d['lasso'] = [rmse]
    print (rmse, '(' + lasso_name + ')')
    en = ElasticNet(random_state=0).fit(X_train, y_train)
    rmse, en_name = get_scores(en, X_test, y_test)
    d['en'] = [rmse]
    print (rmse, '(' + en_name + ')')

    sgdr = SGDRegressor(random_state=0, max_iter=1000, tol=0.001)
    sgdr.fit(X_train, y_train)
    rmse, sgdr_name = get_scores(sgdr, X_test, y_test)
    print (rmse, '(' + sgdr_name + ')', br)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    print ('rmse scaled:')
    lr_std = LinearRegression().fit(X_train_std, y_train)
    rmse, lr_std_name = get_scores(lr_std, X_test_std, y_test)
    print (rmse, '(' + lr_std_name + ')')
    rr_std = Ridge(random_state=0).fit(X_train_std, y_train)
    rmse, rr_std_name = get_scores(rr_std, X_test_std, y_test)
    print (rmse, '(' + rr_std_name + ')')
    lasso_std = Lasso(random_state=0).fit(X_train_std, y_train)

    rmse, lasso_std_name = get_scores(lasso_std, X_test_std, y_test)
    print (rmse, '(' + lasso_std_name + ')')
    en_std = ElasticNet(random_state=0).fit(X_train_std, y_train)
    rmse, en_std_name = get_scores(en_std, X_test_std, y_test)
    print (rmse, '(' + en_std_name + ')')
    sgdr_std = SGDRegressor(random_state=0, max_iter=1000, tol=0.001)
    sgdr_std.fit(X_train_std, y_train)
    rmse, sgdr_std_name = get_scores(sgdr_std, X_test_std, y_test)
    d['sgdr_std'] = [rmse]
    print (rmse, '(' + sgdr_std_name + ')', br)
    pipe = Pipeline([('poly', PolynomialFeatures(degree=2)),
                     ('linear', LinearRegression())])
    model = pipe.fit(X_train, y_train)
    rmse, poly_name = get_scores(model, X_test, y_test)
    d['poly'] = [rmse]
    print (PolynomialFeatures().__class__.__name__, '(rmse):')
    print (rmse, '(' + poly_name + ')')
    algo, rmse = [], []
    for key, value in d.items():
        algo.append(key)
        rmse.append(value[0])
    plt.figure('RMSE')
    sns.set(style="whitegrid")
    ax = sns.barplot(algo, rmse)
    plt.title('Red Wine Algorithm Comparison')
    plt.xlabel('regressor')
    plt.ylabel('RMSE')
    plt.show()

Listing 4-7Exploring red wine data with regression algorithms

执行清单 4-7 的输出应该如下所示:

rmse (unscaled):
0.5694654840286635 (RandomForestRegressor)
0.6200574149384266 (LinearRegression)
0.6185762657415644 (Ridge)
0.7455442007369433 (Lasso)
0.7450232657227877 (ElasticNet)
51120537008.37402 (SGDRegressor)

rmse scaled:
0.6216027053463463 (LinearRegression)
0.6215826846730879 (Ridge)
0.7584549718351333 (Lasso)
0.7584549718351333 (ElasticNet)
0.6234205584462227 (SGDRegressor)

PolynomialFeatures (rmse):
0.6382400985644077 (Pipeline)

列表 4-7 也显示图 4-1 。图 4-1 显示了本实验中使用的算法的 RMSE 分数。

img/481580_1_En_4_Fig1_HTML.jpg

图 4-1

红酒 RMSE 评分对比

代码从导入多项式特性、管道、seaborn 和其他必需的包开始。函数 get_scores 返回 RMSE 和型号名称。主程序块首先创建一个字典来存储来自训练实验的最佳 RMSE 分数。算法训练数据有无缩放,最好的分数保存在字典 d 中。接下来,数据被分成训练测试子集。

代码继续使用 LinearRegression、Ridge、Lasso、ElasticNet、SGDRegressor 和 RandomForestRegressor 训练未缩放的数据。在该数据集上性能最好的算法是 RandomForestRegressor,其 RMSE 约为 0.569。然后代码用线性回归、岭、套索、弹性网和 SGDRegressor 训练缩放的数据。在该数据集上,性能最好的算法是 RMSE 约为 0.622 的 Ridge 算法,这并不比其未缩放的 RMSE 更好。尽管 RMSE·斯格雷索不是表现最好的,但请注意缩放对算法的影响有多大!最后,用多项式特征训练未缩放的数据。

多项式特性提供了一个通过转换输入而不是改进模型来提高性能的机会。多项式回归允许不同程度的输入的线性组合。在本例中,我们对输入进行平方(度数=2)以探究对性能的影响。为了训练数据,我们将多项式特征转化为线性回归。

我们可以用 degree 做实验,看看性能会发生什么变化。我们可以立方体输入(度数=3),四倍输入(度数=4),等等。多项式模型对于非线性机器学习实验非常有用,但要小心高阶多项式模型,因为它们通常表现不佳。也就是说,它们会产生不必要的剧烈波动。正则化可以减轻多项式的不当行为。

多项式特征的训练是通过将转换后的输入(平方数据)转化为线性回归来完成的。该代码通过基于存储在字典 d 中的最佳 RMSE 分数创建可视化来结束。

清单 4-8 中显示的下一个代码示例用多项式拟合进行实验。在前面的示例中,我们对输入数据求平方,训练模型,并计算 RMSE。在这个模型中,我们采用输入数据的二次(平方)、三次和四次幂,训练每个模型,并计算和显示 RMSE 进行比较。与之前代码的另一个变化是,我们用多项式特征算法来转换训练和测试数据,而不是将多项式特征转化为线性回归。然后,我们用转换后的数据训练线性回归。一旦数据被训练,我们显示每个实验的 RMSE。

import numpy as np, pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import PolynomialFeatures

def get_scores(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

if __name__ == "__main__":
    br = '\n'
    d = dict()
    X = np.load('data/X_red.npy')
    y = np.load('data/y_red.npy')
    X_train, X_test, y_train, y_test =  train_test_split(
        X, y, test_size=0.2, random_state=0)
    poly = PolynomialFeatures(degree=2)
    poly.fit(X_train, y_train)
    X_train_poly = poly.transform(X_train)
    lr = LinearRegression().fit(X_train_poly, y_train)
    X_test_poly = poly.transform(X_test)
    rmse, lr_name = get_scores(lr, X_test_poly, y_test)
    print (rmse, '(squared polynomial fitting)')
    poly = PolynomialFeatures(degree=3)
    poly.fit(X_train, y_train)
    X_train_poly = poly.transform(X_train)
    lr = LinearRegression().fit(X_train_poly, y_train)
    X_test_poly = poly.transform(X_test)
    rmse, lr_name = get_scores(lr, X_test_poly, y_test)
    print (rmse, '(cubic polynomial fitting)')
    poly = PolynomialFeatures(degree=4)
    poly.fit(X_train, y_train)
    X_train_poly = poly.transform(X_train)

    lr = LinearRegression().fit(X_train_poly, y_train)
    X_test_poly = poly.transform(X_test)
    rmse, lr_name = get_scores(lr, X_test_poly, y_test)
    print (rmse, '(quartic polynomial fitting)')

Listing 4-8Polynomial fitting with red wine data

执行清单 4-8 的输出应该如下所示:

0.6382400985644077 (squared polynomial fitting)
0.8284645679714848 (cubic polynomial fitting)
97.85391125320886 (quartic polynomial fitting)

代码导入必需的包。函数 get_scores 返回 RMSE 和型号名称。主程序块将数据加载到 X 和 y 中。它继续将数据分割成训练测试子集。接下来,用输入数据的平方(次数=2)用多项式特征拟合训练和测试数据。然后转换训练数据。线性回归对转换后的数据进行训练,并显示 RMSE。度=3 和度=4 时遵循相同的过程。对于此数据集,平方输入可提供最佳 RMSE。

小费

多项式特征对于非线性数据集建模是一种非常有用的技术,并且易于实现。

清单 4-9 中显示的下一个代码示例从 CSV 文件中加载白酒数据,显示特性重要性,并将数据保存到 NumPy 文件中。

import numpy as np, pandas as pd
from sklearn.ensemble import RandomForestRegressor

if __name__ == "__main__":
    br = '\n'
    f = 'data/whitewine.csv'
    white_wine = pd.read_csv(f)
    X = white_wine.drop(['quality'], axis=1)
    y = white_wine['quality']
    print (X.shape)
    print (y.shape, br)
    features = list(X)
    rfr = RandomForestRegressor(random_state=0, n_estimators=100)
    rfr.fit(X, y)
    feature_importances = rfr.feature_importances_
    importance = sorted(zip(feature_importances, features), reverse=True)
    for row in importance:
        print (row)
    print ()
    print (white_wine[['alcohol', 'sulphates', 'volatile acidity', 'total sulfur dioxide', 'quality']]. head())
    X_file = 'data/X_white'
    y_file = 'data/y_white'
    np.save(X_file, X)
    np.save(y_file, y)

Listing 4-9Exploring and saving white wine data

执行清单 4-9 的输出应该如下所示:

(4898, 11)
(4898,)

(0.24186185906056268, 'alcohol')
(0.1251626059551235, 'volatile acidity')
(0.11524332271725685, 'free sulfur dioxide')
(0.07170261049200727, 'pH')
(0.06940456299270928, 'total sulfur dioxide')
(0.06899334812486085, 'residual sugar')
(0.06259740092261244, 'chlorides')
(0.06227404207074219, 'sulphates')
(0.061557623671947746, 'density')
(0.060982526101159625, 'citric acid')
(0.060220097891017656, 'fixed acidity')

   alcohol  sulphates  volatile acidity  total sulfur dioxide  quality
0      8.8       0.45              0.27                 170.0      6.0
1      9.5       0.49              0.30                 132.0      6.0
2     10.1       0.44              0.28                  97.0      6.0
3      9.9       0.40              0.23                 186.0      6.0
4      9.9       0.40              0.23                 186.0      6.0

代码从导入必需的包开始。主程序块从 CSV 文件中加载白酒数据。接下来,通过从 Pandas 数据帧中剥离目标列质量来创建特征集 X,并且从质量列中创建目标 y。然后显示 x 和 y 形状。请注意,白葡萄酒数据集由 4898 个数据元素组成,而红葡萄酒数据集只有 1599 个。代码最后在 RandomForestRegressor 的帮助下显示特性的重要性,并将数据保存到 NumPy 文件中。

清单 4-10 中显示的最终代码示例使用各种回归算法对白酒数据进行了实验。

import numpy as np, pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression,\
     Ridge, Lasso, ElasticNet, SGDRegressor
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
import matplotlib.pyplot as plt, seaborn as sns

def get_scores(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

if __name__ == "__main__":
    br = '\n'
    d = dict()
    X = np.load('data/X_white.npy')
    y = np.load('data/y_white.npy')
    X_train, X_test, y_train, y_test =  train_test_split(
        X, y, test_size=0.2, random_state=0)
    print ('rmse (unscaled):')

    rfr = RandomForestRegressor(random_state=0, n_estimators=100)
    rfr.fit(X_train, y_train)
    rmse, rfr_name = get_scores(rfr, X_test, y_test)
    d['rfr'] = [rmse]
    print (rmse, '(' + rfr_name + ')')
    lr = LinearRegression().fit(X_train, y_train)
    rmse, lr_name = get_scores(lr, X_test, y_test)
    d['lr'] = [rmse]
    print (rmse, '(' + lr_name + ')')
    ridge = Ridge(random_state=0).fit(X_train, y_train)
    rmse, ridge_name = get_scores(ridge, X_test, y_test)
    d['ridge'] = [rmse]
    print (rmse, '(' + ridge_name + ')')
    lasso = Lasso(random_state=0).fit(X_train, y_train)
    rmse, lasso_name = get_scores(lasso, X_test, y_test)
    d['lasso'] = [rmse]
    print (rmse, '(' + lasso_name + ')')
    en = ElasticNet(random_state=0).fit(X_train, y_train)
    rmse, en_name = get_scores(en, X_test, y_test)
    d['en'] = [rmse]
    print (rmse, '(' + en_name + ')', br)
    scaler = StandardScaler()

    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    print ('rmse scaled:')
    sgd = SGDRegressor(max_iter=1000, tol=0.001, random_state=0)
    sgd.fit(X_train_std, y_train)
    rmse, sgd_name = get_scores(sgd, X_test_std, y_test)
    d['sgd'] = [rmse]
    print (rmse, '(' + sgd_name + ')', br)
    pipe = Pipeline([('poly', PolynomialFeatures(degree=2)),
                     ('linear', LinearRegression())])
    model = pipe.fit(X_train, y_train)
    rmse, pf_name = get_scores(model, X_test, y_test)
    d['poly'] = [rmse]
    print (PolynomialFeatures().__class__.__name__,'(rmse):')
    print (rmse, '(' + pf_name + ')')
    poly = PolynomialFeatures(degree=2)
    poly.fit(X_train, y_train)
    X_train_poly = poly.transform(X_train)
    lr = LinearRegression().fit(X_train_poly, y_train)
    X_test_poly = poly.transform(X_test)
    rmse, lr_name = get_scores(lr, X_test_poly, y_test)
    print (rmse, '(without Pipeline)')
    algo, rmse = [], []
    for key, value in d.items():
        algo.append(key)
        rmse.append(value[0])
    plt.figure('RMSE')
    sns.set(style="whitegrid")
    ax = sns.barplot(algo, rmse)
    plt.title('White Wine Algorithm Comparison')
    plt.xlabel('regressor')
    plt.ylabel('RMSE')
    plt.show()

Listing 4-10Exploring white wine data with regression algorithms

执行清单 4-10 的输出应该如下所示:

rmse (unscaled):
0.687111151629689 (RandomForestRegressor)
0.8123086554972433 (LinearRegression)
0.8141615403447382 (Ridge)
0.9255803421282806 (Lasso)
0.9242810596011943 (ElasticNet)

rmse scaled:
0.8092835779827245 (SGDRegressor)

PolynomialFeatures (rmse):
0.7767527802246017 (Pipeline)
0.7767527802246017 (without Pipeline)

列表 4-10 也显示图 4-2 。图 4-2 显示了本实验中使用的算法的 RMSE 分数。

img/481580_1_En_4_Fig2_HTML.jpg

图 4-2

白葡萄酒 RMSE 评分对比

代码从导入必需的包开始。函数 get_scores 返回 RMSE 和型号名称。主程序块首先创建一个字典来存储来自训练实验的最佳 RMSE 分数。它继续将白葡萄酒数据从 NumPy 文件加载到 X 和 y 中。然后,数据被分成训练测试子集。接下来,算法在缩放和不缩放的情况下训练数据,最佳分数保存在字典 d 中。

代码继续使用 LinearRegression、Ridge、Lasso、ElasticNet、SGDRegressor 和 RandomForestRegressor 训练未缩放的数据。在该数据集上性能最好的算法是 RandomForestRegressor,其 RMSE 约为 0.687。然后代码用 SGDRegressor 训练缩放后的数据。我已经确定(通过此处未显示的实验)山脊、套索和弹性网的 RMSE 不会随着缩放而改进,所以我没有包含代码。最后,用有和没有流水线的多项式特征来训练未缩放的数据。因为两个 RMSE 分数是相同的,所以使用哪种技术并不重要。

五、简单训练集的分类器调优

调优是在不过度拟合、欠拟合或产生高方差的情况下最大化算法性能的过程。过度拟合是指算法训练数据过于精确,以至于可能无法拟合新数据或可靠地预测未来结果。当模型对于它试图训练的数据来说过于复杂时,通常会发生过度拟合。过于复杂的模型可以很好地训练数据,但也会拟合不属于数据的噪声。因此,当用于训练新数据时,会引入噪声,导致不可预测的结果。

欠拟合是指算法无法充分捕捉数据的底层结构。拟合不足的模型表现不佳,因为它不够复杂,无法捕捉数据的含义。高方差是指算法在预测中引入过多误差,导致性能不佳。

高性能调整是通过从模型中选择最佳超参数来实现的。模型超参数是模型外部的配置,其值无法从数据中估计。大多数机器学习算法都有一组超参数。有些算法很少,有些算法很多。超参数越少的算法越容易调优,因为需要考虑的调整越少。

调整机器学习算法非常困难,因为它通常是一个非直观、耗时且系统的试错过程。由于必须在训练开始前手动设置超参数,因此难度增加。通过阅读学术文章、行业书籍、在线文章(例如 Scikit-Learn 文档)、观看 YouTube 视频、数据和数据集经验、勤奋、努力工作以及简单的实践,可以增强调优专业知识。

为我们的调整示例选择的机器学习算法不是巧合。我选择它们是基于许多小时的实验、阅读和洞察力。对于给定的数据集,表现最好的算法被包括在内,表现差的算法则不包括在内。

Scikit-Learn 提供了两种优化超参数调优的工具:GridSearchCV 和 RandomizedSearchCV。 GridSearchCV 对估计器(或机器学习算法)的指定参数值进行彻底搜索,并返回最佳性能的超参数组合。

因此,我们需要做的就是指定我们想要试验的超参数及其值的范围,GridSearchCV 使用交叉验证来执行超参数值的所有可能组合。因此,我们自然会限制超参数的选择及其取值范围。理论上,我们可以为模型的所有超参数指定一组参数值,但是这种搜索会消耗大量的计算机资源和时间。

RandomizedSearchCV 基于超参数的预定子集进行评估,从给定域中随机选择选定数量的超参数对,并仅测试那些被选择的超参数对。RandomizedSearchCV 的计算成本和耗时更少,因为它不会评估每个可能的超参数组合。这种方法极大地简化了分析,而没有明显牺牲优化。对于高维数据,RandomizedSearchCV 通常是一个很好的选择,因为它可以非常快速地返回一个好的超参数组合。

小费

在给定足够的计算资源的情况下,使用 GridSearchCV 进行调优适合于彻底搜索性能最佳的超参数。使用 RandomizedSearchCV 进行调优适用于良好的搜索,或者如果调优高维数据。

学习调整分类器可以通过使用各种数据集和分类器的例子来加速。但是,我也建议遵循结构化流程:

  1. 总是从使用基线算法的默认超参数开始。

  2. 尝试训练和测试规模。

  3. 处理高维数据时使用降维。

  4. 处理大型数据集时,随机抽取样本。

  5. 缩放数据(在适当的情况下)以潜在地提高性能。

  6. 使用 GridSearchCV 或 RandomizedSearchCV 进行调整。

  7. 一旦使用基线算法进行了调整,就可以使用复杂的算法进行实验。

一个基线算法具有很少的超参数,因此很容易调整。它还允许我们在预测建模问题上建立基线性能。使用基线提供了一个与更高级算法的比较点,您可以在稍后的调优过程中对这些算法进行评估。

小费

从基线算法(及其默认的超参数)开始调优,以建立基线性能。

一旦在样本数据上创建了优化的算法,如果有足够的计算机资源可用,就可以对整个数据集进行实验。虽然这样的实验可能会很昂贵,但至少我们有一个很好的模型可以使用。想象一下没有采样的试错实验的代价!

因为调优是一项复杂的工作,所以通过处理简单的数据集来学习是一个好主意。简单来说,我们指的是低维的小的数据集。首先,调整简单的数据允许在没有大量计算费用或时间的情况下进行实验。简单数据集上的试错调优实验消耗相对较少的计算机和人员时间。第二,简单的数据集易于处理和理解。第三,我们可以通过 GridSearchCV 或 RandomizedSearchCV 使用许多(如果不是全部)超参数。

调整数据集

我们专注于四个数据集:虹膜、数字、银行和葡萄酒。Iris 数据集由 150 个数据元素组成,代表三种 Iris。数字数据集由 1797 个数字图像组成。银行数据集由 41188 个代表客户订阅的数据元素组成。葡萄酒数据集由 178 个代表葡萄酒质量的数据元素组成。

调谐虹膜数据

清单 5-1 中所示的代码示例使用 KNeighborsClassifier、GridSearchCV 和 RandomizedSearchCV 来训练和调整 load_iris。因为我们在下面的代码片段中使用了人性化的软件包,并且它通常不是预装的,所以请按如下方式安装:

import numpy as np, humanfriendly as hf
import time
from sklearn.datasets import load_iris
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import RandomizedSearchCV
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.model_selection import cross_val_score

def get_scores(model, Xtrain, ytrain, Xtest, ytest):
    y_pred = model.predict(Xtrain)
    train = accuracy_score(ytrain, y_pred)
    y_pred = model.predict(Xtest)
    test = accuracy_score(ytest, y_pred)
    return train, test, model.__class__.__name__

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

if __name__ == "__main__":
    br = '\n'
    iris = load_iris()
    X = iris.data
    y = iris.target
    targets = iris.target_names
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
    knn = KNeighborsClassifier()
    print (knn, br)
    distances = [1, 2, 3, 4, 5]
    k_range = list(range(1, 31))
    leaf = [10]
    param_grid = dict(n_neighbors=k_range, p=distances, leaf_size=leaf)
    start = time.perf_counter()
    grid = GridSearchCV(knn, param_grid, cv=10, scoring="accuracy")
    grid.fit(X, y)
    see_time('GridSearchCV total tuning time:')
    bp = grid.best_params_
    print ()
    print ('best parameters:')
    print (bp, br)
    knn_best = KNeighborsClassifier(**bp).fit(X_train, y_train)
    train, test, name = get_scores(knn_best, X_train, y_train

, X_test, y_test)
    print (name, 'train/test scores (GridSearchCV):')
    print (train, test, br)
    scores = get_cross(knn, X, y)
    print ('cross-val scores:')
    print (scores, br)
    print ('avg cross-val score:', np.mean(scores), br)
    d = grid.cv_results_
    print ('mean grid score:', np.mean(d['mean_test_score']), br)
    vector = [[3, 5, 4, 2]]
    vectors = [[2, 5, 3, 5], [1, 4, 2, 1]]
    y_pred = knn_best.predict(vector)
    print (targets[y_pred])
    y_preds = knn_best.predict(vectors)
    print (targets[y_preds], br)
    start = time.perf_counter()
    rand = RandomizedSearchCV(knn, param_grid, cv=10, random_state=0, scoring="accuracy", n_iter=10)
    rand.fit(X, y)
    see_time('RandomizedSearchCV total tuning time:')
    bp = rand.best_params_
    print()
    print ('best parameters:')
    print (bp, br)
    knn_best = KNeighborsClassifier(**bp).fit(X_train, y_train)
    train, test, name = get_scores(knn_best, X_train, y_train, X_test, y_test)
    print (name, 'train/test scores (RandomizedSearchCV):')
    print (train, test)

Listing 5-1Tuning Iris data with KNeighborsClassifier

pip install humanfriendly

继续执行清单 5-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。

执行清单 5-1 的输出应该如下所示:

KNeighborsClassifier(algorithm='auto', leaf_size=30, metric="minkowski", metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights="uniform")

GridSearchCV total tuning time: 7 seconds and 388.94 milliseconds

best parameters:
{'leaf_size': 10, 'n_neighbors': 6, 'p': 3}

KNeighborsClassifier train/test scores (GridSearchCV):
0.9732142857142857 0.9736842105263158

cross-val scores:
[1\.         0.93333333 1\.         1\.         0.86666667 0.93333333
 0.93333333 1\.         1\.         1\.        ]

avg cross-val score: 0.9666666666666668

mean grid score: 0.9673333333333333

['versicolor']
['versicolor' 'setosa']

RandomizedSearchCV total tuning time: 473.88 milliseconds

best parameters:
{'p': 3, 'n_neighbors': 13, 'leaf_size': 10}

KNeighborsClassifier train/test scores (RandomizedSearchCV):
0.9642857142857143 0.9736842105263158

代码从导入 GridSearchCV、RandomizedSearchCV、cross_val_score 和其他必需的包开始。函数 get_scores 返回训练测试精度和算法名称。函数 get_cross 返回交叉验证分数。函数 see_time 返回经过的时间。主模块加载数据,并将其分成训练测试子集。然后显示 KNeighborsClassifier 的超参数。

小费

在调整之前显示算法的超参数总是一个好主意。

我们通过调整 pleaf_sizen_neighbors 来调优 KNeighborsClassifier。

小费

Scikit-Learn 和其他在线文档是了解算法的超参数的好地方。

p 是用于调整距离的闵可夫斯基度量的功率参数。调整叶子 _ 大小以减少邻居的候选数量。调整 n_neighbors 控制邻居数量。

代码继续为 GridSearchCV 创建参数网格。我们想测试距离(或 p)从 1 到 5,n_neighbors 从 1 到 31,leaf_size 为 10。通过将列表[10]分配给 leaf_size,我们覆盖了它的默认值。

小费

如果搜索中不包括超参数,则使用其默认值。如果搜索中包含单个值(作为列表),则其值会覆盖默认值,但不会增加搜索的计算开销。

接下来,我们使用 GridSearchCV 进行调优。注意,我们使用 X 和 y,因为 GridSearchCV 自己对数据集进行交叉验证。我们继续显示最佳参数。然后,我们使用 KNeighborsClassifier 的最佳参数。

结果非常好,因为分数超过 97%,几乎完全符合。我们对训练数据进行交叉验证,以了解一个算法应该执行得有多好。交叉验证分数略低于 97%,这意味着我们的结果是可靠的。调整实验的准确度应该接近或超过交叉验证分数。

小费

交叉验证分数接近我们可以从算法中获得的最佳性能。所以,我们的表现应该接近或者有望更好。

然后我们计算并显示 GridSearchCV 的平均分数。用我们调整过的模型,我们做一些预测。代码以使用具有相同参数网格的 RandomizedSearchCV 进行调优结束。分数几乎相同,但是使用 RandomizedSearchCV 所用的时间要好得多!

调谐数字数据

清单 5-2 中所示的代码示例使用 KNeighborsClassifier、LogisticRegression 和 GridSearchCV 来训练和调整 load_digits。

import numpy as np, humanfriendly as hf
import time
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split,\
     cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import GridSearchCV

def get_scores(model, Xtrain, ytrain, Xtest, ytest):
    y_pred = model.predict(Xtrain)
    train = accuracy_score(ytrain, y_pred)
    y_pred = model.predict(Xtest)
    test = accuracy_score(ytest, y_pred)
    return train, test, model.__class__.__name__

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

if __name__ == "__main__":
    br = '\n'
    digits = load_digits()
    X = digits.data
    y = digits.target
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
    knn = KNeighborsClassifier().fit(X_train, y_train)
    print (knn, br)
    train, test, name = get_scores(knn, X_train, y_train, X_test, y_test)
    knn_name, acc1, acc2 = name, train, test
    print (str(knn_name) + ':')
    print ('train:', np.round(acc1, 2),
           'test:', np.round(acc2, 2), br)
    param_grid = {'n_neighbors': np.arange(1, 31, 2),
                  'metric': ['euclidean', 'cityblock']}
    start = time.perf_counter()
    grid = GridSearchCV(knn, param_grid, cv=5, n_jobs=-1)
    grid.fit(X, y)
    see_time('GridSearchCV total tuning time:')
    best_params = grid.best_params_
    print (best_params, br)
    knn_tuned = KNeighborsClassifier(**best_params)
    knn_tuned.fit(X_train, y_train)
    train, test, name = get_scores(knn_tuned, X_train, y_train, X_test, y_test)
    knn_name, acc1, acc2 = name, train, test
    print (knn_name + ' (tuned):')
    print ('train:', np.round(acc1, 2),
           'test:', np.round(acc2, 2), br)
    lr = LogisticRegression(random_state=0, max_iter=4000,
                            multi_class='auto'

, solver="lbfgs")
    print (lr, br)
    lr.fit(X_train, y_train)
    train, test, name = get_scores(lr, X_train, y_train, X_test, y_test)
    lr_name, acc1, acc2 = name, train, test
    print (lr_name + ':')
    print ('train:', np.round(acc1, 2),
           'test:', np.round(acc2, 2), br)
    param_grid = {'penalty': ['l2'],
                  'solver': ['newton-cg', 'lbfgs', 'sag'],
                  'max_iter': [4000], 'multi_class': ['auto'],
                  'C': [0.001, 0.01, 0.1]}
    start = time.perf_counter()
    grid = GridSearchCV(lr, param_grid, cv=5, n_jobs=-1)
    grid.fit(X, y)
    see_time('GridSearchCV total tuning time:')
    bp = grid.best_params_
    print (bp)
    lr_tuned = LogisticRegression(**bp, random_state=0)
    lr_tuned.fit(X_train, y_train)
    train, test, name = get_scores(lr_tuned, X_train, y_train, X_test, y_test)
    lr_name, acc1, acc2 = name, train, test

    print (lr_name + ' (tuned):')
    print ('train:', np.round(acc1, 2),
           'test:', np.round(acc2, 2), br)
    print ('cross-validation score knn:')
    knn = KNeighborsClassifier()
    scores = get_cross(knn, X, y)
    print (np.mean(scores))

Listing 5-2Tuning digits data with two algorithms

执行清单 5-2 的输出应该如下所示:

KNeighborsClassifier(algorithm='auto', leaf_size=30,
           metric='minkowski', metric_params=None, n_jobs=None,
           n_neighbors=5, p=2, weights="uniform")

KNeighborsClassifier:
train: 0.99 test: 0.98

GridSearchCV total tuning time: 9 seconds and 609.98 milliseconds
{'metric': 'euclidean', 'n_neighbors': 3}

KNeighborsClassifier (tuned):
train: 0.99 test: 0.99

LogisticRegression(C=1.0, class_weight=None, dual=False,
          fit_intercept=True, intercept_scaling=1, max_iter=4000,
          multi_class='auto', n_jobs=None, penalty="l2",
          random_state=0, solver="lbfgs", tol=0.0001, verbose=0,
          warm_start=False)

LogisticRegression:
train: 1.0 test: 0.95

GridSearchCV total tuning time: 12 seconds and 708.79 milliseconds
{'C': 0.01, 'max_iter': 4000, 'multi_class': 'auto', 'penalty': 'l2', 'solver': 'lbfgs'}
LogisticRegression (tuned):
train: 0.99 test: 0.96

cross-validation score knn:
0.9739482872546906

代码从导入必需的包开始。函数 get_scores 返回精确度分数和模型名称。函数 see_time 返回经过的时间。主模块将数字数据加载到 X 和 y 中,并将其分成训练测试子集。接下来,KNeighborsClassifier(使用默认超参数)对数据进行训练,并显示结果。

代码继续使用 GridSearchCV 进行调优。对于调优实验,KNeighborsClassifier 是模型,我们调整 n_neighbors度量超参数。根据我的经验和研究,邻居的数量是 KNeighborsClassifier 需要调整的最重要的超参数。超参数度量是用于树的距离。

调整提高了性能,因为我们有一个理想的拟合。当然,load_digits 经过了大量的预处理,这使得它很容易调优。然而,调优是如此复杂,以至于从简单的数据集学习基础知识是一个好主意。

代码继续使用 LogisticRegression 进行调优。算法(及其默认参数)太复杂,因为结果显示过度拟合。也就是说,该算法完美地训练了数据,但是测试集精度相当低。

小费

当测试精度比训练精度低很多时,训练算法对于数据集来说太复杂,因此会发生过拟合。

用 GridSearchCV 调优 LogisticRegression 减少了过度拟合,但在数据上的表现不如 KNeighborsClassifier。本次调优实验调整的超参数包括罚值解算器max_iterC罚值涉及正则化的类型。解算器指定优化过程中使用的算法。 max_iter 超参数表示求解器收敛所需的最大迭代次数。最后, C 表示正则化强度的倒数。C 值越小,正则化越强。

代码以使用 KNeighborsClassifier 进行交叉验证结束。我们用这个算法进行了交叉验证,因为它在数据上表现最好。交叉验证是用于评估机器学习模型性能的重采样程序。在这种情况下,我们的性能接近 99%,比交叉验证分数要好。因此,我们确信我们的最佳模型(KNeighborsClassifier)的性能是最佳的。如果交叉验证与我们最好的建模实验非常不同,我们可能做错了什么,或者需要继续调整。

小费

如果有足够的计算资源,交叉验证是测试算法准确性的一种很好的技术。

虽然 LogisticRegression 的性能不如 KNeighborsClassifier,但调优确实提高了性能。也就是说,测试性能向上调整,而训练性能向下调整。当调整将训练和测试性能相互调整时,我们就取得了进展。但是,如果我们仍然对性能不满意,我们应该继续试验。但是,至少我们正朝着积极的方向前进。

小费

随着调优实验将训练和测试分数相互调整,我们知道我们的调优实验正在取得进展。

调谐库数据

清单 5-3 中显示的第一个代码示例使用 svm.SVC 调优从银行数据集中抽取的随机样本。

import numpy as np, humanfriendly as hf, random
import time
from sklearn.model_selection import train_test_split,\
     RandomizedSearchCV, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

def get_scores(model, xtrain, ytrain, xtest, ytest):
    ypred = model.predict(xtest)
    train = model.score(xtrain, ytrain)
    test = model.score(xtest, y_test)
    name = model.__class__.__name__
    return (name, train, test)

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups)

def prep_data(data, target):
    d = [data[i] for i, _ in enumerate(data)]
    t = [target[i] for i, _ in enumerate(target)]
    return list(zip(d, t))

def create_sample(d, n, replace="yes"):
    if replace == 'yes': s = random.sample(d, n)
    else: s = [random.choice(d)
               for i, _ in enumerate(d) if i < n]
    Xs = [row[0] for i, row in enumerate(s)]
    ys = [row[1] for i, row in enumerate(s)]
    return np.array(Xs), np.array(ys)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_bank.npy')
    y = np.load('data/y_bank.npy')
    sample_size = 4000
    data = prep_data(X, y)
    Xs, ys = create_sample(data, sample_size, replace="no")
    Xs = StandardScaler().fit_transform(Xs)
    X_train, X_test, y_train, y_test = train_test_split\

                                       (Xs, ys, random_state=0)
    svm = SVC(gamma='scale', random_state=0)
    print (svm, br)
    svm.fit(X_train, y_train)
    svm_scores = get_scores(svm, X_train, y_train, X_test, y_test)
    print (svm_scores[0] + ' (train, test):')
    print (svm_scores[1], svm_scores[2], br)
    Cs = [0.0001, 0.001]
    param_grid = {'C': Cs}
    start = time.perf_counter()
    rand = RandomizedSearchCV(svm, param_grid, cv=3, n_jobs = -1, random_state=0, verbose=2, n_iter=2)
    rand.fit(X, y)
    see_time('RandomizedSearchCV total tuning time:')
    bp = rand.best_params_
    print (bp, br)
    svm_tuned = SVC(**bp, gamma="scale", random_state=0)
    svm_tuned.fit(X_train, y_train)
    svm_scores = get_scores(svm_tuned, X_train, y_train, X_test, y_test)
    print (svm_scores[0] + ' (train, test):')
    print (svm_scores[1], svm_scores[2], br)
    print ('cross-validation score:')
    svm = SVC(gamma='scale')
    scores = get_cross(svm, Xs, ys)
    print (np.mean(scores))

Listing 5-3Tuning a bank data random sample with svm.SVC

执行清单 5-3 的输出应该如下所示:

SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma="scale",
  kernel='rbf', max_iter=-1, probability=False, random_state=0,
  shrinking=True, tol=0.001, verbose=False)

SVC (train, test):
0.949 0.893

Fitting 3 folds for each of 2 candidates, totalling 6 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done   3 out of   6 | elapsed:   55.4s remaining:   55.4s
[Parallel(n_jobs=-1)]: Done   6 out of   6 | elapsed:   57.0s finished
RandomizedSearchCV total tuning time: 1 minute, 31 seconds and 171.06 milliseconds
{'C': 0.0001}

SVC (train, test):
0.891 0.875

cross-validation score:
0.9102441546509665

代码从导入 RandomizedSearchCV,svm 开始。SVC 和其他必备的包。函数 get_scores 返回精确度分数和模型名称。函数 prep_data 为函数 create_sample 中的样本处理准备数据。函数 create_sample 创建一个随机样本。函数 see_time 返回经过的时间。

主程序块加载数据,创建 4000 个无替换的样本,并将数据分成训练测试子集。接下来,我们缩放数据,用 svm 进行训练。SVC(使用默认超参数),并显示结果。代码通过调整 svm 继续。随机搜索。

我们只调整 C 超参数,它是一个正则化参数,控制在训练数据上实现低误差和最小化权重范数之间的权衡。当我们增加 C 时,模型的复杂性增加,这增加了过度拟合的机会。另请注意,verbose 设置为 2(verbose = 2)。

详细参数(而非超参数)控制详细程度。我们设置的数字越高,收到的消息就越多。因此,在执行时,我们会注意到关于调优过程中发生了什么的消息。GridSearchCV 还有一个 verbosity 选项。

仅仅通过调整 C 的两个值,运行时间已经超过一分钟了!然而,我们似乎达到了很好的契合。交叉验证分数证实了我们在 svm 方面做得很好。SVC,但是通过更多的调优实验可以做得更好。也就是说,我们也许能够从 svm 中挤出更多的性能。SVC 与更多的实验。

小费

如果我们最好的模型测试分数接近交叉验证分数,我们就不需要继续调优了。

通过大量的调优实验,我能够大幅降低调优的复杂性。我不仅仅从两个 C 值开始,也不仅仅用 C 进行调优。此外,我最初尝试用整个数据集进行调优,但发现计算开销太大,所以我用一个样本进行了训练。

清单 5-4 中显示的下一个代码示例使用 KNeighborsClassifier 调优从银行数据集中抽取的随机样本。

import numpy as np, humanfriendly as hf, random
import time
from sklearn.model_selection import train_test_split,\
     RandomizedSearchCV, cross_val_score
from sklearn.neighbors import KNeighborsClassifier

def get_scores(model, xtrain, ytrain, xtest, ytest):
    ypred = model.predict(xtest)
    train = model.score(xtrain, ytrain)
    test = model.score(xtest, y_test)
    name = model.__class__.__name__
    return (name, train, test)

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups)

def prep_data(data, target):
    d = [data[i] for i, _ in enumerate(data)]
    t = [target[i] for i, _ in enumerate(target)]
    return list(zip(d, t))

def create_sample(d, n, replace="yes"):
    if replace == 'yes': s = random.sample(d, n)
    else: s = [random.choice(d)
               for i, _ in enumerate(d) if i < n]
    Xs = [row[0] for i, row in enumerate(s)]
    ys = [row[1] for i, row in enumerate(s)]
    return np.array(Xs), np.array(ys)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_bank.npy')
    y = np.load('data/y_bank.npy')
    sample_size = 4000
    data = prep_data(X, y)
    Xs, ys = create_sample(data, sample_size, replace="no")
    X_train, X_test, y_train, y_test = train_test_split\
                                       (Xs, ys, random_state=0)
    knn = KNeighborsClassifier()
    print (knn, br)
    knn.fit(X_train, y_train)
    knn_scores = get_scores(knn, X_train, y_train, X_test, y_test)
    print (knn_scores[0] + ' (train, test):')
    print (knn_scores[1], knn_scores[2], br)
    param_grid = {'n_neighbors': np.arange(1, 31, 2),
                  'metric': ['euclidean']}
    start = time.perf_counter()
    rand = RandomizedSearchCV(knn, param_grid, cv=3, n_jobs = -1,
                              random_state=0, verbose=2)
    rand.fit(X, y)
    see_time('RandomizedSearchCV total tuning time:')
    bp = rand.best_params_
    print (bp, br)
    file = 'data/bp_bank'
    np.save(file, bp)
    knn_tuned = KNeighborsClassifier(**bp).fit(X_train

, y_train)
    knn_scores = get_scores(knn_tuned, X_train, y_train, X_test, y_test)
    print (knn_scores[0] + ' (train, test):')
    print (knn_scores[1], knn_scores[2], br)
    print ('cross-validation score:')
    knn = KNeighborsClassifier()
    scores = get_cross(knn, Xs, ys)
    print (np.mean(scores))

Listing 5-4Tuning a bank data random sample with KNeighborsClassifier

执行清单 5-4 的输出应该如下所示:

KNeighborsClassifier(algorithm='auto', leaf_size=30,
           metric='minkowski', metric_params=None, n_jobs=None,
           n_neighbors=5, p=2, weights="uniform")

KNeighborsClassifier (train, test):
0.927 0.906

Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  30 out of  30 | elapsed:   59.6s finished
RandomizedSearchCV total tuning time: 1 minute and 654.85 milliseconds
{'n_neighbors': 29, 'metric': 'euclidean'}

KNeighborsClassifier (train, test):
0.913 0.91

cross-validation score:
0.9032489046806542

代码从导入必需的包开始。函数 get_scores 返回精确度分数和模型名称。函数 prep_data 为函数 create_sample 中的样本处理准备数据。函数 create_sample 创建一个随机样本。函数 see_time 返回经过的时间。

主程序块加载数据,创建 4000 个无替换的样本,并将数据分成训练测试子集。接下来,我们使用 KNeighborsClassifier(使用默认的超参数)进行训练,并显示结果。代码通过用 RandomizedSearchCV 调谐来继续。我们调整 n_neighbors 并强制度量欧几里得。我们还保存了最佳参数,供下一个代码示例使用。

通过实验,我发现欧几里德效果最好。调整 KNeighborsClassifier 提供了比使用默认参数的模型更好的拟合。交叉验证分数证实了我们做得很好,因为它非常接近我们的测试分数。请注意,我们确实使用了 10 个折叠进行交叉验证。我的经验表明,5 或 10 的交叉验证似乎效果很好。但是,要小心,因为更多的交叉验证会增加处理时间。

清单 5-5 中显示的本节中的最终代码示例使用 KNeighborsClassifier 和从之前的调优练习中获得的最佳参数对整个银行数据集进行建模。

import numpy as np, humanfriendly as hf, random
import time
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier

def get_scores(model, xtrain, ytrain, xtest, ytest):
    ypred = model.predict(xtest)
    train = model.score(xtrain, ytrain)
    test = model.score(xtest, y_test)
    name = model.__class__.__name__
    return (name, train, test)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_bank.npy')
    y = np.load('data/y_bank.npy')
    bp = np.load('data/bp_bank.npy')
    bp = bp.tolist()
    print ('best parameters:')
    print (bp, br)
    X_train, X_test, y_train, y_test = train_test_split\
                                       (X, y, random_state=0)
    start = time.perf_counter()
    knn = KNeighborsClassifier(**bp)
    knn.fit(X_train, y_train)
    see_time('training time:')
    start = time.perf_counter()
    knn_scores = get_scores(knn, X_train, y_train, X_test, y_test)
    see_time('scoring time:')
    print ()
    print (knn_scores[0] + ' (train, test):')
    print (knn_scores[1], knn_scores[2])

Listing 5-5Tuning bank data with KNeighborsClassifier

执行清单 5-5 的输出应该如下所示:

best parameters:
{'n_neighbors': 29, 'metric': 'euclidean'}

training time: 461.58 milliseconds
scoring time: 10 seconds and 62.98 milliseconds

KNeighborsClassifier (train, test):
0.9154769997733968 0.9138584053607847

该代码示例导入必需的包。函数 get_scores 返回精确度分数和模型名称。函数 see_time 返回经过的时间。主程序块为 KNeighborsClassifier 加载银行数据和最佳参数。接下来,数据被分成训练测试子集。代码以使用最佳参数训练模型并显示结果结束。

结果表明,我们实现了非常好的拟合,整个处理时间不到 11 秒!因此,用随机样本进行调优是减少计算开销的一个好方法。

小费

随机采样是一种计算成本低廉的调整方式。

调整葡萄酒数据

清单 5-6 中显示的第一个代码示例利用了 load_wine 上的 SGDClassifier 和 LinearDiscriminantAnalysis。

import numpy as np, random
from sklearn.datasets import load_wine
from sklearn.preprocessing import StandardScaler
from sklearn.discriminant_analysis\
     import LinearDiscriminantAnalysis as LDA
from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import train_test_split,\
     cross_val_score
from sklearn import metrics

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups)

if __name__ == "__main__":
    br = '\n'
    wine = load_wine()
    X = wine.data
    y = wine.target
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
    lda = LDA().fit(X_train, y_train)
    print (lda, br)
    lda_name = lda.__class__.__name__
    y_pred = lda.predict(X_train)
    accuracy = metrics.accuracy_score(y_train, y_pred)
    accuracy = str(accuracy * 100) + '%'
    print (lda_name + ':')
    print ('train:', accuracy)
    y_pred_test = lda.predict(X_test)
    accuracy = metrics.accuracy_score(y_test, y_pred_test)
    accuracy = str(round(accuracy * 100, 2)) + '%'
    print ('test: ', accuracy, br)
    print ('cross-validation:')
    scores = get_cross(lda, X, y)
    print (np.mean(scores), br)
    n, ls = 100, []
    for i, row in enumerate(range(n)):
        rs = random.randint(1, 100)
        sgd = LDA().fit(X_train, y_train)
        y_pred = lda.predict(X_test)
        accuracy = metrics.accuracy_score(y_test, y_pred)
        ls.append(accuracy)
    avg = sum(ls) / len(ls)
    print ('MCS')
    print (avg, br)
    X = StandardScaler().fit_transform(X)
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
    sgd = SGDClassifier(max_iter=100, random_state=1)
    print (sgd, br)
    sgd.fit(X_train, y_train)
    sgd_name = sgd.__class__.__name__
    y_pred = sgd.predict(X_train)
    y_pred_test = sgd.predict(X_test)
    print (sgd_name + ':')
    print('train: {:.2%}'.format(metrics.accuracy_score\
                                 (y_train, y_pred)))
    print('test:  {:.2%}\n'.format(metrics.accuracy_score\
                                   (y_test, y_pred_test)))
    print ('cross-validation:')
    scores = get_cross(sgd, X, y)
    print (np.mean(scores), br)
    n, ls = 100, []

    for i, row in enumerate(range(n)):
        rs = random.randint(1, 100)
        sgd = SGDClassifier(max_iter=100).fit(X_train, y_train)
        y_pred = sgd.predict(X_test)
        accuracy = metrics.accuracy_score(y_test, y_pred)
        ls.append(accuracy)
    avg = sum(ls) / len(ls)
    print ('MCS:')
    print (avg)

Listing 5-6Exploring wine data with two classifiers

执行清单 5-6 的输出应该如下所示:

LinearDiscriminantAnalysis(n_components=None, priors=None,
              shrinkage=None, solver="svd",
              store_covariance=False, tol=0.0001)

LinearDiscriminantAnalysis:
train: 100.0%
test:  97.78%

cross-validation:
0.9832989336085312

MCS
0.9777777777777754

SGDClassifier(alpha=0.0001, average=False, class_weight=None,
       early_stopping=False, epsilon=0.1, eta0=0.0,
       fit_intercept=True, l1_ratio=0.15,
       learning_rate='optimal', loss="hinge", max_iter=100,
       n_iter=None, n_iter_no_change=5, n_jobs=None,
       penalty='l2', power_t=0.5, random_state=1, shuffle=True,
       tol=None, validation_fraction=0.1, verbose=0,
       warm_start=False)

SGDClassifier:
train: 100.00%
test:  97.78%

cross-validation:
0.9616959064327485

MCS:
0.9966666666666663

代码从导入 LinearDiscriminantAnalysis、SGDClassifier 和其他必需的包开始。主模块加载 wine 数据,将其分成训练测试集,并使用线性判别分析进行训练。代码继续显示准确性分数、交叉验证和 MCS 分数。

交叉验证和 MCS 分数表明,调整很可能不会增加线性判别分析的测试准确性。所以,我们不会开始调谐实验。然而,这个例子确实证明了在不进行调整实验的情况下获得高精度分数是可能的。但是,请记住,load_wine 数据集是经过大量处理的。在工业中,数据很少如此干净或处理得如此漂亮。

代码的下一部分用 SGDClassifier 对数据进行定型。请注意,我们在将数据分成训练测试子集之前对其进行了缩放。我在没有缩放的情况下运行了一个实验,获得了非常差的结果。因此,SGDClassifier 往往会从数据缩放中受益匪浅。

线性判别分析的缩放数据不会改变结果,因此我们在该实验中没有使用缩放数据。同样,SGDClassifier 在没有调优的情况下,精度得分非常高。然而,在取得满分时要谨慎。我做了几个实验,调整了随机状态参数,结果分数变了。当然,变化不是很大,但是训练测试分数并不完美。

尽管计算成本很高,但 MCS 是一个很好的指标,可以反映算法在数据集上的表现。对于 load_wine 数据,MCS 不是问题,因为数据集很小,并且不是由高维数据组成的。交叉验证也是算法性能的一个优秀指标,但它往往比 MCS 更保守。它在计算上也比 MCS 便宜得多。

我们可以调整随机状态参数来修改结果。通过将 SGD 分类器上的随机状态从 0 改为 1,测试准确度下降到 97.78%。这是运行交叉验证和 MCS(给定足够的计算资源)的另一个原因,以了解数据集上给定算法的基线准确性。

小费

调整随机状态参数会改变评分结果,因此运行交叉验证以建立稳定的基线准确度分数始终是一个好主意。

清单 5-7 中显示的最后一个代码示例用各种分类器对 load_wine 进行了一个实验。我用这个例子来演示如何探索给定数据集的算法的可行性。当然,我们必须考虑计算开销。但是,考虑到进行这种实验的资源,从长远来看,我们可能会节省时间和金钱。

from sklearn.datasets import load_wine
from sklearn.neighbors import KNeighborsClassifier as knn
from sklearn.svm import SVC
from sklearn.gaussian_process import\
     GaussianProcessClassifier as gpc
from sklearn.gaussian_process.kernels import RBF as rbf
from sklearn.tree import DecisionTreeClassifier as dt
from sklearn.ensemble import RandomForestClassifier as rf,\
     AdaBoostClassifier as ada
from sklearn.naive_bayes import GaussianNB as gnb
from sklearn.discriminant_analysis import\
     QuadraticDiscriminantAnalysis as qda,\
     LinearDiscriminantAnalysis as lda
from sklearn.linear_model import SGDClassifier as sgd
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn import metrics

if __name__ == "__main__":
    br = '\n'
    wine = load_wine()
    X = wine.data
    y = wine.target
    X = StandardScaler().fit_transform(X)
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=.4, random_state=0)
    classifiers = [knn(3), qda(), lda(), gnb(),
                   SVC(kernel='linear', gamma="scale",
                       random_state=0),
                   ada(random_state=0), dt(random_state=0),
                   sgd(max_iter=100, random_state=0),
                   gpc(1.0 * rbf(1.0), random_state=0),
                   rf(random_state=0, n_estimators=100)]
    for clf in classifiers:
        clf.fit(X_train, y_train)
        train_score = clf.score(X_train, y_train)
        test_score = clf.score(X_test, y_test)
        name = clf.__class__.__name__
        print (name + '(train/test scores):')
        print (train_score, test_score)

Listing 5-7Exploring wine data with a variety of classifiers

执行清单 5-7 的输出应该如下所示:

KNeighborsClassifier(train/test scores):
0.9905660377358491 0.9027777777777778
QuadraticDiscriminantAnalysis(train/test scores):
0.9905660377358491 1.0
LinearDiscriminantAnalysis(train/test scores):
1.0 0.9722222222222222
GaussianNB(train/test scores):
0.9905660377358491 0.9444444444444444
SVC(train/test scores):
1.0 0.9722222222222222
AdaBoostClassifier(train/test scores):
1.0 0.9027777777777778
DecisionTreeClassifier(train/test scores):
1.0 0.9166666666666666
SGDClassifier(train/test scores):
1.0 0.9861111111111112
GaussianProcessClassifier(train/test scores):
1.0 0.9722222222222222
RandomForestClassifier(train/test scores):
1.0 0.9583333333333334

代码首先加载必要的包和各种分类器,包括 SGDClassifier 和 LinearDiscriminantAnalysis(在前面的示例中演示过)。请记住,这个例子只是一个给定适当计算资源的有趣实验。

主模块加载 wine 数据,对其进行缩放,并将其分成训练测试子集。代码继续创建分类器列表。该代码通过遍历分类器列表、用每个分类器训练数据并显示准确度得分来结束。

从结果来看,对于 load_wine 最可行的分类器是 SGD 分类器、线性判别分析、二次判别分析、svm。SVC 和 GaussianProcessClassifier。RandomForestClassifier 和 GaussianNB 也有潜力,但不如首先列出的那些。QuadraticDiscriminantAnalysis 产生了一个几乎完美的分数令人难以置信的拟合!

我们可以调整其他算法来提高它们的性能,但是我的经验和这个实验告诉我,我们应该使用最有前途的算法来节省时间和金钱。这个实验也是接触各种分类算法的好方法。

六、复杂训练集的分类器调优

既然我们已经练习了对低维(或简单)数据的调优,我们就可以开始实验对高维(或复杂)数据集的调优了。低维数据由有限数量的特征组成,而高维数据由非常多的特征组成。

在机器学习文献中,最常用来描述数据集维度的术语是特征空间。特征空间指用于表征数据集的特征集合。也就是说,特征空间指的是变量所在的 n 维空间(不包括目标变量,如果它存在的话)。

与优化低维数据一致,我们在优化高维数据时遵循一个结构化的过程:

  1. 总是从使用基线算法的默认超参数开始。

  2. 尝试训练和测试规模。

  3. 处理高维数据时使用降维。

  4. 处理大型数据集时,随机抽取样本。

  5. 缩放数据(在适当的情况下)以潜在地提高性能。

  6. 使用 GridSearchCV 或 RandomizedSearchCV 进行调整。

  7. 一旦使用基线算法进行了调整,就可以使用复杂的算法进行实验。

调整数据集

我们专注于三个数据集:fetch_1fw_people、MNIST 和 fetch_20newsgroups。fetch_1fw_people 数据集包含 1288 张人脸图像和 7 个目标。每个人脸图像由一个 50 × 37 的像素矩阵表示。MNIST 数据集包含 70000 个从 0 到 9 的手写数字图像。每个数字由一个 28 × 28 的矩阵表示。fetch_20newsgroups 数据集包含大约 18000 篇关于 20 个主题的帖子。数据被分成训练集和测试集。这种拆分基于特定日期前后发布的消息。

调整 fetch_1fw_people

人脸识别是机器学习中一个非常复杂的话题。但是,Scikit-Learn 提供了 fetch_1fw_people,这是一个很好的数据集,可以在其上进行实验和学习。通过经验和实验,我确定了两种 Scikit-Learn 算法——SGD classifier 和 svm。SVC——它对数据集的处理相对较好。

清单 6-1 中显示的第一个代码示例使用 SGDClassifier 调优数据。

import numpy as np, humanfriendly as hf, warnings
import time
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split,\
     GridSearchCV, cross_val_score
from sklearn.linear_model import SGDClassifier
from sklearn.metrics import classification_report

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups)

if __name__ == "__main__":
    br = '\n'
    warnings.filterwarnings("ignore", category=DeprecationWarning)
    X = np.load('data/X_faces.npy')
    y = np.load('data/y_faces.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    pca = PCA(n_components=0.95, whiten=True, random_state=1)
    pca.fit(X_train)
    X_train_pca = pca.transform(X_train)
    X_test_pca = pca.transform(X_test)
    pca_name = pca.__class__.__name__
    print ('<<' + pca_name + '>>')
    print ('features (before PCA):', X.shape[1])
    print ('features (after PCA):', pca.n_components_, br)
    sgd = SGDClassifier(max_iter=1000, tol=.001, random_state=0)
    sgd.fit(X_train_pca, y_train)
    y_pred = sgd.predict(X_test_pca)
    cr = classification_report(y_test, y_pred)
    print (cr)
    sgd_name = sgd.__class__.__name__

    param_grid = {'alpha': [1e-3, 1e-2, 1e-1, 1e0], 'max_iter': [1000], 'loss': ['log', 'perceptron'], 'penalty': ['l1'], 'tol': [.001]}
    grid = GridSearchCV(sgd, param_grid, cv=5)
    start = time.perf_counter()
    grid.fit(X_train_pca, y_train)
    see_time('training time:')
    print ()
    bp = grid.best_params_
    print ('best parameters:')
    print (bp, br)
    sgd = SGDClassifier(**bp, random_state=1)
    sgd.fit(X_train_pca, y_train)
    y_pred = sgd.predict(X_test_pca)
    cr = classification_report(y_test, y_pred)
    print (cr)
    print ('cross-validation:')
    scores = get_cross(sgd, X_train_pca, y_train)
    print (np.mean(scores))

Listing 6-1Tuning fetch_1fw_people with SGDClassifier

继续执行清单 6-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。

执行清单 6-1 的输出应该如下所示:

<<PCA>>
features (before PCA): 1850
features (after PCA): 135

              precision    recall  f1-score   support

           0       0.89      0.57      0.70        28
           1       0.80      0.78      0.79        63
           2       0.83      0.62      0.71        24
           3       0.73      0.89      0.80       132
           4       0.55      0.55      0.55        20
           5       0.88      0.32      0.47        22
           6       0.67      0.73      0.70        33

   micro avg       0.74      0.74      0.74       322
   macro avg       0.76      0.64      0.67       322
weighted avg       0.76      0.74      0.73       322

training time: 7 seconds and 745.7 milliseconds

best parameters:
{'alpha': 0.001, 'loss': 'log', 'max_iter': 1000, 'penalty': 'l1', 'tol': 0.001}

              precision    recall  f1-score   support

           0       0.91      0.71      0.80        28
           1       0.79      0.79      0.79        63
           2       0.71      0.71      0.71        24
           3       0.84      0.86      0.85       132
           4       0.48      0.75      0.59        20
           5       0.83      0.45      0.59        22
           6       0.72      0.79      0.75        33

   micro avg       0.78      0.78      0.78       322
   macro avg       0.76      0.72      0.73       322
weighted avg       0.79      0.78      0.78       322

cross-validation:
0.7808966616425951

第一个代码示例从导入必备包开始。函数 see_time 返回经过的时间。主块将数据加载到 X 和 y 中,将其分成训练测试子集,并进行 PCA 以降低特征空间维度。

在调整高维数据时,PCA 至关重要,因为它极大地减少了计算开销,同时信息损失最小。然后,代码用 SGDClassifier 训练数据(以获得基线性能度量)并显示结果。接下来,从 GridSearchCV 开始调优。

小费

PCA 是一个重要的调优工具,因为它以最小的信息损失降低了高维数据集的维数,从而大大减少了调优时间(或计算开销)。

我们调整 alphamax_iter损耗惩罚tol 超参数。超参数 alpha 是乘以正则项的常数。超参数 max_iter 设置训练数据的最大通过次数(或时期)。一个时期是机器要学习的数据集的一个完整呈现。

超参数损失是指用于实验的损失函数。机器通过损失函数进行学习,这是一种评估算法对给定数据集建模程度的方法。超参数罚值指的是模型使用的正则化项。超参数 tol 是停止标准。

两个最重要的超参数是 alpha 和 penalty,因为它们与模型采用的正则化类型和数量直接相关。

接下来构建参数网格。请注意,α是本实验中调整的临界超参数。通过反复试验,我确定 l1 惩罚是最佳选择,因此我将其硬编码到网格中以减少调优时间。调整后,SGDClassifier 会使用最佳参数对数据进行训练,并显示结果。最后,进行交叉验证,以确保模型以最佳状态运行(确实如此)。

小费

通过一次改变一个或两个超参数,并通过硬编码它们的值来保持其他参数不变,可以更容易(也更快)地进行调优实验。

清单 6-2 中显示的第二个代码示例使用 svm.SVC 调优。SVC 的性能优于 SGDClassifier,但是我想通过在本章中包含第一个代码示例来演示至少一些实验性调优过程中固有的严格性。

import numpy as np, humanfriendly as hf
import time
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split,\
     GridSearchCV, cross_val_score
from sklearn.svm import SVC
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups)

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_faces.npy')
    y = np.load('data/y_faces.npy')
    images = np.load('data/faces_images.npy')
    targets = np.load('data/faces_targets.npy')
    _, h, w = images.shape
    n_images, n_features, n_classes = X.shape[0], X.shape[1],\
                                      len(targets)
    X_train, X_test, y_train, y_test = train_test_split(

        X, y, random_state=0)
    pca = PCA(n_components=0.95, whiten=True, random_state=0)
    pca.fit(X_train)
    components = pca.n_components_
    eigenfaces = pca.components_.reshape((components, h, w))
    X_train_pca = pca.transform(X_train)
    pca_name = pca.__class__.__name__
    print ('<<' + pca_name + '>>')
    print ('features (before PCA):', n_features)
    print ('features (after PCA):', components, br)
    X_i = np.array(eigenfaces[0].reshape(h, w))
    fig = plt.figure('eigenface')
    ax = fig.subplots()
    image = ax.imshow(X_i, cmap="bone")
    svm = SVC(random_state=0, gamma="scale")
    print (svm, br)
    svm.fit(X_train_pca, y_train)
    X_test_pca = pca.transform(X_test)
    y_pred = svm.predict(X_test_pca)
    cr = classification_report(y_test, y_pred)
    print (cr)
    svm_name = svm.__class__.__name__    

    param_grid = {'C': [1e2, 1e3, 5e3], 'gamma': [0.001, 0.005, 0.01, 0.1], 'kernel': ['rbf'], 'class_weight': ['balanced']}
    grid = GridSearchCV(svm, param_grid, cv=5)
    start = time.perf_counter()
    grid.fit(X_train_pca, y_train)
    see_time('training time:')
    print ()
    bp = grid.best_params_
    print ('best parameters:')
    print (bp, br)
    svm = SVC(**bp)
    svm.fit(X_train_pca, y_train)
    y_pred = svm.predict(X_test_pca)
    print ()
    cr = classification_report(y_test, y_pred)
    print (cr, br)
    print ('cross-validation:')
    scores = get_cross(svm, X_train_pca, y_train)
    print (np.mean(scores), br)
    file = 'data/bp_face'
    np.save(file, bp)
    bp = np.load('data/bp_face.npy')
    bp = bp.tolist()
    print ('best parameters:')
    print (bp)
    plt.show()

Listing 6-2Tuning fetch_1fw_people with svm.SVC

执行清单 6-2 的输出应该如下所示:

<<PCA>>
features (before PCA): 1850
features (after PCA): 135

SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma="scale",
  kernel='rbf', max_iter=-1, probability=False, random_state=0,
  shrinking=True, tol=0.001, verbose=False)

              precision    recall  f1-score   support

           0       1.00      0.43      0.60        28
           1       0.83      0.87      0.85        63
           2       0.94      0.62      0.75        24
           3       0.71      0.97      0.82       132
           4       1.00      0.70      0.82        20
           5       1.00      0.36      0.53        22
           6       0.96      0.73      0.83        33

   micro avg       0.80      0.80      0.80       322
   macro avg       0.92      0.67      0.74       322
weighted avg       0.84      0.80      0.78       322

training time: 18 seconds

and 143.89 milliseconds

best parameters:
{'C': 100.0, 'class_weight': 'balanced', 'gamma': 0.005, 'kernel': 'rbf'}

              precision    recall  f1-score   support

           0       1.00      0.64      0.78        28
           1       0.76      0.92      0.83        63
           2       0.91      0.88      0.89        24
           3       0.88      0.92      0.90       132
           4       0.74      0.85      0.79        20
           5       1.00      0.64      0.78        22
           6       0.90      0.85      0.88        33

   micro avg       0.86      0.86      0.86       322
   macro avg       0.89      0.81      0.84       322
weighted avg       0.87      0.86      0.86       322

cross-validation:
0.8393624737627647

best parameters:
{'C': 100.0, 'class_weight': 'balanced', 'gamma': 0.005, 'kernel': 'rbf'}

清单 6-2 还显示了图 6-1 ,这是 PCA 创建的第一个特征脸。

img/481580_1_En_6_Fig1_HTML.jpg

图 6-1

PCA 创建的第一个特征脸

代码从导入必需的包开始。函数 see_time 返回经过的时间。主模块将数据加载到 X 和 y 中,将其分成训练测试子集,并进行 PCA 降维。svm 的基准性能。显示 SVC,以便稍后与调整后的 svm 进行比较。SVC 分数。

通过用 C伽马内核class_weight 超参数构建网格来开始调优。超参数 C 是误差项的惩罚参数,所以对于调优非常重要。超参数 gamma 是核系数。超参数内核指定算法使用的内核类型(如线性)。超参数 class_weight 用于设置每个类的权重(或强调)。通过实验,我发现 rbf 内核和平衡类权重是最好的,所以我把它们硬编码到网格里。

我的发现过程如下:首先,我保持所有其他超参数不变,并更改 kernel 以查看产生最佳性能的设置。第二,我保持内核不变,改变了类的权重。

正如您从网格中看到的,我们改变了 C 和 gamma 来提高性能。一旦确定了最佳参数,svm。SVC 用它们训练数据。结果与交叉验证度量一起显示。我们在 svm 方面做得很好。因为我们的表现明显好于交叉验证分数。为了完整性,我们显示降维后的第一个特征脸。最后,保存(并显示)最佳参数。

调优调优调优调优调优调优调优调优调优调优调优调优调优调优调优调优调优调优调优调优调优调优调优调优调

MNIST 不是一个拥有 70000 个样本的大型数据集,但它有一个由 784 个特征组成的高维特征空间。这种特征空间的复杂性增加了计算开销,因此我们在使用计算开销很大的算法(如 svm.SVC)进行实验时必须考虑到这一点。

清单 6-3 中的第一个代码示例使用 RandomForestClassifier 和 ExtraTreesClassifier 调优 MNIST。这些算法有许多超参数,但我们只调整了几个。根据我使用这些算法的经验,我能够极大地简化调优。您可以进一步试验,但是当您调整额外的超参数时,计算开销会大大增加。

import numpy as np, humanfriendly as hf, random
import time
from sklearn.model_selection import train_test_split
from sklearn.model_selection import RandomizedSearchCV,\
     cross_val_score
from sklearn.ensemble import RandomForestClassifier,\
     ExtraTreesClassifier

def get_scores(model, xtrain, ytrain, xtest, ytest):
    ypred = model.predict(xtest)
    train = model.score(xtrain, ytrain)
    test = model.score(xtest, y_test)
    name = model.__class__.__name__
    return (name, train, test)

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups)

def prep_data(data, target):
    d = [data[i] for i, _ in enumerate(data)]
    t = [target[i] for i, _ in enumerate(target)]
    return list(zip(d, t))

def create_sample(d, n, replace="yes"):
    if replace == 'yes': s = random.sample(d, n)
    else: s = [random.choice(d) for i, _ in enumerate(d) if i < n]
    Xs = [row[0] for i, row in enumerate(s)]
    ys = [row[1] for i, row in enumerate(s)]
    return np.array(Xs), np.array(ys)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

if __name__ == "__main__":
    br = '\n'
    X_file = 'data/X_mnist'
    y_file = 'data/y_mnist'
    X = np.load('data/X_mnist.npy')
    y = np.load('data/y_mnist.npy')
    X = X.astype(np.float32)
    data = prep_data(X, y)
    sample_size = 7000
    Xs, ys = create_sample(data, sample_size)
    rf = RandomForestClassifier(random_state=0, n_estimators=100)
    print (rf, br)
    params = {'class_weight': ['balanced'], 'max_depth': [10, 30]}
    random = RandomizedSearchCV(rf, param_distributions = params,
                                cv=3, n_iter=2, random_state=0)
    start = time.perf_counter()
    random.fit(Xs, ys)
    see_time('RandomizedSearchCV total tuning time:')
    bp = random.best_params_

    print (bp, br)
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    rf = RandomForestClassifier(**bp, random_state=0, n_estimators=100)
    start = time.perf_counter()
    rf.fit(X_train, y_train)
    rf_scores = get_scores(rf, X_train, y_train, X_test, y_test)
    see_time('total time:')
    print (rf_scores[0] + ' (train, test):')
    print (rf_scores[1], rf_scores[2], br)
    et = ExtraTreesClassifier(random_state=0, n_estimators=200)
    print (et, br)
    params = {'class_weight': ['balanced'], 'max_depth': [10, 30]}
    random = RandomizedSearchCV(et, param_distributions = params,
                                cv=3, n_iter=2, random_state=0)
    start = time.perf_counter()
    random.fit(Xs, ys)
    see_time('RandomizedSearchCV total tuning time:')
    bp = random.best_params_
    print (bp, br)
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    et = ExtraTreesClassifier(**bp, random_state=0, n_estimators=200)
    start = time.perf_counter()
    et.fit(X_train, y_train)
    et_scores = get_scores(et, X_train, y_train

, X_test, y_test)
    see_time('total time:')
    print (et_scores[0] + ' (train, test):')
    print (et_scores[1], et_scores[2], br)
    print ('cross-validation (et):')
    start = time.perf_counter()
    scores = get_cross(rf, X, y)
    see_time('total time:')
    print (np.mean(scores), br)
    file = 'data/bp_mnist_et'
    np.save(file, bp)
    bp = np.load('data/bp_mnist_et.npy')
    bp = bp.tolist()
    print ('best parameters:')
    print (bp)

Listing 6-3Tuning with RandomForestClassifier and ExtraTreesClassifier

执行清单 6-3 的输出应该如下所示:

RandomForestClassifier(bootstrap=True, class_weight=None,
            criterion='gini', max_depth=None,
            max_features='auto', max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf='deprecated', min_samples_split=2,
            min_weight_fraction_leaf='deprecated',
            n_estimators=100, n_jobs=None, oob_score=False,
            random_state=0, verbose=0, warm_start=False)

RandomizedSearchCV total tuning time: 13 seconds and 398.73 milliseconds
{'max_depth': 30, 'class_weight': 'balanced'}

total time: 32 seconds and 589.23 milliseconds
RandomForestClassifier (train, test):
0.9999809523809524 0.9701142857142857

ExtraTreesClassifier(bootstrap=False, class_weight=None,
           criterion='gini', max_depth=None, max_features="auto",
           max_leaf_nodes=None, min_impurity_decrease=0.0,
           min_impurity_split=None,
           min_samples_leaf='deprecated', min_samples_split=2,
           min_weight_fraction_leaf='deprecated',
           n_estimators=200, n_jobs=None, oob_score=False,
           random_state=0, verbose=0, warm_start=False)

RandomizedSearchCV total tuning time: 23 seconds and 342.93 milliseconds
{'max_depth': 30, 'class_weight': 'balanced'}

total time: 1 minute, 8 seconds and 270.59 milliseconds
ExtraTreesClassifier (train, test):
1.0 0.9732

cross-validation (et):
total time: 5 minutes, 40 seconds and 788.07 milliseconds
0.9692001937716965

best parameters:
{'max_depth': 30, 'class_weight': 'balanced'}

代码从导入必需的包开始。函数 get_scores 返回精确度分数和模型名称。函数 get_cross 返回交叉验证分数。函数 prep_data 为函数 create_sample 准备数据。函数 create sample 创建一个有或没有替换的随机样本。函数 see_time 返回经过的时间。主块加载数据,创建随机样本,并实例化算法 RandomForestClassifier。

通过构建具有 class_weightmax_depth 超参数的网格开始调整。超参数 class_weight 用于设置每个类的权重(或强调)。超参数 max_depth 用于建立树的最大深度。通过许多小时的实验,我发现这两个参数是提高性能的关键。通过利用 RandomizedSearchCV 获得最佳参数来继续调优。注意,调优时间只有 13 秒多一点,因为网格非常简单。

现在我们可以用最佳参数测试 RandomForestClassifier。请注意,我们在算法中包括了超参数 n_estimators 以及最佳参数。超参数 n_estimators 代表森林中的树木数量,可能是提高性能的最重要的超参数。

出于两个原因,我们在算法中包括了 n 个估计量(而不是把它放在网格中)。首先,它是一个如此重要的超参数,我们可以通过在调整实验之外调整它来节省时间。也就是说,我们可以非常容易地调整它,而不会给调整实验增加计算开销。然而,增加它的值确实增加了处理算法的计算费用。第二,它必须包含在这个算法中,以避免令人讨厌的警告。

优化 ExtraTreesClassifier 遵循完全相同的逻辑,只有一点不同。我们将 n 估计量增加到 200 棵树。请注意,这种增加会导致处理时间增加一倍以上,但性能会更好。

最后,我们运行交叉验证(在 extractreesclassifier 上),并保存来自 extractreesclassifier 的最佳参数以供将来处理。从交叉验证分数中,我们知道我们的准确度分数是可靠的。但是,交叉验证需要超过 5 分钟的处理时间!如果你不想等待,你可以注释掉代码的交叉验证部分。

积极的一面是,交叉验证只需要在一个算法上执行一次。我建议您在开始调优实验之前运行交叉验证。然后,您可以运行试错实验,直到达到或超过交叉验证分数。

小费

交叉验证只需运行一次,因为它不可调整。

总体性能良好,准确率超过 97%,没有太多的过度拟合。但是,不要被我的调优实验误导而产生错误的安全感。调优耗费大量的时间和耐心。我只能给你一些例子和提示,帮助你成为一个更有成就的数据科学家。

我强烈建议进行定时调优实验,尤其是那些计算量很大的实验(比如在各种值范围内使用大量超参数进行调优)。否则,很难知道你的实验进展如何。当我第一次开始调整机器学习算法时,我没有计算实验的时间。我的进度很慢,因为当我不能通过运行时间区分调优实验时,我变得非常沮丧。

小费

总是调整实验时间来衡量进展。

清单 6-4 中显示的下一个代码示例用 svm.SVC 调优 MNIST

import numpy as np, humanfriendly as hf, random
import time
from sklearn.model_selection import train_test_split
from sklearn.model_selection import RandomizedSearchCV
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

def get_scores(model, xtrain, ytrain, xtest, ytest):
    ypred = model.predict(xtest)
    train = model.score(xtrain, ytrain)
    test = model.score(xtest, y_test)
    name = model.__class__.__name__
    return (name, train, test)

def prep_data(data, target):
    d = [data[i] for i, _ in enumerate(data)]
    t = [target[i] for i, _ in enumerate(target)]
    return list(zip(d, t))

def create_sample(d, n, replace="yes"):
    if replace == 'yes': s = random.sample(d, n)
    else: s = [random.choice(d) for i, _ in enumerate(d) if i < n]
    Xs = [row[0] for i, row in enumerate(s)]
    ys = [row[1] for i, row in enumerate(s)]
    return np.array(Xs), np.array(ys)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

if __name__ == "__main__":
    br = '\n'
    X_file = 'data/X_mnist'
    y_file = 'data/y_mnist'
    X = np.load('data/X_mnist.npy')
    y = np.load('data/y_mnist.npy')
    X = X.astype(np.float32)
    data = prep_data(X, y)
    sample_size = 7000
    Xs, ys = create_sample(data, sample_size)
    pca = PCA(n_components=0.95, random_state=0)
    Xs = StandardScaler().fit_transform(Xs)
    Xs_reduced = pca.fit_transform(Xs)
    X_train, X_test, y_train, y_test = train_test_split(
        Xs_reduced, ys, random_state=0)
    svm = SVC(gamma='scale', random_state=0)
    print (svm, br)
    start = time.perf_counter()
    svm.fit(X_train, y_train)
    svm_scores = get_scores(svm, X_train, y_train

, X_test, y_test)
    print (svm_scores[0] + ' (train, test):')
    print (svm_scores[1], svm_scores[2])
    see_time('time:')
    print ()
    param_grid = {'C': [30, 35, 40], 'kernel': ['poly'], 'gamma': ['scale'], 'degree': [3], 'coef0': [0.1]}
    start = time.perf_counter()
    rand = RandomizedSearchCV(svm, param_grid, cv=3, n_jobs = -1,
                              random_state=0, n_iter=3, verbose=2)
    rand.fit(X_train, y_train)
    see_time('RandomizedSearchCV total tuning time:')
    bp = rand.best_params_
    print (bp, br)
    svm = SVC(**bp, random_state=0)
    start = time.perf_counter()
    svm.fit(X_train, y_train)
    svm_scores = get_scores(svm, X_train, y_train, X_test, y_test)
    print (svm_scores[0] + ' (train, test):')
    print (svm_scores[1], svm_scores[2])
    see_time('total time:')

Listing 6-4Tuning MNIST with svm.SVC

执行清单 6-4 的输出应该如下所示:

SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma="scale",
  kernel='rbf', max_iter=-1, probability=False, random_state=0,
  shrinking=True, tol=0.001, verbose=False)

SVC (train, test):
0.9845714285714285 0.9228571428571428
time: 13 seconds and 129.03 milliseconds

Fitting 3 folds for each of 3 candidates, totalling 9 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done   4 out of   9 | elapsed:   14.0s remaining:   17.6s
[Parallel(n_jobs=-1)]: Done   9 out of   9 | elapsed:   19.3s remaining:    0.0s
[Parallel(n_jobs=-1)]: Done   9 out of   9 | elapsed:   19.3s finished
RandomizedSearchCV total tuning time: 23 seconds and 824.72 milliseconds
{'kernel': 'poly', 'gamma': 'scale', 'degree': 3, 'coef0': 0.1, 'C': 30}

SVC (train, test):
1.0 0.9542857142857143
total time: 10 seconds and 810.06 milliseconds

像第一个 MNIST 调谐码的例子,我们采取随机抽样。但是,由于 svm.SVC 固有的巨大计算开销,我们也使用 PCA 进行降维。

小费

对于计算量大的算法,我们建议使用 PCA 抽取随机样本进行降维,以加快处理速度。

代码从导入必需的包开始。上一个例子我们已经讲过函数了,这里就不需要讨论了。

主程序块加载数据并随机抽取 7000 个样本。PCA 用于降维,信息损失 5%。接下来,我们缩放训练数据,因为 svm。SVC 对缩放响应良好。代码继续将数据分割成训练测试子集。接下来,svm。用默认参数训练 SVC 以测量性能。

代码继续使用 RandomizedSearchCV 进行调整。我们用超参数 C内核伽马度数coef0 创建一个网格。我们已经讨论了超参数 C、kernel 和 gamma,所以我们不需要在这里重复。超参数次数表示多项式核函数的次数。我们包含它是因为我们选择了 poly 作为内核。超参数 coef0 与多项式内核的结合使用。

通过实验,我发现超参数 C 是最需要调整的。因此,网格只改变 c 的值。

代码继续使用 svm.SVC 调优实验中的最佳参数。我们能够大幅提高测试性能,但我们仍然面临过度拟合的问题。

我们没有包括交叉验证有两个原因。第一,svm。SVC 的表现没有 ExtraTreeClassifier 好(那还有什么意义?).其次,在 svm 上运行交叉验证需要大量时间。SVC。

调整 fetch _ 新闻组

和人脸识别一样,文本探索是机器学习中一个非常复杂的课题。但是,Scikit-Learn 提供了 fetch_20newsgroups,这是一个很好的数据集,可以在其上进行实验和学习。

因为流水线模型(具有多项式 inb 和 TfidfVectorizer)包括两组超参数(每个算法一个),所以调整复杂性大大增加。

调整多项式 lNB 本身非常容易,因为只需调整 alpha 超参数。超参数阿尔法允许我们调整平滑。然而,调整 TfidfVectorizer 要困难得多,因为它包含大量的超参数。

当用 RandomizedSearchCV 调整流水线模型时,我们会遇到更大的困难,因为超参数的名称是不同的。来自流水线模型的每个超参数必须以算法名为前缀,以便 RandomizedSearchCV 可以正确解释。这是有意义的,因为算法可以共享相同的超参数。

清单 6-5 中显示的代码示例调优了一个流水线模型。

import numpy as np, humanfriendly as hf
import time
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
from sklearn.metrics import f1_score
from sklearn.model_selection import RandomizedSearchCV,\
     cross_val_score

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

if __name__ == "__main__":
    br = '\n'
    train = fetch_20newsgroups(subset='train')
    test = fetch_20newsgroups(subset='test')
    categories = ['rec.autos', 'rec.motorcycles', 'sci.space', 'sci.med']
    train = fetch_20newsgroups(subset='train', categories=categories, remove=('headers', 'footers', 'quotes'))
    test = fetch_20newsgroups(subset='test', categories=categories, remove=('headers', 'footers', 'quotes'))
    targets = train.target_names

    mnb = MultinomialNB()
    tf = TfidfVectorizer()
    print (mnb, br)
    print (tf, br)
    pipe = make_pipeline(tf, mnb)
    pipe.fit(train.data, train.target)
    labels = pipe.predict(test.data)
    f1 = f1_score(test.target, labels, average="micro")
    print ('f1_score', f1, br)
    print (pipe.get_params().keys(), br)
    param_grid = {'tfidfvectorizer__ngram_range': [(1, 1), (1, 2)],
                  'tfidfvectorizer__use_idf': [True, False],
                  'multinomialnb__alpha': [1e-2, 1e-3],
                  'multinomialnb__fit_prior': [True, False]}
    start = time.perf_counter()
    rand = RandomizedSearchCV(pipe, param_grid, cv=3, n_jobs = -1, random_state=0, n_iter=16, verbose=2)
    rand.fit(train.data, train.target)
    see_time('RandomizedSearchCV tuning time:')
    bp = rand.best_params_
    print ()
    print ('best parameters:')
    print (bp, br)
    rbs = rand.best_score_
    mnb = MultinomialNB(alpha=0.01)
    tf = TfidfVectorizer(ngram_range=(1, 1), use_idf=False)
    pipe = make_pipeline(tf, mnb)
    pipe.fit(train.data, train.target)
    labels = pipe.predict(test.data)
    f1 = f1_score(test.target, labels, average="micro")
    print ('f1_score', f1, br)
    file = 'data/bp_news'
    np.save(file, bp)
    bp = np.load('data/bp_news.npy')
    bp = bp.tolist()
    print ('best parameters:')
    print (bp, br)
    start = time.perf_counter()
    scores = get_cross(pipe, train.data, train.target)
    see_time('cross-validation:')
    print (np.mean(scores))

Listing 6-5Tuning fetch_20newsgroups with a pipelined model

执行清单 6-5 的输出应该如下所示:

MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True)

TfidfVectorizer(analyzer='word', binary=False,
        decode_error='strict', dtype=<class 'numpy.float64'>,
        encoding='utf-8', input="content", lowercase=True,
        max_df=1.0, max_features=None, min_df=1,
        ngram_range=(1, 1), norm="l2", preprocessor=None,
        smooth_idf=True, stop_words=None, strip_accents=None,
        sublinear_tf=False, token_pattern='(?u)\\b\\w\\w+\\b',
        tokenizer=None, use_idf=True, vocabulary=None)

f1_score 0.8440656565656567

dict_keys(['memory', 'steps', 'tfidfvectorizer', 'multinomialnb', 'tfidfvectorizer__analyzer', 'tfidfvectorizer__binary', 'tfidfvectorizer__decode_error', 'tfidfvectorizer__dtype', 'tfidfvectorizer__encoding', 'tfidfvectorizer__input', 'tfidfvectorizer__lowercase', 'tfidfvectorizer__max_df', 'tfidfvectorizer__max_features', 'tfidfvectorizer__min_df', 'tfidfvectorizer__ngram_range', 'tfidfvectorizer__norm', 'tfidfvectorizer__preprocessor', 'tfidfvectorizer__smooth_idf', 'tfidfvectorizer__stop_words', 'tfidfvectorizer__strip_accents', 'tfidfvectorizer__sublinear_tf', 'tfidfvectorizer__token_pattern', 'tfidfvectorizer__tokenizer', 'tfidfvectorizer__use_idf', 'tfidfvectorizer__vocabulary', 'multinomialnb__alpha', 'multinomialnb__class_prior', 'multinomialnb__fit_prior'])

Fitting 3 folds for each of 16 candidates, totalling 48 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  25 tasks      | elapsed:    7.6s
[Parallel(n_jobs=-1)]: Done  48 out of  48 | elapsed:   12.4s finished
RandomizedSearchCV tuning time: 12 seconds and 747.04 milliseconds

best parameters:
{'tfidfvectorizer__use_idf': False, 'tfidfvectorizer__ngram_range': (1, 1), 'multinomialnb__fit_prior': False, 'multinomialnb__alpha': 0.01}

f1_score 0.8611111111111112

best parameters

:
{'tfidfvectorizer__use_idf': False, 'tfidfvectorizer__ngram_range': (1, 1), 'multinomialnb__fit_prior': False, 'multinomialnb__alpha': 0.01}

cross-validation: 2 seconds and 750.36 milliseconds
0.8735201157292913

代码从导入必需的包开始。接下来是函数 get_cross 和 see_time。主程序块首先从 fetch_20newsgroups 数据集创建训练集和测试集。接下来,我们创建子类别并将数据分成训练测试子集。代码继续创建一个基线管道模型,并显示 f1_score,以便稍后与调整后的模型进行比较。

流水线模型的可能超参数可以用 pipe.get_params()显示。按键()。这是重要的一步,因为我们必须包含 RandomizedSearchCV 调优的确切名称。

小费

您可以(也应该)用 model_name.get_params()显示流水线模型的超参数。按键()

参数网格是用tfidf 矢量器 __ngram_rangetfidf 矢量器 __use_idf多项式 b__alpha多项式 b__fit_prior 创建的。

超参数多项式 inb _ _ alpha与多项式 inb 中的 alpha 完全相同。唯一的区别是前缀 multinomialnb 被包含进来,以通知 RandomizedSearchCV 它所属的算法。超参数多项式 inb _ _ fit _ prior表示是否学习类别先验概率。

超参数tfidf 矢量器 __ngram_rangetfidf 矢量器 __use_idf 属于算法 tfidf 矢量器,如其前缀所示。 ngram_range 表示要从文档中提取的不同 n 元语法的 n 值范围的上下边界。 use_idf 启用或禁用逆文档频率重新加权。

基于参数网格值,从 RandomizedSearchCV 开始调整。通过调整,我们能够将性能提高 86%以上。然而,交叉验证表明我们可以从我们的模型中挤出更多的性能。

七、回归调优

回归预测建模(或简称为回归)就是学习自变量(或特征)和连续因变量(或结果)之间关联强度的问题。优化回归算法类似于优化分类算法。也就是说,我们调整模型的超参数,直到我们达到最优解。

不同之处在于,回归调优的目标是降低均方根误差(RMSE),而分类调优的目标是最大化精度。 RMSE 的一个好处是误差分数的单位与预测值相同。虽然回归预测可以使用 RMSE 进行评估,但分类预测却不能。

小费

回归调优的目标是最小化 RMSE。

为我们的调整示例选择的机器学习算法不是巧合。我选择它们是基于许多小时的实验、阅读和洞察力。对于给定的数据集,表现最好的算法被包括在内,表现差的算法则不包括在内。

对于本章中的回归实验,我们利用 GridSearchCV 进行调优。

小费

在给定足够的计算资源的情况下,使用 GridSearchCV 进行调优适合于彻底搜索性能最佳的超参数。使用 RandomizedSearchCV 进行调优适用于良好的搜索,或者如果调优高维数据。

学习调整回归算法可以通过使用各种数据集和回归变量的例子来加速。但是,我也建议遵循结构化流程:

  1. 总是从使用基线算法的默认超参数开始。

  2. 尝试训练和测试规模。

  3. 处理高维数据时使用降维。

  4. 处理大型数据集时,随机抽取样本。

  5. 缩放数据(在适当的情况下)以潜在地提高性能。

  6. 使用 GridSearchCV 或 RandomizedSearchCV 进行调整。

  7. 一旦使用基线算法进行了调整,就可以使用复杂的算法进行实验。

小费

从基线算法(及其默认的超参数)开始调优,以建立基线性能。

调整数据集

我们专注于四个数据集:小费、波士顿和葡萄酒(红和白)。小费数据由餐馆的服务员小费和相关因素组成,包括小费、餐费和时间。波士顿数据由波士顿不同地点的房价组成。葡萄酒数据由两个数据集(红色和白色)组成,这两个数据集由葡萄牙 Vinho Verde 葡萄酒的变体组成。

调谐提示

清单 7-1 中所示的代码示例基于未缩放和缩放的数据计算各种回归算法的 RMSE。因为 tips 是如此小的数据集,所以运行这种类型的实验在计算上是廉价的。

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.ensemble import RandomForestRegressor as rfr,\
     AdaBoostRegressor as ada, GradientBoostingRegressor as gbr
from sklearn.linear_model import LinearRegression as lr,\
     BayesianRidge as bay, Ridge as rr, Lasso as l,\
     LassoLars as ll, ElasticNet as en,\
     ARDRegression as ard, RidgeCV as rcv
from sklearn.svm import SVR
from sklearn.tree import DecisionTreeRegressor as dtr
from sklearn.neighbors import KNeighborsRegressor as knn
from sklearn.preprocessing import StandardScaler

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_tips.npy')
    y = np.load('data/y_tips.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    regressors = [lr(), bay(), rr(alpha=.5, random_state=0),
                  l(alpha=0.1, random_state=0), ll(), knn(),
                  ard(), rfr(random_state=0, n_estimators=100),
                  SVR(gamma='scale', kernel="rbf"),
                  rcv(fit_intercept=False), en(random_state=0),
                  dtr(random_state=0), ada(random_state=0),
                  gbr(random_state=0)]
    print ('unscaled:', br)
    for reg in regressors:
        reg.fit(X_train, y_train)
        rmse, name = get_error(reg, X_test, y_test)
        name = reg.__class__.__name__
        print (name + '(rmse):', end=' ')
        print (rmse)
    print ()
    print ('scaled:', br)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    for reg in regressors

:
        reg.fit(X_train_std, y_train)
        rmse, name = get_error(reg, X_test_std, y_test)
        name = reg.__class__.__name__
        print (name + '(rmse):', end=' ')
        print (rmse)

Listing 7-1Calculating RMSE for tips data with regression algorithms

继续执行清单 7-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。

执行清单 7-1 的输出应该如下所示:

unscaled:

LinearRegression(rmse): 0.9474705746817206
BayesianRidge(rmse): 0.9245282337469829
Ridge(rmse): 0.9471900902779103
Lasso(rmse): 0.9158574785712037
LassoLars(rmse): 1.333812899498391
KNeighborsRegressor(rmse): 1.086204460049883
ARDRegression(rmse): 0.9264801346401996
RandomForestRegressor(rmse): 0.8850975551298138
SVR(rmse): 0.9441992099702836
RidgeCV(rmse): 0.9426372075893412
ElasticNet(rmse): 0.9307377813721578
DecisionTreeRegressor(rmse): 1.2994272932036561
AdaBoostRegressor(rmse): 0.932681302158466
GradientBoostingRegressor(rmse): 0.9112440690311495

scaled:

LinearRegression(rmse): 0.9007751177881488
BayesianRidge(rmse): 0.9096801291989541

Ridge(rmse): 0.9010890080377257
Lasso(rmse): 0.8785977911833892
LassoLars(rmse): 1.333812899498391
KNeighborsRegressor(rmse): 0.9613578099280607
ARDRegression(rmse): 0.8745960871430548
RandomForestRegressor(rmse): 0.893772251516372
SVR(rmse): 0.9749204385201592
RidgeCV(rmse): 3.1960055364135638
ElasticNet(rmse): 1.1310151423347359
DecisionTreeRegressor(rmse): 1.1835900827021861
AdaBoostRegressor(rmse): 0.986987944835978
GradientBoostingRegressor(rmse): 0.8908489427010696

代码从导入必要的包和各种回归算法开始。函数 get_error 返回型号名称和 RMSE。主程序块从从 NumPy 文件加载预处理的 tips 数据开始。请记住,我们在第四章中对 tips 数据进行了编码并保存,以备将来处理。

小费

Scikit-Learn 允许您试验各种算法来测试性能,而不需要它们的上下文知识。

代码继续将数据分割成训练测试子集。接下来,我们创建一个回归算法列表。代码继续在未缩放的数据上训练每个算法并显示结果。然后,代码缩放数据,根据缩放后的数据训练每个算法,并显示结果。

缩放数据是这个实验的一个非常重要的部分,因为许多算法报告的 RMSE 结果比它们未缩放的兄弟要低。对缩放数据执行最好的算法是 Lasso 和 ARDRegression。

小费

在调优过程中,缩放可能是一项非常重要的技术。

所以,实验成功了!它引导我们找到了两种算法,我们可以集中精力进行调优。

清单 7-2 中显示的下一个代码示例用套索调整提示。

import numpy as np, humanfriendly as hf
import time
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Lasso
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_tips.npy')
    y = np.load('data/y_tips.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    lasso = Lasso(random_state=0, alpha=0.1)
    print (lasso, br)
    lasso.fit(X_train_std, y_train)
    rmse, name = get_error(lasso, X_test_std, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    alpha_lasso = [1e-1]
    params = {'alpha': alpha_lasso, 'positive': [True, False],
              'max_iter': [10, 50, 100]}
    grid = GridSearchCV(lasso, params, cv=5, n_jobs=-1, verbose=1)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_

    print (bp, br)
    lasso = Lasso(**bp, random_state=0).fit(X_train_std, y_train)
    rmse, name = get_error(lasso, X_test_std, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(lasso, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-2Tuning tips with Lasso

执行清单 7-2 的输出应该如下所示:

Lasso(alpha=0.1, copy_X=True, fit_intercept=True, max_iter=1000,
   normalize=False, positive=False, precompute=False,
   random_state=0, selection="cyclic", tol=0.0001,
   warm_start=False)

Lasso(rmse): 0.8785977911833892

Fitting 5 folds for each of 6 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  30 out of  30 | elapsed:    2.1s finished
training time: 2 seconds and 246.86 milliseconds
{'alpha': 0.1, 'max_iter': 10, 'positive': True}

Lasso(rmse): 0.8781319871042923

cross-validation rmse: 8.58 milliseconds
1.0379804468729155

代码从导入必需的包开始。函数 get_error 返回 RMSE。函数 see_time 返回经过的时间。函数 get_cross 返回交叉验证 RMSE。

主程序块从加载预处理的 tips 数据开始。代码继续将数据分割成训练测试子集。接下来,我们扩展数据。然后,我们用 Lasso 训练数据,并显示结果,以便与调整后的 RMSE 进行基线比较。

Lasso 是一种使用 L1 罚函数进行正则化的算法。我们根据之前的实验调整α最大值超参数。

超参数 alpha 是乘以 L1 罚项的常数。也是用套索调音最重要的超参数。超参数强制系数为正。超参数 max_iter 代表最大迭代次数。

使用 GridSearchCV 和网格参数开始调优。通过调整,我们能够将 RMSE 降低一个非常小的量。交叉验证表明我们做得很好。

小费

请记住,函数 get_error 会返回负的均方误差,因此我们必须将结果乘以-1 并取结果的平方根以获得 RMSE,从而使结果为正。

清单 7-3 中显示的下一个代码示例使用 ARDRegression 调整提示。

import numpy as np, humanfriendly as hf
import time
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import ARDRegression
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_tips.npy')
    y = np.load('data/y_tips.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    ard = ARDRegression().fit(X_train_std, y_train)
    print (ard, br)
    rmse, name = get_error(ard, X_test_std, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    iters = [50]
    a1 = [1e5, 1e4]
    a2 = [1e5, 1e4]
    params = {'n_iter': iters, 'alpha_1': a1, 'alpha_2': a2}
    grid = GridSearchCV(ard, params, cv=5, n_jobs=-1, verbose=1)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_

    print (bp, br)
    ard = ARDRegression(**bp).fit(X_train_std, y_train)
    rmse, name = get_error(ard, X_test_std, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(ard, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-3Tuning tips with ARDRegression

执行清单 7-3 的输出应该如下所示:

ARDRegression(alpha_1=1e-06, alpha_2=1e-06, compute_score=False,
       copy_X=True, fit_intercept=True, lambda_1=1e-06,
       lambda_2=1e-06, n_iter=300, normalize=False,
       threshold_lambda=10000.0, tol=0.001, verbose=False)

ARDRegression(rmse): 0.8745960871430548

Fitting 5 folds for each of 4 candidates, totalling 20 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  20 out of  20 | elapsed:    3.5s finished
training time: 4 seconds and 286.03 milliseconds
{'alpha_1': 10000.0, 'alpha_2': 100000.0, 'n_iter': 50}

ARDRegression(rmse): 0.8645625277607758

cross-validation rmse: 4 seconds and 10.17 milliseconds
1.0376527153700184

代码从导入必需的包开始。函数 get_error 返回 RMSE。函数 see_time 返回经过的时间。函数 get_cross 返回交叉验证 RMSE。

主程序块从加载预处理的 tips 数据开始。代码继续将数据分割成训练测试子集。接下来,我们扩展数据。然后,我们使用 ARDRegression 训练数据,并显示结果,以便与调整后的 RMSE 进行基线比较。

ARDRegression (自动相关性确定回归)用贝叶斯岭回归拟合回归模型。模型的估计是通过迭代最大化观测值的边际对数似然来完成的。

我们用 n_iteralpha_1alpha_2 调谐。超参数 n_iter 是最大迭代次数。超参数 alpha_1 是优先于 alpha 参数的伽马分布的形状参数。超参数 alpha_2 是优先于 alpha 参数的伽马分布的逆比例参数(或速率参数)。

我们能够通过调谐来降低 RMSE。此外,交叉验证显示我们做得非常好。

调谐波士顿

清单 7-4 中所示的代码示例基于未缩放和缩放的数据计算各种回归算法的 RMSE。由于波士顿是一个相对较小的数据集,所以运行这种类型的实验在计算上并不昂贵。

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.ensemble import RandomForestRegressor as rfr,\
     AdaBoostRegressor as ada, GradientBoostingRegressor as gbr
from sklearn.linear_model import LinearRegression as lr,\
     BayesianRidge as bay, Ridge as rr, Lasso as l,\
     LassoLars as ll, ElasticNet as en,\
     ARDRegression as ard, RidgeCV as rcv
from sklearn.svm import SVR
from sklearn.tree import DecisionTreeRegressor as dtr
from sklearn.neighbors import KNeighborsRegressor as knn
from sklearn.preprocessing import StandardScaler

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_boston.npy')
    y = np.load('data/y_boston.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    regressors = [lr(), bay(), rr(alpha=.5, random_state=0),
                  l(alpha=0.1, random_state=0), ll(), knn(),
                  ard(), rfr(random_state=0, n_estimators=100),
                  SVR(gamma='scale', kernel="rbf"),
                  rcv(fit_intercept=False), en(random_state=0),
                  dtr(random_state=0), ada(random_state=0),
                  gbr(random_state=0)]
    print ('unscaled:', br)
    for reg in regressors:
        reg.fit(X_train, y_train)
        rmse, name = get_error(reg, X_test, y_test)
        name = reg.__class__.__name__
        print (name + '(rmse):', end=' ')
        print (rmse)
    print ()
    print ('scaled:', br)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    for reg in regressors:
        reg.fit(X_train_std, y_train)
        rmse, name = get_error(reg, X_test_std, y_test)
        name = reg.__class__.__name__
        print (name + '(rmse):', end=' ')
        print (rmse)

Listing 7-4Calculating RMSE for boston data with regression algorithms

执行清单 7-4 的输出应该如下所示:

unscaled:

LinearRegression(rmse): 4.236710574387242
BayesianRidge(rmse): 4.317939916221959
Ridge(rmse): 4.243658717030716
Lasso(rmse): 4.300740333025026
LassoLars(rmse): 8.754893348840868
KNeighborsRegressor(rmse): 5.9934937623789
ARDRegression(rmse): 4.28415048500826
RandomForestRegressor(rmse): 3.37169151536684
SVR(rmse): 7.100029068343849
RidgeCV(rmse): 4.392246392993031
ElasticNet(rmse): 4.88844846745213
DecisionTreeRegressor(rmse): 4.346328232622458
AdaBoostRegressor(rmse): 3.652816906059683
GradientBoostingRegressor(rmse): 3.1941117128039194

scaled:

LinearRegression(rmse): 4.398269524691269
BayesianRidge(rmse): 4.419543929268475
Ridge(rmse): 4.400075160458176
Lasso(rmse): 4.489952156682322
LassoLars(rmse): 8.754893348840868
KNeighborsRegressor(rmse): 4.757936288305807
ARDRegression(rmse): 4.383622227159
RandomForestRegressor(rmse): 4.053037237125816
SVR(rmse): 5.083294658978756
RidgeCV(rmse): 22.34757636411328
ElasticNet(rmse): 5.277752330669967
DecisionTreeRegressor(rmse): 5.2796587719252726
AdaBoostRegressor(rmse): 4.100148076529094
GradientBoostingRegressor(rmse): 3.7490071027496015

代码从导入必要的包和各种回归算法开始。函数 get_error 返回型号名称和 RMSE。主程序块首先从 NumPy 文件加载清理后的波士顿数据。请记住,我们清理了波士顿数据,并在第四章中将其保存以备将来处理。

代码继续将数据分割成训练测试子集。接下来,我们创建一个回归算法列表。代码继续在未缩放的数据上训练每个算法并显示结果。然后,代码缩放数据,根据缩放后的数据训练每个算法,并显示结果。

在这个实验中表现最好的算法是 GradientBoostingRegressor 和 RandomForestRegressor(两者都具有未缩放的数据)。所以,缩放数据并没有增加这个数据集的价值。

清单 7-5 中显示的下一个代码示例使用 GradientBoostingRegressor 调优波士顿数据集。

import numpy as np, humanfriendly as hf, warnings, sys
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start

    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":
    br = '\n'
    if not sys.warnoptions:
        warnings.simplefilter('ignore')
    X = np.load('data/X_boston.npy')
    y = np.load('data/y_boston.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    gbr = GradientBoostingRegressor(random_state=0)
    print (gbr, br)
    gbr.fit(X_train, y_train)
    rmse, name = get_error(gbr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    loss = ['ls', 'lad', 'huber']
    lr = [1e-2, 1e-1, 1e-0]
    n_est = [150, 200, 300, 500]
    alpha = [0.9]
    params = {'loss': loss, 'learning_rate': lr,
              'n_estimators': n_est, 'alpha': alpha}
    grid = GridSearchCV(gbr, params, cv=5, n_jobs=-1,
                        verbose=1, refit=False)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_

    print (bp, br)
    gbr = GradientBoostingRegressor(**bp, random_state=0)
    gbr.fit(X_train, y_train)
    rmse, name = get_error(gbr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(gbr, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-5Tuning boston data with GradientBoostingRegressor

执行清单 7-5 的输出应该如下所示:

GradientBoostingRegressor(alpha=0.9, criterion="friedman_mse",
             init=None, learning_rate=0.1, loss="ls",
             max_depth=3, max_features=None,
             max_leaf_nodes=None, min_impurity_decrease=0.0,
             min_impurity_split=None,
             min_samples_leaf='deprecated', min_samples_split=2,
             min_weight_fraction_leaf='deprecated',
             n_estimators=100, n_iter_no_change=None,
             presort='auto', random_state=0, subsample=1.0,
             tol=0.0001, validation_fraction=0.1, verbose=0,
             warm_start=False)

GradientBoostingRegressor(rmse): 3.1941117128039194

Fitting 5 folds for each of 36 candidates, totalling 180 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  34 tasks      | elapsed:    3.1s
[Parallel(n_jobs=-1)]: Done 180 out of 180 | elapsed:    9.1s finished
training time: 9 seconds and 170.11 milliseconds
{'alpha': 0.9, 'learning_rate': 0.1, 'loss': 'huber', 'n_estimators': 300}

GradientBoostingRegressor(rmse): 3.0839764165411934

cross-validation rmse: 3 seconds and 258.29 milliseconds
3.7929403445012064

代码从导入 GradientBoostingRegressor 以及其他必需的包开始。GradientBoostingRegressor通过建立一个前向模式的附加模型来执行回归的梯度推进。

函数 get_error 返回给定算法的 RMSE 和模型名称。函数 see_time 返回经过的时间。函数 get_cross 返回负的均方误差。

主块加载波士顿数据,将其分成训练测试子集,并使用 GradientBoostingRegressor 训练数据。代码继续显示带有默认参数的 RMSE,以提供与调优的 RMSE 进行比较的基线分数。接下来,用超参数损失学习率n 估计量α来调整模型。

超参数损失是要优化的损失函数。超参数 learning_rate 控制我们相对于损失梯度调整模型学习的程度。超参数 n_estimators 是要执行的升压阶段的数量。Hypeparameter alpha 是 huber 损失函数的 alpha 分位数。

通过调整可以降低 RMSE。我们通过运行交叉验证来结束。因为我们调整的 RMSE 低于交叉验证的 RMSE,所以我们的情况很好。

小费

您可能需要偶尔重启计算机,因为调整需要大量的计算资源。

本节的最后一个代码示例(如清单 7-6 所示)使用 RandomForestRegressor 调优波士顿数据集。

import numpy as np, humanfriendly as hf, warnings, sys
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":
    br = '\n'
    if not sys.warnoptions:
        warnings.simplefilter('ignore')
    X = np.load('data/X_boston.npy')
    y = np.load('data/y_boston.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    rfr = RandomForestRegressor(random_state=0)
    print (rfr, br)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    n_est = [100, 500, 1000]
    boot = [True, False]
    params = {'n_estimators': n_est, 'bootstrap': boot}
    grid = GridSearchCV(rfr, params, cv=5, n_jobs=-1,
                        verbose=1, refit=False)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_
    print (bp, br)
    rfr = RandomForestRegressor(**bp, random_state=0)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(rfr, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-6Tuning boston data with RandomForestRegressor

执行清单 7-6 的输出应该如下所示:

RandomForestRegressor(bootstrap=True, criterion="mse",
           max_depth=None, max_features="auto",
           max_leaf_nodes=None, min_impurity_decrease=0.0,
           min_impurity_split=None,
           min_samples_leaf='deprecated', min_samples_split=2,
           min_weight_fraction_leaf='deprecated',
           n_estimators='warn', n_jobs=None, oob_score=False,
           random_state=0, verbose=0, warm_start=False)

RandomForestRegressor(rmse): 3.5587794792757004

Fitting 5 folds for each of 6 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  30 out of  30 | elapsed:    8.3s finished
training time: 8 seconds and 453.84 milliseconds
{'bootstrap': True, 'n_estimators': 100}

RandomForestRegressor(rmse): 3.37169151536684

cross-validation rmse: 1 second and 845.76 milliseconds
3.6815463792891623

代码从导入 RandomForestRegressor 以及其他必需的包开始。 RandomForestRegressor 在数据集的各种子样本上拟合多个分类决策树,并使用平均来提高预测精度和控制过度拟合。

函数 get_error 返回给定算法的 RMSE 和模型名称。函数 see_time 返回经过的时间。函数 get_cross 返回负的均方误差。

主块加载波士顿数据,将其分成训练测试子集,并用 RandomForestRegressor 训练数据。代码继续显示带有默认参数的 RMSE,以提供与调优的 RMSE 进行比较的基线分数。接下来,用超参数 n 估计器自助来调整模型。

超参数 n_estimators 是森林中树木的数量。超参数 bootstrap 决定构建树时是否使用 bootstrap 样本。

通过调整可以降低 RMSE。我们通过运行交叉验证来结束。因为我们调整的 RMSE 低于交叉验证的 RMSE,所以我们的情况很好。

调酒

通过运行类似于清单 7-1 和 7-4 中所示的实验,我们发现 RandomForestRegressor(具有未缩放的数据)为红葡萄酒和白葡萄酒数据提供了最低的 RMSE。如果你愿意,继续做你自己的实验来验证我们的结果。

清单 7-7 中的代码示例用 RandomForestRegressor 调优红酒数据集。

import numpy as np, humanfriendly as hf
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_red.npy')
    y = np.load('data/y_red.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    rfr = RandomForestRegressor(random_state=0, n_estimators=10)
    print (rfr, br)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    n_est = [100, 500]
    boot = [True, False]
    params = {'n_estimators': n_est, 'bootstrap': boot}
    grid = GridSearchCV(rfr, params, cv=5, n_jobs=-1, verbose=1)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_
    print (bp, br)
    rfr = RandomForestRegressor(**bp, random_state=0)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(rfr, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-7Tuning red wine data with RandomForestRegressor

执行清单 7-7 的输出应该如下所示:

RandomForestRegressor(bootstrap=True, criterion="mse",
                      max_depth=None, max_features="auto",
                      max_leaf_nodes=None,
                      min_impurity_decrease=0.0,
                      min_impurity_split=None,
                      min_samples_leaf='deprecated',
                      min_samples_split=2,
                      min_weight_fraction_leaf='deprecated',
                      n_estimators=10, n_jobs=None,
                      oob_score=False, random_state=0, verbose=0,
                      warm_start=False)

RandomForestRegressor(rmse): 0.626079068488957

Fitting 5 folds for each of 4 candidates, totalling 20 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  20 out of  20 | elapsed:    7.1s finished
training time: 7 seconds and 629.56 milliseconds
{'bootstrap': True, 'n_estimators': 100}

RandomForestRegressor(rmse): 0.5847897057917487

cross-validation rmse: 4 seconds and 804.96 milliseconds
0.6498982966515346

代码从导入必需的包开始。函数 get_error 返回给定算法的 RMSE 和模型名称。函数 see_time 返回经过的时间。函数 get_cross 返回负的均方误差。

主块加载红酒数据,将其分成训练测试子集,并用 RandomForestRegressor 训练数据。代码继续显示带有默认参数的 RMSE,以提供与调优的 RMSE 进行比较的基线分数。接下来,用超参数 n 估计器自助来调整模型。

通过调整可以降低 RMSE。我们通过运行交叉验证来结束。因为我们调整的 RMSE 低于交叉验证的 RMSE,所以我们的情况很好。

清单 7-8 中显示的最后一个代码示例用 RandomForestRegressor 调优了白葡萄酒数据集。

import numpy as np, humanfriendly as hf
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":

    br = '\n'
    X = np.load('data/X_white.npy')
    y = np.load('data/y_white.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    rfr = RandomForestRegressor(random_state=0, n_estimators=10)
    print (rfr, br)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    n_est = [100, 500]
    boot = [True, False]
    params = {'n_estimators': n_est, 'bootstrap': boot}
    grid = GridSearchCV(rfr, params, cv=5, n_jobs=-1, verbose=1)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_
    print (bp, br)
    rfr = RandomForestRegressor(**bp, random_state=0)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(rfr, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-8Tuning white wine data with RandomForestRegressor

执行清单 7-8 的输出应该如下所示:

RandomForestRegressor(bootstrap=True, criterion="mse",
           max_depth=None, max_features="auto",
           max_leaf_nodes=None, min_impurity_decrease=0.0,
           min_impurity_split=None,
           min_samples_leaf='deprecated', min_samples_split=2,
           min_weight_fraction_leaf='deprecated',n_estimators=10,
           n_jobs=None, oob_score=False, random_state=0,
           verbose=0, warm_start=False)

RandomForestRegressor(rmse): 0.6966098665124181

Fitting 5 folds for each of 4 candidates, totalling 20 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  20 out of  20 | elapsed:   18.7s finished
training time: 25 seconds and 709.64 milliseconds
{'bootstrap': True, 'n_estimators': 500}

RandomForestRegressor(rmse): 0.6728175517621279

cross-validation rmse: 1 minute, 24 seconds and 70.99 milliseconds 0.7183073387927801

代码从导入必需的包开始。函数 get_error 返回给定算法的 RMSE 和模型名称。函数 see_time 返回经过的时间。函数 get_cross 返回负的均方误差。

主块加载白葡萄酒数据,将其分成训练测试子集,并用 RandomForestRegressor 训练数据。代码继续显示带有默认参数的 RMSE,以提供与调优的 RMSE 进行比较的基线分数。接下来,用超参数 n 估计器自助来调整模型。

通过调整可以降低 RMSE。我们通过运行交叉验证来结束。因为我们调整的 RMSE 低于交叉验证的 RMSE,所以我们的情况很好。

八、把所有的放在一起

旅程

在我们的机器学习之旅中,我们使用了 11 个数据集。七个是分类数据集,而四个是预测建模回归数据集。

四个分类数据集是简单的。三是复杂的。简单数据集是那些特征很少的数据集。具有很少特征的数据集通常被称为具有低维特征空间的数据集。复杂数据集是具有许多特征的数据集。这种数据集通常被称为具有高维特征空间的数据集。所有四个预测建模回归(或只是回归)数据集都很简单,因为它们各自的特征空间具有很少的特征。

机器学习中的特征空间维度依赖于每个特征代表一个维度的概念。因此,具有少量特征的特征集具有低维度,而具有大量维度的特征集具有高维度。特征空间维度在机器学习中很重要,因为具有高维特征空间的数据集在计算上是昂贵的。也就是说,用这样的数据进行算法机器学习,需要丰富的计算机资源。

分类预测数据所属的类别,而回归预测基于以前观察到的数据的数值。因此,分类被用来预测离散的反应,如性别或一种水果。回归用于预测连续值,如房价或利润。

我们通过演示机器学习算法如何从数据中学习来继续我们的旅程。在第 2 和 3 章中,我们开始用各种分类算法训练简单和复杂的分类数据集。然后我们在第四章中用各种回归算法训练回归数据集。

对于分类和回归学习,我们演示了如何通过训练好的算法进行预测。预测让我们看到算法训练的结果。通过分类,我们可以根据新数据预测谨慎的目标。通过回归,我们可以根据新数据预测连续的结果。在这两种情况下,我们都使用从完整数据集中分离出来的测试数据作为新数据。然而,预测只和我们的训练一样好。

要评估培训绩效,我们必须知道如何衡量学习。对于分类,我们向您展示了如何获得准确性。准确性是我们预测正确的百分比。对于回归,我们向您展示了如何推导 RMSE(均方根误差)。RMSE 是预测值和观察值之间的差异。简单地说,RMSE 测量误差。

虽然我们向您展示了如何使用机器学习算法来训练数据,但通过模型调整来提高性能是可能的。因此,我们在第五章和第六章和第七章中向您展示了如何调整分类算法以及回归算法。如您所知,调优是一个非常复杂、艰巨、耗时且需要实验的过程。因此,您需要耐心和毅力通过调优来提高性能。

调整也是减少过度拟合的一种非常有效的方法。过度拟合是指算法在记忆数据,而不是从中学习。当你的训练精度比测试精度高很多时,你知道你的模型是过度拟合的。

然而,机器学习之旅才刚刚开始。

价值和成本

学习机器学习的技术层面是不够的。工业中的数据集往往很大,甚至非常大。即使具有低维特征空间的大型数据集在计算上也可能是昂贵的。想象一下用高维特征空间训练一个极其庞大的数据集的计算开销!如果包括调优,计算费用和数据科学家的时间可能会过高。

数据科学家希望数据处于自然原始状态。他们想在它产生的时候从源头收集它。他们不想访问关系数据库中的数据或遗留系统中以各种形式存储的数据。然而,当前的遗留系统通常将数据存储在关系数据库中。此外,新数据经常被放入相同的系统中。最后,组织制定了关于谁可以访问数据、可以访问多少数据以及何时可以访问数据的规则。

数据科学家希望生成的原始数据与自然界中实际发生的情况相匹配。也就是说,他们想要模仿现实。由于机器学习的想法是从数据中学习,如何、在哪里、何时以及为什么收集数据是至关重要的。如果组织将数据收集并处理到系统中,数据的自然含义(和时间)就会丢失。而且,数据科学家甚至可能很难获得他们需要的数据。

因此,数据科学家不仅要努力获取自然状态的数据,他们还必须浏览所有的规则、安全策略和政策,以访问当前或新的数据。当前的组织结构并不是为了让数据科学家能够在他们想要的时候以他们想要的形式获得他们想要的数据。此外,数据科学家在组织中是一个相对较新的现象。因此,他们的政治影响力往往较小,他们的角色可能会被误解,他们对数据所做的事情与过去所做的事情不符。

政治影响力的减弱使得很难说服那些拥有金融资源的人(或投资人)在当前分配的基础上为昂贵的计算资源分配更多资金。如果有钱人误解了数据科学家的作用,误解了过去资源是如何分配的,数据科学家可能得不到自己需要的资源。此外,数据科学家的工资很高,而且受过良好的教育。自然地,得到更多报酬的人会更加嫉妒和争权夺利。此外,数据科学家可以被视为无所不知,因为他们的教育,组织的新成员,以及对数据的不同看法。

那么,如何才能说服有钱人为数据科学家的数据需求做预算呢?首先,我们必须了解什么对他们来说是重要的。第二,我们必须意识到,他们自然希望我们成功,因为工业界正以不可思议的速度拥抱机器学习。

对于理财人士来说,只有两件事至关重要——价值和成本。价值是决定组织健康状况的因素。成本是任何有损组织健康和福利的东西。因此,我们必须能够向投资者展示我们的案例,证明我们所做的既有价值又能降低成本。我们还必须认识到,投资人不是数据科学家,很可能不是技术导向型的。

本章的剩余部分通过用非常简单的术语展示本书中构建的三个复杂的代码示例,展示了资金的价值和成本。每个代码示例都有输出,并通过强调价值和成本节约进行简单解释。代码不解释。它只是通过展示数据科学家实际做的事情来展示算法学习的复杂性。最后,我们选择了最复杂的数据集来证明它们可以向非技术人员解释。

MNIST 价值和成本

我们从之前调优的 MNIST 代码示例开始,如清单 8-1 所示。我们加载数据,运行机器学习算法,并显示结果。然后,我们从商业价值和成本的角度解释结果。

import numpy as np, humanfriendly as hf
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

def get_scores(model, xtrain, ytrain, xtest, ytest):
    ypred = model.predict(xtest)
    train = model.score(xtrain, ytrain)
    test = model.score(xtest, y_test)
    name = model.__class__.__name__
    return (name, train, test, ypred)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def find_misses(test, pred):
    return [i for i, row in enumerate(test) if row != pred[i]]

if __name__ == "__main__":
    br = '\n'
    X_file = 'data/X_mnist'
    y_file = 'data/y_mnist'
    X = np.load('data/X_mnist.npy')
    y = np.load('data/y_mnist.npy')
    X = X.astype(np.float32)
    bp = np.load('data/bp_mnist_et.npy')
    bp = bp.tolist()
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    et = ExtraTreesClassifier(**bp, random_state=0, n_estimators=200)
    start = time.perf_counter()
    et.fit(X_train, y_train)
    et_scores = get_scores(et, X_train, y_train, X_test, y_test)
    see_time('total time:')
    print (et_scores[0] + ' (train, test):')
    print (et_scores[1], et_scores[2], br)
    y_pred = et_scores[3]
    cm = confusion_matrix(y_test, y_pred)
    plt.figure(1)
    ax = plt.axes()
    sns.heatmap(cm.T, annot=True, fmt="d", cmap="gist_ncar_r", ax=ax)
    ax.set_title(et_scores[0] + 'confustion matrix')
    plt.xlabel('true value')
    plt.ylabel('predicted value')
    indx = find_misses(y_test, y_pred)
    print ('pred', 'actual')
    misses = [(y_pred[row], y_test[row], i)
              for i, row in enumerate(indx)]
    [print (row[0], '  ', row[1]) for i, row in

enumerate(misses)
     if i < 10]
    print()
    img_act = y_test[indx[0]]
    img_pred = y_pred[indx[0]]
    print ('actual', img_act)
    print ('pred', img_pred)
    text = str(img_pred)
    test_images = X_test.reshape(-1, 28, 28)
    plt.figure(2)
    plt.imshow(test_images[indx[0]], cmap="gray", interpolation="gaussian")
    plt.text(0, 0.05, text, color="r", bbox=dict(facecolor='white'))
    title = str(img_act) + ' misclassified as ' + text
    plt.title(title)
    plt.show()

Listing 8-1MNIST value and cost

执行清单 8-1 的输出应该如下所示:

total time: 1 minute, 8 seconds and 650.43 milliseconds
ExtraTreesClassifier (train, test):
1.0 0.9732

pred actual
3.0    9.0
7.0    3.0
4.0    9.0
2.0    3.0
3.0    2.0
6.0    5.0
9.0    7.0
9.0    3.0
8.0    6.0
9.0    4.0

actual 9.0
pred 3.0

列表 8-1 还显示了数字 8-1 和 8-2 。图 8-1 显示混淆矩阵,图 8-2 显示第一次误分类。

img/481580_1_En_8_Fig2_HTML.jpg

图 8-2

第一次错误预测

img/481580_1_En_8_Fig1_HTML.jpg

图 8-1

混淆矩阵

向有钱人解释 MNIST

代码示例显示了我们从训练 MNIST 数据集中学到了多少。MNIST 数据代表从 0 到 9 的数字图像。数据集中的每个元素都由作为一组像素的图像以及该图像所代表的内容组成。也就是说,这组像素(就像我们在电视屏幕上看到的图像)代表 0 到 9 之间的一个数字。

例如,MNIST 数据中的第一个数据元素由一组表示数字 0 的像素组成。因此,我们可以基于这样的知识来训练整个数据集,即每个数据元素由一组像素组成,在这些像素上组成数字图像,并且实际数字是 0 到 9 之间的数字。

向投资者解释产出

输出显示我们了解了数据集的所有内容,因为训练性能是 1.0(或 100%)。这听起来很棒,但我们必须考虑到我们从新数据中学习的程度。

通常,机器学习实践者从数据集中切下一部分数据,并在训练过程中隐藏这些数据。也就是说,他们只对一部分数据进行训练,并使用在训练前切掉的隐藏(或未触及)数据上所学到的东西。我们从中学习的数据称为训练数据,而在训练过程中未被触及的隐藏数据称为测试数据。

在新数据(或测试数据)上使用我们从训练数据中学到的东西是至关重要的,因为我们根据测试数据来预测未来的性能。我们使用测试数据预测未来的原因是,技术培训过程从未见过测试数据。因此,测试数据是我们收集的未来数据的代表。

在测试数据(或新数据)上的训练性能为 97.32%。所以,训练是成功的,因为我们有信心超过 97%的预测是正确的。

输出的其余部分只是显示一些错误分类(或错误),以帮助机器学习专家验证结果。请记住,虽然性能超过 97%,但我们仍然有 2.68%的误差。

向理财人士解释混淆矩阵

混淆矩阵之所以得名,是因为它可能会混淆矩阵中每个数字的推导方式。然而,解释矩阵的实际输出非常简单。

左侧下方的数字表示每个数字的预测值(标记为预测值),底部的数字表示每个数字的实际值(标记为真值)。正确的预测是从左上角到右下角对角线上的数字。不正确的预测是对角线上的数字而不是

我们的混淆矩阵显示,我们正确预测了 1621 0 位数字(左上角)。当我们沿着对角线向下移动时,我们看到我们正确地预测了 2011 1 位数,等等。

要更详细地了解预测性能,我们可以查看每个数字的预测或实际数字是如何预测的。为了分析每个数字的预测,我们查看行值。为了分析实际数字是如何预测的,我们来看看列值。

让我们先来看看数字 0 的预测,它们从左到右位于顶行。第一行值是 1621(混淆矩阵的左上角),代表正确的预测。因此,我们正确地预测了数字 0 的 1621 次。这一行剩余的数字代表我们预测的数字 0,但实际的数字是其他值。例如,顶行的最后一个值是 11。因此,当实际值是数字 9 时,我们做了 11 个不正确的数字 0 预测。

现在,让我们来看看当实际值为数字 0 时,位于第一列上下的预测。第一列值是 1621。因为这个数字在对角线上,所以它代表数字 0 的正确预测。但是,数字 0 被错误地预测为数字 2 两次,数字 5 一次,数字 6 四次,数字 7 一次,数字 8 七次,数字 9 一次。

因此,混淆矩阵的结果可以有两种解释。一种方法是看预测性能。另一种方法是观察实际值是如何预测的。然而,这两种方式都导致相同的结果。

例如,进一步研究的明确领域是找出为什么数字 9 有这么多不正确的预测。最坏的原因是显示二十八(28)个不正确预测的值。我们可以认为这是 28 次错误地预测了数字 9,而实际值是数字 4。相反,我们可以把这看作是数字 4 被错误地预测为数字 9 28 次。

混淆矩阵是一个很好的方法来看看我们基于新数据的预测有多好。它还提供了一种识别问题区域的方法。例如,我们错误预测数字 9 的次数比任何其他数字都多。因此,机器学习专家可以确定哪些地方需要做更多的工作来提高性能。

向金融人士解释可视化

可视化显示了我们在训练中做出的第一个错误预测。我们可以清楚地看到,实际数字是 9(中间的大图像),但它被归类为数字 3(位于左上方的红色小图像)。像这种可视化的线索可以节省时间和金钱,因为我们至少可以推测为什么训练认为数字是 3,而实际上是 9。也许训练不知何故看到了左上角开口关闭的图像,这将使它看起来像一个 9。

价值和成本

数字识别是更复杂的图像识别项目(如人脸识别)的一个有价值的起点。我们从图像识别中学到的东西直接有助于人脸识别学习练习。

我们证明了我们的训练对新数据提供了超过 97%的准确率。也就是说,我们知道 100 次预测中至少有 97 次是正确的。因此,我们可以放心地使用我们在其他机器学习项目中所学到的东西。

我们也能够评估字面上的成本。我们的培训成本是 2.68%。也就是说,在 100 次预测中,我们不正确的预测不到 3 次。

最后,借助混淆矩阵和可视化,我们可以轻松确定潜在的改进和成本节约领域。混乱矩阵向我们展示了训练失败的地方。可视化给出了为什么训练可能做出不正确预测的线索。虽然我们只展示了一个可视化,但我们可以为进一步的研究制作所有不正确预测的可视化。

高效定位潜在问题领域的能力节省了机器学习专家的时间和金钱,因为他们至少有集中精力的教育线索。没有这样的线索,他们将在黑暗中进行实验。

具有图像识别的训练练习也提供了潜在的竞争优势。我们可以使用这样的练习来进行更复杂的图像识别实验,如人脸和文本识别。图像识别提供了人工识别人们正在交流什么以及他们如何出现的基础。由于进入机器学习的热潮令人难以置信,我们可以从这个例子中了解其潜在的好处和成本。

最后,像 MNIST 这样的图像数据有力地证明了为什么机器学习专家需要原始形式的数据。(任何类型的)图像数据都不适合存储在关系系统或遗留系统中,也不能从这些系统中访问。机器学习专家需要在其他人处理和稀释原始形式的数据之前,在数据进入组织时获得这些数据。

当然,向有钱人展示计算开销要困难得多。然而,这个极其简单的例子仅仅运行一个已经调好的算法就要花费一分多钟。因此,当我们创建更复杂、计算成本更高的例子时,我们可以向有钱人展示更直接的好处,比如准确破译书面数字的能力。

fetch_lfw_people 价值和成本

我们继续之前调优的 fetch_lfw_people 代码示例,如清单 8-2 所示。我们加载数据,运行机器学习算法,并显示结果。然后,我们从商业价值和成本的角度解释结果。

import numpy as np
from random import randint
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt
import seaborn as sns

def find_misses(test, pred):
    return [i for i, row in enumerate(test) if row != pred[i]]

def find_hit(n, ls):
    return True if n in ls else False

def build_fig(indx, pos, color, one, two):
    X_i = np.array(X_test[indx]).reshape(50, 37)
    t = targets[y_test[indx]]
    p = targets[y_pred[indx]]
    ax = fig.add_subplot(pos)
    image = ax.imshow(X_i,  cmap='bone')
    ax.set_axis_off()
    ax.set_title(t)
    ax.text(one, two, p, color=color,
            bbox=dict(facecolor='white'))

def chk_acc(rnds):
    logic = [1 if y_test[row] == y_pred[row] else 0
             for row in rnds]
    colors = ['g' if row == 1 else 'r' for row in logic]
    return colors

if __name__ == "__main__":
    br = '\n'

    X = np.load('data/X_faces.npy')
    y = np.load('data/y_faces.npy')
    bp = np.load('data/bp_face.npy')
    bp = bp.tolist()
    images = np.load('data/faces_images.npy')
    targets = np.load('data/faces_targets.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    pca = PCA(n_components=0.95, whiten=True, random_state=0)
    pca.fit(X_train)
    X_train_pca = pca.transform(X_train)
    X_test_pca = pca.transform(X_test)
    svm = SVC(**bp)
    svm.fit(X_train_pca, y_train)
    y_pred = svm.predict(X_test_pca)
    print ()
    cr = classification_report(y_test, y_pred)
    print (cr)
    misses = find_misses(y_test, y_pred)
    miss = misses[0]
    hit = 1
    X_hit = np.array(X_test[hit]).reshape(50, 37)
    y_test_hit = targets[y_test[hit]]
    y_pred_hit = targets[y_pred[hit]]
    X_miss = np.array(X_test[miss]).reshape(50, 37)
    y_test_miss = targets[y_test[miss]]
    y_pred_miss = targets[y_pred[miss]]
    fig = plt.figure('1st Hit and Miss')
    fig.suptitle('Visualize 1st Hit and Miss',
                 fontsize=18, fontweight="bold")
    build_fig(hit, 121, 'g', 0.4, 1.9)
    build_fig(miss, 122, 'r', 0.4, 1.9)
    rnd_ints = [randint(0, y_test.shape[0]-1)
                for row in range(4)]
    colors = chk_acc(rnd_ints)
    fig = plt.figure('Four Random Predictions')
    build_fig(rnd_ints[0], 221, colors[0], .9, 4.45)
    build_fig(rnd_ints[1], 222, colors[1], .9, 4.45)
    build_fig(rnd_ints[2], 223, colors[2], .9, 4.45)
    build_fig(rnd_ints[3], 224, colors[3], .9, 4.45)
    plt.tight_layout()
    plt.show()

Listing 8-2fetch_lfw_people value and cost

执行清单 8-2 的输出应该如下所示:

              precision    recall  f1-score   support

           0       1.00      0.64      0.78        28
           1       0.76      0.92      0.83        63
           2       0.91      0.88      0.89        24
           3       0.88      0.92      0.90       132
           4       0.74      0.85      0.79        20
           5       1.00      0.64      0.78        22
           6       0.90      0.85      0.88        33

   micro avg       0.86      0.86      0.86       322
   macro avg       0.89      0.81      0.84       322
weighted avg       0.87      0.86      0.86       322

列表 8-2 还显示了数字 8-3 和 8-4 。图 8-3 显示了第一个正确预测和第一个错误预测的可视化。图 8-4 显示了四个随机预测的可视化。

img/481580_1_En_8_Fig4_HTML.jpg

图 8-4

四个随机预测

img/481580_1_En_8_Fig3_HTML.jpg

图 8-3

首次正确和首次错误预测

向投资者解释投资人

该代码示例显示了我们从 fetch_lfw_people 数据集的训练中学习的程度。fetch_lfw_people 数据集是名人的 JPEG 图像集合。数据集中的每个元素由图像和图像所代表的人组成。

向投资者解释产出

测试数据(或新数据)的训练性能为 87%。该值显示在左侧精度栏的底部。所以,我们有信心 87%的预测是正确的。但是,这也意味着我们有 13%的时间是不正确的。当然,这个例子是非常简单和便宜的。随着面部识别技术的不断进步,学习精度接近完美。然而,与捕获图像、提取样本以创建模板、将收集的数据与现有模板进行比较以及将收集的数据与工业水平的模板进行匹配相关联的成本可能是昂贵的。

虽然面部识别是一个非常复杂的话题,但可视化使我们很容易看到我们从数据中学到了什么。我们只显示了四个随机预测,但我们可以显示更多的预测以供进一步分析。

向金融人士解释可视化

图 8-3 和图 8-4 中显示的两种可视化表示我们根据从数据中了解到的情况做出的预测。图 8-3 显示一个正确的预测(绿色中的名字嵌入图中)和一个错误的预测(红色中的名字嵌入图中)。图 8-4 显示了四个随机预测。请注意,我们做出了三个正确的预测,只有一个错误的预测。也就是说,我们正确地预测了科林·鲍威尔、乔治·w·布什和阿里尔·沙龙,而我们错误地预测了乌戈·查韦斯是乔治·w·布什。

价值和成本

面部识别是发展最快的生物识别技术,其唯一目的是识别人脸。苹果的 iPhone X 已经在使用出色的面部识别技术来解锁智能手机。

面部识别至关重要的一个相关领域是安全性。组织可以通过跟踪安全区域中的员工和访客移动,使用这种技术来保护他们的场所。

另一个领域是与现有软件的集成。目前的面部识别技术工具与现有的安全软件配合良好。这种简单的集成对企业来说非常重要,因为组织不需要花费额外的资金和时间来重新开发自己的系统以使用面部识别技术。

随着 3D 面部识别技术和红外摄像机的出现,当前面部识别技术的准确性比以往任何时候都高。当然,准确性是极其重要的,因为错误的识别可能是有害的。

面部识别系统可以完全自动化。因此,组织不需要员工监控摄像头。

最后,时间欺诈可以大幅减少。由于每个人都必须通过面部扫描设备来登记(或检查)工作或参观场所,所以组织不必担心安全人员的友情。此外,由于技术控制了入住或退房流程,这一过程要快得多。如果出现问题,技术还可以记录活动。

但是,根据需要收集和处理的数据量,数据捕获和处理的成本可能会很高。初始和持续的技术成本也可能很高。不要忘记,组织仍然需要有能力的人来监控、修复、更新和升级技术。想法是将成本从手工流程转移到自动化流程。从短期来看,成本可能会很高,但从长期来看,自动化应该会显著降低成本,提高流程效率,并减少人为错误。

图像质量也是一个问题。例如,组织可以存储不同质量级别的图像。高质量的图像需要更多的存储空间,但在实时匹配实际人脸时会更好。当然,更多的存储是昂贵的。

监视角度也是一个问题。捕捉新图像必须从不同的角度进行处理,因为可以从不同的角度实时看到真实的人脸。戴墨镜、不刮胡子、散发不同的面部表情,甚至是不同的发型也会让人看起来与众不同。

最好的技术会考虑这些问题。当然,最好的技术也是要花钱的。然而,计算机能做的事,人做不到。一个例子是同时将人脸与数千人的数据库进行比较。

随着面部识别选项变得更具成本竞争力,组织可能别无选择,只能参与这项技术。我们已经讨论了面部识别的价值和成本。因此,自动化带来的成本节约、更高的准确性和更少的欺诈提供了不可忽视的竞争优势。

fetch _ 新闻组值和开销

我们以之前调优的 fetch_20newsgroups 代码示例结束,如清单 8-3 所示。我们采用了一个更现实的场景,从我们训练的文档子集中删除了页眉、页脚和引号。

我们这样做的原因是为了消除关于文档主题的线索,以确保机器学习算法可以正确地从不容易识别的文本中识别含义。请记住,页眉、页脚和引号通常不会出现在大多数书面文件和书面交流中。

我们加载数据,运行机器学习算法,并显示结果。然后,我们从商业价值和成本的角度解释结果。

import numpy as np
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
from sklearn.metrics import f1_score, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

def predict_category(s, m, t):
    pred = m.predict([s])
    return t[pred[0]]

if __name__ == "__main__":
    br = '\n'
    train = fetch_20newsgroups(subset='train')
    test = fetch_20newsgroups(subset='test')
    categories = ['rec.autos', 'rec.motorcycles', 'sci.space', 'sci.med']
    train = fetch_20newsgroups(subset='train', categories=categories, remove=('headers', 'footers', 'quotes'))
    test = fetch_20newsgroups(subset='test', categories=categories, remove=('headers', 'footers', 'quotes'))
    targets = train.target_names
    print ('targets:')
    print (targets, br)
    bp = np.load('data/bp_news.npy')
    bp = bp.tolist()
    print ('best parameters:')
    print (bp, br)
    mnb = MultinomialNB(alpha=0.01, fit_prior=False)
    tf = TfidfVectorizer(ngram_range=(1, 1), use_idf=False)
    pipe = make_pipeline(tf, mnb)
    pipe.fit(train.data, train.target)
    labels = pipe.predict(test.data)
    f1 = f1_score(test.target, labels, average="micro")
    print ('f1_score', f1, br)
    labels = pipe.predict(test.data)
    cm = confusion_matrix(test.target, labels)
    plt.figure('confusion matrix')
    sns.heatmap(cm.T, square=True, annot=True, fmt="d", xticklabels=train.target_names, yticklabels=train.target_names, cbar=False)
    plt.xlabel('true label')
    plt.ylabel('predicted label')
    plt.tight_layout()
    print ('***PREDICTIONS***:')
    doc1 = 'imagine the stars ...'
    doc2 = 'crashed on highway without seatbelt

'
    doc3 = 'dad hated the medicine ...'
    y_pred = predict_category(doc1, pipe, targets)
    print (y_pred)
    y_pred = predict_category(doc2, pipe, targets)
    print (y_pred)
    y_pred = predict_category(doc3, pipe, targets)
    print (y_pred)
    plt.show()

Listing 8-3fetch_20newsgroups value and cost

执行清单 8-3 的输出应该如下所示:

targets:
['rec.autos', 'rec.motorcycles', 'sci.med', 'sci.space']

best parameters:
{'tfidfvectorizer__use_idf': False, 'tfidfvectorizer__ngram_range': (1, 1), 'multinomialnb__fit_prior': False, 'multinomialnb__alpha': 0.01}

f1_score 0.8680555555555556

***PREDICTIONS***:
sci.space
rec.autos
sci.med

列表 8-3 也显示图 8-5 。图 8-5 显示了混淆矩阵。

img/481580_1_En_8_Fig5_HTML.jpg

图 8-5

混淆矩阵

向金融人士解释 fetch _ 新闻组

该代码示例演示了我们从 fetch_20newsgroups 数据集的训练中学到了什么。fetch_20newsgroups 数据集是 20 个主题的 18000 篇新闻组帖子的集合。对于这个训练示例,我们将数据集过滤为四个子集——汽车、摩托车、空间和医药——以简化和减少计算开销。

向投资者解释产出

我们首先显示从中学习的子集。接下来,我们显示最佳参数,以便机器学习专家可以适当地构建提供最佳性能的算法。

测试数据(或新数据)上的训练性能几乎是 87%。该值显示为 f1_score。因此,我们有信心几乎 87%的预测是正确的,但这也意味着我们有超过 13%的时间是不正确的。

最后,我们从三个简单的文档中进行预测。请注意,作为人类,我们可以很容易地预测想象中的星星……属于哪一类,但学习实验并不像我们一样拥有如此丰富的词汇知识。它与复杂的文档操作技术和机器学习算法一起工作,产生相当好的结果。

向投资者解释混淆矩阵

因为我们已经在第一个代码示例中解释了什么是混淆矩阵以及如何分析它,所以我们在这里不再重复。然而,它确实展示了一些有趣的见解。

注意第一行第二列,我们做了 47 个错误的预测。在这种情况下,我们错误地预测了汽车,而实际值是摩托车。这有一定的意义,因为学习算法在区分两种类型的车辆方面比其他不正确的预测更困难。

相反,当实际值是汽车时,我们错误地预测了 34 次摩托车。我们再次看到,学习算法很难区分这两种类型的车辆,所以大多数不正确的预测是在试图区分两种类型的车辆时做出的。

价值和成本

从文本中提取意义通常被称为文本挖掘。文本挖掘是从文本中获取高质量信息的一种手段。高质量的信息没有价值,除非它能带来商业洞察力(或价值)。

由于组织收集的大多数数据从未被分析过,因此文本挖掘提供了一种高效且有望有效的分析方法。一个组织高效的收集和挖掘文本,并不代表它能创造价值。

至少可以在三个有影响力的领域创造价值。首先,它可以增强合规性和威胁检测。其次,它可以培养客户参与度。第三,它可以促进更好的决策。

通过提供早期欺诈检测(如洗钱),挖掘文本可以极大地影响合规性问题。组织还需要自动遵守策略和程序的方法。文本挖掘可以通过检测文本输入(如在线表单、电子邮件、文本和其他消息服务)中的不符合性来自动化此类过程。对安全的威胁也可以通过挖掘进出组织的消息来检测。

通过法规遵从性和威胁检测服务的自动化,成本肯定会降低,因为管理此类程序所需的人员减少了。如果涉及的人员较少,人为错误也会减少。

与客户的互动为文本挖掘提供了不可思议的机会。例如,亚马逊分析用户偏好,以便更好地告知客户他们可能想要购买的产品。

文本挖掘是了解客户想法的自然工具。也许顾客对目前的服务不满意。也许客户想要目前没有提供的产品或服务。一旦一个组织想出了如何从客户那里获得洞察力,它就可以自动化这样的过程。自动化节省了时间和金钱,减少了人为错误。

如果一个组织能够洞察客户的想法,价值就被创造出来了。如果一个组织能够自动化这种洞察力,竞争优势就产生了。想象一下,如果你的竞争对手不参与文本挖掘。即使他们参与文本挖掘,您的组织的竞争优势是更高效和有效地管理客户洞察的能力。

最后,文本挖掘可以带来更好的商业决策。数据分析师和经理需要数据来为业务提供准确的见解。文本挖掘为获得准确的洞察力提供了一个强大的工具。

当然,启动、实现、管理和监控文本挖掘活动是有成本的。首先,您需要胜任的文本挖掘人员、足够的计算资源、利用文本挖掘创建业务洞察力的顶级支持,以及组建由技术和业务成员混合组成的团队的能力。

文本挖掘专家擅长实现算法,但在定义组织正在寻求的适当业务洞察力方面需要帮助。因此,非常需要一个由来自组织不同方面的各种团队成员组成的多样化团队。只有当你的组织比你的竞争对手更擅长文本挖掘时,你才能获得竞争优势。

posted @   绝不原创的飞龙  阅读(63)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2020-10-02 《线性代数》(同济版)——教科书中的耻辱柱
点击右上角即可分享
微信分享提示