应用人工智能研讨会(二)

原文:annas-archive.org/md5/0323474b1674bee4360c291447287161

译者:飞龙

协议:CC BY-NC-SA 4.0

第三章:3. 分类简介

概述

本章将带您了解分类。您将实现各种技术,如 k 近邻和支持向量机(SVM)。您将使用欧几里得距离和曼哈顿距离来处理 k 近邻算法。您将应用这些概念来解决一些有趣的问题,例如预测一个信用卡申请人是否有违约风险,或者判断一个员工是否会在公司工作超过两年。在本章结束时,您将足够自信使用分类来处理任何数据,并得出明确结论。

简介

在上一章中,您了解了回归模型,并学习了如何拟合一个包含单一或多个变量的线性回归模型,以及如何使用高次多项式进行拟合。

与回归模型不同,回归模型侧重于学习如何预测连续的数值(这些数值可以有无限多种可能性),而分类问题(将在本章中介绍)则是将数据划分成不同的组,也叫做类。

例如,模型可以被训练来分析电子邮件,并预测它们是否是垃圾邮件。在这种情况下,数据被分类为两种可能的类别(或类)。这种分类类型也称为二分类,我们将在本章中看到一些例子。然而,如果有多个(超过两个)类别(或类),那么您将处理多分类问题(您将在第四章,决策树简介中遇到一些示例)。

那么什么是真实世界中的分类问题呢?考虑一个模型,试图预测一个用户对电影的评分,其中评分只能取以下值:喜欢中立不喜欢。这是一个分类问题。

在本章中,我们将学习如何使用 k 近邻分类器和 SVM 算法进行数据分类。就像我们在上一章中做的回归一样,我们将基于清理和准备好的训练数据来构建分类器,并使用测试数据来测试分类器的性能。

我们将从分类的基础知识开始。

分类的基础

如前所述,任何分类问题的目标是使用训练集将数据准确地划分为相关的组。此类项目在不同行业中有很多应用,例如在教育行业中,模型可以预测学生是否通过考试;在医疗保健行业中,模型可以评估每位患者某种疾病的严重程度。

分类器是一个模型,用于确定任何数据点所属的标签(输出)或值(类)。例如,假设您有一组观察数据,包含信用良好的个人,另一组则包含信用偿还倾向上存在风险的个人。

我们将第一组称为 P,第二组称为 Q。以下是此类数据的示例:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_01.jpg

](https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_01.jpg)

图 3.1:示例数据集

使用这些数据,你将训练一个分类模型,该模型能够将新的观察结果正确分类到这两组之一(这是二分类问题)。模型可以找到一些模式,例如年薪超过 60,000 美元的人风险较低,或者拥有超过 10 倍的按揭/收入比率使个人更有可能无法偿还债务。这将是一个多类别分类练习。

分类模型可以分为不同的算法家族。最著名的几种如下:

  • 基于距离的算法,如k-近邻

  • 线性模型,如逻辑回归支持向量机(SVM)

  • 基于树的算法,如随机森林

在本章中,你将接触到来自前两种类型家族的两种算法:k-近邻(基于距离的)和支持向量机(SVM)(线性模型)。

注意

我们将在第四章《决策树简介》中为你讲解基于树的算法,如随机森林。

但是,在深入研究模型之前,我们需要清理并准备本章将要使用的数据集。

在接下来的部分,我们将使用德国信用批准数据集,并进行所有数据准备,以便进入建模阶段。我们先从加载数据开始。

练习 3.01:预测信用卡违约风险(加载数据集)

在本练习中,我们将数据集加载到 pandas DataFrame 中并探索其内容。我们将使用德国信用批准的数据集来判断一个人是否有违约风险。

注意

该数据集的 CSV 版本可以在我们的 GitHub 仓库中找到:

packt.live/3eriWTr

原始数据集及其相关信息可以在 archive.ics.uci.edu/ml/datasets/Statlog+%28German+Credit+Data%29 找到。

数据文件位于 archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/

引用 - Dua, D., & Graff, C… (2017). UCI 机器学习库

  1. 打开一个新的 Jupyter Notebook 文件。

  2. 导入 pandas 包并将其命名为 pd

    import pandas as pd
    
  3. 创建一个新变量 file_url,它将包含原始数据集文件的 URL,如以下代码片段所示:

    file_url = 'https://raw.githubusercontent.com/'\
               'PacktWorkshops/'\
               'The-Applied-Artificial-Intelligence-Workshop/'\
               'master/Datasets/german_credit.csv'
    
  4. 使用 pd.read_csv() 方法导入数据:

    df = pd.read_csv(file_url)
    
  5. 使用 df.head() 打印 DataFrame 的前五行:

    df.head()
    

    期望的输出是这样的:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_02.jpg

    图 3.2:数据集的前五行

    如你所见,前面的截图输出展示了数据集的特征,这些特征可以是数值型或类别型(文本)。

  6. 现在,使用df.tail()打印 DataFrame 的最后五行:

    df.tail()
    

    预期的输出是这样的:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_03.jpg

    图 3.3:数据集的最后五行

    DataFrame 的最后几行与我们之前看到的前几行非常相似,因此我们可以假设行间结构是一致的。

  7. 现在,使用df.dtypes打印列及其数据类型的列表:

    df.dtypes
    

    预期的输出是这样的:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_04.jpg

    ](https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_04.jpg)

图 3.4:列及其数据类型的列表

注意

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

你还可以在线运行此示例,网址为packt.live/3fN0DrT。你必须执行整个 Notebook,才能获得期望的结果。

从前面的输出中,我们可以看到该 DataFrame 有一些数值特征(int64),但也有文本特征(object)。我们还可以看到这些特征大多数是与个人相关的细节,如年龄,或财务信息,如信用历史或信用额度。

通过完成这个练习,我们已经成功地将数据加载到 DataFrame 中,并且初步了解了它所包含的特征和信息。

在接下来的章节中,我们将讨论如何对这些数据进行预处理。

数据预处理

在构建分类器之前,我们需要格式化数据,以便将相关数据保持在最适合分类的格式中,并删除我们不感兴趣的所有数据。

以下几点是实现这一目标的最佳方法:

  • 数据集中的N/A(或NA)值,我们可能最好将这些值替换为我们可以处理的数值。回顾上一章,NA表示缺失值,或者将其替换为异常值。

    df.fillna(-1000000, inplace=True)
    

    fillna()方法将所有NA值更改为数值。

    这个数值应该远离 DataFrame 中任何合理的值。负一百万被分类器识别为异常值,假设只有正值存在,如前面的说明所提到的。

  • 0) 指定我们删除行,而不是列。第二个参数(inplace=True)指定我们在不克隆 DataFrame 的情况下执行删除操作,并将结果保存在同一个 DataFrame 中。由于该 DataFrame 没有缺失值,因此dropna()方法没有改变 DataFrame。

    df.drop(['telephone'], 1, inplace=True)
    

    第二个参数(值为1)表示我们要删除列,而不是行。第一个参数是我们想要删除的列的枚举(在这里是['telephone'])。inplace参数用于让该操作修改原始的 DataFrame。

  • MinMaxScaler方法属于 scikit-learn 的preprocessing工具,代码片段如下所示:

    from sklearn import preprocessing
    import numpy as np
    data = np.array([[19, 65], \
                     [4, 52], \
                     [2, 33]])
    preprocessing.MinMaxScaler(feature_range=(0,1)).fit_transform(data)
    

    预期的输出是这样的:

    array([[1\.        , 1\.        ],
           [0.11764706, 0.59375   ],
           [0\.        , 0\.        ]])
    

    二值化将数据基于条件转换为 1 和 0,如下代码片段所示:

    preprocessing.Binarizer(threshold=10).transform(data)
    

    预期输出是这样的:

    array([[1, 1],
           [0, 1],
           [0, 1]])
    

在上面的例子中,我们根据每个值是否大于 10(由 threshold=10 参数定义)将原始数据 ([19, 65],[4, 52],[2, 33]) 转换为二进制形式。例如,第一个值 19 大于 10,因此在结果中被替换为 1

标签编码对于准备特征(输入)以进入建模阶段非常重要。尽管某些特征是字符串标签,scikit-learn 算法期望这些数据转换为数字。

这时,scikit-learn 的 preprocessing 库派上了用场。

注意

你可能注意到,在信用评分的例子中,有两个数据文件。一个包含字符串形式的标签,另一个包含整数形式的标签。我们加载了带字符串标签的数据,以便你能体验如何使用标签编码器正确地预处理数据。

标签编码不是火箭科学。它创建了字符串标签和数值之间的映射,以便我们可以向 scikit-learn 提供数字,以下是一个示例:

from sklearn import preprocessing
labels = ['Monday', 'Tuesday', 'Wednesday', \
          'Thursday', 'Friday']
label_encoder = preprocessing.LabelEncoder()
label_encoder.fit(labels)

让我们列举一下编码:

[x for x in enumerate(label_encoder.classes_)]

预期输出是这样的:

[(0, 'Friday'),
 (1, 'Monday'),
 (2, 'Thursday'),
 (3, 'Tuesday'),
 (4, 'Wednesday')]

上面的结果显示,scikit-learn 为每周的每一天创建了一个映射关系;例如,Friday 映射为 0Tuesday 映射为 3

注意

默认情况下,scikit-learn 通过按字母顺序排序原始值来分配映射的数字。这就是为什么 Friday 被映射为 0 的原因。

现在,我们可以使用这个映射(也叫做编码器)来转换数据。

让我们通过 transform() 方法在两个例子上试试:WednesdayFriday

label_encoder.transform(['Wednesday', 'Friday'])

预期输出是这样的:

array([4, 0], dtype=int64)

如预期所示,我们得到了 40 的结果,这分别是 WednesdayFriday 的映射值。

我们还可以使用这个编码器通过 inverse_transform 函数执行逆向转换。让我们用值 04 来试试:

label_encoder.inverse_transform([0, 4])

预期输出是这样的:

array(['Friday', 'Wednesday'], dtype='<U9')

如预期所示,我们得到了 FridayWednesday 的值。现在,让我们在德国数据集上练习我们学到的内容。

练习 3.02:应用标签编码将类别变量转换为数值变量

在本练习中,我们将使用我们刚学到的一个预处理技术——标签编码,将所有类别变量转换为数值变量。在训练任何机器学习模型之前,这一步是必要的。

注意

我们将使用与上一个练习中相同的数据集:德国信用审批数据集:packt.live/3eriWTr

以下步骤将帮助你完成此练习:

  1. 打开一个新的 Jupyter Notebook 文件。

  2. 导入 pandas 包并命名为 pd

    import pandas as pd
    
  3. 创建一个新的变量 file_url,其中将包含原始数据集的 URL:

    file_url = 'https://raw.githubusercontent.com/'\
               'PacktWorkshops/'\
               'The-Applied-Artificial-Intelligence-Workshop/'\
               'master/Datasets/german_credit.csv'
    
  4. 使用 pd.read_csv() 方法加载数据:

    df = pd.read_csv(file_url)
    
  5. 导入scikit-learn中的preprocessing

    from sklearn import preprocessing
    
  6. 定义一个名为fit_encoder()的函数,该函数接受一个 DataFrame 和列名作为参数,并在该列的值上拟合一个标签编码器。你将使用preprocessing中的.LabelEncoder().fit()以及pandas中的.unique()(它将提取 DataFrame 列中的所有可能值):

    def fit_encoder(dataframe, column):
        encoder = preprocessing.LabelEncoder()
        encoder.fit(dataframe[column].unique())
        return encoder
    
  7. 定义一个名为encode()的函数,该函数接受一个 DataFrame、列名和标签编码器作为参数,并使用标签编码器转换该列的值。你将使用.transform()方法来完成这项工作:

    def encode(dataframe, column, encoder):
        return encoder.transform(dataframe[column])
    
  8. 创建一个名为cat_df的新 DataFrame,其中只包含非数字列,并打印其前五行。你将使用 pandas 中的.select_dtypes()方法,并指定exclude='number'

    cat_df = df.select_dtypes(exclude='number')
    cat_df.head()
    

    预期输出(并未显示所有列)如下:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_05.jpg

    图 3.5:只包含非数字列的 DataFrame 前五行

  9. 创建一个名为cat_cols的列表,其中包含cat_df的列名,并打印其内容。你将使用 pandas 中的.columns来完成:

    cat_cols = cat_df.columns
    cat_cols
    

    预期输出如下:

    Index(['account_check_status', 'credit_history', 'purpose', 
           'savings', 'present_emp_since', 'other_debtors', 
           'property', 'other_installment_plans', 'housing', 
           'job', 'telephone', 'foreign_worker'], dtype='object')
    
  10. 创建一个for循环,迭代cat_cols中的每一列,使用fit_encoder()来拟合标签编码器,并用encode()函数转换该列:

    for col in cat_cols:
        label_encoder = fit_encoder(df, col)
        df[col] = encode(df, col, label_encoder)
    
  11. 打印df的前五行:

    df.head()
    

    预期输出如下:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_06.jpg

图 3.6:编码后的 DataFrame 前五行

注意

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

你也可以在网上运行这个示例,网址是packt.live/2YZhtx5。你必须执行整个 Notebook 才能得到期望的结果。

我们已经成功编码了非数字列。现在,我们的 DataFrame 仅包含数字值。

特征和标签识别

在训练模型之前,我们仍然需要执行两个最终步骤。第一个步骤是将特征与标签分开(也称为响应变量或因变量)。label列是我们希望模型预测的内容。对于德国信用数据集,在我们的案例中,它将是名为default的列,它告诉我们一个人是否存在违约风险。

特征是数据集中所有其他列。模型将使用这些列中的信息,找到相关的模式,以准确预测相应的标签。

scikit-learn 包要求标签和特征存储在两个不同的变量中。幸运的是,pandas 包提供了一个方法.pop()来提取 DataFrame 中的一列。

我们将提取default列,并将其存储在一个名为label的变量中:

label = df.pop('default')
label

预期输出如下:

0      0
1      1
2      0
3      0
4      1
      ..
995    0
996    0
997    0
998    1
999    0
Name: default, Length: 1000, dtype: int64

现在,如果我们查看df的内容,我们会看到default列已经不再存在:

df.columns

预期的输出结果是这样的:

Index(['account_check_status', 'duration_in_month',
       'credit_history', 'purpose', 'credit_amount',
       'savings', 'present_emp_since',
       'installment_as_income_perc', 'other_debtors',
       'present_res_since', 'property', 'age',
       'other_installment_plans', 'housing', 
       'credits_this_bank', 'job', 'people_under_maintenance',
       'telephone', 'foreign_worker'],
      dtype='object')

现在我们已经准备好了特征和标签,接下来需要将数据集分成训练集和测试集。

使用 Scikit-Learn 拆分数据为训练集和测试集

在训练分类器之前需要完成的最后一步是将数据拆分为训练集和测试集。我们已经在第二章回归简介中看过如何做了:

from sklearn import model_selection
features_train, features_test, \
label_train, label_test = \
model_selection.train_test_split(df, label, test_size=0.1, \
                                 random_state=8)

train_test_split方法会将我们的特征和标签打乱并拆分为训练集和测试集。

我们可以将测试集的大小指定为介于01之间的一个数字。test_size0.1意味着10%的数据将进入测试数据集。你还可以指定random_state,这样如果再次运行这段代码,结果将是完全相同的。

我们将使用训练集来训练我们的分类器,并使用测试集来评估其预测性能。通过这种方式,我们可以评估我们的模型是否过拟合,并且是否学习到仅对训练集相关的模式。

在接下来的章节中,我们将为你介绍著名的 k-近邻分类器。

K-近邻分类器

现在我们已经有了训练数据和测试数据,接下来是准备我们的分类器来执行 k-近邻分类。在介绍完 k-近邻算法后,我们将使用 scikit-learn 来执行分类。

介绍 K-近邻算法(KNN)

分类算法的目标是将数据划分,以便我们能够确定哪些数据点属于哪个组。

假设我们给定了一组已分类的点。我们的任务是确定一个新的数据点属于哪个类别。

为了训练一个 k-近邻分类器(也称为 KNN),我们需要为训练集中的每个观测值提供相应的类别,也就是它属于哪个组。该算法的目标是找出特征之间的相关关系或模式,这些关系或模式将引导至这个类别。k-近邻算法基于一种接近度度量,计算数据点之间的距离。

两种最著名的接近度(或距离)度量是欧几里得距离和曼哈顿距离。我们将在下一节中详细介绍。

对于任何新的给定点,KNN 将找到它的 k 个最近邻,查看这 k 个邻居中哪个类别最频繁,并将其分配给这个新的观测值。但你可能会问,k 是什么?确定 k 的值完全是任意的。你需要预先设置这个值。这不是一个可以由算法学习的参数;它需要由数据科学家设置。这类参数称为超参数。理论上,你可以将 k 的值设定为 1 到正无穷之间的任何数。

有两种主要的最佳实践需要考虑:

  • k 应该始终是一个奇数。这样做的原因是我们希望避免出现平局的情况。例如,如果你设置 k=4,恰好有两个邻居属于 A 类,另外两个邻居属于 B 类,那么 KNN 就无法决定应该选择哪个类。为了避免这种情况,最好选择 k=3k=5

  • k 越大,KNN 的准确度就越高。例如,如果我们比较 k=1k=15 的情况,后者会让你更有信心,因为 KNN 在做出决策之前会查看更多邻居。另一方面,k=1 只查看最接近的邻居,并将同一类分配给观测值。但我们怎么能确定它不是异常值或特殊情况呢?询问更多的邻居会降低做出错误决策的风险。不过,这也有一个缺点:k 越大,KNN 做出预测的时间就越长。这是因为它需要执行更多计算,才能获得观测点所有邻居之间的距离。因此,你需要找到一个“甜蜜点”,既能给出正确的预测,又不至于在预测时间上妥协太多。

使用 K 最近邻分类器的距离度量在 Scikit-Learn 中

许多距离度量方法都可以与 k 最近邻算法一起使用。我们将介绍其中最常用的两种:欧几里得距离和曼哈顿距离。

欧几里得距离

两个点 AB 之间的距离,其中 A=(a1, a2, …, an)B=(b1, b2, …, bn),是连接这两个点的线段的长度。例如,如果 A 和 B 是二维数据点,欧几里得距离 d 将如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_07.jpg

图 3.7:A 点和 B 点之间的欧几里得距离的可视化表示

计算欧几里得距离的公式如下:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_08.jpg

图 3.8:A 点和 B 点之间的距离

因为本书将使用欧几里得距离,接下来我们来看一下如何使用 scikit-learn 来计算多个点之间的距离。

我们需要从 sklearn.metrics.pairwise 导入 euclidean_distances。这个函数接受两组点并返回一个矩阵,矩阵包含每个点与第一组和第二组点之间的成对距离。

让我们以一个观测点 Z 为例,其坐标为 (4, 4)。在这里,我们希望计算与另外 3 个点 A、B 和 C 的欧几里得距离,这些点的坐标分别为 (2, 3)、(3, 7) 和 (1, 6):

from sklearn.metrics.pairwise import euclidean_distances
observation = [4,4]
neighbors = [[2,3], [3,7], [1,6]]
euclidean_distances([observation], neighbors)

预期的输出结果如下:

array([[2.23606798, 3.16227766, 3.60555128]])

这里,Z=(4,4) 和 B=(3,7) 之间的距离大约为 3.162,这就是我们在输出中得到的结果。

我们还可以计算同一组中各点之间的欧几里得距离:

euclidean_distances(neighbors)

预期的输出是这样的:

array([[0\.        , 4.12310563, 3.16227766],
       [4.12310563, 0\.        , 2.23606798],
       [3.16227766, 2.23606798, 0\.        ]])

包含 0 值的对角线对应于每个数据点与自身的欧几里得距离。这个矩阵是关于对角线对称的,因为它计算了两个点之间的距离以及反向的距离。例如,第一行的值 4.12310563 是 A 和 B 之间的距离,而第二行的相同值则是 B 和 A 之间的距离。

曼哈顿/汉明距离

曼哈顿(或汉明)距离的公式与欧几里得距离非常相似,但它并不使用平方根,而是依赖于计算数据点坐标差的绝对值:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_09.jpg

图 3.9:曼哈顿距离与汉明距离

你可以把曼哈顿距离想象成我们在网格上计算距离,而不是使用直线:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_10.jpg

图 3.10:曼哈顿距离在 A 和 B 之间的可视化表示

如上图所示,曼哈顿距离将遵循网格定义的路径,从 A 到达 B。

另一个有趣的属性是,A 和 B 之间可能有多个最短路径,但它们的曼哈顿距离将相等。在上面的示例中,如果网格中的每个单元格表示 1 单位,那么所有三条突出显示的最短路径的曼哈顿距离将都是 9。

欧几里得距离是更准确的距离泛化方法,而曼哈顿距离稍微容易计算一些,因为你只需要计算绝对值之间的差异,而不是计算平方差后再取平方根。

练习 3.03:在 Matplotlib 中展示 K 最近邻分类器算法

假设我们有一份员工数据列表。我们的特征是每周工作的小时数和年薪。我们的标签表示员工是否在公司工作超过 2 年。如果停留时间少于 2 年,则用零表示,若大于或等于 2 年,则用一表示。

我们希望创建一个三近邻分类器,来判断一名员工是否会在公司待满至少 2 年。

然后,我们希望使用这个分类器来预测一名要求每周工作 32 小时并且年收入 52,000 美元的员工是否会在公司工作 2 年或更长时间。

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

注意

上述数据集可以在 GitHub 上找到,链接为 packt.live/2V5VaV9

  1. 打开一个新的 Jupyter Notebook 文件。

  2. 导入 pandas 包并命名为 pd

    import pandas as pd
    
  3. 创建一个新的变量,命名为 file_url(),它将包含原始数据集的 URL:

    file_url = 'https://raw.githubusercontent.com/'\
               'PacktWorkshops/'\
               'The-Applied-Artificial-Intelligence-Workshop/'\
               'master/Datasets/employees_churned.csv'
    
  4. 使用 pd.read_csv() 方法加载数据:

    df = pd.read_csv(file_url)
    
  5. 打印数据框的行:

    df
    

    预期的输出是这样的:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_11.jpg

    图 3.11:员工数据集的 DataFrame

  6. scikit-learn导入preprocessing

    from sklearn import preprocessing
    
  7. 实例化一个MinMaxScaler,并设置feature_range=(0,1),将其保存到名为scaler的变量中:

    scaler = preprocessing.MinMaxScaler(feature_range=(0,1))
    
  8. 使用.fit_transform()缩放数据框,将结果保存到名为scaled_employees的新变量中,并打印其内容:

    scaled_employees = scaler.fit_transform(df)
    scaled_employees
    

    预期输出如下:

    array([[0\.        , 0.18518519, 0\.        ],
           [0.2       , 0\.        , 0\.        ],
           [0.6       , 0.11111111, 0\.        ],
           [0.2       , 0.37037037, 0\.        ],
           [1\.        , 0.18518519, 0\.        ],
           [1\.        , 0.62962963, 1\.        ],
           [1\.        , 0.11111111, 1\.        ],
           [0.6       , 0.37037037, 1\.        ],
           [1\.        , 1\.        , 1\.        ],
           [0.6       , 0.55555556, 1\.        ]])
    

    在前面的代码片段中,我们已经将原始数据集缩放,使得所有值都在 0 到 1 之间。

  9. 从缩放后的数据中提取每一列,并将它们保存到名为hours_workedsalaryover_two_years的三个变量中,如下所示的代码片段所示:

    hours_worked = scaled_employees[:, 0]
    salary = scaled_employees[:, 1]
    over_two_years = scaled_employees[:, 2]
    
  10. 导入matplotlib.pyplot包,并命名为plt

    import matplotlib.pyplot as plt
    
  11. 使用plt.scatter创建两个散点图,hours_worked作为* x 轴,salary作为 y 轴,然后根据over_two_years的值创建不同的标记。你可以使用plt.xlabelplt.ylabel添加 x 轴和 y *轴的标签。使用plt.show()显示散点图:

    plt.scatter(hours_worked[:5], salary[:5], marker='+')
    plt.scatter(hours_worked[5:], salary[5:], marker='o')
    plt.xlabel("hours_worked")
    plt.ylabel("salary")
    plt.show()
    

    预期输出如下:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_12.jpg

    + points represent the employees that stayed less than 2 years, while the o ones are for the employees who stayed for more than 2 years. 
    

    现在,假设我们有一个新的观察值,并且我们想计算与缩放数据集中的数据的欧几里得距离。

  12. 创建一个名为observation的新变量,坐标为[0.5, 0.26]

    observation = [0.5, 0.26]
    
  13. sklearn.metrics.pairwise导入euclidean_distances函数:

    from sklearn.metrics.pairwise import euclidean_distances
    
  14. 创建一个名为features的新变量,它将提取缩放数据集中的前两列:

    features = scaled_employees[:,:2]
    
  15. 使用euclidean_distances计算observationfeatures之间的欧几里得距离,将结果保存到名为dist的变量中,并打印其值,如下所示的代码片段所示:

    dist = euclidean_distances([observation], features)
    dist
    

    预期输出如下:

    array([[0.50556627, 0.39698866, 0.17935412, 0.3196586 ,
            0.50556627, 0.62179262, 0.52169714, 0.14893495,
            0.89308454, 0.31201456]])
    

    注意

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

    你也可以在在线运行此示例,网址为packt.live/3esx7HF。你必须执行整个 Notebook 才能获得期望的结果。

从前面的输出中,我们可以看到三个最近的邻居如下:

  • 对于点[0.6, 0.37037037, 1.],欧几里得距离是0.1564897

  • 对于点[0.6, 0.11111111, 0.],欧几里得距离是0.17114358

  • 对于点[0.6, 0.55555556, 1.],欧几里得距离是0.32150303

如果选择k=3,KNN 将查看这三个最近邻居的类别,由于其中有两个的标签为1,它将把此类别分配给我们的新观察值[0.5, 0.26]。这意味着我们的三邻居分类器会将这个新员工分类为更有可能至少待满 2 年的员工。

通过完成此练习,我们了解了 KNN 分类器如何通过找到新观察值的三个最近邻居,并使用欧几里得距离将最频繁的类别分配给它来对新观察值进行分类。

在 scikit-learn 中对 K-最近邻分类器进行参数化

分类器的参数化是你微调分类器准确度的地方。由于我们尚未学习所有可能的 k 近邻变种,我们将专注于你基于本主题能够理解的参数:

注意

你可以在这里访问 k 近邻分类器的文档:scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html

  • n_neighbors:这是 k 近邻算法中的 k 值。默认值为5

  • metric:在创建分类器时,你会看到一个名称——Minkowski。不要担心这个名字——你已经学过了第一阶和第二阶明可夫斯基度量。这个度量有一个power参数。对于p=1,明可夫斯基度量和曼哈顿度量相同;对于p=2,明可夫斯基度量和欧几里得度量相同。

  • p:这是明可夫斯基度量的幂次。默认值为2

创建分类器时,你必须指定以下这些参数:

classifier = neighbors.KNeighborsClassifier(n_neighbors=50, p=2)

然后,你需要使用训练数据来拟合 KNN 分类器:

classifier.fit(features, label)

predict()方法可用于预测任何新数据点的标签:

classifier.predict(new_data_point)

在下一个练习中,我们将使用 scikit-learn 的 KNN 实现,自动查找最近邻并分配相应的类别。

练习 3.04:在 scikit-learn 中进行 K 近邻分类

在本练习中,我们将使用 scikit-learn,自动训练一个 KNN 分类器,基于德国信用审批数据集,并尝试不同的n_neighborsp超参数值,以获得最优的输出值。在拟合 KNN 之前,我们需要对数据进行缩放。

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

注意

本练习是练习 3.02的延续,应用标签编码将分类变量转化为数值型。我们已经将练习 3.02的结果数据集保存到 GitHub 仓库:packt.live/2Yqdb2Q

  1. 打开一个新的 Jupyter Notebook。

  2. 导入pandas包并将其命名为pd

    import pandas as pd
    
  3. 创建一个名为file_url的新变量,该变量将包含原始数据集的 URL:

    file_url = 'https://raw.githubusercontent.com/'\
               'PacktWorkshops/'\
               'The-Applied-Artificial-Intelligence-Workshop/'\
               'master/Datasets/german_prepared.csv'
    
  4. 使用pd.read_csv()方法加载数据:

    df = pd.read_csv(file_url)
    
  5. scikit-learn导入preprocessing

    from sklearn import preprocessing
    
  6. 使用feature_range=(0,1)实例化MinMaxScaler,并将其保存为名为scaler的变量:

    scaler = preprocessing.MinMaxScaler(feature_range=(0,1))
    
  7. 拟合缩放器并使用.fit_transform()方法应用相应的转换到 DataFrame,并将结果保存到名为scaled_credit的变量中:

    scaled_credit = scaler.fit_transform(df)
    
  8. response变量(第一列)提取到一个新的变量中,命名为label

    label = scaled_credit[:, 0]
    
  9. 将特征(所有列,除了第一列)提取到一个名为features的新变量中:

    features = scaled_credit[:, 1:]
    
  10. sklearn导入model_selection.train_test_split

    from sklearn.model_selection import train_test_split
    
  11. 使用train_test_split将缩放后的数据集分成训练集和测试集,test_size=0.2random_state=7

    features_train, features_test, \
    label_train, label_test = \
    train_test_split(features, label, test_size=0.2, \
                     random_state=7)
    
  12. sklearn导入neighbors

    from sklearn import neighbors
    
  13. 实例化KNeighborsClassifier并将其保存到名为classifier的变量中:

    classifier = neighbors.KNeighborsClassifier()
    
  14. 在训练集上拟合 K 最近邻分类器:

    classifier.fit(features_train, label_train)
    

    由于我们没有提到 k 的值,默认值是5

  15. 使用.score()打印训练集的准确率:

    acc_train = classifier.score(features_train, label_train)
    acc_train
    

    你应该得到以下输出:

    0.78625
    

    通过这些操作,我们在训练集上获得了0.78625的准确率,使用的是默认的超参数值:k=5 和欧几里得距离。

    让我们看一下测试集的得分。

  16. 使用.score()打印测试集的准确率:

    acc_test = classifier.score(features_test, label_test)
    acc_test
    

    你应该得到以下输出:

    0.75
    

    测试集的准确率降至0.75。这意味着我们的模型出现了过拟合,不能很好地对未见过的数据进行泛化。在下一个活动中,我们将尝试不同的超参数值,看看是否能改善这一点。

    注意

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

    你还可以在线运行此示例,地址是packt.live/2VbDTKx。你必须执行整个 Notebook 才能获得预期结果。

在本练习中,我们学习了如何将数据集划分为训练集和测试集,并拟合 KNN 算法。我们的最终模型可以准确预测一个人 75%的概率是否会违约。

活动 3.01:提高信用评分的准确性

在本活动中,你将实现 K 最近邻分类器的参数化并观察最终结果。目前,信用评分的准确率是 75%。你需要找到一种方法,将其提高几个百分点。

你可以尝试不同的 k 值(510152550),以及欧几里得距离和曼哈顿距离。

注意

这项活动要求你首先完成练习 3.04scikit-learn 中的 K 最近邻分类,因为我们将在这里使用之前准备好的数据。

以下步骤将帮助你完成这项活动:

  1. sklearn导入neighbors

  2. 创建一个函数来实例化指定超参数的KNeighborsClassifier,用训练数据拟合,并返回训练集和测试集的准确率。

  3. 使用你创建的函数,评估 k =(510152550)时,欧几里得距离和曼哈顿距离的准确率。

  4. 寻找最佳的超参数组合。

预期输出如下:

(0.775, 0.785)

注意

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

在接下来的部分,我们将介绍另一种机器学习分类器:支持向量机SVM)。

支持向量机分类

我们在 第二章回归入门 中首先使用了 SVM 进行回归。在本节中,你将学习如何使用 SVM 进行分类。和往常一样,我们将使用 scikit-learn 来实践我们的示例。

什么是支持向量机分类器?

SVM 的目标是找到一个 n 维空间中的表面,将该空间中的数据点分成多个类别。

在二维空间中,这个表面通常是直线。然而,在三维空间中,SVM 通常找到一个平面。这些表面是最优的,因为它们基于机器可以利用的信息,从而优化了 n 维空间的分隔。

SVM 找到的最佳分隔面被称为最佳分隔超平面。

SVM 用于找到一个分隔两组数据点的表面。换句话说,SVM 是 二分类器。这并不意味着 SVM 只能用于二分类。尽管我们只讨论了一个平面,SVM 可以通过对任务本身进行推广,将空间划分为任意数量的类别。

分隔面是最优的,因为它最大化了每个数据点与分隔面之间的距离。

向量是定义在 n 维空间中的数学结构,具有大小(长度)和方向。在二维空间中,你可以从原点绘制向量 (x, y) 到点 (x, y)。基于几何学,你可以使用勾股定理计算向量的长度,并通过计算向量与水平轴之间的角度来确定向量的方向。

例如,在二维空间中,向量 (3, -4) 的大小如下:

np.sqrt( 3 * 3 + 4 * 4 )

预期输出如下:

5.0

它具有以下方向(以度为单位):

np.arctan(-4/3) / 2 / np.pi * 360

预期输出如下:

-53.13010235415597

理解支持向量机

假设给定了两组具有不同类别(类别 0 和类别 1)的点。为了简单起见,我们可以想象一个二维平面,具有两个特征:一个映射在水平轴上,另一个映射在垂直轴上。

SVM 的目标是找到最佳分隔线,将属于类别 0 的点ADCBH与属于类别 1 的点EFG分开:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_13.jpg

图 3.13:分隔红色和蓝色成员的线

但是,分隔并不总是那么明显。例如,如果类别 0 的新点位于 EFG 之间,就没有一条线能够分开所有的点而不导致错误。如果类别 0 的点围绕类别 1 的点形成一个完整的圆圈,就没有直线能够分开这两组点:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_14.jpg

图 3.14:带有两个异常点的图

例如,在前面的图中,我们容忍了两个异常点,OP

在以下解决方案中,我们不容忍异常值,而是通过两个半线来构建最佳分隔路径,代替使用一条线:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_03_15.jpg

图 3.15:移除两个异常值的分隔图

完美分隔所有数据点通常并不值得投入过多资源。因此,支持向量机可以通过正则化来简化并限制最佳分隔形状的定义,从而允许异常值的存在。

支持向量机的正则化参数决定了允许的误差率或禁止误分类的程度。

支持向量机有一个核函数参数。线性核使用线性方程来严格描述最佳分隔超平面。多项式核使用多项式,而指数核使用指数表达式来描述超平面。

边距是围绕分隔超平面的一片区域,其边界由最靠近分隔超平面的点所限定。一个平衡的边距是从每个类别中选出的点,它们距离分隔线等远。

当涉及到定义最佳分隔超平面的允许误差率时,gamma 参数决定了在确定分隔超平面位置时,是仅考虑接近分隔超平面的点,还是考虑最远离分隔线的点。gamma 值越高,影响分隔超平面位置的点数越少。

scikit-learn 中的支持向量机

我们的切入点是活动 3.02scikit-learn 中的支持向量机优化。一旦我们划分了训练数据和测试数据,就可以设置分类器了:

features_train, features_test, \
label_train, label_test = \
model_selection.train_test_split(scaled_features, label,\
                                 test_size=0.2)

我们将使用 svm.SVC() 分类器,而不是使用 k 最近邻分类器:

from sklearn import svm
classifier = svm.SVC()
classifier.fit(features_train, label_train)
classifier.score(features_test, label_test)

期望的输出是这样的:

0.745

看起来 scikit-learn 的默认支持向量机分类器比 k 最近邻分类器稍微更好。

scikit-learn 支持向量机的参数

以下是 scikit-learn 支持向量机的参数:

  • kernel:这是一个字符串或可调用参数,用于指定算法中使用的核函数。预定义的核函数包括 linearpolyrbfsigmoidprecomputed。默认值是 rbf

  • degree:当使用多项式核时,你可以指定多项式的度数。默认值是 3

  • gamma:这是用于 rbfpolysigmoid 核函数的核系数。默认值是 auto,它的计算方式是 1/特征数量

  • C:这是一个浮动数值,默认值为 1.0,表示误差项的惩罚参数。

    注意

    你可以在参考文档中阅读关于这些参数的详细信息,地址为 scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html

下面是一个支持向量机的例子:

classifier = svm.SVC(kernel="poly", C=2, degree=4, gamma=0.05)

活动 3.02:scikit-learn 中的支持向量机优化

在这个活动中,你将使用、比较和对比不同 SVM 分类器的参数。通过这些,你将找到一组在我们加载并准备好的训练和测试数据中,能够产生最高分类准确率的参数,这些数据在活动 3.01中已经准备好,提高信用评分的准确性

你必须使用不同的 SVM 超参数组合:

  • kernel="linear"

  • kernel="poly", C=1, degree=4, gamma=0.05

  • kernel="poly", C=1, degree=4, gamma=0.05

  • kernel="poly", C=1, degree=4, gamma=0.25

  • kernel="poly", C=1, degree=4, gamma=0.5

  • kernel="poly", C=1, degree=4, gamma=0.16

  • kernel="sigmoid"

  • kernel="rbf", gamma=0.15

  • kernel="rbf", gamma=0.25

  • kernel="rbf", gamma=0.5

  • kernel="rbf", gamma=0.35

以下步骤将帮助你完成本次活动:

  1. 打开一个新的 Jupyter Notebook 文件,并执行前面提到的所有步骤,练习 3.04在 scikit-learn 中进行 K 近邻分类

  2. sklearn导入svm

  3. 创建一个函数,用于实例化一个 SVC 模型,设置指定的超参数,使用训练数据进行拟合,并返回训练集和测试集的准确度评分。

  4. 使用你创建的函数,评估不同超参数组合的准确度评分。

  5. 找到最佳的超参数组合。

预期输出如下:

(0.78125, 0.775)

注意

本次活动的解答可以在第 347 页找到。

总结

在本章中,我们学习了分类的基础知识,以及回归问题之间的区别。分类是关于预测一个具有有限可能值的响应变量。对于任何数据科学项目,数据科学家都需要在训练模型之前准备好数据。在本章中,我们学习了如何标准化数值并替换缺失值。接着,你了解了著名的 k 近邻算法,并发现它如何使用距离度量来寻找与数据点最接近的邻居,并从中分配最常见的类别。我们还学习了如何将 SVM 应用于分类问题,并调优其一些超参数,以提高模型性能并减少过拟合。

在下一章中,我们将带你了解另一种类型的算法,叫做决策树。

第四章:4. 决策树简介

概述

本章将详细介绍两种类型的监督学习算法。第一个算法将帮助你使用决策树对数据点进行分类,而另一个算法则帮助你使用随机森林对数据点进行分类。此外,你还将学习如何手动和自动计算模型的精确度、召回率和 F1 分数。到本章结束时,你将能够分析用于评估数据模型效用的指标,并基于决策树和随机森林算法对数据点进行分类。

介绍

在前两章中,我们学习了回归问题与分类问题的区别,并且我们看到了如何训练一些最著名的算法。本章中,我们将介绍另一种算法类型:基于树的模型。

基于树的模型非常流行,因为它们可以建模复杂的非线性模式,并且相对容易理解。本章中,我们将介绍决策树和随机森林算法,这些是行业中最广泛使用的基于树的模型之一。

决策树

决策树有叶子节点、分支和节点。节点是做出决策的地方。决策树由我们用来制定决策(或预测)数据点的规则组成。

决策树的每个节点代表一个特征,而从内部节点出来的每条边代表树的一个可能值或值区间。树的每个叶子节点代表树的一个标签值。

这可能听起来有些复杂,但让我们来看一个应用实例。

假设我们有一个数据集,具有以下特征,并且响应变量是判断一个人是否有信用:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_01.jpg

图 4.1:用于制定规则的示例数据集

记住,决策树只是一些规则的集合。查看图 4.1中的数据集,我们可以得出以下规则:

  • 所有有房贷的人都被确定为有信用的人。

  • 如果债务人有工作并且在学习,那么贷款是有信用的。

  • 年收入超过 75,000 的人是有信用的。

  • 年收入在 75,000 以下的、有车贷并且有工作的人员是有信用的。

按照我们刚才定义的规则顺序,我们可以建立一棵树,如图 4.2所示,并描述一种可能的信用评分方法:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_02.jpg

图 4.2:贷款类型的决策树

首先,我们确定贷款类型。根据第一个规则,房屋贷款自动为信用良好。学习贷款由第二个规则描述,结果是一个包含另一个关于就业的决策的子树。由于我们已经涵盖了房屋贷款和学习贷款,因此只剩下汽车贷款。第三个规则描述了收入决策,而第四个规则描述了关于就业的决策。

每当我们必须对一个新的债务人进行评分,以确定其是否具有信用时,我们必须从决策树的顶部到底部进行遍历,并观察底部的真假值。

显然,基于七个数据点的模型准确性非常低,因为我们无法推广那些与现实完全不符的规则。因此,规则通常是基于大量数据来确定的。

这并不是构建决策树的唯一方法。我们还可以基于其他规则的顺序来构建决策树。让我们从图 4.1的数据集中提取一些其他规则。

观察 1:注意到所有收入超过 75,000 的个人都是信用良好的。

收入 > 75,000 => 信用良好 这个规则成立。

规则 1 将七个数据点中的四个(ID C、E、F、G)分类;对于剩下的三个数据点,我们需要更多的规则。

观察 2:在剩下的三个数据点中,有两个没有就业。其中一个是就业的(ID D),并且是信用良好的。由此,我们可以得出以下规则:

收入 <= 75,000 时,以下情况成立:在职 == true => 信用良好

注意,使用这个第二个规则,我们也可以将剩下的两个数据点(ID A 和 B)分类为不信用良好。通过这两个规则,我们准确地分类了该数据集中的所有观察点:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_03.jpg

图 4.3:收入的决策树

第二个决策树比较简单。同时,我们不能忽视这样一个事实:模型表示,低收入的在职人员更可能无法偿还贷款。不幸的是,训练数据不足(这个例子中只有七个观察点),这使得我们很可能得出错误的结论。

过拟合是决策树中常见的问题,当我们仅基于少量数据点做出决策时,这个决策通常不能代表整体情况。

由于我们可以按照任何可能的顺序构建决策树,因此定义一种高效的决策树构建方式是有意义的。因此,我们将探讨一种用于在决策过程中排序特征的度量方法。

在信息论中,熵衡量一个属性的可能值分布的随机程度。随机程度越高,属性的熵值越高。

熵是事件的最高可能性。如果我们事先知道事件的结果,那么这个事件就没有随机性。因此,熵值为

我们使用熵来排序决策树中节点的分裂。以之前的例子为例,我们应该从哪个规则开始?是Income <= 75000还是is employed?我们需要使用一个度量标准来告诉我们哪个特定的分裂比另一个更好。一个好的分裂可以通过它清楚地将数据分成两个同质的组来定义。一个这样的度量是信息增益,它基于熵。

这里是计算熵的公式:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_04.jpg

图 4.4:熵公式

pi 表示目标变量某一可能值发生的概率。所以,如果这一列有n个不同的唯一值,那么我们将为每个值计算概率*([p1, p2, …, pn])*并应用公式。

要在 Python 中手动计算分布的熵,我们可以使用 NumPy 库中的np.log2np.dot()方法。在numpy中没有自动计算熵的函数。

看看下面的例子:

import numpy as np
probabilities = list(range(1,4)) 
minus_probabilities = [-x for x in probabilities]
log_probabilities = [x for x in map(np.log2, probabilities)]
entropy_value = np.dot(minus_probabilities, log_probabilities)

概率以 NumPy 数组或常规列表的形式给出,在第 2 行pi。

我们需要创建一个包含第 3 行分布中取反值的向量:- pi。

第 4 行,我们必须对分布列表中的每个值取以 2 为底的对数:logi pi。

最后,我们通过标量积来计算总和,也称为两个向量的点积:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_05.jpg

图 4.5:两个向量的点积

注意

你第一次在第二章《回归分析导论》中学习了点积。两个向量的点积是通过将第一个向量的第i个坐标与第二个向量的第i个坐标相乘来计算的,针对每个i。一旦我们得到所有的乘积,就将它们求和:

np.dot([1, 2, 3], [4, 5, 6])

这将得到 14 + 25 + 3*6 = 32。

在下一个练习中,我们将计算一个小样本数据集的熵。

练习 4.01:计算熵

在这个练习中,我们将计算数据集中特征的熵,如图 4.6所示:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_06.jpg

图 4.6:用于制定规则的样本数据集

注意

数据集文件也可以在我们的 GitHub 仓库中找到:

packt.live/2AQ6Uo9

我们将为EmployedIncomeLoanTypeLoanAmount特征计算熵。

以下步骤将帮助你完成这个练习:

  1. 打开一个新的 Jupyter Notebook 文件。

  2. 导入numpy包并命名为np

    import numpy as np
    
  3. 定义一个名为entropy()的函数,它接收一个概率数组并返回计算得到的熵值,如下代码片段所示:

    def entropy(probabilities):
        minus_probabilities = [-x for x in probabilities]
        log_probabilities = [x for x in map(np.log2, \
                                            probabilities)]
        return np.dot(minus_probabilities, log_probabilities)
    

    接下来,我们将计算 Employed 列的熵。该列仅包含两个可能的值:truefalse。在七行数据中,true 出现了四次,因此其概率为 4/7。类似地,false 的概率为 3/7,因为它在数据集中出现了三次。

  4. 使用 entropy() 函数计算 Employed 列的熵,概率分别为 4/73/7

    H_employed = entropy([4/7, 3/7])
    H_employed
    

    你应该得到以下输出:

    0.9852281360342515
    

    这个值接近零,意味着这些组是相当同质的。

  5. 现在,使用 entropy() 函数计算 Income 列的熵及其对应的概率列表:

    H_income = entropy([1/7, 2/7, 1/7, 2/7, 1/7])
    H_income
    

    你应该得到以下输出:

    2.2359263506290326
    

    Employed 列相比,Income 的熵较高。这意味着该列的概率分布更为分散。

  6. 使用 entropy 函数计算 LoanType 列的熵及其对应的概率列表:

    H_loanType = entropy([3/7, 2/7, 2/7])
    H_loanType
    

    你应该得到以下输出:

    1.5566567074628228
    

    这个值大于 0,因此该列的概率分布较为分散。

  7. 让我们使用 entropy 函数计算 LoanAmount 列的熵及其对应的概率列表:

    H_LoanAmount = entropy([1/7, 1/7, 3/7, 1/7, 1/7])
    H_LoanAmount
    

    你应该得到以下输出:

    2.128085278891394
    

    LoanAmount 的熵较高,因此其值相当随机。

    注意

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

    你也可以在线运行这个示例,网址为 packt.live/2By7aI6。你必须执行整个 Notebook 才能得到预期的结果。

在这里,你可以看到 Employed 列的熵在四个不同的列中是最低的,因为它的值变化最小。

完成这个练习后,你已经学会了如何手动计算数据集每一列的熵。

信息增益

当我们根据某个属性的值对数据集中的数据点进行划分时,我们减少了系统的熵。

为了描述信息增益,我们可以计算标签的分布。在 图 4.1 中,我们的数据集包含五个可信和两个不可信的个体。初始分布的熵如下:

H_label = entropy([5/7, 2/7])
H_label

输出结果如下:

0.863120568566631

让我们看看如果根据贷款金额是否大于 15,000 来划分数据集,会发生什么:

  • 在组 1 中,我们得到一个属于 15,000 贷款金额的数据点。这个数据点是不可信的。

  • 在组 2 中,我们有五个可信的个体和一个不可信的个体。

每个组中标签的熵如下:

对于组 1,我们有如下情况:

H_group1 = entropy([1]) 
H_group1

输出结果如下:

-0.0

对于组 2,我们有如下情况:

H_group2 = entropy([5/6, 1/6]) 
H_group2

输出结果如下:

0.6500224216483541

为了计算信息增益,让我们计算组熵的加权平均值:

H_group1 * 1/7 + H_group2 * 6/7

输出结果如下:

0.5571620756985892

现在,为了找到信息增益,我们需要计算原始熵(H_label)与我们刚刚计算出的熵之间的差异:

Information_gain = 0.863120568566631 - 0.5572
Information_gain

输出如下:

0.30592056856663097

通过使用这个规则拆分数据,我们获得了一些信息。

在创建决策树时,在每个节点,我们的任务是使用能够最大化信息增益的规则来划分数据集。

我们也可以使用基尼不纯度代替基于熵的信息增益来构建最佳的决策树拆分规则。

基尼不纯度

除了熵之外,还有另一种广泛使用的度量标准可以用来衡量分布的随机性:基尼不纯度。

基尼不纯度的定义如下:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_07.jpg

图 4.7:基尼不纯度

pi 代表目标变量可能值之一发生的概率。

熵的计算可能稍微慢一些,因为涉及到对数运算。另一方面,基尼不纯度在衡量随机性时的精确度较低。

注意

一些程序员更倾向于使用基尼不纯度,因为不需要进行对数计算。从计算的角度来看,这两种方法都不算特别复杂,因此可以使用其中任意一种。在性能方面,以下研究得出的结论是,这两种度量标准之间的差异通常非常小:www.unine.ch/files/live/sites/imi/files/shared/documents/papers/Gini_index_fulltext.pdf

通过这一点,我们已经了解到,可以通过基于信息增益或基尼不纯度来优化决策树。不幸的是,这些度量标准仅适用于离散值。如果标签是在一个连续区间内定义的,比如价格范围或薪资范围,该怎么办呢?

我们必须使用其他度量标准。理论上,你可以理解基于连续标签创建决策树的思路,那就是回归。我们在本章中可以重用的一个度量标准是均方误差。与基尼不纯度或信息增益不同,我们必须最小化均方误差以优化决策树。由于这是一个初学者课程,我们将省略这一度量标准。

在下一部分,我们将讨论决策树的退出条件。

退出条件

我们可以根据越来越具体的规则持续地拆分数据点,直到决策树的每个叶子节点的熵为零。问题是,这种最终状态是否是理想的。

通常,这不是我们所期望的,因为我们可能会面临过拟合模型的风险。当模型的规则过于具体且过于挑剔,而做出决策的样本量又过小,我们就有可能得出错误的结论,从而在数据集中识别出一个实际上并不存在于现实中的模式。

例如,如果我们转动轮盘三次,得到的结果分别是 12、25 和 12,这就得出结论:每次奇数次转动结果为 12 的策略并不明智。假设每次奇数次转动结果都是 12,我们就发现了一个完全由随机噪音引起的规则。

因此,对我们仍然可以分割的数据集的最小大小施加限制是一个在实际中效果良好的退出条件。例如,如果在数据集小于 50、100、200 或 500 时就停止分割,就可以避免对随机噪音得出结论,从而最大限度地减少过拟合模型的风险。

另一种常见的退出条件是树的最大深度限制。一旦达到固定的树深度,我们就在叶子节点对数据点进行分类。

使用 scikit-learn 构建决策树分类器

我们已经学习了如何从.csv文件加载数据,如何对数据进行预处理,以及如何将数据拆分为训练集和测试集。如果你需要复习这些知识,可以回到前面的章节,在回归和分类的背景下重新学习这一过程。

现在,我们假设已经通过scikit-learn train-test-split调用返回了一组训练特征、训练标签、测试特征和测试标签:

from sklearn import model_selection
features_train, features_test, \
label_train, label_test = \
model_selection.train_test_split(features, label, test_size=0.1, \
                                 random_state=8)

在前面的代码片段中,我们使用了train_test_split将数据集(特征和标签)拆分为训练集和测试集。测试集占观测数据的 10%(test_size=0.1)。random_state参数用于获取可重复的结果。

我们不会专注于如何获得这些数据点,因为这个过程与回归和分类的情况完全相同。

现在是时候导入并使用 scikit-learn 的决策树分类器了:

from sklearn.tree import DecisionTreeClassifier
decision_tree = DecisionTreeClassifier(max_depth=6)
decision_tree.fit(features_train, label_train)

我们在DecisionTreeClassifier中设置了一个可选参数,即max_depth,用于限制决策树的深度。

注意

你可以阅读官方文档,获取参数的完整列表:scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html

以下是一些更重要的参数:

  • criterion: Gini 代表基尼不纯度,entropy 代表信息增益。这将定义在每个节点上用于评估分割质量的度量标准。

  • max_depth: 这是定义树的最大深度的参数。

  • min_samples_split: 这是分割内部节点所需的最小样本数。

你也可以尝试文档中列出的所有其他参数。我们将在本节中省略它们。

一旦模型构建完成,我们就可以使用决策树分类器进行数据预测:

decision_tree.predict(features_test)

你将在本节末尾的活动中构建一个决策树分类器。

分类器的性能评估指标

在拆分训练数据和测试数据之后,决策树模型有一个 score 方法,用来评估模型对测试数据分类的效果(也称为准确率)。我们在前两章中学习了如何使用 score 方法:

decision_tree.score(features_test, label_test)

score 方法的返回值是一个小于或等于 1 的数字。我们越接近 1,模型就越好。

现在,我们将学习另一种评估模型的方法。

注意

你也可以将此方法应用于你在上一章构建的模型。

假设我们有一个测试特征和一个测试标签:

predicted_label = decision_tree.predict(features_test)

让我们使用之前的信用评分示例,假设我们训练了一个决策树,现在有了它的预测结果:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_08.jpg

图 4.8:用于制定规则的示例数据集

我们的模型一般来说做出了很好的预测,但也有少数错误。它错误地预测了 ID ADE 的结果。它的准确率得分将是 4 / 7 = 0.57。

我们将使用以下定义来定义一些度量标准,帮助你评估分类器的好坏:

  • Creditworthy 列(在我们的示例中)和相应的预测值都是 Yes。在我们的示例中,ID CFG 将属于这一类别。

  • No。只有 ID B 会被分类为真正负类。

  • Yes,但真实标签实际上是 No。这种情况适用于 ID A

  • No,但真实标签实际上是 Yes,例如 ID DE

使用前面四个定义,我们可以定义四个度量标准,用来描述我们的模型如何预测目标变量。#( X ) 符号表示 X 中的值的数量。从技术术语上讲,#( X ) 表示 X 的基数:

定义(准确率)#(真正例)+ #(真正负类) / #(数据集)

准确率是一个用来衡量分类器给出正确答案次数的指标。这是我们用来评估分类器得分的第一个度量标准。

在我们之前的示例中(图 4.8),准确率得分将是 TP + TN / 总数 = (3 + 1) / 7 = 4/7。

我们可以使用 scikit-learn 提供的函数来计算模型的准确率:

from sklearn.metrics import accuracy_score
accuracy_score(label_test, predicted_label)

定义(精度)#真正例 / (#真正例 + #假正例)

精度关注的是分类器认为是正类的值。这些结果中,有些是真正例,而有些是假正例。高精度意味着假正例的数量相对于真正例非常低。这意味着一个精确的分类器在找到正类时很少犯错。

定义(召回率)#真正例 / (#真正例 + #假负例)

召回率关注的是测试数据中正类值的情况。分类器找到的这些结果是正例(True Positive)。那些分类器未找到的正类值是假阴性(False Negative)。一个召回率高的分类器能够找到大多数正类值。

使用我们之前的示例(图 4.8),我们将得到以下度量:

  • 精确度 = TP / (TP + FP) = 4 / (4 + 1) = 4/6 = 0.8

  • 召回率 = TP / (TP + FN) = 4 / (4 + 2) = 4/6 = 0.6667

通过这两个度量,我们可以轻松看到我们的模型在哪些地方表现更好或更差。在这个例子中,我们知道它倾向于误分类假阴性案例。这些度量比准确率分数更为细致,准确率分数仅提供一个整体分数。

F1 分数是一个结合精确度和召回率的度量。它的值范围在 0 到 1 之间。如果 F1 分数为 1,则表示模型完美地预测了正确的结果。另一方面,F1 分数为 0 则表示模型无法准确预测目标变量。F1 分数的优点是它考虑了假阳性和假阴性。

计算 F1 分数的公式如下:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_09.jpg

图 4.9:计算 F1 分数的公式

最后需要指出的是,scikit-learn 包还提供了一个非常实用的函数,可以一次性显示所有这些度量:classification_report()。分类报告有助于检查我们预测的质量:

from sklearn.metrics import classification_report
print(classification_report(label_test, predicted_label))

在下一个练习中,我们将练习如何手动计算这些分数。

练习 4.02:精确度、召回率和 F1 分数计算

在本练习中,我们将计算两个不同分类器在模拟数据集上的精确度、召回率和 F1 分数。

以下步骤将帮助你完成此练习:

  1. 打开一个新的 Jupyter Notebook 文件。

  2. 使用以下代码导入numpy包,并将其命名为np

    import numpy as np
    
  3. 创建一个名为real_labelsnumpy数组,包含值[True, True, False, True, True]。该列表表示我们模拟数据集的目标变量的真实值。打印其内容:

    real_labels = np.array([True, True, False, True, True])
    real_labels
    

    预期输出如下:

    array([ True, True, False, True, True])
    
  4. 创建一个名为model_1_predsnumpy数组,包含值[True, False, False, False, False]。该列表表示第一个分类器的预测值。打印其内容:

    model_1_preds = np.array([True, False, False, False, False])
    model_1_preds
    

    预期输出如下:

    array([ True, False, False, False, False])
    
  5. 创建另一个名为model_2_predsnumpy数组,包含值[True, True, True, True, True]。该列表表示第一个分类器的预测值。打印其内容:

    model_2_preds = np.array([True, True, True, True, True])
    model_2_preds
    

    预期输出如下:

    array([ True,  True,  True,  True,  True])
    
  6. 创建一个名为model_1_tp_cond的变量,用来找到第一个模型的真正例:

    model_1_tp_cond = (real_labels == True) \
                       & (model_1_preds == True)
    model_1_tp_cond
    

    预期输出如下:

    array([ True, False, False, False, False])
    
  7. 创建一个名为model_1_tp的变量,通过求和model_1_tp_cond来获取第一个模型的真正例数量:

    model_1_tp = model_1_tp_cond.sum()
    model_1_tp
    

    预期输出如下:

    1
    

    第一个模型只有1个真实阳性案例。

  8. 创建一个名为model_1_fp的变量,用于获取第一个模型的假阳性数量:

    model_1_fp = ((real_labels == False) \
                   & (model_1_preds == True)).sum()
    model_1_fp
    

    预期输出将如下所示:

    0
    

    第一个模型没有假阳性。

  9. 创建一个名为model_1_fn的变量,用于获取第一个模型的假阴性数量:

    model_1_fn = ((real_labels == True) \
                   & (model_1_preds == False)).sum()
    model_1_fn
    

    预期输出将如下所示:

    3
    

    第一个分类器有3个假阴性案例。

  10. 创建一个名为model_1_precision的变量,用于计算第一个模型的精度:

    model_1_precision = model_1_tp / (model_1_tp + model_1_fp)
    model_1_precision
    

    预期输出将如下所示:

    1.0
    

    第一个分类器的精度得分为1,因此它没有预测出任何假阳性。

  11. 创建一个名为model_1_recall的变量,用于计算第一个模型的召回率:

    model_1_recall = model_1_tp / (model_1_tp + model_1_fn)
    model_1_recall
    

    预期输出将如下所示:

    0.25
    

    第一个模型的召回率仅为0.25,因此它预测了相当多的假阴性。

  12. 创建一个名为model_1_f1的变量,用于计算第一个模型的 F1 分数:

    model_1_f1 = 2*model_1_precision * model_1_recall\
                 / (model_1_precision + model_1_recall)
    model_1_f1
    

    预期输出将如下所示:

    0.4
    

    如预期所示,第一个模型的 F1 分数相当低。

  13. 创建一个名为model_2_tp的变量,用于获取第二个模型的真实阳性数量:

    model_2_tp = ((real_labels == True) \
                   & (model_2_preds == True)).sum()
    model_2_tp
    

    预期输出将如下所示:

    4
    

    第二个模型有4个真实阳性案例。

  14. 创建一个名为model_2_fp的变量,用于获取第二个模型的假阳性数量:

    model_2_fp = ((real_labels == False) \
                   & (model_2_preds == True)).sum()
    model_2_fp
    

    预期输出将如下所示:

    1
    

    第二个模型只有一个假阳性。

  15. 创建一个名为model_2_fn的变量,用于获取第二个模型的假阴性数量:

    model_2_fn = ((real_labels == True) \
                   & (model_2_preds == False)).sum()
    model_2_fn
    

    预期输出将如下所示:

    0
    

    第二个分类器没有假阴性。

  16. 创建一个名为model_2_precision的变量,用于计算第二个模型的精度:

    model_2_precision = model_2_tp / (model_2_tp + model_2_fp) 
    model_2_precision
    

    预期输出将如下所示:

    0.8
    

    第二个模型的精度得分相当高:0.8。它在假阳性方面并没有犯太多错误。

  17. 创建一个名为model_2_recall的变量,用于计算第二个模型的召回率:

    model_2_recall = model_2_tp / (model_2_tp + model_2_fn)
    model_2_recall
    

    预期输出将如下所示:

    1.0
    

    在召回率方面,第二个分类器表现出色,没有将任何观测值错误分类为假阴性。

  18. 创建一个名为model_2_f1的变量,用于计算第二个模型的 F1 分数:

    model_2_f1 = 2*model_2_precision*model_2_recall \
                 / (model_2_precision + model_2_recall)
    model_2_f1
    

    预期输出将如下所示:

    0.888888888888889
    

    第二个模型的 F1 分数相当高。

    注意

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

    你也可以在packt.live/2NoxLdo在线运行此示例。你必须执行整个笔记本以获得预期的结果。

在本次练习中,我们展示了如何手动计算两个不同模型的精度、召回率和 F1 分数。第一个分类器具有优秀的精度,但召回率较差,而第二个分类器具有优秀的召回率和相当不错的精度。

使用 scikit-learn 评估分类器的性能

scikit-learn 包提供了一些函数,用于自动计算精度、召回率和 F1 分数。你需要先导入这些函数:

from sklearn.metrics import recall_score, \
precision_score, f1_score

要获得精度分数,你需要从模型中获取预测结果,如下所示的代码片段:

label_predicted = decision_tree.predict(data)
precision_score(label_test, predicted_label, \
                average='weighted')

计算 recall_score 可以这样进行:

recall_score(label_test, label_predicted, average='weighted')

计算 f1_score 可以这样进行:

f1_score(label_test, predicted_label, average='weighted')

在下一部分,我们将学习如何使用另一个工具——混淆矩阵,来分析分类器的性能。

混淆矩阵

之前,我们学习了如何使用一些计算指标来评估分类器的性能。还有另一个非常有趣的工具可以帮助你评估多类分类模型的性能:混淆矩阵。

混淆矩阵是一个方阵,其中行数和列数等于不同标签值(或类别)的数量。在矩阵的列中,我们放置每个测试标签值。在矩阵的行中,我们放置每个预测标签值。

混淆矩阵如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_10.jpg

图 4.10:示例混淆矩阵

在前面的示例中,混淆矩阵的第一行展示了模型的表现:

  • 正确预测 A 类 88

  • 在真实值为 B 时预测 A 类 3

  • 在真实值为 C 时预测 A 类 2

我们还可以看到,当模型预测 C 类时,真实值为 A(16 次)的错误情况。混淆矩阵是一个强大的工具,可以快速轻松地发现模型在某些类别上表现良好或不良。

scikit-learn 包提供了一个函数来计算并显示混淆矩阵:

from sklearn.metrics import confusion_matrix
confusion_matrix(label_test, predicted_label)

在下一个活动中,你将构建一个决策树模型,用来将汽车分类为不可接受、可接受、良好和非常好四个类别,以便客户使用。

活动 4.01:汽车数据分类

在本活动中,你将构建一个可靠的决策树模型,帮助公司找到客户可能购买的汽车。我们假设租车公司正在专注于与客户建立长期关系。你的任务是构建一个决策树模型,将汽车分类为不可接受、可接受、良好和非常好四个类别。

注意

数据集文件也可以在我们的 GitHub 仓库中找到:packt.live/2V95I6h

本活动的数据集可以在这里访问:archive.ics.uci.edu/ml/datasets/Car+Evaluation

引用 – Dua, D., & Graff, C… (2017). UCI Machine Learning Repository

它由六个不同的特征组成:buyingmaintenancedoorspersonsluggage_bootsafety。目标变量对给定汽车的接受度进行排名。它可以取四个不同的值:unaccaccgoodvgood

以下步骤将帮助你完成此活动:

  1. 将数据集加载到 Python 中并导入必要的库。

  2. 使用 scikit-learn 中的LabelEncoder()进行标签编码。

  3. 使用pop()从 pandas 中提取label变量。

  4. 现在,使用 scikit-learn 中的train_test_split()将训练数据和测试数据分开。我们将使用 10%的数据作为测试数据。

  5. 使用DecisionTreeClassifier()及其方法fit()predict()构建决策树分类器。

  6. 使用score()检查基于测试数据的模型评分。

  7. 使用 scikit-learn 中的classification_report()对模型进行更深入的评估。

预期输出:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_11.jpg

图 4.11:显示预期分类报告的输出

注意

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

在接下来的部分,我们将研究随机森林分类器。

随机森林分类器

如果你考虑一下随机森林分类器这个名称,可以这样解释:

  • 一片森林由多棵树组成。

  • 这些树可以用于分类。

  • 由于到目前为止我们唯一使用的分类树是决策树,因此可以理解随机森林是一片决策树的森林。

  • 树的随机性意味着我们的决策树是以随机化的方式构建的。

因此,我们将根据信息增益或基尼不纯度来构建决策树。

一旦你理解了这些基本概念,你就基本上知道随机森林分类器的内容了。森林中的树木越多,预测的准确性就越高。在执行预测时,每棵树都执行分类。我们收集结果,获得最多投票的类别获胜。

随机森林可以用于回归和分类。在使用随机森林进行回归时,我们不再统计某个类别的最多投票数,而是取预测结果的算术平均值(平均数)并返回。尽管随机森林在分类中的表现非常理想,但在回归中的效果不如分类,因为用于预测值的模型通常是失控的,且经常返回一个范围较广的值。这些值的平均值往往意义不大。管理回归中的噪声比分类更为困难。

随机森林通常比简单的决策树表现更好,因为它们提供了冗余性。它们能更好地处理异常值,并且具有较低的过拟合模型的概率。决策树在使用训练数据时表现得很好,但一旦用于预测新数据,随机森林则失去其优势。随机森林广泛应用于分类问题,无论是银行或电子商务的客户细分、图像分类,还是医学领域。如果你拥有一台带有 Kinect 的 Xbox,你的 Kinect 设备就包含了一个随机森林分类器,用于检测你的身体。

随机森林是一种集成算法。集成学习的思想是,我们通过多个可能有不同弱点的代理的决策来获得一个综合视角。由于集体投票,这些弱点会被抵消,大多数投票结果很可能代表正确的结果。

使用 scikit-learn 进行随机森林分类

正如你可能已经猜到的,scikit-learn 包提供了RandomForest分类器的实现,使用的是RandomForestClassifier类。这个类提供了与迄今为止所有 scikit-learn 模型完全相同的方法——你需要实例化一个模型,然后使用.fit()方法对其进行训练,最后使用.predict()方法进行预测:

from sklearn.ensemble import RandomForestClassifier
random_forest_classifier = RandomForestClassifier()
random_forest_classifier.fit(features_train, label_train)
labels_predicted = random_forest_classifier.predict\
                   (features_test)

在下一节中,我们将查看随机森林分类器的参数化。

随机森林分类器的参数化

我们将基于已有的知识,考虑可能的参数子集,这些知识来源于构建随机森林的描述:

  • n_estimators:随机森林中的树木数量。默认值为 10。

  • criterion:使用 Gini 或熵来确定在每棵树中是使用 Gini 不纯度还是信息增益。它将用于找到每个节点的最佳分裂。

  • max_features:每棵树考虑的最大特征数。可能的值包括整数。你还可以添加一些字符串,如sqrt,表示特征数量的平方根。

  • max_depth:每棵树的最大深度。

  • min_samples_split:在给定节点中,数据集中的最小样本数,以执行分裂。这也可以减少树的大小。

  • bootstrap:一个布尔值,指示在构建树时是否对数据点使用自助法。

特征重要性

随机森林分类器为你提供了每个特征在数据分类过程中有多重要的信息。记住,我们使用了许多随机构建的决策树来对数据点进行分类。我们可以衡量这些数据点的表现准确性,同时也能看到在决策过程中哪些特征是至关重要的。

我们可以通过以下查询来检索特征重要性得分的数组:

random_forest_classifier.feature_importances_

在这个包含六个特征的分类器中,第四个和第六个特征显然比其他特征更重要。第三个特征的重要性得分非常低。

特征重要性分数在我们有很多特征时特别有用,尤其是当我们想要减少特征数量以避免分类器在细节中迷失时。当我们有大量特征时,我们容易导致模型过拟合。因此,通过剔除最不重要的特征来减少特征数量通常是有帮助的。

交叉验证

之前,我们学习了如何使用不同的度量标准来评估分类器的性能,例如在训练集和测试集上的准确率、精确率、召回率或 F1 分数。目标是在两个数据集上都获得较高的分数,并且这两个分数要非常接近。如果是这样,你的模型就能表现良好,并且不容易过拟合。

测试集用于作为代理,评估你的模型是否能够很好地泛化到未见过的数据,或者是否仅仅学习了对训练集有意义的模式。

但是,在需要调整多个超参数的情况下(例如对于RandomForest),你需要训练大量不同的模型并在测试集上测试它们。这种做法实际上削弱了测试集的作用。把测试集当作最终考试,决定你是否能通过某一科目。你不会被允许反复通过或重考。

避免过度使用测试集的一个解决方案是创建验证集。你将在训练集上训练模型,并使用验证集根据不同的超参数组合来评估模型的得分。一旦找到最佳模型,你将使用测试集确保它不会过度拟合。这通常是任何数据科学项目推荐的做法。

这种方法的缺点是,你减少了用于训练集的观测数据。如果你的数据集包含数百万行,这不是问题。但对于一个小型数据集,这可能会造成问题。这就是交叉验证派上用场的地方。

以下图 4.12显示了这种技术,在这种技术中,你会创建多个训练数据的分割。对于每个分割,训练数据被分为若干个折叠(本例中为五个),其中一个折叠将作为验证集,其余折叠用于训练。

例如,在上面的分割中,第五折将用于验证,其余四个折叠(1 到 4)将用于训练模型。你将对每个分割执行相同的过程。经过每个分割后,你将使用整个训练数据,最终的性能得分将是每个分割上训练的所有模型的平均值:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_04_12.jpg

图 4.12:交叉验证示例

使用 scikit-learn,你可以轻松地执行交叉验证,以下是一个代码示例:

from sklearn.ensemble import RandomForestClassifier
random_forest_classifier = RandomForestClassifier()
from sklearn.model_selection import cross_val_score
cross_val_score(random_forest_classifier, features_train, \
                label_train, cv=5, scoring='accuracy')

cross_val_score函数接受两个参数:

在下一部分,我们将探讨RandomForest的一个特定变体,称为extratrees

极端随机森林

极端随机树通过在已经随机化的随机森林因素基础上随机化分裂规则,从而增加了随机森林内部的随机化程度。

参数化方式类似于随机森林分类器。你可以在这里查看完整的参数列表:scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesClassifier.html

Python 实现如下:

from sklearn.ensemble import ExtraTreesClassifier
extra_trees_classifier = \
ExtraTreesClassifier(n_estimators=100, \
                     max_depth=6)
extra_trees_classifier.fit(features_train, label_train)
labels_predicted = extra_trees_classifier.predict(features_test)

在接下来的活动中,我们将优化在活动 4.01中构建的分类器,汽车数据分类

活动 4.02:为你的汽车租赁公司进行随机森林分类

在本活动中,你将优化分类器,以便在为你的车队选择未来的汽车时,更好地满足客户需求。我们将对你在本章前一个活动中使用的汽车经销商数据集进行随机森林和极端随机森林分类。

以下步骤将帮助你完成此活动:

  1. 按照之前活动 4.01步骤 1 - 4汽车数据分类

  2. 使用RandomForestClassifier创建一个随机森林。

  3. 使用.fit()训练模型。

  4. 导入confusion_matrix函数来评估RandomForest的质量。

  5. 使用classification_report()打印分类报告。

  6. 使用.feature_importance_打印特征重要性。

  7. 使用extratrees模型重复步骤 2 到 6

预期输出:

array([0.08844544, 0.0702334 , 0.01440408, 0.37662014,
       0.05965896, 0.39063797])

注意

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

完成本活动后,你已经学会了如何拟合RandomForestextratrees模型,并分析它们的分类报告和特征重要性。现在,你可以尝试不同的超参数,看看能否提高结果。

总结

在本章中,我们学习了如何使用决策树进行预测。通过集成学习技术,我们创建了复杂的强化学习模型来预测任意数据点的类别。

决策树在表面上表现得非常准确,但它们容易导致模型过拟合。随机森林和极端随机树通过引入一些随机元素和投票算法来减少过拟合,其中多数投票获胜。

除了决策树、随机森林和极端随机树之外,我们还了解了评估模型效用的新方法。在使用了众所周知的准确率评分后,我们开始使用精确率、召回率和 F1 评分指标来评估我们的分类器的表现。所有这些数值都是从混淆矩阵中得出的。

在接下来的章节中,我们将描述聚类问题,并比较和对比两种聚类算法。

第五章:5. 人工智能:聚类

概述

本章将介绍聚类的基础知识,这是一种无监督学习方法,与前几章中看到的监督学习方法相对。你将实现不同类型的聚类,包括使用 k-means 算法的平面聚类,以及使用均值漂移算法和聚合层次模型的层次聚类。你还将学习如何通过内在和外在方法来评估你的聚类模型的表现。到本章结束时,你将能够使用聚类分析数据,并将这一技能应用于解决各种领域的挑战。

引言

在上一章中,你已经了解了决策树及其在分类中的应用。你还在第二章《回归介绍》中了解了回归。回归和分类都是监督学习方法的一部分。然而,在本章中,我们将探讨无监督学习方法;我们将处理没有标签(输出)的数据集。机器需要根据我们定义的一组参数来告诉我们标签是什么。在本章中,我们将通过使用聚类算法来执行无监督学习。

我们将使用聚类分析数据,以发现特定的模式并创建群体。除此之外,聚类还可以用于许多其他目的:

  • 市场细分帮助识别市场中你应该关注的最佳股票。

  • 客户细分通过使用顾客的消费模式来识别顾客群体,以更好地推荐产品。

  • 在计算机视觉中,图像分割是通过聚类来执行的。通过这种方式,我们可以在图像中找到不同的物体。

  • 聚类也可以与分类相结合,生成多种特征(输入)的紧凑表示,然后可以将其输入分类器。

  • 聚类还可以通过检测异常值来筛选数据点。

无论我们是在对基因学、视频、图像还是社交网络应用聚类,如果我们使用聚类分析数据,我们可能会发现数据点之间的相似性,值得将其统一处理。

例如,考虑一个店铺经理,负责确保店铺的盈利。店铺中的产品被划分为不同的类别,而有不同的顾客偏好不同的商品。每个顾客有自己的偏好,但他们之间也有一些相似之处。你可能会有一个顾客对生物产品感兴趣,倾向于选择有机产品,这也正是素食顾客感兴趣的商品。即使他们有所不同,但在偏好或模式上有相似之处,因为他们都倾向于购买有机蔬菜。这可以看作是一个聚类的例子。

第三章分类简介中,你学习了分类,它是监督学习方法的一部分。在分类问题中,我们使用标签来训练模型,以便能够对数据点进行分类。而在聚类中,由于我们没有特征标签,我们需要让模型自己找出这些特征属于哪个簇。这通常基于每个数据点之间的距离。

在本章中,你将学习 k-means 算法,它是最广泛使用的聚类算法,但在此之前,我们需要先定义聚类问题。

定义聚类问题

我们将定义聚类问题,以便能够发现数据点之间的相似性。例如,假设我们有一个由多个数据点组成的数据集。聚类帮助我们通过描述这些数据点的分布情况来理解数据的结构。

让我们看一下图 5.1中二维空间中数据点的示例:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_01.jpg

图 5.1:二维空间中的数据点

现在,看看图 5.2。很明显,存在三个簇:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_02.jpg

图 5.2:使用二维空间中的数据点形成的三个簇

这三个簇很容易识别,因为这些点彼此接近。这里你可以看到,聚类算法能够识别彼此接近的数据点。你可能还注意到,数据点M1、O1 和 N1 并不属于任何一个簇;这些是离群点。你构建的聚类算法应该能够妥善处理这些离群点,而不是将它们归入某个簇。

虽然在二维空间中很容易识别簇,但我们通常处理的是多维数据点,即具有多个特征的数据。因此,了解哪些数据点彼此接近非常重要。此外,定义用于检测数据点之间接近程度的距离度量也至关重要。一个著名的距离度量是欧几里得距离,我们在第一章人工智能简介中学习过。在数学中,我们通常使用欧几里得距离来测量两个点之间的距离。因此,欧几里得距离是聚类算法中一个直观的选择,使我们能够在定位簇时判断数据点的接近度。

然而,大多数距离度量方法(包括欧几里得距离)存在一个缺点:随着维度的增加,这些距离会变得更加均匀。只有在维度或特征较少时,才容易看到哪些点与其他点最接近。然而,当我们添加更多特征时,相关特征与所有其他数据一起嵌入,并且很难从中区分出真正的相关特征,因为它们像噪声一样影响模型。因此,去除这些噪声特征可能会大大提高我们聚类模型的准确性。

注意

数据集中的噪声可以是无关信息或不需要的随机性。

在接下来的部分,我们将探讨两种不同的聚类方法。

聚类方法

聚类有两种类型:

  • 平面聚类

  • 层次聚类

在平面聚类中,我们需要指定机器要找到的聚类数目。平面聚类的一个例子是 k-means 算法,其中 k 指定我们希望算法使用的聚类数目。

然而,在层次聚类中,机器学习算法本身会自动确定需要的聚类数量。

层次聚类也有两种方法:

  • 凝聚性或自底向上的层次聚类开始时将每个点视为一个独立的聚类。然后,最接近的聚类会合并在一起。这个过程会一直重复,直到最终得到一个包含所有数据点的单一聚类。

  • 分裂性或自顶向下的层次聚类开始时将所有数据点视为一个聚类。然后,选择最远的数据点将聚类拆分成更小的聚类。这个过程会一直重复,直到每个数据点都变成自己的聚类。

图 5.3 给出了这两种聚类方法的更准确描述。

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_03.jpg

图 5.3:展示这两种方法的图

现在我们已经熟悉了不同的聚类方法,接下来让我们看看 scikit-learn 支持的不同聚类算法。

scikit-learn 支持的聚类算法

在本章中,我们将学习 scikit-learn 支持的两种聚类算法:

  • k-means 算法

  • 均值漂移算法

K-means 是平面聚类的一个示例,其中我们必须事先指定聚类的数量。k-means 是一种通用的聚类算法,当聚类数量不是很高且聚类大小相对均匀时,它表现良好。

均值漂移是层次聚类的一种示例,在这种算法中,聚类算法会自动确定聚类的数量。当我们事先不知道聚类数量时,可以使用均值漂移。与 k-means 相比,均值漂移支持那些聚类数量很多且大小差异较大的应用场景。

Scikit-learn 包含许多其他算法,但本章我们将重点关注 k-means 和均值漂移算法。

注意

有关聚类算法的完整描述,包括性能比较,请访问 scikit-learn 的聚类页面:scikit-learn.org/stable/modules/clustering.html

在下一节中,我们将从 k-means 算法开始。

K-Means 算法

k-means 算法是一种平面聚类算法,正如前面提到的,它的工作原理如下:

  • 设置 k 的值。

  • 从数据集中选择 k 个数据点作为各个聚类的初始中心。

  • 计算每个数据点到所选中心点的距离,并将每个点归类到初始中心点最接近的聚类中。

  • 一旦所有点都被分配到 k 个聚类中,计算每个聚类的中心点。这个中心点不必是数据集中现有的数据点,它只是一个平均值。

  • 重复这一过程,将每个数据点分配给与数据点最接近的聚类中心。重复此过程,直到中心点不再移动。

为了确保 k-means 算法能够终止,我们需要以下条件:

  • 设定一个最大阈值,算法将在达到此值时终止。

  • 移动点的最大重复次数。

由于 k-means 算法的特性,它很难处理大小差异较大的聚类。

k-means 算法有许多应用案例,已经成为我们日常生活的一部分,例如:

  • 市场细分:公司收集各种关于客户的数据。对客户进行 k-means 聚类分析将揭示具有明确特征的客户群体(聚类)。属于同一细分市场的客户可以看作有着相似的模式或偏好。

  • 内容标签化:任何内容(视频、书籍、文档、电影或照片)都可以分配标签,以便将相似的内容或主题进行分组。这些标签是聚类的结果。

  • 欺诈和犯罪活动检测:欺诈者通常会留下与其他客户行为不同的异常线索。例如,在车险行业,正常客户会因事故索赔损坏的汽车,而欺诈者则会索赔故意损坏的车辆。聚类可以帮助检测损坏是否来自真实事故或伪造的事故。

在下一个练习中,我们将实现 scikit-learn 中的 k-means 算法。

练习 5.01:在 scikit-learn 中实现 K-Means。

在这个练习中,我们将在二维平面上绘制一个数据集,并使用 k-means 算法对其进行聚类。

以下步骤将帮助你完成这个练习:

  1. 打开一个新的 Jupyter Notebook 文件。

  2. 现在创建一个作为 NumPy 数组的人工数据集,以演示 k-means 算法。数据点如下代码片段所示:

    import numpy as np
    data_points = np.array([[1, 1], [1, 1.5], [2, 2], \
                            [8, 1], [8, 0], [8.5, 1], \
                            [6, 1], [1, 10], [1.5, 10], \
                            [1.5, 9.5], [10, 10], [1.5, 8.5]])
    
  3. 现在,使用matplotlib.pyplot在二维平面上绘制这些数据点,如下代码片段所示:

    import matplotlib.pyplot as plot
    plot.scatter(data_points.transpose()[0], \
                 data_points.transpose()[1])
    

    预期的输出是这样的:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_04.jpg

    图 5.4:使用 matplotlib.pyplot 在二维平面上显示数据点的图表

    注意

    我们使用了transpose数组方法来获取第一个特征和第二个特征的值。我们也可以使用适当的数组索引来访问这些列:dataPoints[:,0],这等同于dataPoints.transpose()[0]

    现在我们已经有了数据点,是时候对其执行 k-means 算法了。

  4. 在 k-means 算法中,将k定义为3。我们期望图表的左下角、左上角和右下角各有一个聚类。添加random_state = 8以便重现相同的结果:

    from sklearn.cluster import KMeans
    k_means_model = KMeans(n_clusters=3,random_state=8)
    k_means_model.fit(data_points)
    

    在前面的代码片段中,我们使用了sklearn.cluster中的KMeans模块。像往常一样,在使用sklearn时,我们需要先定义一个模型并设置参数,然后将模型应用到数据集上。

    预期的输出是这样的:

    KMeans(algorithm='auto', copy_x=True, init='k-means++', 
           max_iter=300, n_clusters=3, n_init=10, n_jobs=None,
           precompute_distances='auto',
           random_state=8, tol=0.0001, verbose=0)
    

    输出显示了我们 k-means 模型的所有参数,但重要的参数是:

    max_iter:表示 k-means 算法的最大迭代次数。

    n_clusters:表示由 k-means 算法形成的聚类数量。

    n_init:表示 k-means 算法初始化随机点的次数。

    tol:表示检查 k-means 算法是否可以终止的阈值。

  5. 聚类完成后,访问每个聚类的中心点,如下代码片段所示:

    centers = k_means_model.cluster_centers_
    centers
    

    centers的输出将如下所示:

    array([[7.625     , 0.75      ],
           [3.1       , 9.6       ],
           [1.33333333, 1.5       ]])
    

    该输出显示了我们三个聚类中心的坐标。如果你回顾图 5.4,你会看到聚类的中心点似乎位于图表的左下角(1.3, 1.5)、左上角(3.1, 9.6)和右下角(7.265, 0.75)。左上角聚类的x坐标为3.1,很可能是因为它包含了我们位于[10, 10]的异常数据点。

  6. 接下来,用不同的颜色绘制聚类及其中心点。要找出哪个数据点属于哪个聚类,我们必须查询 k-means 分类器的labels属性,如下代码片段所示:

    labels = k_means_model.labels_
    labels
    

    labels的输出将如下所示:

    array([2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1])
    

    输出数组显示了每个数据点属于哪个聚类。这就是我们绘制数据所需要的所有信息。

  7. 现在,按照以下代码片段绘制数据:

    plot.scatter(centers[:,0], centers[:,1])
    for i in range(len(data_points)):
        plot.plot(data_points[i][0], data_points[i][1], \
                  ['k+','kx','k_'][k_means_model.labels_[i]])
    plot.show()
    

    在前面的代码片段中,我们使用了matplotlib库将数据点与每个坐标的中心一起绘制。每个聚类都有自己的标记(x+-),其中心由一个实心圆表示。

    预期的输出是这样的:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_05.jpg

    图 5.5:显示三个簇的中心点图

    查看图 5.5,你可以看到中心点位于它们的簇内,这些簇由x+-标记表示。

  8. 现在,重新使用相同的代码,只选择两个簇而不是三个:

    k_means_model = KMeans(n_clusters=2,random_state=8)
    k_means_model.fit(data_points)
    centers2 = k_means_model.cluster_centers_
    labels2 = k_means_model.labels_
    plot.scatter(centers2[:,0], centers2[:,1])
    for i in range(len(data_points)):
        plot.plot(data_points[i][0], data_points[i][1], \
                  ['k+','kx'][labels2[i]])
    plot.show()
    

    期望输出为:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_06.jpg

    图 5.6:显示两个簇的数据点图

    这一次,我们只有x+两种点,并且可以清楚地看到一个底部簇和一个顶部簇。有趣的是,第二次尝试中的顶部簇包含了与第一次尝试中的顶部簇相同的点。第二次尝试中的底部簇由第一次尝试中连接左下角和右下角簇的数据点组成。

  9. 最后,使用 k-means 模型进行预测,如以下代码片段所示。输出将是一个包含每个数据点所属簇编号的数组:

    predictions = k_means_model.predict([[5,5],[0,10]])
    predictions
    

    predictions的输出如下:

    array([0, 1], dtype=int32)
    

    这意味着我们的第一个点属于第一个簇(位于底部),第二个点属于第二个簇(位于顶部)。

    注意

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

    你也可以在packt.live/2Nnv7F2上在线运行此示例。你必须执行整个 Notebook 才能获得期望的结果。

完成本练习后,你能够在样本数据点上使用一个简单的 k-means 聚类模型。

scikit-learn 中的 K-Means 算法参数化

第二章《回归介绍》、第三章《分类介绍》和第四章《决策树介绍》中的分类和回归模型类似,k-means 算法也可以进行参数化。完整的参数列表可以在scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html找到。

一些示例如下:

  • n_clusters:将数据点分为的簇的数量。默认值为8

  • max_iter:最大迭代次数。

  • tol:检查是否可以终止 k-means 算法的阈值。

我们还使用了两个属性来检索簇中心点和簇本身:

  • cluster_centers_:返回簇中心点的坐标。

  • labels_:返回一个整数数组,表示数据点所属的簇编号。编号从零开始。

练习 5.02:检索中心点和标签

在本练习中,你将能够理解cluster_centers_labels_的用法。

以下步骤将帮助你完成练习:

  1. 打开一个新的 Jupyter Notebook 文件。

  2. 接下来,创建与练习 5.01在 scikit-learn 中实现 K-Means相同的 12 个数据点,但在这里执行四个簇的 k-means 聚类,如下所示的代码片段:

    import numpy as np
    import matplotlib.pyplot as plot
    from sklearn.cluster import KMeans
    data_points = np.array([[1, 1], [1, 1.5], [2, 2], \
                            [8, 1], [8, 0], [8.5, 1], \
                            [6, 1], [1, 10], [1.5, 10], \
                            [1.5, 9.5], [10, 10], [1.5, 8.5]])
    k_means_model = KMeans(n_clusters=4,random_state=8)
    k_means_model.fit(data_points)
    centers = k_means_model.cluster_centers_
    centers
    

    centers的输出如下:

    array([[ 7.625     ,  0.75      ],
           [ 1.375     ,  9.5       ],
           [ 1.33333333,  1.5       ],
           [10\.        , 10\.        ]])
    

    cluster_centers_属性的输出显示了中心点的xy坐标。

    从输出结果中,我们可以看到4个中心,分别是右下角(7.6, 0.75)、左上角(1.3, 9.5)、左下角(1.3, 1.5)和右上角(10, 10)。我们还可以注意到,第四个簇(右上角簇)只有一个数据点。这个数据点可以被认为是一个异常值

  3. 现在,在簇上应用labels_ 属性

    labels = k_means_model.labels_
    labels
    

    labels的输出如下:

    array([2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 3, 1], dtype=int32)
    

    labels_属性是一个长度为12的数组,显示了每个12个数据点所属的簇。第一个簇与数字 0 相关,第二个与 1 相关,第三个与 2 相关,以此类推(请记住,Python 的索引从 0 开始,而不是从 1 开始)。

    注意

    要访问该部分的源代码,请参考packt.live/3dmHsDX

    您还可以在packt.live/2B0ebld上在线运行此示例。您必须执行整个 Notebook 以获得期望的结果。

通过完成此练习,您可以获取簇的中心坐标。同时,您也可以看到每个数据点被分配到哪个标签(簇)。

销售数据的 K-Means 聚类

在接下来的活动中,我们将查看销售数据,并对这些销售数据执行 k-means 聚类。

活动 5.01:使用 K-Means 聚类销售数据

在本活动中,您将处理销售交易数据集(Weekly dataset),该数据集包含了 800 种产品在 1 年内的每周销售数据。我们的数据集不会包含任何有关产品的信息,除了销售数据。

你的目标是使用 k-means 聚类算法识别具有相似销售趋势的产品。你需要尝试不同的簇数,以找到最佳簇数。

注意

数据集可以在archive.ics.uci.edu/ml/datasets/Sales_Transactions_Dataset_Weekly找到。

数据集文件也可以在我们的 GitHub 存储库中找到:packt.live/3hVH42v

引用:Tan, S., & San Lau, J. (2014). 时间序列聚类:市场篮子分析的优越替代方法。在《第一届国际先进数据与信息工程会议论文集(DaEng-2013)》(第 241-248 页)中

以下步骤将帮助您完成此活动:

  1. 打开一个新的 Jupyter Notebook 文件。

  2. 将数据集加载为 DataFrame,并检查数据。

  3. 使用 pandas 的drop函数创建一个没有不必要列的新 DataFrame(即数据集中的前55列),并使用 pandas 的inplace参数。

  4. 创建一个包含8个集群且random_state = 8的 k-means 聚类模型。

  5. 从第一个聚类模型中提取标签。

  6. 从第一个 DataFrame,df,只保留W列和标签作为新列。

  7. 使用 pandas 中的groupby函数进行所需的聚合,以便获取每个集群的年度平均销售额。

预期输出如下:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_07.jpg

图 5.7:使用 k-means 算法处理销售交易数据的预期输出

注意

本活动的解决方案见第 363 页。

现在你已经详细了解了 k-means 算法,我们将继续介绍另一种聚类算法,即均值漂移算法。

均值漂移算法

均值漂移是一种层次聚类算法,通过计算集群中心并在每次迭代时将其移动到模式位置来分配数据点到某个集群。模式是数据点最多的区域。在第一次迭代中,随机选择一个点作为集群中心,然后算法将计算所有在某个半径范围内的邻近数据点的均值。该均值将成为新的集群中心。第二次迭代将从计算所有邻近数据点的均值并将其设为新集群中心开始。每次迭代时,集群中心将向数据点最多的地方移动。当新集群中心无法包含更多数据点时,算法将停止。当算法停止时,每个数据点将被分配到一个集群。

与 k-means 算法不同,均值漂移算法还会确定所需的集群数量。这一点非常有优势,因为我们通常并不知道需要多少个集群。

这个算法也有许多应用场景。例如,Xbox Kinect 设备使用均值漂移算法来检测人体部位。每个主要部位(头部、手臂、腿部、手等)都是由均值漂移算法分配的数据点集群。

在下一个练习中,我们将实现均值漂移算法。

练习 5.03:实现均值漂移算法

在这个练习中,我们将使用均值漂移算法来实现聚类。

我们将使用scipy.spatial库来计算欧几里得距离,见于第一章人工智能导论。这个库简化了计算坐标列表之间的距离(如欧几里得距离或曼哈顿距离)。更多关于此库的细节可以在docs.scipy.org/doc/scipy/reference/spatial.distance.html#module-scipy.spatial.distance找到。

以下步骤将帮助你完成练习:

  1. 打开一个新的 Jupyter Notebook 文件。

  2. 我们将使用练习 5.01中的数据点,在 scikit-learn 中实现 K 均值算法

    import numpy as np
    data_points = np.array([[1, 1], [1, 1.5], [2, 2], \
                            [8, 1], [8, 0], [8.5, 1], \
                            [6, 1], [1, 10], [1.5, 10], \
                            [1.5, 9.5], [10, 10], [1.5, 8.5]])
    import matplotlib.pyplot as plot
    plot.scatter(data_points.transpose()[0], \
                 data_points.transpose()[1])
    

    我们现在的任务是找到点 P(x, y),使得从点 P 出发,半径 R 范围内的数据点数量最大化。点的分布如下:

    https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_08.jpg

    图 5.8:显示数据点的图表,来自 data_points 数组

  3. 将点P1等同于我们列表中的第一个数据点[1, 1]

    P1 = [1, 1]
    
  4. 找到位于此点r = 2距离范围内的点。我们将使用scipy库,它简化了数学计算,包括空间距离:

    from scipy.spatial import distance
    r = 2
    points1 = np.array([p0 for p0 in data_points if \
                        distance.euclidean(p0, P1) <= r])
    points1
    

    在前面的代码片段中,我们使用了欧几里得距离来找到所有位于点P1的半径r范围内的点。

    points1的输出将如下所示:

    array([[1\. , 1\. ],
           [1\. , 1.5],
           [2\. , 2\. ]])
    

    从输出中,我们可以看到我们找到了三个位于P1半径内的点。它们是我们之前在图表左下方看到的三个点,位于本章的图 5.8中。

  5. 现在,计算数据点的均值,以获得P2的新坐标:

    P2 = [np.mean( points1.transpose()[0] ), \
          np.mean(points1.transpose()[1] )]
    P2
    

    在前面的代码片段中,我们计算了包含三个数据点的数组的均值,以便获得P2的新坐标。

    P2的输出将如下所示:

    [1.3333333333333333, 1.5]
    
  6. 现在,P2已被计算出来,再次检索给定半径内的点,如下所示的代码片段:

    points2 = np.array([p0 for p0 in data_points if \
                        distance.euclidean( p0, P2) <= r])
    points2
    

    points的输出将如下所示:

    array([[1\. , 1\. ],
           [1\. , 1.5],
           [2\. , 2\. ]])
    

    这些是我们在步骤 4中找到的相同的三个点,因此我们可以在此停止。我们已经找到了围绕均值[1.3333333333333333, 1.5]的三个点。以该中心为圆心,半径为2的点形成了一个簇。

  7. 由于数据点[1, 1.5][2, 2]已经与[1,1]形成一个簇,我们可以直接继续使用我们列表中的第四个点[8, 1]

    P3 = [8, 1]
    points3 = np.array( [p0 for p0 in data_points if \
                         distance.euclidean(p0, P3) <= r])
    points3
    

    在前面的代码片段中,我们使用了与步骤 4相同的代码,但采用了新的P3

    points3的输出将如下所示:

    array([[8\. , 1\. ],
           [8\. , 0\. ],
           [8.5, 1\. ],
           [6\. , 1\. ]])
    

    这次,我们找到了四个位于P4半径r内的点。

  8. 现在,按照以下代码片段计算均值:

    P4 = [np.mean(points3.transpose()[0]), \
          np.mean(points3.transpose()[1])]
    P4
    

    在前面的代码片段中,我们计算了包含四个数据点的数组的均值,以便获得P4的新坐标,如步骤 5所示。

    P4的输出将如下所示:

    [7.625, 0.75]
    

    这个均值不会改变,因为在下一次迭代中,我们会找到相同的数据点。

  9. 请注意,我们在选择点[8, 1]时比较幸运。如果我们从P = [8, 0]P = [8.5, 1]开始,我们只能找到三个点,而不是四个。让我们尝试使用P5 = [8, 0]

    P5 = [8, 0]
    points4 = np.array([p0 for p0 in data_points if \
                       distance.euclidean(p0, P5) <= r])
    points4
    

    在前面的代码片段中,我们使用了与步骤 4相同的代码,但采用了新的P5

    points4的输出将如下所示:

    array([[8\. , 1\. ],
           [8\. , 0\. ],
           [8.5, 1\. ]])
    

    这次,我们找到了三个位于P5的半径r内的点。

  10. 现在,使用步骤 5中所示的偏移均值重新运行距离计算:

    P6 = [np.mean(points4.transpose()[0]), \
          np.mean(points4.transpose()[1])]
    P6
    

    在前面的代码片段中,我们计算了包含三个数据点的数组的均值,以获得新的 P6 坐标。

    P6 的输出将如下所示:

    [8.166666666666666, 0.6666666666666666]
    
  11. 现在再做一次,但使用 P7 = [8.5, 1]

    P7 = [8.5, 1]
    points5 = np.array([p0 for p0 in data_points if \
                        distance.euclidean(p0, P7) <= r])
    points5
    

    在前面的代码片段中,我们使用了与 步骤 4 中相同的代码,但用了一个新的 P7

    points5 的输出将如下所示:

    array([[8\. , 1\. ],
           [8\. , 0\. ],
           [8.5, 1\. ]])
    

    这一次,我们又在 P 的半径 r 内找到了相同的三个点。这意味着,从 [8,1] 开始,我们得到了一个比从 [8, 0][8.5, 1] 开始更大的聚类。因此,我们必须选择包含最多数据点的中心点。

  12. 现在,让我们看看如果从第四个数据点 [6, 1] 开始会发生什么:

    P8 = [6, 1]
    points6 = np.array([p0 for p0 in data_points if \
                        distance.euclidean(p0, P8) <= r])
    points6
    

    在前面的代码片段中,我们使用了与 步骤 4 中相同的代码,但用了一个新的 P8

    points6 的输出将如下所示:

    array([[8., 1.],
           [6., 1.]])
    

    这次,我们只在 P8 的半径 r 内找到了两个数据点。我们成功找到了数据点 [8, 1]

  13. 现在,将均值从 [6, 1] 移动到计算得到的新均值:

    P9 = [np.mean(points6.transpose()[0]), \
          np.mean(points6.transpose()[1]) ]
    P9
    

    在前面的代码片段中,我们计算了包含三个数据点的数组的均值,以获得新的 P9 坐标,正如 步骤 5 中所示。

    P9 的输出将如下所示:

    [7.0, 1.0]
    
  14. 检查是否通过这个新的 P9 得到了更多的点:

    points7 = np.array([p0 for p0 in data_points if \
                        distance.euclidean(p0, P9) <= r])
    points7
    

    在前面的代码片段中,我们使用了与 步骤 4 中相同的代码,但用了一个新的 P9

    points7 的输出将如下所示:

    array([[8\. , 1\. ],
           [8\. , 0\. ],
           [8.5, 1\. ],
           [6\. , 1\. ]])
    

    我们成功找到了所有四个点。因此,我们已经成功定义了一个大小为 4 的聚类。均值将与之前相同:[7.625, 0.75]

    注意

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

    你还可以在 packt.live/2YoSu78 在线运行这个示例。你必须执行整个 Notebook 才能得到期望的结果。

这是一个简单的聚类示例,应用了均值迁移算法。我们只是展示了算法在寻找聚类时会考虑什么。

然而,仍然有一个问题,那就是半径的值应该是多少?

请注意,如果没有设置 2 的半径,我们可以简单地从一个包含所有数据点的大半径开始,然后逐步减小半径,或者从一个非常小的半径开始,确保每个数据点都在其所属的聚类中,然后逐步增加半径,直到得到期望的结果。

在下一节中,我们将介绍均值迁移算法,但这次使用 scikit-learn。

scikit-learn 中的均值迁移算法

让我们使用与 k-means 算法相同的数据点:

import numpy as np
data_points = np.array([[1, 1], [1, 1.5], [2, 2], \
                        [8, 1], [8, 0], [8.5, 1], \
                        [6, 1], [1, 10], [1.5, 10], \
                        [1.5, 9.5], [10, 10], [1.5, 8.5]])

均值迁移聚类算法的语法与 k-means 聚类算法的语法类似:

from sklearn.cluster import MeanShift
mean_shift_model = MeanShift()
mean_shift_model.fit(data_points)

一旦聚类完成,我们就可以访问每个聚类的中心点:

mean_shift_model.cluster_centers_

期望的输出是这样的:

array([[ 1.375     ,  9.5       ],
       [ 8.16666667,  0.66666667],
       [ 1.33333333,  1.5       ],
       [10\.        , 10\.        ],
       [ 6\.        ,  1\.        ]])

均值迁移模型找到了五个聚类,中心如前面的代码所示。

和 k-means 一样,我们也可以获得标签:

mean_shift_model.labels_

预期输出是这样的:

array([2, 2, 2, 1, 1, 1, 4, 0, 0, 0, 3, 0], dtype=int64)

输出数组显示哪个数据点属于哪个簇。这是我们绘制数据所需要的全部内容:

import matplotlib.pyplot as plot
plot.scatter(mean_shift_model.cluster_centers_[:,0], \
             mean_shift_model.cluster_centers_[:,1])
for i in range(len(data_points)): 
    plot.plot(data_points[i][0], data_points[i][1], \
              ['k+','kx','kv', 'k_', 'k1']\
              [mean_shift_model.labels_[i]])
plot.show()

在之前的代码片段中,我们绘制了数据点和五个簇的中心。属于同一簇的每个数据点将使用相同的标记。簇中心则用点标记。

预期输出是这样的:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_09.jpg

图 5.9:显示五个簇的数据点的图形

我们可以看到,三个簇包含多个点(左上、左下和右下)。那两个作为单独簇的数据点可以被视为异常值,正如之前提到的,它们离其他簇太远,因此不属于任何簇。

现在我们已经了解了均值漂移算法,可以来看看层次聚类,尤其是聚合层次聚类(自下而上的方法)。

层次聚类

层次聚类算法分为两类:

  • 聚合(或自下而上)层次聚类

  • 分割(或自上而下)层次聚类

本章我们将只讨论聚合层次聚类,因为它是两种方法中最广泛使用且最有效的。

聚合层次聚类(Agglomerative Hierarchical Clustering)最开始将每个数据点视为一个单独的簇,然后依次将最接近的簇按对进行合并(或聚合)。为了找到最接近的数据簇,聚合层次聚类使用欧几里得距离或曼哈顿距离等启发式方法来定义数据点之间的距离。还需要一个聚类函数来聚合簇内数据点之间的距离,从而定义簇之间的接近度的唯一值。

聚类函数的示例包括单一聚类(简单距离)、平均聚类(平均距离)、最大聚类(最大距离)和沃德聚类(平方差)。具有最小聚类值的簇对将被归为一组。此过程将重复进行,直到只剩下一个包含所有数据点的簇。最终,当只剩下一个簇时,该算法终止。

为了直观地表示簇的层次结构,可以使用树状图(dendrogram)。树状图是一棵树,底部的叶子表示数据点。两个叶子之间的每个交点表示这两个叶子的分组。根(顶部)表示一个包含所有数据点的唯一簇。请看图 5.10,它展示了一个树状图。

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_10.jpg

图 5.10:树状图示例

scikit-learn 中的聚合层次聚类

看一下以下示例,我们使用了与 k-means 算法相同的数据点:

import numpy as np
data_points = np.array([[1, 1], [1, 1.5], [2, 2], \
                        [8, 1], [8, 0], [8.5, 1], \
                        [6, 1], [1, 10], [1.5, 10], \
                        [1.5, 9.5], [10, 10], [1.5, 8.5]])

为了绘制树状图,我们需要先导入scipy库:

from scipy.cluster.hierarchy import dendrogram
import scipy.cluster.hierarchy as sch

然后我们可以使用 SciPy 中的ward连接函数绘制一个树状图,因为它是最常用的连接函数:

dendrogram = sch.dendrogram(sch.linkage(data_points, \
                            method='ward'))

树状图的输出结果如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_11.jpg

图 5.11:基于随机数据点的树状图

使用树状图时,我们通常可以通过简单地在图 5.12中所示的最大垂直距离区域画一条水平线,并计算交点数量,来大致猜测一个合适的聚类数。在这个例子中,应该是两个聚类,但我们将选择下一个较大的区域,因为两个聚类的数量太小了。

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_12.jpg

图 5.12:树状图中的聚类划分

y轴表示聚类的接近度,而x轴表示每个数据点的索引。因此,我们的前三个数据点(0,1,2)属于同一个聚类,接下来的四个数据点(3,4,5,6)组成另一个聚类,数据点10独立成一个聚类,剩余的(7,8,9,11)则组成最后一个聚类。

层次聚类算法的语法与 k-means 聚类算法类似,不同之处在于我们需要指定affinity的类型(在这里我们选择欧几里得距离)和连接方式(在这里我们选择ward连接):

from sklearn.cluster import AgglomerativeClustering
agglomerative_model = AgglomerativeClustering(n_clusters=4, \
                                              affinity='euclidean', \
                                              linkage='ward')
agglomerative_model.fit(data_points)

输出结果为:

AgglomerativeClustering(affinity='euclidean', 
                        compute_full_tree='auto',
                        connectivity=None, 
                        distance_threshold=None,
                        linkage='ward', memory=None,
                        n_clusters=4, pooling_func='deprecated')

类似于 k-means,我们也可以通过以下代码片段获得标签:

agglomerative_model.labels_

预期输出为:

array([2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 3, 1], dtype=int64)

输出数组显示了每个数据点所属的聚类。这就是我们绘制数据所需的全部信息:

import matplotlib.pyplot as plot
for i in range(len(data_points)): 
    plot.plot(data_points[i][0], data_points[i][1], \
              ['k+','kx','kv', 'k_'][agglomerative_model.labels_[i]])
plot.show()

在前面的代码片段中,我们绘制了数据点和四个聚类中心的图。每个属于同一聚类的数据点将具有相同的标记。

预期输出为:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_13.jpg

图 5.13:显示四个聚类数据点的图

我们可以看到,与均值漂移方法的结果相比,层次聚类能够正确地将位于(6,1)的数据点与右下角的聚类组合在一起,而不是单独形成一个聚类。在像这样的情况下,当数据量非常少时,层次聚类和均值漂移比 k-means 效果更好。然而,它们的计算时间需求非常高,这使得它们在非常大的数据集上表现不佳。不过,k-means 非常快速,是处理大数据集的更好选择。

现在我们已经了解了一些不同的聚类算法,接下来需要开始评估这些模型并进行比较,以选择最适合聚类的最佳模型。

聚类性能评估

与监督学习不同,监督学习中我们始终可以使用标签来评估预测结果,而无监督学习则稍显复杂,因为我们通常没有标签。为了评估聚类模型,可以根据是否有标签数据采取两种方法:

  • 第一种方法是外在方法,它需要标签数据的存在。这意味着,如果没有标签数据,必须有人为干预来标记数据,或者至少是其中的一部分。

  • 另一种方法是内在方法。一般来说,外在方法试图根据标签数据为聚类分配一个分数,而内在方法则通过检查聚类的分离度和紧凑度来评估聚类。

    注意

    我们将跳过数学解释,因为它们相当复杂。

    您可以在 sklearn 网站上找到更多数学细节,网址为:scikit-learn.org/stable/modules/clustering.html#clustering-performance-evaluation

我们将从外在方法开始(因为它是最广泛使用的方法),并使用 sklearn 在我们的 k 均值示例中定义以下分数:

  • 调整后的兰德指数

  • 调整后的互信息

  • 同质性

  • 完整性

  • V-度量

  • Fowlkes-Mallows 分数

  • 互信息矩阵

让我们看一个示例,在这个示例中,我们首先需要从sklearn.cluster导入metrics模块:

from sklearn import metrics

我们将重用在练习 5.01中使用的代码,在 scikit-learn 中实现 K 均值算法:

import numpy as np
import matplotlib.pyplot as plot
from sklearn.cluster import KMeans
data_points = np.array([[1, 1], [1, 1.5], [2, 2], \
                        [8, 1], [8, 0], [8.5, 1], \
                        [6, 1], [1, 10], [1.5, 10], \
                        [1.5, 9.5], [10, 10], [1.5, 8.5]])
k_means_model = KMeans(n_clusters=3,random_state = 8)
k_means_model.fit(data_points)
k_means_model.labels_

我们使用k_means_model.labels_预测标签的输出是:

array([2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1])

最后,定义该数据集的真实标签,如下代码片段所示:

data_labels = np.array([0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 3, 1])

调整后的兰德指数

调整后的兰德指数是一个度量聚类预测与标签之间相似性的函数,同时忽略排列顺序。当标签是大而相等的聚类时,调整后的兰德指数表现得很好。

调整后的兰德指数的范围是**[-1.1]**,其中负值是不理想的。负分数意味着我们的模型表现比随机分配标签还要差。如果我们随机分配标签,分数接近 0。但是,越接近 1,说明我们的聚类模型在预测正确标签方面表现越好。

使用sklearn,我们可以通过以下代码轻松计算调整后的兰德指数:

metrics.adjusted_rand_score(data_labels, k_means_model.labels_)

期望的输出是这样的:

0.8422939068100358

在这种情况下,调整后的兰德指数表明我们的 k 均值模型与真实标签之间的差距不大。

调整后的互信息

调整后的互信息是一个度量聚类预测与标签之间的熵的函数,同时忽略排列顺序。

调整互信息没有固定的范围,但负值被认为是差的。我们越接近 1,聚类模型预测正确标签的效果就越好。

使用sklearn,我们可以通过以下代码轻松计算它:

metrics.adjusted_mutual_info_score(data_labels, \
                                   k_means_model.labels_)

预期输出如下:

0.8769185235006342

在这种情况下,调整互信息表明我们的 k-means 模型相当优秀,与真实标签的差距不大。

V-Measure、同质性和完整性

V-Measure 被定义为同质性和完整性的调和均值。调和均值是一种平均值(其他类型的平均值有算术平均和几何平均),它使用倒数(倒数是一个数的倒数。例如,2 的倒数是https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_13a.png,3 的倒数是https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_13b.png)。

调和均值的公式如下:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_13a.png

](https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_14.jpg)

图 5.14:调和均值公式

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_14a.png是值的数量,https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_14b.png是每个点的值。

为了计算 V-Measure,我们首先需要定义同质性和完整性。

完美的同质性指的是每个聚类中的数据点都属于相同的标签。该同质性得分将反映我们的每个聚类在将数据从相同标签分组时的效果。

完美的完整性指的是所有属于相同标签的数据点被聚集到同一个聚类中的情况。同质性得分将反映每个标签的所有数据点是否都被很好地分组到同一个聚类中。

因此,V-Measure 的公式如下:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_15.jpg

](https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_15.jpg)

图 5.15:V-Measure 公式

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_15a.png的默认值为1,但可以根据需要进行调整,以进一步强调同质性或完整性。

这三个得分的范围是[0,1],其中0表示最差得分,1表示完美得分。

使用sklearn,我们可以通过以下代码轻松计算这三个得分:

metrics.homogeneity_score(data_labels, k_means_model.labels_)
metrics.completeness_score(data_labels, k_means_model.labels_)
metrics.v_measure_score(data_labels, k_means_model.labels_, \
                        beta=1)

homogeneity_score的输出如下:

0.8378758055108827

在这种情况下,同质性得分表明我们的 k-means 模型中的聚类包含了不同的标签。

completeness_score的输出如下:

1.0

在这种情况下,完整性得分表明我们的 k-means 模型成功地将每个标签的所有数据点放入同一个聚类中。

v_measure_score的输出如下:

0.9117871871412709

在这种情况下,V-Measure 表明我们的 k-means 模型虽然不完美,但总体得分良好。

Fowlkes-Mallows 得分

Fowlkes-Mallows 得分是衡量标签聚类内相似性和聚类预测的一个指标,它定义为精确度和召回率的几何均值(你在第四章决策树入门中学到过这个概念)。

Fowlkes-Mallows 分数的公式如下:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_16.jpg

图 5.16:Fowlkes-Mallows 公式

我们来分解一下:

  • 真阳性(TP):是所有预测与标签簇相同的观测值。

  • 假阳性(FP):是所有预测在同一簇中,但与标签簇不同的观测值。

  • 假阴性(FN):是所有预测不在同一簇中,但位于同一标签簇中的观测值。

Fowlkes-Mallows 分数的范围是 [0, 1],其中 0 是最差的分数,1 是完美的分数。

使用 sklearn,我们可以通过以下代码轻松计算它:

metrics.fowlkes_mallows_score(data_labels, k_means_model.labels_)

期望的输出是:

0.8885233166386386

在这种情况下,Fowlkes-Mallows 分数表明我们的 k-means 模型相当不错,并且与真实标签相差不远。

聚类矩阵

聚类矩阵不是一个分数,而是报告每个真实/预测簇对的交集基数以及所需的标签数据。它非常类似于在《第四章,决策树简介》中看到的 混淆矩阵。该矩阵必须与标签和簇名称一致,因此我们需要小心地将簇命名为与标签相同,而在之前看到的分数中并未做到这一点。

我们将把标签从以下内容修改为:

data_labels = np.array([0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 3, 1])

到此:

data_labels = np.array([2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 3, 0])

然后,使用 sklearn,我们可以通过以下代码轻松计算聚类矩阵:

from sklearn.metrics.cluster import contingency_matrix
contingency_matrix(k_means_model.labels_,data_labels)

contingency_matrix 的输出如下:

array([[0, 4, 0, 0],
       [4, 0, 0, 1],
       [0, 0, 3, 0]])

contingency_matrix 输出的第一行表示有 4 个数据点的真实簇为第一簇(0)。第二行表示有 4 个数据点的真实簇为第二簇(1);然而,在这个簇中错误地预测了一个额外的 1,但它实际上属于第四簇(3)。第三行表示有 3 个数据点的真实簇为第三簇(2)。

现在我们将看一下内在方法,当我们没有标签时,这种方法是必需的。我们将使用 sklearn 在我们的 k-means 示例中定义以下分数:

  • Silhouette Coefficient(轮廓系数)

  • Calinski-Harabasz 指数

  • Davies-Bouldin 指数

Silhouette Coefficient(轮廓系数)

Silhouette Coefficient(轮廓系数)是一个内在评价的例子。它衡量数据点与其簇之间的相似度,相对于其他簇的相似度。

它包括两个分数:

  • a:数据点与同一簇内所有其他数据点之间的平均距离。

  • b:数据点与最近簇内所有数据点之间的平均距离。

轮廓系数公式为:

https://github.com/OpenDocCN/freelearn-dl-pt7-zh/raw/master/docs/app-ai-ws/img/B16060_05_17.jpg

图 5.17:轮廓系数公式

轮廓系数的范围为[-1,1],其中**-1表示错误的聚类。接近零的分数表示聚类存在重叠。接近1**的分数表示所有数据点都分配到了合适的聚类中。

然后,使用sklearn,我们可以通过以下代码轻松计算轮廓系数:

metrics.silhouette_score(data_points, k_means_model.labels_)

silhouette_score的输出如下:

0.6753568188872228

在这种情况下,轮廓系数表明我们的 k-means 模型存在一些重叠的聚类,并且可以通过将某些数据点从其中一个聚类中分离出来来进行改进。

Calinski-Harabasz 指数

Calinski-Harabasz 指数衡量每个聚类内部数据点的分布情况。它被定义为聚类之间方差与每个聚类内部方差的比值。Calinski-Harabasz 指数没有范围,起始值为0。分数越高,聚类越密集。密集的聚类表明聚类定义得较好。

使用sklearn,我们可以通过以下代码轻松计算它:

metrics.calinski_harabasz_score(data_points, k_means_model.labels_)

calinski_harabasz_score的输出如下:

19.52509172315154

在这种情况下,Calinski-Harabasz 指数表明我们的 k-means 模型聚类比较分散,建议我们可能有重叠的聚类。

Davies-Bouldin 指数

Davies-Bouldin 指数衡量聚类之间的平均相似性。相似性是聚类与其最近聚类之间的距离与聚类内每个数据点与聚类中心之间的平均距离之比。Davies-Bouldin 指数没有范围,起始值为0。分数越接近0越好,这意味着聚类之间分离得很好,是聚类效果良好的指示。

使用sklearn,我们可以通过以下代码轻松计算 Davies-Bouldin 指数:

metrics.davies_bouldin_score(data_points, k_means_model.labels_)

davies_bouldin_score的输出如下:

0.404206621415983

在这种情况下,Calinski-Harabasz 评分表明我们的 k-means 模型存在一些重叠的聚类,通过更好地分离某些数据点,可以对其中一个聚类进行改进。

活动 5.02:使用均值漂移算法和聚合层次聚类对红酒数据进行聚类

在这个活动中,您将使用红酒质量数据集,具体来说是红葡萄酒数据。该数据集包含 1,599 个红酒的质量数据以及它们的化学测试结果。

您的目标是构建两个聚类模型(使用均值漂移算法和聚合层次聚类),以识别相似质量的葡萄酒是否具有相似的物理化学性质。您还需要使用外部和内部方法评估并比较这两个聚类模型。

注意

数据集可以通过以下网址找到:archive.ics.uci.edu/ml/datasets/Wine+Quality

数据集文件可以在我们的 GitHub 仓库中找到,链接:packt.live/2YYsxuu

引用:P. Cortez, A. Cerdeira, F. Almeida, T. Matos 和 J. Reis. 通过数据挖掘物理化学性质建模葡萄酒偏好。在《决策支持系统》期刊,Elsevier,47(4):547-553,2009

以下步骤将帮助你完成该活动:

  1. 打开一个新的 Jupyter Notebook 文件。

  2. 将数据集加载为 DataFrame,使用sep = ";",并检查数据。

  3. 创建一个均值迁移聚类模型,然后提取该模型的预测标签及其创建的聚类数。

  4. 在创建树状图并选择最佳聚类数后,创建一个聚合层次聚类模型。

  5. 从第一个聚类模型中提取标签。

  6. 为两个模型计算以下外部方法得分:

    调整后的兰德指数

    调整后的互信息

    V 度量

    Fowlkes-Mallows 分数

  7. 为两个模型计算以下内部方法得分:

    轮廓系数

    Calinski-Harabasz 指数

    Davies-Bouldin 指数

期望输出为:

均值迁移聚类模型的每个得分值如下:

  • 调整后的兰德指数:0.0006771608724007207

  • 调整后的互信息:0.004837187596124968

  • V 度量:0.021907254751144124

  • Fowlkes-Mallows 分数:0.5721233634622408

  • 轮廓系数:0.32769323700400077

  • Calinski-Harabasz 指数:44.62091774102674

  • Davies-Bouldin 指数:0.8106334674570222

聚合层次聚类的每个得分值如下:

  • 调整后的兰德指数:0.05358047852603172

  • 调整后的互信息:0.05993098663692826

  • V 度量:0.07549735446050691

  • Fowlkes-Mallows 分数:0.3300681478007641

  • 轮廓系数:0.1591882574407987

  • Calinski-Harabasz 指数:223.5171774491095

  • Davies-Bouldin 指数:1.4975443816135114

    注意

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

完成此活动后,你已对多个产品的多个列进行了均值迁移和聚合层次聚类。你还学习了如何使用外部和内部方法评估聚类模型。最后,你利用模型的结果及其评估,找到了现实世界问题的答案。

总结

在本章中,我们学习了聚类的基础知识。聚类是一种无监督学习形式,其中给定了特征,但没有标签。聚类算法的目标是根据数据点的相似性找到标签。

我们还学到聚类有两种类型,平面聚类和层次聚类,其中第一种类型需要指定聚类的数量,而第二种类型能够自动找到最佳的聚类数量。

k-means 算法是平面聚类的一个例子,而均值漂移和凝聚层次聚类则是层次聚类算法的例子。

我们还学习了评估聚类模型性能的众多评分标准,包括有标签的外部方法和无标签的内部方法。

第六章神经网络与深度学习中,你将接触到一个在过去十年因计算能力的爆炸性增长以及廉价、可扩展的在线服务器容量而变得流行的领域。这个领域是神经网络与深度学习的科学。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值