Python-数据科学本质论-二-

63 阅读1小时+

Python 数据科学本质论(二)

原文:Python Data Science Essentials

协议:CC BY-NC-SA 4.0

三、数据管道

到目前为止,我们已经探索了如何将数据加载到 Python 中并进行处理,以创建一个包含数值(您的数据集)的二维 NumPy 数组。 现在,我们准备完全融入数据科学,从数据中提取含义,并开发潜在的数据产品。 关于数据处理和转换的这一章以及关于机器学习的下一章是整本书中最具挑战性的部分。

在本章中,您将学习如何执行以下操作:

  • 简要浏览数据并创建新特征
  • 降低数据的维数
  • 发现并处理异常值
  • 确定项目的最佳得分或损失指标
  • 应用科学的方法论并有效地测试您的机器学习假设的表现
  • 通过减少特征部件数量来降低数据科学问题的复杂性
  • 优化您的学习参数

介绍 EDA

探索性数据分析EDA)或数据探索是数据科学过程中的第一步。 约翰·图基(John Tukey)在 1977 年首次撰写他的著作《探索性数据分析》时就创造了这个名词,强调了 EDA 的重要性。 要求 EDA 更好地理解数据集,检查其特征和形状,验证您要记住的一些第一个假设,并获得有关在后续后续数据科学任务中要进行的下一步的初步思路。

在本节中,您将处理上一章中已使用的鸢尾花数据集。 首先,让我们加载数据集:

In: import pandas as pd
    iris_filename = 'datasets-uci-iris.csv'
    iris = pd.read_csv(iris_filename, header=None, 
            names= ['sepal_length', 'sepal_width', 
            'petal_length', 'petal_width', 'target'])
    iris.head()

调用head方法将显示前五行:

伟大的! 使用一些命令,您已经加载了数据集。 现在,调查阶段开始。 .describe()方法提供了一些很好的见解,可以按如下方式使用:

In: iris.describe() 

随即出现了对数据集的描述,包括频率,均值和其他描述性信息:

对于所有数字特征,您都有观测值的数量,它们各自的平均值,标准差,最小值和最大值以及一些常规报告的分位数(以25百分比,50百分比和75百分比), 所谓的四分位数。 这为您提供了有关每个特征的分布的一个好主意。 如果要可视化此信息,只需使用boxplot()方法,如下所示:

In: boxes = iris.boxplot(return_type='axes') 

每个变量的箱线图将出现:

有时,本章中介绍的图形/图表可能与在本地计算机上获得的图形/图表略有不同,因为图形布局的初始化是使用随机参数进行的。

如果需要了解其他分位数,可以使用.quantile()方法。 例如,如果您需要的值是值分布的 10% 和 90%,则可以尝试以下代码:

In: iris.quantile([0.1, 0.9]) 

以下是所需百分位数的值:

最后,要计算中位数,可以使用.median()方法。 同样,为了获得均值和标准差,分别使用了.mean().std()方法。 对于分类特征,要获取有关特征中存在的级别的信息(即特征所采用的不同值),可以使用.unique()方法,如下所示:

In: iris.target.unique()

Out: array(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica'], 
     dtype=object)

要检查特征之间的关系,可以创建同现矩阵或相似矩阵。 在下面的示例中,我们将针对petal_width函数的相同计数计算petal_length函数出现的次数多于平均值的次数。 为此,您需要使用crosstab方法,如下所示:

In: pd.crosstab(iris['petal_length'] > 3.758667, 
                iris['petal_width'] > 1.198667)

该命令生成一个双向表:

结果,您将注意到这些特征几乎总是联合出现的。 因此,您可以假设这两个事件之间存在很强的关系。 以图形方式,您可以使用以下代码检查这样的假设:

In: scatterplot = iris.plot(kind='scatter', 
                            x='petal_width', y='petal_length', 
                            s=64, c='blue', edgecolors='white')

您将获得指定为xy的变量的散点图:

趋势十分明显。 我们推断xy密切相关。 您通常在 EDA 期间执行的最后一项操作是检查特征的分布。 要使用 Pandas 进行管理,您可以使用直方图来估计分布,这要归功于以下代码段:

In: distr = iris.petal_width.plot(kind='hist', alpha=0.5, bins=20)

结果,显示直方图:

经过仔细搜索,我们选择了 20 个箱子。 在其他情况下,20 个箱子可能是一个极低或极高的值。 根据经验,绘制分布直方图时,起始值为观察数的平方根。 初始可视化之后,您将需要修改箱子的数量,直到您在分布中识别出众所周知的形状为止。

我们建议您探索所有特征,以检查它们之间的关系并估计其分布。 实际上,鉴于其分布,您可能决定对每个特征进行不同的处理,以便随后获得最大的分类或回归表现。

建立新特征

有时,您会发现自己与特征和target变量没有真正的联系。 在这种情况下,您可以修改输入数据集。 您可以应用线性或非线性变换来提高系统的精度,等等。 这是整个过程中非常重要的一步,因为它完全取决于数据科学家的技能,后者是人为地更改数据集并调整输入数据以更好地适合学习模型的人。 尽管此步骤直观地增加了复杂性,但是这种方法通常可以提高学习器的表现; 这就是为什么它被诸如深度学习之类的尖端技术所使用的原因。

例如,如果您要预测房屋的价值并且知道每个房间的高度,宽度和长度,则可以人为地构建代表房屋体积的特征。 严格来说,这不是可观察的特征,而是在现有特征之上构建的特征。 让我们从一些代码开始:

In: import numpy as np
    from sklearn import datasets
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import mean_squared_error
    cali = datasets.california_housing.fetch_california_housing()
    X = cali['data']
    Y = cali['target']
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, 
                                                  test_size=0.2)

我们导入了包含加州房价的数据集。 这是一个回归问题,因为target变量是房价(即实数)。 立即应用称为 KNN 回归器的简单回归器(以简单学习器为例;将在第 4 章,机器学习),在测试数据集上的平均绝对误差MAE)结尾。 如果您不能完全理解代码,请不要担心。 本书稍后将介绍 MAE 和其他回归变量。 现在,假设 MAE 代表误差。 因此,MAE 的值越低,解决方案越好:

In: from sklearn.neighbors import KNeighborsRegressor
    regressor = KNeighborsRegressor()
    regressor.fit(X_train, Y_train)
    Y_est = regressor.predict(X_test)
    print ("MAE=", mean_squared_error(Y_test, Y_est))

Out: MAE= 1.07452795578

1.07MAE结果看似不错,但让我们努力做得更好。 我们将使用 Z 分数对输入特征进行归一化,并比较此新特征集上的回归任务。 Z 归一化只是将每个特征映射到具有零均值和单一方差的新特征。 使用 Scikit-learn,可以通过以下方式实现:

In: from sklearn.preprocessing import StandardScaler
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)
    regressor = KNeighborsRegressor()
    regressor.fit(X_train_scaled, Y_train) 
 Y_est = regressor.predict(X_test_scaled)
    print ("MAE=", mean_squared_error(Y_test, Y_est)) Out: MAE= 0.402334179429

借助这一简单的步骤,我们将 MAE 降低了一半以上,现在的值约为0.40

请注意,我们没有使用原始特征。 我们使用了它们的线性修改,这更适合使用 KNN 回归器进行学习。

代替 Z 归一化,我们可以在对异常值更鲁棒的特征上使用缩放函数,即RobustScaler。这样的缩放器使用中位数和四分位间距IQR),而不是使用均值和标准差来独立缩放每个特征。 它比异常值更健壮,因为如果几个点(最终只有一个)距离中心较远,例如由于错误的读数,传输错误或传感器损坏:

In: from sklearn.preprocessing import RobustScaler
    scaler2 = RobustScaler()
    X_train_scaled = scaler2.fit_transform(X_train)
    X_test_scaled = scaler2.transform(X_test)
    regressor = KNeighborsRegressor()
    regressor.fit(X_train_scaled, Y_train)
    Y_est = regressor.predict(X_test_scaled)
    print ("MAE=", mean_squared_error(Y_test, Y_est)) Out: MAE=0.41749216189 

现在,让我们尝试为特定特征添加非线性修改。 我们可以假设输出大致与房屋的占用人数有关。 实际上,一个人居住的房屋的价格与三个人住在同一所房屋的价格之间存在很大差异。 但是,居住在那里的 10 个人的价格与居住在那里的 12 个人的价格之间的差异并不大(尽管两者之间仍然存在差异)。 因此,让我们尝试添加另一个特征,该特征是对另一个特征进行非线性转换而构建的:

In: non_linear_feat = 5 # AveOccup 
    X_train_new_feat = np.sqrt(X_train[:,non_linear_feat])
    X_train_new_feat.shape = (X_train_new_feat.shape[0], 1)
    X_train_extended = np.hstack([X_train, X_train_new_feat])
    X_test_new_feat = np.sqrt(X_test[:,non_linear_feat])
    X_test_new_feat.shape = (X_test_new_feat.shape[0], 1)
    X_test_extended = np.hstack([X_test, X_test_new_feat])
    scaler = StandardScaler()
    X_train_extended_scaled = scaler.fit_transform(X_train_extended)
    X_test_extended_scaled = scaler.transform(X_test_extended)
    regressor = KNeighborsRegressor()
    regressor.fit(X_train_extended_scaled, Y_train)
    Y_est = regressor.predict(X_test_extended_scaled)
    print ("MAE=", mean_squared_error(Y_test, Y_est)) 

Out: MAE= 0.325402604306 

通过添加此新特征,我们额外减少了MAE,最终获得了更令人满意的回归指标。 当然,我们可以尝试其他转换来改善此情况,但是这个简单的示例应该表明,对您分析 EDA 发现的线性和非线性转换的应用并获得在概念上与转换更为相关的特征有多重要。 输出变量。

降维

通常,您必须处理包含大量特征的数据集,其中许多特征可能是不必要的。 这是一个典型的问题,其中某些特征对预测很有帮助,某些特征以某种方式相关,而某些特征则完全不相关(也就是说,它们仅包含噪声或不相关的信息)。 仅保留有趣的特征是一种方法,不仅可以使数据集更易于管理,而且可以使预测算法更好地工作,而不会因数据中的噪声而使其预测迷惑。

因此,降维是消除输入数据集的某些特征并创建一组受限制的特征的操作,这些特征包含了以更有效和可靠的方式预测target变量所需的所有信息。 如前所述,减少特征部件的数量通常还会减少输出变异性和学习过程的复杂性(以及所需的时间)。

许多用于归约的算法的主要假设是与加性高斯白噪声AWGN)有关的一种假设。 我们假设一个独立的高斯型噪声已添加到数据集的每个特征中。 因此,减小维数也会减少噪声的能量,因为您要减小其跨度设置。

协方差矩阵

协方差矩阵为您提供了所有不同对特征之间的相关性的概念。 通常,这是降维的第一步,因为它使您可以了解高度相关的特征数量(因此可以丢弃的特征数量)和独立的特征数量。 使用每个观测具有四个特征的鸢尾花数据集,可以轻松地计算相关矩阵,并且您可以借助简单的图形表示来了解其结果,可以通过以下代码获得:

In: from sklearn import datasets
    import numpy as np
    iris = datasets.load_iris()
    cov_data = np.corrcoef(iris.data.T)
    print (iris.feature_names)
    print (cov_data) Out: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 
      'petal width (cm)']
     [[ 1\.         -0.10936925  0.87175416  0.81795363]
      [-0.10936925  1\.         -0.4205161  -0.35654409]
      [ 0.87175416 -0.4205161   1\.          0.9627571 ]
      [ 0.81795363 -0.35654409  0.9627571   1\.        ]] 

使用热图,让我们以图形形式可视化协方差矩阵:

In: import matplotlib.pyplot as plt
    img = plt.matshow(cov_data, cmap=plt.cm.rainbow)
    plt.colorbar(img, ticks=[-1, 0, 1], fraction=0.045)
    for x in range(cov_data.shape[0]):
        for y in range(cov_data.shape[1]):
            plt.text(x, y, "%0.2f" % cov_data[x,y], 
                     size=12, color='black', ha="center", va="center")
    plt.show() 

这是生成的热图:

从上图中,您可以看到主要对角线的值为 1。这是因为我们使用的是协方差矩阵的归一化版本(将每个特征协方差归一化为 1.0)。 我们还可以注意到第一和第三特征,第一和第四以及第三和第四特征之间的高度相关性。 另外,我们可以验证只有第二个特征几乎独立于其他特征; 所有其他特征都以某种方式相互关联。

现在,我们有了关于简化集中潜在特征数量的想法,想象一下如何压缩相关矩阵指出的重复信息-我们可以将所有内容简单地简化为两个特征。

主成分分析

主成分分析PCA)是一种有助于定义更小且更相关的特征集的技术。 从 PCA 获得的新特征是当前特征的线性组合(即旋转),即使它们是二进制的。 输入空间旋转后,输出集的第一个向量包含信号的大部分能量(或换句话说,其方差)。 第二个正交于第一个,它包含大部分剩余能量; 第三个与前两个向量正交,并且包含大部分剩余能量,依此类推。 就像通过将尽可能多的信息聚合到 PCA 产生的初始向量上来重构数据集中的信息一样。

在 AWGN 的理想​​情况下,初始向量包含输入信号的所有信息; 靠近末端的仅包含噪音。 此外,由于输出基础是正交的,因此您可以分解并合成输入数据集的近似版本。 能量是决定人能使用多少个基向量的关键参数。 由于该算法在本质上是用于奇异值分解的,因此在阅读有关 PCA 时经常会引用两个特征向量(基本向量)和特征值(与该向量相关的标准差)。 通常,输出集的基数是保证存在 95%(在某些情况下需要 90% 或 99%)输入能量(或方差)的基数。 对 PCA 的严格解释超出了本书的范围,因此,我们仅向您介绍有关如何在 Python 中使用此功能强大的工具的准则。

这是有关如何将数据集缩小为二维的示例。 在上一节中,我们推论 2 是降维的好选择; 让我们检查是否正确:

In: from sklearn.decomposition import PCA
    pca_2c = PCA(n_components=2)
    X_pca_2c = pca_2c.fit_transform(iris.data)
    X_pca_2c.shape 

Out: (150, 2) In: plt.scatter(X_pca_2c[:,0], X_pca_2c[:,1], c=iris.target, alpha=0.8, 
                s=60, marker='o', edgecolors='white')
    plt.show()
    pca_2c.explained_variance_ratio_.sum() 

Out: 0.97763177502480336

在执行代码时,您还会获得前两个组件的散点图:

前两个组件的散点图

我们可以立即看到,在应用 PCA 之后,输出集仅具有两个特征。 这是因为在n_components参数设置为2的情况下调用了PCA()对象。 获得相同结果的另一种方法是对123组件运行PCA(),然后从解释的方差比和视觉检查得出结论,对于n_components = 2,我们得到了最好的结果。 然后,我们将有证据表明,当使用两个基向量时,输出数据集包含几乎 98% 的输入信号能量,并且在模式中,这些类几乎可以很好地分离。 每种颜色位于二维欧几里得空间的不同区域。

请注意,此过程是自动的,训练 PCA 时不需要提供标签。 实际上,PCA 是一种无监督算法,它不需要与自变量有关的数据来旋转投影基础。

对于好奇的读者,可以通过以下代码查看转换矩阵(将原始数据集转换为 PCA 重组的数据集):

In: pca2c.componentsOut: array([[ 0.36158968, -0.08226889,  0.85657211,  0.35884393],
            [-0.65653988, -0.72971237,  0.1757674 ,  0.07470647]]) 

转换矩阵由四列(即输入特征的数量)和两行(即归约特征的数量)组成。

有时,您会发现自己处于 PCA 不够有效的情况下,尤其是在处理高维数据时,这是因为特征可能非常相关,同时差异不平衡。 对于这种情况,可能的解决方案是尝试使信号变白(或使其更加球形)。 在这种情况下,特征向量被迫以单位分量方差为单位。 变白会删除信息,但有时会提高 PCA 减少后将使用的机器学习算法的准确率。 这是采用美白时的代码外观(在我们的示例中,除了输出减少的数据集的比例外,它没有任何改变):

In: pca_2cw = PCA(n_components=2, whiten=True)
    X_pca_1cw = pca_2cw.fit_transform(iris.data)
    plt.scatter(X_pca_1cw[:,0], X_pca_1cw[:,1], c=iris.target, alpha=0.8, 
                s=60, marker='o', edgecolors='white')
    plt.show()
    pca_2cw.explained_variance_ratio_.sum() 

Out: 0.97763177502480336  

您还可以使用变白来获得 PCA 的第一部分的散点图:

现在,让我们看看如果将输入数据集投影到 PCA 生成的一维空间上会发生什么,如下所示:

In: pca_1c = PCA(n_components=1)
    X_pca_1c = pca_1c.fit_transform(iris.data)
    plt.scatter(X_pca_1c[:,0], np.zeros(X_pca_1c.shape),
 c=iris.target, alpha=0.8, s=60, marker='o', edgecolors='white')
    plt.show()
    pca_1c.explained_variance_ratio_.sum() Out: 0.9246162071742684  

投影沿一条水平线分布:

在这种情况下,输出能量较低(原始信号的 92.4%),并且输出点被添加到一维欧几里德空间中。 这可能不是一个很棒的特征简化步骤,因为许多具有不同标签的点被混合在一起。

最后,这是一个把戏。 为确保生成的输出集至少包含 95% 的输入能量,您可以在第一次调用 PCA 对象时指定该值。 可以使用以下代码获得等于两个向量的结果:

In: pca_95pc = PCA(n_components=0.95)
    X_pca_95pc = pca_95pc.fit_transform(iris.data)
    print (pca_95pc.explained_variance_ratio_.sum())
    print (X_pca_95pc.shape) Out: 0.977631775025
     (150, 2)

大数据 PCA ——随机 PCA

PCA 的主要问题是进行还原操作的基础奇异值分解SVD)算法的复杂性,使得整个过程很难扩展。 Scikit-learn 中有一种基于随机 SVD 的更快算法。 这是一种较轻但近似的迭代分解方法。 使用随机 SVD,全秩重构并不完美,并且在每次迭代过程中都对基向量进行了局部优化。 另一方面,它只需要几个步骤就可以得到一个很好的近似值,证明了随机 SVD 比传统 SVD 算法要快得多。 因此,如果训练数据集很大,则此约简算法是一个不错的选择。 在以下代码中,我们将其应用于鸢尾花数据集。 由于问题的规模很小,因此输出非常接近经典 PCA。 但是,将算法应用于大型数据集时,结果会有很大不同:

In: from sklearn.decomposition import PCA
    rpca_2c = PCA(svd_solver='randomized', n_components=2)
    X_rpca_2c = rpca_2c.fit_transform(iris.data)
    plt.scatter(X_rpca_2c[:,0], X_rpca_2c[:,1], 
c=iris.target, alpha=0.8, s=60, marker='o', edgecolors='white')
    plt.show()
    rpca_2c.explained_variance_ratio_.sum() 

Out: 0.97763177502480414 

这是使用 SVD 求解器的 PCA 的前两个组件的散点图:

潜在因子分析

潜在因子分析LFA)是另一种帮助您降低数据集维数的技术。 总体思路类似于 PCA。 但是,在这种情况下,输入信号没有正交分解,因此没有输出基础。 一些数据科学家认为 LFA 是 PCA 的概括,它消除了正交性的约束。 通常,当预期在系统中存在潜在因子或构建体时,将使用 LFA。 在这种假设下,所有特征都是对变量的观察,这些变量是由线性变换的潜因子导出或影响的,并且具有任意波形生成器AWG)噪声。 通常假设潜因子具有高斯分布和一元协方差。 因此,在这种情况下,不是破坏信号的能量/方差,而是在输出数据集中说明变量之间的协方差。 Scikit-learn 工具箱实现了迭代算法,使其适用于大型数据集。

下面的代码通过假设系统中的两个潜在因素来降低鸢尾花数据集的维数:

In: from sklearn.decomposition import FactorAnalysis
    fact_2c = FactorAnalysis(n_components=2)
    X_factor = fact_2c.fit_transform(iris.data)
    plt.scatter(X_factor[:,0], X_factor[:,1], 
                c=iris.target, alpha=0.8, s=60, 
                marker='o', edgecolors='white')
    plt.show()

这是散点图中表示的两个潜在因素(与以前的 PCA 不同的解决方案):

线性判别分析

严格来说,线性判别分析LDA)是分类器(现代统计之父 Ronald Fisher 开发的一种经典统计方法),但通常用于降维。 它不能很好地扩展到较大的数据集(像许多统计方法一样),但这是有待尝试的方法,它可以带来比其他分类方法(例如逻辑回归)更好的结果。 由于这是一种有监督的方法,因此需要标签集来优化减少步骤。 LDA 输出输入特征的线性组合,从而尝试对最好区分它们的类之间的差异进行建模(因为 LDA 使用标签信息)。 与 PCA 相比,在 LDA 的帮助下获得的输出数据集包含了类之间的精妙区别。 但是,它不能用于回归问题,因为它来自分类过程。

这是 LDA 在鸢尾花数据集上的应用:

In: from sklearn.lda import LDA
    lda_2c = LDA(n_components=2)
    X_lda_2c = lda_2c.fit_transform(iris.data, iris.target)
    plt.scatter(X_lda_2c[:,0], X_lda_2c[:,1], 
                c=iris.target, alpha=0.8, edgecolors='none')
 plt.show() 

此散点图是由 LDA 生成的前两个组件得出的:

潜在语义分析

通常,潜在语义分析LSA)在通过TfidfVectorizerCountVectorizer处理后应用于文本。 与 PCA 相比,它将 SVD 应用于输入数据集(通常是稀疏矩阵),从而生成通常与同一概念关联的单词语义集。 这就是为什么当特征是同质的(即文档中的所有单词)并大量存在时使用 LSA 的原因。

Python 中带有文本和TfidfVectorizer的示例如下。 输出显示了潜在向量的部分内容:

In: from sklearn.datasets import fetch_20newsgroups
    categories = ['sci.med', 'sci.space']
    twenty_sci_news = fetch_20newsgroups(categories=categories)
    from sklearn.feature_extraction.text import TfidfVectorizer
    tf_vect = TfidfVectorizer()
    word_freq = tf_vect.fit_transform(twenty_sci_news.data)
    from sklearn.decomposition import TruncatedSVD
    tsvd_2c = TruncatedSVD(n_components=50)
    tsvd_2c.fit(word_freq)
    arr_vec = np.array(tf_vect.get_feature_names())
 arr_vec[tsvd_2c.components_[20].argsort()[-10:][::-1]]

Out: array(['jupiter', 'sq', 'comet', 'of', 'gehrels', 'zisfein',            'jim', 'gene', 'are', 'omen'], dtype='<U79')

独立成分分析

可以从名称中猜出,独立分量分析ICA)是一种尝试从输入信号派生独立分量的方法。 实际上,ICA 是一种允许您从初始多元输入信号创建最大独立的加性子成分的技术。 该技术的主要假设集中在子组件及其非高斯分布的统计独立性上。 ICA 在神经病学数据中有许多应用,并且在神经科学领域中被广泛使用。

可能需要使用 ICA 的典型方案是盲源分离。 例如,两个或多个麦克风将录制两种声音(例如,一个人讲话并同时播放一首歌曲)。 在这种情况下,ICA 可以将两种声音分成两个输出特征。

Scikit-learn 包提供了算法的更快版本(sklearn.decomposition.FastICA),其用法与迄今为止介绍的其他技术相似。

核 PCA

核 PCA 是一种使用核将信号映射到(通常是)非线性空间并使其线性可分离(或接近获得)的技术。 它是 PCA 的扩展,其中映射是线性子空间上的实际投影。 有许多众所周知的核(当然,您始终可以随时构建自己的核),但是最常用的核是线性多项式RBFSigmoid余弦。 它们都只能为输入数据集提供不同的配置,因为它们只能线性化某些选定类型的数据。 例如,假设我们有一个磁盘状的数据集,就像我们将要使用以下代码创建的那样:

In: def circular_points (radius, N):
        return np.array([[np.cos(2*np.pi*t/N)*radius,    
                          np.sin(2*np.pi*t/N)*radius] for t in range(N)])
    N_points = 50
    fake_circular_data = np.vstack([circular_points(1.0, N_points), 
                                    circular_points(5.0, N_points)])
    fake_circular_data += np.random.rand(*fake_circular_data.shape)
    fake_circular_target = np.array([0]*N_points + [1]*N_points)
    plt.scatter(fake_circular_data[:,0], fake_circular_data[:,1], 
                c=fake_circular_target, alpha=0.8, 
                s=60, marker='o', edgecolors='white')
    plt.show()

这是示例的输出:

对于此输入数据集,由于数据集包含圆周形状的类,因此所有线性变换都将无法分离蓝色点和红色点。 现在,让我们使用 RBF 核与核 PCA 一起尝试一下,看看会发生什么:

In: from sklearn.decomposition import KernelPCA
    kpca_2c = KernelPCA(n_components=2, kernel='rbf')
    X_kpca_2c = kpca_2c.fit_transform(fake_circular_data)
    plt.scatter(X_kpca_2c[:,0], X_kpca_2c[:,1], c=fake_circular_target, 
                alpha=0.8, s=60, marker='o', edgecolors='white')
    plt.show()

下图表示示例的转换:

本章中的图形/图表可能与在本地计算机上获得的图形/图表不同,因为图形布局的初始化是使用随机参数进行的。

我们实现了我们的目标-蓝点在左边,红点在右边。 感谢核 PCA 的转换,您现在可以使用线性技术来处理此数据集。

T-SNE

PCA 是一种广泛的降维技术,但是当我们处理大数据并呈现许多特征时,我们首先需要了解特征空间中的情况。 实际上,在 EDA 阶段,您通常会对数据进行几次散点图绘制,以了解特征之间的关系。 在这一点上,T 分布随机邻居嵌入或 T-SNE 可以为您提供帮助,因为它的设计目标是将高维数据嵌入 2D 或 3D 空间中以充分利用散点图。 它是由 Laurens van der Maaten 和 Geoffrey Hinton 开发的一种非线性降维技术,该算法的核心是基于两个规则:第一个是周期性的相似观测必须对输出有更大的贡献(这是通过概率实现的)。 分配特征); 其次,高维空间中的分布必须类似于小空间中的分布(这是通过最小化 Kullback-LeiblerKL 来实现的),两者概率分布函数之间的差异)。 输出在视觉上是不错的,并允许您猜测特征之间的非线性相互作用。

让我们通过将 T-SNE 应用于鸢尾花数据集并将其绘制到二维空间来查看一个简单的示例如何工作:

In: from sklearn.manifold import TSNE
    from sklearn.datasets import load_iris    

    iris = load_iris()
    X, y = iris.data, iris.target
    X_tsne = TSNE(n_components=2).fit_transform(X)
    plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, alpha=0.8, 
                s=60, marker='o', edgecolors='white')
    plt.show()

这是T_SNE的结果,将一个类与另一个类完全分开:

受限玻尔兹曼机

受限玻尔兹曼机RBM)是另一种由线性函数(通常称为隐藏单元或神经元)组成的技术,可对输入数据进行非线性变换。 隐藏单元表示系统的状态,而输出数据集实际上是该层的状态。

该技术的主要假设是,输入数据集由表示概率的特征([0,1]范围内的二进制值或实数值)组成,因为 RBM 是一种概率方法。 在下面的示例中,我们将使用图像的二值化像素作为特征(1 为白色,0 为黑色)来馈送 RBM,并且将打印系统的潜在组件。 这些组件代表原始图像中出现的不同通用面孔:

In: from sklearn import preprocessing 
    from sklearn.neural_network   
    import BernoulliRBM 
    n_components = 64 # Try with 64, 100, 144 
    olivetti_faces = datasets.fetch_olivetti_faces() 
    X = preprocessing.binarize(   
           preprocessing.scale(olivetti_faces.data.astype(float)),
           0.5) 
    rbm = BernoulliRBM(n_components=n_components, learning_rate=0.01, 
                      n_iter=100) 
    rbm.fit(X) 
    plt.figure(figsize=(4.2, 4)) 
    for i, comp in enumerate(rbm.components_):
 plt.subplot(int(np.sqrt(n_components+1)),                     int(np.sqrt(n_components+1)), i + 1)
         plt.imshow(comp.reshape((64, 64)), cmap=plt.cm.gray_r, 
                    interpolation='nearest')
         plt.xticks(()); plt.yticks(())
    plt.suptitle(str(n_components) + ' components extracted by RBM', 
                 fontsize=16)
    plt.subplots_adjust(0.08, 0.02, 0.92, 0.85, 0.08, 0.23)
    plt.show()

这是 RBM 提取的 64 个组件:

请注意,Scikit-learn 仅包含 RBM 处理的基础层。 如果您正在处理大型数据集,则最好使用基于 GPU 的工具箱(例如基于 CUDA 或 OpenCL 顶部构建的工具箱),因为 RBM 具有高度可并行性。

异常值的检测和处理

在数据科学中,示例是从数据过程中学习的核心。 如果将异常,不一致或错误的数据输入到学习过程中,则结果模型可能无法正确概括任何新数据的容纳情况。 除了歪曲诸如均值和方差之类的描述性度量外,变量中存在的异常高的值还会使多少个机器学习算法从数据中学习,从而导致预测失真。

当数据点与样本中的其他数据点明显偏离时,称为离群值。 任何其他预期的观察结果都标记为内部值

数据点可能由于以下三个普遍原因而异常(并且每个隐含着不同的补救措施):

  • 考虑到可用数据只是原始数据分布的一个事实,该点表示很少出现,但它也是一个可能的值。 在这种情况下,生成点的基本过程对于所有点都是相同的,但是由于其稀有性,外围点可能被认为不适合通过机器学习进行概括。 在这种情况下,通常会删除该点或将其权重降低。 另一种解决方案是增加样本数量,从而使异常值在数据集中的相关性降低。
  • 该点表示通常发生的另一种分布。 当发生类似情况时,可以想象一下影响样本生成的错误或规格错误。 无论如何,您的学习算法都会从无关的分布中获取数据,而这些分布并不是您的数据科学项目关注的重点(重点是泛化)。 在这种情况下,只需删除异常值即可。
  • 显然,这是某种错误。 由于某些原因,存在数据输入错误或数据完整性问题,这些问题修改了原始值,并用不一致的值替换了它。 最好的做法是删除该值,并将其视为随机丢失的值。 在这种情况下,通常根据平均数或最常见的类替换异常值,这取决于它是回归问题还是分类问题。 如果这样做不方便或不可能,那么我们建议您从数据集中删除示例。

单变量离群值检测

为了解释数据点为何是异常值的原因,首先需要在数据中找到可能的异常值。 有很多方法-有些是单变量的(您可以一次观察每个奇异变量),而另一些是多变量的(他们同时考虑更多的变量)。 单变量方法通常基于 EDA 和可视化显示,例如箱形图(在本章的开头已介绍;我们将在第 5 章,“可视化,见解和结果”中更详细地讨论箱形图)。

通过检查单个变量来追踪异常值时,需要牢记一些经验法则。 实际上,离群值可能会被视为极值:

  • 如果您正在观察 Z 分数,则绝对值高于 3 的观察结果必须视为可疑离群值。
  • 如果您正在观察数据描述,则可以将小于 25% 百分值的观察值减去IQR * 1.5(即 75% 与 25% 百分值之间的差),大于第 75 个百分点值的观察值加上IQR * 1.5视为可疑离群值。 通常,您可以借助箱线图来实现这种区分。

为了说明我们如何使用 Z 分数轻松检测出一些离群值,让我们加载并浏览“波士顿房屋价格”数据集。 正如数据集的描述所指出的那样(您可以在boston.DESCR的帮助下获得),索引为 3 的变量CHAS是二进制的。 因此,在检测异常值时使用它几乎没有意义。 实际上,此类变量的值只能为 0 或 1:

In: from sklearn.datasets import load_boston
    boston = load_boston()
    continuous_variables = [n for n in range(boston.data.shape[1]) if n!=3]

现在,让我们使用 Sklearn 的StandardScaler函数快速标准化所有连续变量。 我们的目标是对boston.data boston.data[:,continuous_variables]进行华丽索引,以便创建另一个包含除上一个索引为 3 的变量以外的所有变量的数组。

StandardScaler自动标准化为零均值和单位方差。 这是必需的常规操作,应在将数据馈送到学习阶段之前执行。 否则,许多算法将无法正常工作(例如由梯度下降和支持向量机支持的线性模型)。

最后,让我们找到高于三个标准差的绝对值的值:

In: import numpy as np
    from sklearn import preprocessing
    scaler= preprocessing.StandardScaler()
 normalized_data = scaler.fit_transform(
                                     boston.data[:,continuous_variables])
    outliers_rows, outliers_columns = np.where(np.abs(normalized_data)>3)

outliers_rowsoutliers_columns变量包含可疑离群值的行和列索引。 我们可以打印示例的索引:

In: print(outliers_rows) Out: [ 55  56  57 102 141 199 200 201 202 203 204 225 256 257 262 283 284
 ... 

另外,我们可以在数组中显示行/列坐标的元组:

In: print (list(zip(outliers_rows, outliers_columns))) Out: [(55, 1), (56, 1), (57, 1), (102, 10), (141, 11), (199, 1), (200, 1), 
      ...

单变量方法可以揭示很多潜在的异常值。 它不会披露没有极值的离群值-相反,它的特征是两个或多个变量中的值异常组合。 在这种情况下,所涉及变量的值甚至可能不是极端值,因此,异常值可能会在单变量检查中未被注意到而消失。 这些离群值称为多元离群值。

为了发现多元离群值,您可以使用降维算法,例如前面说明的 PCA,然后检查超出三个标准差的组件的绝对值,或目视检查双变量图以找到孤立的聚类。 数据点。

Scikit-learn 包提供了两个类,这些类可以直接为您自动工作并发出所有可疑情况的信号:

  • covariance.EllipticEnvelope类适合您的数据的可靠分布估计,指出可能污染数据集的异常值,因为它们是数据一般分布中的极端点。
  • svm.OneClassSVM类是一种支持向量机算法,可以近似于数据的形状并确定是否应将提供的任何新实例视为新颖性(它充当新颖性检测器,因为默认情况下,它假定数据中没有异常值)。 通过仅修改其参数,它也可以在存在异常值的数据集上工作,从而提供比EllipticEnvelope更鲁棒和可靠的异常值检测系统。

这两个类都基于不同的统计和机器学习方法,需要在建模阶段进行了解和应用。

EllipticEnvelope

EllipticEnvelope是一项函数,它通过假设您的整个数据是基础多元高斯分布的表达式来尝试找出数据总体分布的关键参数。 这个假设不能适用于所有数据集,但是当它成立时,就证明了一种发现异常值的有效方法。 我们可以说,它尽可能地简化了算法背后的复杂估计,它可以检查每个观测值相对于考虑到数据集中所有变量的均值的距离。 因此,它可以发现单变量和多变量离群值。

使用协方差模块中的此函数时,您唯一需要考虑的参数是污染参数,该参数的值最多为 0.5。 它为算法提供有关数据集中存在的异常值比例的信息。 情况因数据集而异。 但是,作为一个开始的数字,我们建议使用 0.01-0.02 的值,因为它是观察值的百分比应落在标准化正态分布中距平均值的 Z 分数距离的绝对值 3 之上。 因此,我们认为默认值 0.1 太高。

让我们在综合分发的帮助下看一下该算法的作用:

In: from sklearn.datasets import make_blobs
    blobs = 1
    blob = make_blobs(n_samples=100, n_features=2, centers=blobs, 
                      cluster_std=1.5, shuffle=True, random_state=5)
    # Robust Covariance Estimate
    from sklearn.covariance import EllipticEnvelope
    robust_covariance_est = EllipticEnvelope(contamination=.1).fit(blob[0])
    detection = robust_covariance_est.predict(blob[0])
    outliers = np.where(detection==-1)[0]
    inliers = np.where(detection==1)[0]
    # Draw the distribution and the detected outliers
    from matplotlib import pyplot as plt
    # Just the distribution
    plt.scatter(blob[0][:,0],blob[0][:,1], c='blue', alpha=0.8, s=60, 
                marker='o', edgecolors='white')
    plt.show()
    # The distribution and the outliers
    in_points = plt.scatter(blob[0][inliers,0],blob[0][inliers,1],  
                            c='blue', alpha=0.8,
 s=60, marker='o', 
                            edgecolors='white')
    out_points = plt.scatter(blob[0][outliers,0],blob[0][outliers,1], 
                             c='red', alpha=0.8, s=60, marker='o', 
                             edgecolors='white')
    plt.legend((in_points,out_points),('inliers','outliers'), 
               scatterpoints=1,               loc='lower right')
    plt.show()

让我们仔细检查这段代码。

make_blobs函数在二维空间中创建一定数量的分布,总共 100 个示例(n_samples参数)。 分布的数量(参数中心)与用户定义的变量斑点有关,该变量最初设置为 1。

创建完人工示例数据后,以 10% 的污染率运行EllipticEnvelope可帮助您找出分布中的极值。 该模型首先通过在EllipticEnvelope类上使用.fit()方法来部署拟合。 然后,通过使用.predict()方法获得用于拟合的数据的预测。

通过使用matplotlibpyplot模块的plot函数,可以通过散点图显示与值 1 和 -1 的向量相对应的结果(其中 -1 为异常示例的标记)。

离群值和离群值的区别记录在变量的离群值和离群值中,其中包含示例的索引。

现在,让我们在更改斑点的数目之后再运行几次代码,并检查斑点的值为14时的结果:

更改斑点数后的数据点分布如下:

在唯一的基础多元分布的情况下(当变量blob = 1时),EllipticEnvelope算法已成功将观测值的 10% 定位在分布本身的边缘,并因此向所有可疑异常值发出信号。

相反,当数据中存在多个分布时,好像有两个或两个以上的自然群集时,该算法试图拟合唯一的一般分布,往往会将潜在异常值定位在最远端的群集上,从而忽略了数据的其他区域,它们可能会受到外围案例的影响。

对于真实数据,这并非罕见情况,它代表了EllipticEnvelope算法的重要局限性。

现在,让我们回到最初的波士顿房屋价格数据集,以验证比合成斑点更真实的更多数据。 这是我们可以用于实验的代码的第一部分:

In: from sklearn.decomposition import PCA
    # Normalized data relative to continuos variables
    continuous_variables = [n for n in range(boston.data.shape[1]) if n!=3]
    scaler = preprocessing.StandardScaler()
 normalized_data = scaler.fit_transform(
                                       boston.data[:,continuous_variables])
    # Just for visualization purposes pick the first 2 PCA components
    pca = PCA(n_components=2)
    Zscore_components = pca.fit_transform(normalized_data)
    vtot = 'PCA Variance explained ' + str(round(np.sum( pca.explained_variance_ratio_),3))
    v1 = str(round(pca.explained_variance_ratio_[0],3))
    v2 = str(round(pca.explained_variance_ratio_[1],3))

在此脚本中,我们将首先对数据进行标准化,然后仅出于随后的可视化目的,通过使用 PCA 来减少两个分量。

这两个 PCA 分量约占初始方差的 62%,该初始方差由数据集中的 12 个连续变量(.explained_variance_ratio_变量的总和,位于已拟合的PCA类的内部)表示。

尽管只有两个 PCA 组件足以实现可视化目的,但是通常,此数据集将获得两个以上的组件,因为目标是要有足够的分量以至少占总方差的 95%(如本章前面所述) 。

我们将继续执行脚本:

In: robust_covariance_est = EllipticEnvelope(store_precision=False, 
                                             assume_centered = False, 
                                             contamination=.05)
    robust_covariance_est.fit(normalized_data)
    detection = robust_covariance_est.predict(normalized_data)
    outliers = np.where(detection==-1)
    regular = np.where(detection==1)

In: # Draw the distribution and the detected outliers
    from matplotlib import pyplot as plt
    in_points = plt.scatter(Zscore_components[regular,0],
 Zscore_components[regular,1],                            c='blue', alpha=0.8, s=60, marker='o',        
                            edgecolors='white')
    out_points = plt.scatter(Zscore_components[outliers,0],
 Zscore_components[outliers,1],                             c='red', alpha=0.8, s=60, marker='o', 
                             edgecolors='white')
    plt.legend((in_points,out_points),('inliers','outliers'), 
                scatterpoints=1, loc='best')
    plt.xlabel('1st component ('+v1+')')
    plt.ylabel('2nd component ('+v2+')')
    plt.xlim([-7,7])
    plt.ylim([-6,6])
    plt.title(vtot)
    plt.show()

前两个组件的可视化占原始差异的 62.2%:

与前面的示例一样,在我们假设等于 0.05 的低污染的情况下,基于EllipticEnvelope的代码将预测异常值,并将其存储在数组中的方式与存储异常值的方式相同。 最后,是可视化(如前所述,我们将在第 5 章,“可视化,见解和结果”中讨论所有可视化方法)。

现在,让我们观察由散点图提供的结果,该散点图用于可视化数据的前两个PCA分量并标记外围观察。 关于我们示例中数据点的总体分布,由两个组成部分提供,它们约占数据方差的 62%,看来波士顿似乎有两个不同的房价集群,分别对应于市场中存在高端和低端设备。 一般而言,对于EllipticEnvelope估计,数据中群集的存在并不是最佳的情况。 实际上,根据我们在使用合成斑点进行实验时已经注意到的结果,该算法仅指出了一个群集上的离群值-较小的一个。 鉴于这样的结果,有充分的理由相信我们刚刚收到了有偏见的,部分的回应,在将这些点视为异常值之前,需要进行一些进一步的调查。 Scikit-learn 包实际上将鲁棒的协方差估计方法(从根本上讲是一种统计方法)与一种扎根于机器学习的另一种方法集成在一起:OneClassSVM类。 现在,我们将继续进行试验。

在离开此示例之前,请注意,为了同时适合 PCA 和EllipticEnvelope,我们使用了一个名为normalized_data的数组,该数组仅包含标准化的连续数据集变量。 请始终注意,使用非标准化数据并将二进制或分类数据与连续数据混合可能会导致EllipticEnvelope算法的错误和近似估计。

OneClassSVM

由于EllipticEnvelope利用参数和统计假设拟合假设的高斯分布,因此OneClassSVM是一种机器学习算法,可从数据本身学习特征的分布,因此适用于多种情况,其中您希望能够捕获所有离群值以及异常数据示例。

如果您已经拥有一个干净的数据集,并且已通过机器学习算法进行了拟合,那就太好了。 之后,可以召唤OneClassSVM来检查是否有任何新示例适合历史分布,如果不合适,它将发出一个新示例的信号,该示例可能是错误,也可能是某些新的,以前看不见的情况。

只需将数据科学情况视为经过训练的机器学习分类算法,即可识别网站上的帖子和新闻并采取在线操作。 OneClassSVM可以轻松地发现与网站上其他帖子(垃圾邮件,也许是?)不同的帖子,而其他算法只会尝试将新示例适合现有主题的分类。

但是,OneClassSVM也可以用来发现现有的异常值。 如果这个专门的 SVM 类不能容纳某些数据(指出该数据处于数据分布的边缘),那么这些示例肯定有一些问题。

为了使OneClassSVM作为离群值检测器,您需要研究其核心参数; 它要求您定义核,度,伽玛和nu

  • 核和阶数:它们相互关联。 通常,我们根据经验建议的值是默认值。 核的类型应为rbf,其阶数应为 3。此类参数将通知OneClassSVM创建一系列跨越三个维度的分类气泡,甚至可以对最复杂的多维分布形式进行建模。

  • Gamma:这是连接到 RBF 核的参数。 我们建议您将其保持在尽可能低的水平。 一个好的经验法则是为其分配一个最小值,该最小值介于案例数和变量之间。 γ在 SVM 中的作用将在第 4 章,“机器学习”中进一步说明。 现在就可以说,较高的gamma值倾向于使算法遵循数据,但更多地定义分类气泡的形状。

  • Nu:此参数确定是否必须拟合精确的分布,或者是否通过不对当前数据示例进行过多调整来尝试获得一定程度的概括(如果存在异常值,则为必要选择)。 可以通过以下公式轻松确定:

nu_estimate = 0.95 * outliers_fraction + 0.05
  • 如果离群分数的值非常小,则nu将很小,并且 SVM 算法将尝试拟合数据点的轮廓。 另一方面,如果分数较高,则参数为,这将强制使内部点分布的边界更平滑。

让我们立即观察该算法在我们之前在波士顿房价数据集上遇到的问题上的表现:

In: from sklearn.decomposition import PCA
    from sklearn import preprocessing
    from sklearn import svm
    # Normalized data relative to continuos variables
    continuous_variables = [n for n in range(boston.data.shape[1]) if n!=3]
 scaler = preprocessing.StandardScaler()
    normalized_data = scaler.fit_transform(
                                       boston.data[:,continuous_variables])
    # Just for visualization purposes pick the first 5 PCA components
    pca = PCA(n_components=5)
    Zscore_components = pca.fit_transform(normalized_data)
    vtot = 'PCA Variance explained ' + str(round( np.sum(pca.explained_variance_ratio_),3))    # OneClassSVM fitting and estimates
    outliers_fraction = 0.02 # 
    nu_estimate = 0.95 * outliers_fraction + 0.05
    machine_learning = svm.OneClassSVM(kernel="rbf", 
                                       gamma=1.0/len(normalized_data), 
                                       degree=3, nu=nu_estimate)
    machine_learning.fit(normalized_data)
    detection = machine_learning.predict(normalized_data)
    outliers = np.where(detection==-1)
    regular = np.where(detection==1) 

现在,我们将可视化结果:

In: # Draw the distribution and the detected outliers
    from matplotlib import pyplot as plt
    for r in range(1,5):
        in_points = plt.scatter(Zscore_components[regular,0],
 Zscore_components[regular,r],                                c='blue', alpha=0.8, s=60, 
                                marker='o', edgecolors='white')
        out_points = plt.scatter(Zscore_components[outliers,0],
 Zscore_components[outliers,r],                                 c='red', alpha=0.8, s=60, 
                                 marker='o', edgecolors='white')
        plt.legend((in_points,out_points),('inliers','outliers'), 
                    scatterpoints=1, loc='best')
        plt.xlabel('Component 1 (' + str(round(
 pca.explained_variance_ratio_[0],3))+')')
        plt.ylabel('Component '+str(r+1)+'('+str(round( pca.explained_variance_ratio_[r],3))+')')
        plt.xlim([-7,7])
        plt.ylim([-6,6])
        plt.title(vtot)
        plt.show()

与先前提供的代码相比,此代码段有所不同,因为最终 PCA 分解由五个组件组成。 为了探索更多的数据维度,需要更大的数量。 增加所得 PCA 组件数量的另一个原因是因为我们打算将转换后的数据集与OneClassSVM一起使用。

核心参数是根据观察次数计算得出的,如下所示:

gamma = 1.0 / len(normalized_data)
nu = no_estimated

nu特别取决于:

nu_estimate = 0.95 * outliers_fraction + 0.05

因此,通过将outliers_fraction(从0.02更改为较大的值,例如0.1),当假定您数据中的异常情况发生率较高时,您需要算法对可能出现的异常现象给予更多关注。

我们还要观察 PCA 组件从 2 到 5 的图形输出,并将其与主组件进行比较(所解释方差的 51%)。 该序列的第一个图(包括四个散点图)如下:

从我们的图形探索中,似乎OneClassSVM很好地模拟了房价数据的分布,并帮助发现了分布边界上的一些极值。

此时,您可以决定我们将要提出的新颖性和异常检测方法之一。 您甚至可以同时使用:

  • 仔细检查异常值的特征以找出存在异常的原因(这一事实可能会使您进一步思考数据的潜在生成过程)
  • 通过对偏远的观察使用权重较低或仅排除它们来尝试构建一些机器学习模型

最后,采用纯粹的数据科学方法,可以帮助您决定对任何遥远的观察采取下一步措施的方法是测试决策结果以及对数据的后续操作。 如何测试和试验关于您的数据的假设是我们将在接下来的部分中与您讨论的主题。

验证指标

为了评估已构建的数据科学系统的表现并检查与目标之间的距离,您需要使用对结果进行评分的函数。 通常,使用不同的评分函数来处理二分类,多标签分类,回归或聚类问题。 现在,让我们看看这些任务中最受欢迎的特征,以及机器学习算法如何使用它们。

学习如何为数据科学项目选择正确的分数/误差度量标准实际上是一个经验问题。 我们发现咨询(并参加)Kaggle 举办的数据科学竞赛非常有帮助,该公司致力于组织来自世界各地的数据科学家之间的数据挑战。 通过观察各种挑战以及他们尝试优化的分数或误差度量,您一定可以对自己的问题获得有用的见解。 Kaggle 的首席技术官本·哈默(Ben Hammer)甚至创建了一个比赛中常用指标的 Python 库,您可以在这个页面上查询并使用pip install ml_metrics安装在计算机上。

多标签分类

当您的任务是预测多个标签时(例如:今天的天气如何?这是什么花?您的工作是什么?),我们将此问题称为多标签分类。 多标签分类是一项非常流行的任务,并且存在许多表现指标来评估分类器。 当然,在二分类的情况下,您可以使用所有这些度量。 现在,让我们使用一个简单的真实示例来说明其工作原理:

In: from sklearn import datasets
    iris = datasets.load_iris()
    # No crossvalidation for this dummy notebook
    from sklearn.model_selection import train_test_split
    X_train, X_test, Y_train, Y_test = train_test_split(iris.data, 
iris.target, test_size=0.50, random_state=4)
    # Use a very bad multiclass classifier
    from sklearn.tree import DecisionTreeClassifier
    classifier = DecisionTreeClassifier(max_depth=2)
    classifier.fit(X_train, Y_train) 
    Y_pred = classifier.predict(X_test)
    iris.target_names Out: array(['setosa', 'versicolor', 'virginica'], dtype='<U10')

现在,让我们看一下多标签分类中常用的度量:

  • 混淆矩阵:在描述多标签分类的表现指标之前,让我们看一下混淆矩阵,该表使我们了解了每个类别的错误分类。 理想情况下,在理想分类中,所有不在对角线上的像元应为 0s。 在下面的示例中,您将看到类别 0(山鸢尾)从未被错误分类,类别 1(杂色鸢尾)被两次错误分类为弗吉尼亚鸢尾,并且类别 2(弗吉尼亚鸢尾)被误分类为杂色鸢尾两次:
In: from sklearn import metrics
    from sklearn.metrics import confusion_matrix
    cm = confusion_matrix(y_test, y_pred)
    print cm

Out: [[30  0  0]
      [ 0 19  3]
      [ 0  2 21]] In: import matplotlib.pyplot as plt
    img = plt.matshow(cm, cmap=plt.cm.autumn)
    plt.colorbar(img, fraction=0.045)
    for x in range(cm.shape[0]):
        for y in range(cm.shape[1]):
            plt.text(x, y, "%0.2f" % cm[x,y], 
              size=12, color='black', ha="center", va="center")
    plt.show() 

混淆矩阵以这种方式用图形表示:

  • 准确率:准确率是预测标签中与实际标签完全相等的部分。 换句话说,它是正确分类的标签总数的百分比:
In: print ("Accuracy:", metrics.accuracy_score(Y_test, Y_pred)) Out: Accuracy: 0.933333333333
  • 精度:这是从信息检索领域中采取的一种措施。 它计算结果集中相关结果的数量。 等效地,在分类任务中,它计算每组分类标签中正确标签的数量。 然后,将所有标签的结果取平均值:
In: print ("Precision:", metrics.precision_score(y_test, y_pred)) Out: Precision: 0.933333333333
  • 召回:这是从信息检索中提取的另一个概念。 与数据集中的所有相关标签相比,它计算结果集中相关结果的数量。 在分类任务中,这是一组中正确分类的标签的数量除以该组的标签总数。 最后,对结果进行平均,如以下代码所示:
In: print ("Recall:", metrics.recall_score(y_test, y_pred)) Out: Recall: 0.933333333333
  • F1 得分:这是精度和召回率的谐波平均值,通常在处理不平衡数据集时使用,以显示分类器在所有类别中的表现是否良好:
In: print ("F1 score:", metrics.f1_score(y_test, y_pred)) Out: F1 score: 0.933267359393

这些是多标签分类中最常用的度量。 方便的函数classification_report显示有关这些措施的报告,非常方便。 支持只是带有该标签的观察次数。 了解数据集是否平衡(即每个类的示例份额是否相同)非常有用:

In: from sklearn.metrics import classification_report
 print classification_report(y_test, y_pred, 
                                target_names=iris.target_names)

这是完整报告,具有精度召回F1 得分支持度(该类案件的数量):

在数据科学实践中,精度召回准确率更为广泛地使用,因为数据问题中的大多数数据集倾向于不平衡。 为了解决这种不平衡,数据科学家经常以精度召回F1 分数来表示其结果。 此外,我们必须注意准确率精度召回F1 分数如何采用[0.0, 1.0]范围。 完美的分类器在所有这些指标上的得分都达到1.0(但是要当心任何完美的分类,如果太令人难以置信,请当心,因为这通常意味着出了点问题;现实世界中的数据问题永远不会有完美的解决方案)。

二分类

除了上一节中显示的误差度量之外,在只有两个输出类的问题中(例如,如果您必须猜测用户的性别或预测用户是否会点击/购买/喜欢该商品), 还有一些其他措施。 因为它非常有用,所以使用最多的是受试者工作特性(ROC)的曲线下面积(AUC)。

ROC 曲线是一种图形化的方式,用于表达分类器的表现在所有可能的分类阈值上如何变化(即,当参数变化时结果的变化)。 具体而言,这些表演具有真正(或命中)率和假正(或失误)率。 第一个是真正面结果的比率,第二个是假正面结果的比率。 该曲线下方的区域表示分类器相对于随机分类器(其 AUC 为 0.50)的效果。

在这里,我们有一个随机分类器(虚线)和一个更好的分类器(实线)的图形示例。 您可以看到随机分类器的 AUC 为 0.5(是平方的一半),另一个具有更高的 AUC(其上限为1.0):

用于使用 Python 计算 AUC 的函数为sklearn.metrics.roc_auc_score()

回归

在必须预测实数或回归的任务中,许多误差度量均来自欧几里得代数:

  • 平均绝对误差或 MAE:这是预测值和实际值之间的差向量的平均 L1 范数:
In: from sklearn.metrics import mean_absolute_error 
mean_absolute_error([1.0, 0.0, 0.0], [0.0, 0.0, -1.0]) Out: 0.66666666666666663
  • 均方误差或 MSE:这是预测值和实际值之间的差向量的平均 L2 范数:
In: from sklearn.metrics import mean_squared_error 
mean_squared_error([-10.0, 0.0, 0.0], [0.0, 0.0, 0.0]) Out: 33.333333333333
  • R² 得分:R² 也称为测定系数。 简而言之,R² 确定在预测变量和target变量之间存在线性拟合的程度。 取值介于 0 和 1(含)之间; R² 越高,模型越好。 这是一个很好的评分标准,但是并不能说明所有有关故事的信息,尤其是在您的数据中存在异常值的情况下。 您可以在《统计》的参考书中找到关于此指标的更多错综复杂的信息。 作为建议,可以使用它,但要同时进行其他评分或错误测量。 在这种情况下使用的函数是sklearn.metrics.r2_score

测试和验证

加载数据,对其进行预处理,创建新的有用特征,检查异常值和其他不一致的数据点并最终选择正确的指标后,我们准备应用机器学习算法。

通过观察一系列示例并将它们与结果配对,机器学习算法能够提取一系列规则,通过正确猜测它们的结果,可以将这些规则成功地推广到新示例。 这就是有监督的学习方法,其中应用了一系列高度专业化的学习算法,我们希望这些算法可以正确地预测(并概括)任何新数据。

但是,我们如何才能正确地应用学习过程,以便获得最佳的预测模型,以便通常将其用于相似但新的数据?

在数据科学中,有一些最佳实践可以遵循,可以确保在将来将模型推广到任何新数据时获得最佳结果。 让我们通过逐步进行说明,首先加载以下示例中将要处理的数据集:

In: from sklearn.datasets import load_digits
    digits = load_digits()
    print (digits.DESCR)
    X = digits.data
    y = digits.target  

数字数据集包含从 0 到 9 的手写数字图像。数据格式由8 x 8此类图像的矩阵组成:

这些数字实际上存储为向量(从每个8 x 8图像的平整度得出),其向量值为 0 到 16 之间的 64 个数值,代表每个像素的灰度色调:

In: X[0] Out: array([0., 0., 5., 13., 9., 1., 0., 0., ...])  

我们还将使用三个不同的支持向量机上载三个不同的机器学习假设(在机器学习语言中,一个假设是一种已将其所有参数设置为可学习的算法)。 它们对于我们的实际示例很有用:

In: from sklearn import svm
    h1 = svm.LinearSVC(C=1.0)
    h2 = svm.SVC(kernel='rbf', degree=3, gamma=0.001, C=1.0)
    h3 = svm.SVC(kernel='poly', degree=3, C=1.0)

作为第一个实验,让我们将线性 SVM 分类器拟合到我们的数据并验证结果:

In: h1.fit(X,y)
    print (h1.score(X,y)) 

Out: 0.984974958264  

第一种方法是使用X数组拟合模型,以便正确预测y向量指示的 10 个类别之一。 此后,通过调用.score()方法并指定相同的预测变量(X数组),该方法根据相对于y向量给定的真实值的平均准确率来评估表现。 结果在预测正确的数字上约为 98.5% 的准确率。

该数字表示样本内表现,即学习算法的表现。 它纯粹是指示性的,尽管它代表了表现的上限(提供不同的示例,但平均表现始终会很差)。 实际上,每种学习算法都具有一定的记忆能力,可以记忆训练过的数据。 因此,样本内表现部分归因于该算法从数据中学习一些一般推断的能力,部分归因于其记忆能力。 在极端情况下,如果模型相对于可用数据而言训练过度或过于复杂,则存储的模式将优先于派生的规则,并且该算法将不适合正确地预测新的观测值(尽管对过去的观测值非常有用)。 这样的问题称为过拟合。 由于在机器学习中,我们无法分离这两种伴随的影响,为了正确估计我们的假设的预测表现,我们需要在没有记忆效应的一些新数据上对其进行测试。

记忆的发生是由于算法的复杂性。 复杂的算法拥有许多系数,可以在其中存储有关训练数据的信息。 不幸的是,由于预测过程变得随机,因此在预测未见实例时,记忆效应会导致估计的高方差。 三种解决方案是可能的:

  • 首先,您可以增加示例数,这样就无法存储所有以前见过的案例的信息,但是查找所有必要数据可能会变得更加昂贵
  • 其次,您可以使用较简单的机器学习算法,该算法不太容易记忆,但是会以使用机器学习解决方案的能力为代价,该解决方案不太适合数据基础规则的复杂性。
  • 第三,您可以使用正则化对极其复杂的模型进行惩罚,并迫使算法过轻,甚至从模型中排除一定数量的变量,从而有效地减少了模型中系数的数量及其存储数据的能力。

在许多情况下,即使没有一定的成本,也无法获得新的数据。 在这种常见情况下,一种好的方法是将初始数据分为训练集(通常为总数据的 70-80%)和测试集(其余为 20-30%)。 考虑到任何可能的不平衡类别分布,训练和测试集之间的划分应该是完全随机的:

In: chosen_random_state = 1
    X_train, X_test, y_train, y_test = model_selection.train_test_split(
                        X, y, 
                        test_size=0.30, random_state=chosen_random_state)
    print ("(X train shape %s, X test shape %s, n/y train shape %s, \
 y test shape %s" % (X_train.shape, X_test.shape,y_train.shape, y_test.shape))
    h1.fit(X_train,y_train)
    print (h1.score(X_test,y_test)) 
    # Returns the mean accuracy on the given test data and labels Out: (X train shape (1257, 64), X test shape (540, 64), 
      y train shape (1257,), y test shape (540,)
      0.953703703704

通过执行前面的代码,model_selection.train_test_split()函数根据参数test_size将初始数据随机分为两个互斥的集合(该整数可以表示测试集示例的确切数目,也可以是浮点数,表示要用于测试目的的总数据的百分比)。 拆分由random_state控制,该操作可确保在不同时间和不同计算机上(即使您使用的是完全不同的操作系统)也可以重现该操作。

目前的平均准确率为 0.94。 如果您尝试使用chosen_random_state参数使用不同的整数值再次运行同一单元,则实际上您会注意到准确率会发生变化,这表明测试集进行的表现评估并非绝对的表现指标,并且应谨慎使用。 给定不同的测试样本,您必须了解它的可变性。

实际上,我们甚至可以从测试集中获得有偏差的表现估计。 如果我们选择(在使用random_state进行各种试验后)可以确认我们假设的测试集,或者开始使用测试集作为参考以对学习过程做出决定(例如,选择符合特定测试样本的最佳假设)。

与仅评估训练数据的拟合度一样,对选定的测试集进行操作也可以确保最终的表现看起来不错。 但是,您建立的模型不会在不同的测试集上复制相同的表现(再次是过拟合问题)。

因此,当我们在将每个假设拟合到训练数据之后必须在多个假设之间进行选择(数据科学中的一个常见实验)时,我们需要一个可用于比较其表现的数据样本,并且它不能是测试集( 由于我们之前提到的原因)。

正确的方法是使用验证集。 我们建议您拆分初始数据-可以为训练集保留 60% 的初始数据,为验证集保留 20% 的数据,为测试集保留 20% 的数据。 为了考虑到这一点,我们可以更改初始代码,并且可以对其进行修改以测试所有三个假设:

In: chosen_random_state = 1
    X_train, X_validation_test, y_train, y_validation_test =  
    model_selection.train_test_split(X, y, 
                                     test_size=.40,                                                   
                                     random_state=chosen_random_state)
    X_validation, X_test, y_validation, y_test = 
    model_selection.train_test_split(X_validation_test, y_validation_test, 
                                    test_size=.50, 
                                    random_state=chosen_random_state)
    print ("X train shape, %s, X validation shape %s, X test shape %s, 
           /ny train shape %s, y validation shape %s, y test shape %s/n" % 
           (X_train.shape, X_validation.shape, X_test.shape,  
            y_train.shape, y_validation.shape, y_test.shape))
    for hypothesis in [h1, h2, h3]:
        hypothesis.fit(X_train,y_train)
        print ("%s -> validation mean accuracy = %0.3f" % (hypothesis,  
        hypothesis.score(X_validation,y_validation))  )  
    h2.fit(X_train,y_train)
    print ("n%s -> test mean accuracy = %0.3f" % (h2,   
h2.score(X_test,y_test)))

Out: X train shape, (1078, 64), X validation shape (359, 64), 
     X test shape (360, 64), 
     y train shape (1078,), y validation shape (359,), y test shape (360,)    

     LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True,
         intercept_scaling=1, loss='squared_hinge', max_iter=1000,
         multi_class='ovr', penalty='l2', random_state=None,  tol=0.0001,
         verbose=0) -> validation mean accuracy = 0.958    

     SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
      decision_function_shape=None, degree=3, gamma=0.001, kernel='rbf',
      max_iter=-1, probability=False, random_state=None, shrinking=True,
      tol=0.001, verbose=False) -> validation mean accuracy = 0.992

     SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
      decision_function_shape=None, degree=3, gamma='auto', kernel='poly',
      max_iter=-1, probability=False, random_state=None, shrinking=True,
      tol=0.001, verbose=False) -> validation mean accuracy = 0.989

     SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
      decision_function_shape=None, degree=3, gamma=0.001, kernel='rbf',
      max_iter=-1, probability=False, random_state=None, shrinking=True,
      tol=0.001, verbose=False) -> test mean accuracy = 0.978

如产出所报告的那样,现在,训练集由 1,078 例(占总数的 60%)组成。 为了将数据分为三个部分-训练,验证和测试-首先使用函数model_selection.train_test_split在训练集和测试/验证数据集之间提取数据(从而提取训练样本)。 然后,使用相同的函数将测试/验证数据集进一步分为两部分。 在对每个假设进行训练后,将根据验证集对其进行测试。 根据验证集,使用 RBF 核的 SVC 的精度为 0.992,是最佳模型。 决定使用此模型后,将在测试集上评估其表现,从而得出 0.978 的准确率(这是该模型实际表现的实测代表)。

由于测试的准确率与验证的准确率不同,因此选择的假设真的是最好的假设吗? 我们建议您尝试多次运行单元格中的代码(理想情况下,至少运行 30 次代码应确保具有统计意义),每次都更改chosen_random_state值。 这样,将针对不同的样本验证相同的学习过程,从而使您对自己的期望更有信心。

交叉验证

如果您运行了先前的实验,则可能已经意识到:

  • 验证和测试结果都不同,因为它们的样本不同。
  • 选择的假设通常是最好的假设,但并非总是如此。

不幸的是,依赖于样本的验证和测试阶段会带来不确定性,同时减少了专门用于训练的学习示例(示例越少,模型估计值的差异就越大)。

一种解决方案是使用交叉验证,Scikit-learn 提供了一个完整的交叉验证和表现评估模块(sklearn.model_selection)。

通过使用交叉验证,您只需要将数据分为训练和测试集,就可以将训练数据用于模型优化和模型训练。

交叉验证如何工作? 这个想法是将您的训练数据划分为一定数量的分区(称为折叠),然后对您的模型进行多达该分区数量的训练,每次从训练阶段就将一个不同的分区排除在外。 每次模型训练后,您都将在未折叠的折痕上测试结果并将其存储起来。 最后,您将获得与折痕一样多的结果,并且您可以计算出折痕的平均值和标准差:

在前面的图形示例中,图表描述了一个数据集,该数据集被分为五个大小相等的折叠,根据迭代的不同,它们在机器学习过程中被用作训练或测试集的一部分。

在我们推荐的交叉验证中,十折是很常见的配置。 对于线性估计等有偏估计量,使用较少的折痕可能会很好,但它可能会惩罚更复杂的机器学习算法。 在某些情况下,您确实需要使用更多的折痕以确保有足够的训练数据供机器学习算法正确归纳。 这在没有足够数据点的医学数据集中很常见。 另一方面,如果手头的示例数量不成问题,则使用更多的折叠会占用更多的计算资源,并且交叉验证完成可能需要更长的时间。 有时,使用五折可以很好地权衡估计的准确率和运行时间。

标准差将提示您模型如何受到训练提供的数据(实际上是模型的方差)的影响,并且平均值提供了对其总体表现的合理估计。 使用从不同模型获得的交叉验证结果的平均值(由于使用了不同的模型类型,或者因为使用了不同的训练变量选择,或者因为模型的不同超参数),您可以放心地选择表现最佳的假设,可进行总体表现测试。

我们强烈建议您将交叉验证仅用于优化目的,而不是用于表现估计(也就是说,找出新数据上的模型误差可能是什么)。 交叉验证只是根据最佳平均结果指出最佳算法和参数选择。 将其用于表现评估将意味着使用找到的最佳结果,这比应该的结果更为乐观。 为了报告您可能的表现的无偏估计,您应该首选使用测试集。

让我们执行一个示例,以查看交叉验证的实际效果。 此时,我们可以回顾一下对数字数据集的三个可能假设的先前评估:

In: choosen_random_state = 1
 cv_folds = 10 # Try 3, 5 or 20
    eval_scoring='accuracy' # Try also f1
    workers = -1 # this will use all your CPU power
    X_train, X_test, y_train, y_test = model_selection.train_test_split(
                                      X, y, 
                                      test_size=0.30, 
                                      random_state=choosen_random_state)
    for hypothesis in [h1, h2, h3]:
        scores = model_selection.cross_val_score(hypothesis, 
                     X_train, y_train, 
                     cv=cv_folds, scoring= eval_scoring, n_jobs=workers)
        print ("%s -> cross validation accuracy: mean = %0.3f \
               std = %0.3f" % (hypothesis, np.mean(scores), 
                               np.std(scores))) 

Out: LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True,
      intercept_scaling=1, loss='squared_hinge', max_iter=1000,
      multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
      verbose=0) -> cross validation accuracy: mean = 0.930 std = 0.021

     SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
      decision_function_shape=None, degree=3, gamma=0.001, kernel='rbf',
      max_iter=-1, probability=False, random_state=None, shrinking=True,
      tol=0.001, verbose=False) -> cross validation accuracy: 
      mean = 0.990 std = 0.007

     SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
      decision_function_shape=None, degree=3, gamma='auto', kernel='poly',
      max_iter=-1, probability=False, random_state=None, shrinking=True,
      tol=0.001, verbose=False) -> cross validation accuracy: 
      mean = 0.987 std = 0.010

脚本的核心是model_selection.cross_val_score函数。 我们脚本中的函数接收以下参数:

  • 学习算法(estimator
  • 一组预测变量(X
  • 目标变量(y
  • 交叉验证器(cv
  • 评分函数(scoring
  • 要使用的 CPU 数(n_jobs

给定这样的输入,该函数将包装一些其他复杂函数。 它创建 n 个重复项,训练 n 个交叉验证样本中的模型,测试结果,并存储从样本外折叠中每次迭代得出的分数。 最后,该函数将报告此类记录的分数列表:

In: scores  Out: array([ 0.96899225, 0.96899225, 0.9921875, 0.98412698, 0.99206349,             1, 1., 0.984, 0.99186992, 0.98347107])

使用cross_val_score的主要优点在于使用简单,并且它自动合并了所有必要步骤以进行正确的交叉验证。 例如,在决定如何将训练样本分成多个折叠时,如果提供了y向量,则它在每个折叠中的目标类别标签所占的比例将与最初提供的y相同。

使用交叉验证迭代器

尽管model_selection模块中的cross_val_score函数充当大多数交叉验证目的的完整帮助器函数,但您可能仍需要构建自己的交叉验证过程。 在这种情况下,相同的model_selection模块可确保对迭代器的强大选择。

在研究最有用的迭代器之前,让我们通过研究其中一个迭代器model_selection.KFold的工作方式,清楚地概述它们的功能。

KFold的功能非常简单。 如果给出 n 次折叠,则它将 n 次迭代返回到训练和验证集的索引以测试每个折叠。

假设我们有一个由 100 个示例组成的训练集,并且我们想创建一个 10 倍的交叉验证。 首先,让我们设置迭代器:

In: kfolding = model_selection.KFold(n_splits=10, shuffle=True, 
                                     random_state=1)
 for train_idx, validation_idx in kfolding.split(range(100)): print (train_idx, validation_idx) Out: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 20 21 22 23 24 25 26 27  
       28 29 30 31 32 34 35 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 
       54 55 56 57 58 59 60 61 62 63 64 66 67 68 70 71 72 73 74 75 76 77 78 79 
       83 85 86 87 88 89 90 91 92 94 95 96 97 98 99] [17 33 36 65 69 80 81 82 
       84 93] ...

通过使用 n 参数,我们可以指示迭代器对 100 个索引执行折叠。 n_splits指定折数。 将随机播放设置为True时,它将随机选择折叠分量。 相反,如果将其设置为False,则将根据索引的顺序创建折叠(因此,第一个折叠将为[0 1 2 3 4 5 6 7 8 9])。

像往常一样,random_state参数可允许产生折痕。

在迭代器循环中,会根据您的假设提供评估和验证的索引,以进行评估。 (让我们通过使用线性 SVC 来了解它是如何工作的。)您只需在花式索引的帮助下同时选择Xy

In: h1.fit(X[train_idx],y[train_idx])
    h1.score(X[validation_idx],y[validation_idx]) Out:0.90000000000000002

如您所见,交叉验证迭代器仅为您提供索引功能,并且在使用索引对假设进行评分评估时由您自己决定。 这为复杂的验证操作打开了机会。

在其他最有用的迭代器中,值得一提的是:

  • StratifiedKFold的工作方式与Kfold相似,但它始终返回与训练集大致相同的类别百分比的折叠。 这样可以使每个折痕保持平衡; 因此,学习器适合正确的类别比例。 代替案例数,它需要输入目标变量 y 作为输入参数。 如上一节所述,默认情况下,迭代器包装在cross_val_score函数中。
  • LeaveOneOut的工作方式类似于Kfold,但它仅作为一个观测值的验证集返回。 因此,最后,折叠数将等于训练集中的示例数。 我们建议您仅在训练集严重不平衡(例如,在欺诈检测问题中)或很小的训练集时使用此交叉验证方法,尤其是在观察值少于 100 的情况下 – K 倍验证会极大减少训练集。
  • LeavePOutLeaveOneOut的优点和局限性方面相似,但其验证集由 P 个案例组成。 因此,总折叠数将是所有可用案例中 P 个案例的组合(随着数据集大小的增长,实际上可能是相当大的数量)。
  • LeaveOneLabelOut提供了一种方便的方法,可根据您预先准备或计算的方案进行交叉验证。 实际上,它的行为类似于Kfolds,但是对于折叠已经被标记并提供给标签参数这一事实。
  • LeavePLabelOutLeaveOneLabelOut的变体。 在这种情况下,根据您事先准备的方案,测试折痕由许多标签组成。

要了解有关每个迭代器所需的特定参数的更多信息,建议您访问 Scikit-learn 网站

实际上,交叉验证也可以用于预测目的。 实际上,对于特定的数据科学项目,可能会要求您从可用数据中构建模型,然后对完全相同的数据进行预测。 如前所述,使用训练预测将导致高方差估计,因为该模型已经拟合到该数据上,因此它已经记住了其许多特征。

应用于预测的交叉验证过程可以解决:

  • 创建一个交叉验证迭代器(最好具有大量的 K 折)。
  • 反复进行交叉验证,每次使用k-1训练倍数训练模型。
  • 在每次迭代中,在验证折痕上(实际上是超出样本的折痕),生成预测并将其存储起来,以跟踪其索引。 这样做的最好方法是拥有一个预测矩阵,该矩阵将通过使用花式索引进行预测。

这种方法通常称为跨验证倍数预测。

采样和自举

在说明了基于折叠,p-out 和自定义方案的迭代器之后,我们将继续对交叉验证迭代器进行概述,并引用所有基于采样的迭代器。

采样方案是不同的,因为它们不会拆分训练集,但是会使用不同的方法对其进行采样:子采样或自举。

当您随机选择可用数据的一部分,从而获得比初始数据集小的数据集时,将执行子采样。

二次采样非常有用,尤其是当您需要广泛地检验假设时,尤其是您不希望从极小的测试样本中获得验证时(因此,您可以选择不采用遗忘式方法或使用KFold大量折叠)。 以下是相同的示例:

In: subsampling = model_selection.ShuffleSplit(n_splits=10, 
    test_size=0.1, random_state=1)
 for train_idx, validation_idx in subsampling.split(range(100)): print (train_idx, validation_idx) Out:[92 39 56 52 51 32 31 44 78 10  2 73 97 62 19 35 94 27 46 38 67 99 54 
     95 88 40 48 59 23 34 86 53 77 15 83 41 45 91 26 98 43 55 24  4 58 49
     21 87  3 74 30 66 70 42 47 89  8 60  0 90 57 22 61 63  7 96 13 68 85 
     14 29 28 11 18 20 50 25  6 71 76  1 16 64 79  5 75  9 72 12 37] [80 
     84 33 81 93 17 36 82 69 65]
     ...

与其他迭代器类似,n_splits将设置子样本数,test_size百分比(如果给出浮点数)或用作测试的观察数。

自举作为一种重采样方法,已经很长时间用于估计统计数据的采样分布。 因此,根据对机器学习假设的样本外表现的评估,这是一种合适的方法。

自举的工作方式是随机选择观察值并允许重复,直到建立了一个与原始数据集大小相同的新数据集。

不幸的是,由于引导是通过替换采样(即允许重复相同的观察值)来工作的,因此,由于以下原因会出现问题:

  • 案例可能同时出现在训练和测试集上(您仅需出于测试目的使用引导外样本观察)
  • 与交叉验证估计相比,方差少且偏差大,这是由于替换采样导致的观察结果不一致。

尽管该功能很有用(至少从我们作为数据科学从业人员的角度来看),但我们向您建议了Bootstrap的简单替代品,该替代品适用于交叉验证,可以通过迭代调用。 它生成与输入数据(索引的长度)大小相同的样本引导程序,以及可用于测试目的的排除索引列表(样本之外):

In: import random
    def Bootstrap(n, n_iter=3, random_state=None):
        """
        Random sampling with replacement cross-validation generator.
        For each iter a sample bootstrap of the indexes [0, n) is 
        generated and the function returns the obtained sample 
        and a list of all the excluded indexes.
        """
        if random_state:
            random.seed(random_state)
        for j in range(n_iter):
            bs = [random.randint(0, n-1) for i in range(n)]
            out_bs = list({i for i in range(n)} - set(bs))
            yield bs, out_bs    

    boot = Bootstrap(n=100, n_iter=10, random_state=1)
    for train_idx, validation_idx in boot:
        print (train_idx, validation_idx)    

Out:[37, 12, 72, 9, 75, 5, 79, 64, 16, 1, 76, 71, 6, 25, 50, 20, 18, 84, 
     11, 28, 29, 14, 50, 68, 87, 87, 94, 96, 86, 13, 9, 7, 63, 61, 22, 57, 
     1, 0, 60, 81, 8, 88, 13, 47, 72, 30, 71, 3, 70, 21, 49, 57, 3, 68, 
     24, 43, 76, 26, 52, 80, 41, 82, 15, 64, 68, 25, 98, 87, 7, 26, 25, 
     22, 9, 67, 23, 27, 37, 57, 83, 38, 8, 32, 34, 10, 23, 15, 87, 25, 71, 
     92, 74, 62, 46, 32, 88, 23, 55, 65, 77, 3] [2, 4, 17, 19, 31, 33, 35, 
     36, 39, 40, 42, 44, 45, 48, 51, 53, 54, 56, 58, 59, 66, 69, 73, 78, 
     85, 89, 90, 91, 93, 95, 97, 99]
     ...

该函数执行二次采样,并接受n_iter索引的参数n以绘制引导程序样本,并接受random_state索引以提高可重复性。

超参数优化

机器学习假设不仅由学习算法确定,还由其超参数(算法的参数必须事先确定,并且在训练过程中无法学习)确定,并选择要用于实现最佳学习参数的变量。

在本节中,我们将探索如何扩展交叉验证方法,以找到能够推广到我们的测试集的最佳超参数。 我们将继续使用 Scikit-learn 包提供的手写数字数据集。 这是有关如何加载数据集的有用提示:

In: from sklearn.datasets import load_digits
    digits = load_digits()
 X, y = digits.data, digits.target 

此外,我们将继续使用支持向量机作为学习算法:

In: from sklearn import svm
    h = svm.SVC()
    hp = svm.SVC(probability=True, random_state=1)

这次,我们将处理两个假设。 第一个假设只是输出标签作为预测的普通 SVC。 第二个假设是通过将random_state固定为1的标签概率(probability=True参数)的计算来增强 SVC,以保证结果的可重复性。 SVC 输出概率可以通过所有需要概率的损失度量来评估,而不是通过结果来进行标签预测,例如 AUC。

运行上述代码段后,我们准备导入model_selection模块并设置要通过交叉验证测试的超参数列表。

我们将使用GridSearchCV函数,该函数将根据搜索时间表自动搜索最佳参数,并根据预定义或自定义评分函数对结果进行评分:

In: from sklearn import model_selection
    search_grid = [
          {'C': [1, 10, 100, 1000], 'kernel': ['linear']},
          {'C': [1, 10, 100, 1000], 'gamma': [0.001, 0.0001], 
           'kernel': ['rbf']},
          ]
    scorer = 'accuracy'

现在,我们导入了模块,使用字符串参数('accuracy')设置了scorer变量,并创建了一个由两个字典组成的列表。

记分器是一个字符串,我们从 Scikit-learn 文档的预定义值部分中找到的可能范围中选择了一个字符串,可以在这个页面

使用预定义值只需要您从列表中选择一个评估指标(有一些用于分类和回归,有一些用于聚类)并通过将字符串直接插入或使用字符串变量插入GridSearchCV函数。

GridSearchCV还接受称为param_grid的参数,该参数可以是一个字典,其中包含要更改的所有超参数的指示作为键,并作为要引用字典键的值包含要测试的参数列表。 因此,如果要测试关于超参数C的假设的表现,则可以创建如下字典:

{'C' : [1, 10, 100, 1000]}

另外,根据您的喜好,您可以使用专门的 NumPy 函数来生成在对数刻度上均匀分布的数字(就像我们在上一章中看到的那样):

{'C' :np.logspace(start=-2, stop=3, num=6, base=10.0)}

因此,您可以枚举所有可能的参数值并测试它们的所有组合。 但是,您还可以堆叠不同的字典,使每个字典仅包含应一起测试的部分参数。 例如,当使用 SVC 时,设置为线性的核会自动排除gamma参数。 实际上,将其与线性核相结合将浪费计算能力,因为它不会对学习过程产生任何影响。

现在,让我们继续进行网格搜索,对它进行计时(由于%timeit命令魔术命令),以了解完成整个过程将花费多少时间:

In: search_func = model_selection.GridSearchCV(estimator=h,  
                                param_grid=search_grid, scoring=scorer, 
n_jobs=-1, iid=False, refit=True, cv=10)
    %timeit search_func.fit(X,y)
    print (search_func.best_estimator_)
    print (search_func.best_params_)
    print (search_func.best_score_) Out: 4.52 s ± 75.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
     SVC(C=10, cache_size=200, class_weight=None, coef0=0.0, degree=3, 
     gamma=0.001,
       kernel='rbf', max_iter=-1, probability=False, random_state=None,
       shrinking=True, tol=0.001, verbose=False)
     {'kernel': 'rbf', 'C': 10, 'gamma': 0.001}
     0.981081122784

在我们的计算机上完成搜索大约需要10秒。 搜索指出,最好的解决方案是具有rbf核,C=10gamma=0.001的支持向量机分类器,交叉验证的平均准确率为0.981

至于GridSearchCV命令,除了我们的假设(估计参数)param_grid和我们刚刚谈到的得分外,我们决定设置其他可选但有用的参数:

  1. 首先,我们将设置n_jobs=-1。 这迫使该函数使用计算机上所有可用的处理器,因此我们运行 Jupyter 单元。

  2. 然后,我们将使用最佳估计器的参数设置refit=True,以使函数适合整个训练集。 现在,我们只需要对新数据应用search_funct.predict()方法即可获得新的预测。

  3. cv参数设置为 10 倍(但是,您可以选择较小的倍数,以牺牲测试精度为代价进行折衷)。

  4. iid参数设置为False。 此参数决定如何计算有关类的误差度量。 如果类是平衡的(如本例所示),则设置iid不会有太大影响。 但是,如果它们不平衡,则默认情况下,iid=True将使带有更多示例的类在全局误差的计算上更具权重。 相反,iid=False意味着应将所有类都视为相同。 由于我们希望 SVC 能够识别从 0 到 9 的每个手写数字,无论给每个手写数字提供了多少示例,将iid参数设置为False是正确的选择。 根据您的数据科学项目,您可以决定实际上更喜欢将默认值设置为True

建立自定义评分函数

对于我们的实验,我们选择了预定义的评分函数。 对于分类,有五种可用的度量(准确率,AUC,精确度,召回率和 F1 得分),对于回归,有三种度量(R²,MAE 和 MSE)。 尽管它们是一些最常见的度量,但您可能不得不使用其他度量。 在我们的示例中,我们发现使用损失函数来找出正确答案的可能性仍然很高,即使分类器是错误的(因此请考虑正确答案是否是第二个或第三个选项) 算法)。 我们该如何处理?

sklearn.metrics模块中,实际上有一个log_loss函数。 我们要做的就是将其包装为GridSearchCV可以使用的方式:

In: from sklearn.metrics import log_loss, make_scorer
    Log_Loss = make_scorer(log_loss, 
                           greater_is_better=False, 
                           needs_proba=True)

这里是。 基本上,它是单线的。 通过从sklearn.metrics调用make_scorerlog_loss误差函数,我们创建了另一个函数(Log_Loss)。 我们还想指出,我们想通过设置greater_is_better=False来最小化该度量(这是损失,而不是分数)。 我们还将指定它适用于概率,而不适用预测(因此,设置needs_proba=True)。 由于它使用概率,因此我们将使用在上一节中刚刚定义的hp假设,因为否则 SVC 不会为其预测提供任何概率:

In: search_func = model_selection.GridSearchCV(estimator=hp, 
                         param_grid=search_grid, scoring=Log_Loss, 
n_jobs=-1, iid=False, refit=True, cv=3)
    search_func.fit(X,y)
    print (search_func.best_score_)
    print (search_func.best_params_) Out: -0.16138394082
     {'kernel': 'rbf', 'C': 1, 'gamma': 0.001}

现在,我们的超参数针对日志损失(而非准确率)进行了优化。

要记住的一件好事是,针对正确的特征进行优化可以为您的项目带来更好的结果。 因此,花费在分数函数上的时间始终是花费在数据科学上的时间。

在这一点上,让我们想象您有一项艰巨的任务。 由于很容易将手写数字 1 和 7 弄错,因此您必须优化算法以最大程度地减少手写数字 1 和 7 的错误。 您可以通过定义新的损失函数来实现此目标:

In: import numpy as np
    from sklearn.preprocessing import LabelBinarizer
    def my_custom_log_loss_func(ground_truth, 
                                p_predictions, 
                                penalty = list(), 
                                eps=1e-15):
        adj_p = np.clip(p_predictions, eps, 1 - eps)
        lb = LabelBinarizer()
        g = lb.fit_transform(ground_truth)
        if g.shape[1] == 1:
            g = np.append(1 - g, g, axis=1)
        if penalty:
            g[:,penalty] = g[:,penalty] * 2
        summation = np.sum(g * np.log(adj_p))
        return summation * (-1.0/len(ground_truth))

通常,函数的第一个参数应该是实际答案,第二个参数应该是预测或预测的概率。 您还可以添加具有默认值的参数,或者稍后在调用make_scorer函数时可以固定其值:

In: my_custom_scorer = make_scorer(my_custom_log_loss_func, 
                                    greater_is_better=False, 
                                    needs_proba=True, penalty = [4,9])

在这种情况下,我们为易混淆的数字49设置了罚款(但是,您可以更改它,甚至将其保留为空,以检查由此产生的损失是否与之前的实验相同) sklearn.metrics.log_loss函数)。

现在,当评估数字49类别的结果时,新的损失函数将log_loss误差计算为两倍:

In: from sklearn import model_selection
    search_grid = [{'C': [1, 10, 100, 1000], 'kernel': ['linear']},
    {'C': [1, 10, 100, 1000], 'gamma': [0.001, 0.0001], 'kernel': ['rbf']}]
    search_func = model_selection.GridSearchCV(estimator=hp, 
                param_grid=search_grid, scoring=my_custom_scorer, n_jobs=1, 
                iid=False, cv=3)
    search_func.fit(X,y)
    print (search_func.best_score_)
    print (search_func.best_params_)

Out: -0.199610271298
     {'kernel': 'rbf', 'C': 1, 'gamma': 0.001}

请注意,对于最后一个示例,我们设置n_jobs=1。 此选择背后有技术原因。 如果您在 Windows 上运行此代码(在任何 Unix 或 macOS 系统中,实际上都可以),则可能会发生错误,该错误可能会阻塞 Jupyter 笔记本。 借助joblib包,Scikit-learn 包中的所有交叉验证函数(以及许多其他函数)都可以使用多处理器来工作。 这样的包要求所有函数都必须在多个处理器上运行,以便将它们导入,并且如果它们是动态定义的,则它们不能接受(它们应该是可选取的)。 可能的解决方法是将该函数保存到磁盘上的文件中,例如custom_measure.py,然后使用from custom_measure import Log_Loss命令将其导入。

减少网格搜索运行时间

通过检查网格规范要求的所有参数组合,GridSearchCV函数实际上可以为您管理大量工作。 无论如何,当数据或网格搜索空间很大时,该过程可能需要很长时间才能计算出来。

model_selection模块采用以下方法可以解决此问题。 RandomizedSearchCV提供了一种程序,可随机抽取组合样本并报告找到的最佳组合。

这具有一些明显的优点:

  • 您可以限制计算数量。
  • 您可以获得良好的结果,或者在最坏的情况下,了解在网格搜索中将精力集中在哪里。
  • RandomizedSearchCV具有与GridSearchCV相同的选项,但:
    1. 有一个n_iter参数,它是随机样本的数量。
    2. 包括param_distributions,其功能与param_grid相同。 但是,它仅接受字典,如果将分布分配为值而不是离散值列表,则效果更好。 例如,您可以分配C:scipy.stats.expon(scale=100)之类的分配,而不是C: [1, 10, 100, 1000]

让我们使用之前的设置来测试此函数:

In: search_dict = {'kernel': ['linear','rbf'],'C': [1, 10, 100, 1000], 
                   'gamma': [0.001, 0.0001]}
    scorer = 'accuracy'
    search_func = model_selection.RandomizedSearchCV(estimator=h, 
                                      param_distributions=search_dict, 
                                      n_iter=7, 
                                      scoring=scorer, 
                                      n_jobs=-1, 
                                      iid=False, 
                                      refit=True, 
                                      cv=10,
 return_train_score=False)
    %timeit search_func.fit(X,y)
    print (search_func.best_estimator_)
    print (search_func.best_params_)
    print (search_func.best_score_) Out: 1.53 s ± 265 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
     SVC(C=10, cache_size=200, class_weight=None, coef0=0.0, degree=3,
       gamma=0.001, kernel='rbf', max_iter=-1, probability=False,
       random_state=None, shrinking=True, tol=0.001, verbose=False)
     {'kernel': 'rbf', 'C': 1000, 'gamma': 0.001}
     0.981081122784

仅使用一半的计算(7 个得出详尽的网格搜索得出的 14 个试验结果),它找到了一个等效的解决方案。 让我们来看看已经测试过的组合:

In: res = search_func.cv*results*  for el in zip(res['mean_test_score'],                  res['std_test_score'], 
                  res['params']):
 print(el) Out: (0.9610800248897716, 0.021913085707003094, {'kernel': 'linear', 
     'gamma': 0.001, 'C': 1000})
 (0.9610800248897716, 0.021913085707003094, {'kernel': 'linear',     'gamma': 0.001, 'C': 1})
 (0.9716408520553866, 0.02044204452092589, {'kernel': 'rbf',     'gamma': 0.0001, 'C': 1000})
 (0.981081122784369, 0.015506818968315338, {'kernel': 'rbf',     'gamma': 0.001, 'C': 10})
 (0.9610800248897716, 0.021913085707003094, {'kernel': 'linear',     'gamma': 0.001, 'C': 10})
 (0.9610800248897716, 0.021913085707003094, {'kernel': 'linear',     'gamma': 0.0001, 'C': 1000})
 (0.9694212166750269, 0.02517929728858225, {'kernel': 'rbf',     'gamma': 0.0001, 'C': 10})

即使没有所有组合的完整概述,一个好的示例也会提示您仅寻找 RBF 核以及某些Cgamma范围,从而将随后的网格搜索限制在潜在搜索空间的有限范围内。

依靠基于随机过程的优化可能看起来是靠运气,但是实际上,这是探索超参数空间的一种非常有效的方法,尤其是在高维空间中。 如果安排得当,随机搜索不会在一定程度上牺牲探索的完整性。 在高维超参数空间中,网格搜索探索趋向于重复相似参数组合的测试,事实证明,在存在不相关参数或效果密切相关的参数的情况下,计算效率低下。

James Bergstra 和 Yoshua Bengio 设计了随机搜索,以使深度学习中超参数的最佳组合搜索更加有效。 原始论文为进一步了解这种方法提供了很好的资源

统计测试表明,要使随机搜索取得良好的效果,您应尝试从最少 30 次试验到最多 60 次试验(此经验法则是基于最佳覆盖了 5% 至 10% 超参数的假设空间,并且 95% 的成功率是可以接受的)。 因此,如果您的网格搜索需要类似的搜索(这样您就可以利用随机搜索的属性)或需要进行大量的实验(从而可以节省计算量),通常可以选择随机搜索。

特征选择

对于将要使用的机器学习算法,不相关且多余的特征可能会导致结果模型的可解释性不足,训练时间长,最重要的是过拟合和泛化性差。

过拟合与观察次数与数据集中可用变量的比率有关。 当变量与观测值相比有很多时,由于变量之间的相关性,您的学习算法将有更多的机会以某种局部优化或某些杂散噪声的拟合结果。

除了降维(这需要您转换数据)之外,特征选择还可以解决上述问题。 通过选择最具预测性的变量集,可以简化高维结构。 也就是说,即使某些特征在独立级别上并不是很好的预测指标,它也会选择能够很好地协同工作的特征。

Scikit-learn 包提供了广泛的特征选择方法:

  • 基于方差的选择
  • 单变量选择
  • 递归消除
  • 随机逻辑回归/稳定性选择
  • 基于 L1 的特征选择
  • 基于树的特征选择

方差,单变量和递归消除可以在feature_selection模块中找到。 其他是特定机器学习算法的副产品。 除了基于树的选择(将在第 4 章,“机器学习”中提到)之外,我们还将介绍所有前面的方法,并指出它们如何帮助您改善您从数据中学习的结果。

基于特征差异的选择

此方法是最简单的特征选择方法,通常用作基准。 它只是删除所有差异较小的特征; 通常低于一组。 默认情况下,VarianceThresholder对象将删除所有零方差特征,但您可以使用阈值参数进行控制。

让我们创建一个由10观测值和5特征组成的小型数据集,其中3具有参考意义:

In: from sklearn.datasets import make_classification
    X, y = make_classification(n_samples=10, n_features=5, 
    n_informative=3, n_redundant=0, random_state=101)

现在,让我们测量它们的Variance

In: print ("Variance:", np.var(X, axis=0)) Out: Variance: [ 2.50852168  1.47239461  0.80912826  1.51763426                   
                1.37205498]

较低的方差与第三个特征相关; 因此,如果要选择四个最佳特征,则应将最小方差阈值设置为1.0。 让我们这样做,看看第一次观察数据集会发生什么:

In: from sklearn.feature_selection import VarianceThreshold
    X_selected = VarianceThreshold(threshold=1.0).fit_transform(X)
    print ("Before:", X[0, :])
    print ("After: ", X_selected[0, :]) Out: Before: [ 1.26873317 -1.38447407  0.99257345  1.19224064 -2.07706183]
     After:  [ 1.26873317 -1.38447407  1.19224064 -2.07706183] 

正如预期的那样,在特征选择过程中删除了第三列,并且所有输出观察都没有该列。 仅保留方差大于 1.0 的那些。 记住在应用VarianceThresholder之前不要对数据集进行 Z 归一化(例如,使用StandardScaler); 否则,所有特征将具有单一方差。

单变量选择

在单变量选择的帮助下,我们打算根据统计测试选择与target变量关联最大的单个变量。

有三种可用的测试可以使我们的选择基于:

  • f_regression对象根据变量与目标的线性回归中的解释方差与无法解释的方差之比,使用 F 检验和 p 值。 这仅对回归问题有用。
  • f_classif对象是 ANOVA F 测试,可用于处理分类问题。
  • Chi2对象是卡方检验,适用于目标是分类且变量是计数或二进制数据(它们应该为正)的情况。

所有测试均具有得分和 p 值。 较高的分数和 p 值表示该变量是关联的,因此对目标有用。 测试未考虑变量为重复变量或与另一个变量高度相关的实例。 因此,最适合排除不那么有用的变量,而不是突出显示最有用的变量。

为了使过程自动化,还有一些可用的选择例程:

  • SelectKBest基于测试的分数,采用k个最佳变量。
  • 根据测试分数,SelectPercentile占执行变量的最高百分比。
  • 根据测试的 p 值,选择SelectFpr(假阳性率测试),SelectFdr(假发现率测试)和SelectFwe(家庭错误率程序)。

您还可以使用score_func参数使用GenericUnivariateSelect函数创建自己的选择过程,该参数将获取预测变量和目标,并根据您喜欢的统计检验返回得分和 p 值。

这些函数提供的最大优势是,它们提供了一系列方法来选择变量(拟合),然后自动将所有集合缩减(转换)为最佳变量。 在我们的示例中,我们使用.get_support()方法来从前 25% 的预测变量的Chi2f_classif测试中获取布尔索引。 然后,我们确定两个测试都选择的变量:

In: X, y = make_classification(n_samples=800, n_features=100, 
                               n_informative=25, 
                               n_redundant=0, random_state=101)

make_classification创建一个800案例和100特征的数据集。 重要变量占总数的四分之一:

In: from sklearn.feature_selection import SelectPercentile
    from sklearn.feature_selection import chi2, f_classif
    from sklearn.preprocessing import Binarizer, scale
    Xbin = Binarizer().fit_transform(scale(X))
    Selector_chi2 = SelectPercentile(chi2, percentile=25).fit(Xbin, y)
    Selector_f_classif = SelectPercentile(f_classif, 
                                          percentile=25).fit(X, y)
    chi_scores = Selector_chi2.get_support()
    f_classif_scores = Selector_f_classif.get_support()
    selected = chi_scores & f_classif_scores # use the bitwise and operator

如果使用卡方关联度量,如上例所示,则输入X必须为非负数:X必须包含布尔值或频率,因此,如果变量高于,则选择在归一化后进行二值化。 平均。

最终选择的变量包含一个布尔向量,指出了两个测试均已证明的 21 个预测变量。

作为基于经验的建议,通过使用不同的统计检验并保留较高百分比的变量,您可以通过排除信息量较小的变量来有效地利用单变量选择,从而简化预测变量集。

递归消除

单变量选择的问题是选择包含冗余信息的子集的可能性,而我们的兴趣是获得与我们的预测器算法一起使用的最小集合。 在这种情况下,递归消除可以帮助提供答案。

通过运行以下脚本,您将发现问题的再现,这是一个非常具有挑战性的问题,并且通常还会在不同案例和可变大小的数据集中遇到该问题:

In: from sklearn.model_selection import train_test_split
    X, y = make_classification(n_samples=100, n_features=100, 
                               n_informative=5,  
                               n_redundant=2, random_state=101)
    X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                        test_size=0.30, 
                                                        random_state=101)   

In: from sklearn.linear_model import LogisticRegression
    classifier = LogisticRegression(random_state=101)
    classifier.fit(X_train, y_train)
    print ('In-sample accuracy: %0.3f' % 
                              classifier.score(X_train, y_train))
    print ('Out-of-sample accuracy: %0.3f' % 
                              classifier.score(X_test, y_test))

Out: In-sample accuracy: 1.000
     Out-of-sample accuracy: 0.667 

我们有一个带有大量变量的小型数据集。 这是p > n类型的问题,其中p是变量数,n是观察数。

在这种情况下,数据集中肯定会有一些信息变量,但是其他变量提供的噪声可能会在为正确的特征分配正确的系数时使学习算法蒙蔽。 请记住,这种情况不是进行数据科学的最佳操作环境。 因此,最多只能期待平庸的结果。

这反映出较高的(在我们的情况下,是完美的)样本内准确率,当在样本外进行测试或使用交叉验证时,准确率会急剧下降。

在这种情况下,提供学习算法,关于评分/损失函数和交叉验证过程的指令RFECV类,开始对所有变量拟合初始模型并基于交叉验证计算分数。 在这一点上,RFECV开始修剪变量,直到达到交叉验证得分开始降低的一组变量为止(而通过修剪,得分应该保持稳定或增加):

In: from sklearn.feature_selection import RFECV
    selector = RFECV(estimator=classifier, step=1, cv=10, 
                     scoring='accuracy')
    selector.fit(X_train, y_train)
    print('Optimal number of features : %d' % selector.n_features_)    

Out: Optimal number of features : 4

在我们的示例中,RFECV100变量中只选择了其中四个。 在转换训练和测试集之后,我们可以在测试集上检查结果,以反映变量修剪:

In: X_train_s = selector.transform(X_train)
    X_test_s = selector.transform(X_test)
    classifier.fit(X_train_s, y_train)
    print ('Out-of-sample accuracy: %0.3f' % 
classifier.score(X_test_s, y_test)) Out: Out-of-sample accuracy: 0.900

通常,当您发现训练结果(基于交叉验证,而不是样本中得分)与样本外结果之间存在较大差异时,递归选择可以通过以下方法帮助您从学习算法中获得更好的表现: 指出一些最重要的变量。

稳定性和基于 L1 的选择

尽管有效,但递归消除实际上是一种逐步算法,其选择基于单个求值序列。 修剪时,它会选择某些选项,可能会排除许多其他选项。 这是将特别具有挑战性和耗时的问题(例如在可能的集合中进行详尽搜索)减少为更易于管理的一个好方法。 无论如何,还有另一种解决问题的方法,就是联合使用所有手头的变量。 一些算法使用正则化来限制系数的权重,从而防止过拟合和最相关变量的选择而不会失去预测能力。 特别是,正则化 L1(套索)在创建变量系数的稀疏选择方面众所周知,因为它会根据设置的正则化强度将许多变量推到 0 值。

一个示例将阐明逻辑回归分类器和我们用于递归消除的综合数据集的用法。

顺便说一句,linear_model.Lasso将计算出 L1 正则化以进行回归,而linear_model.LogisticRegressionsvm.LinearSVC则将其分类:

In: from sklearn.svm import LinearSVC
    classifier = LogisticRegression(C=0.1, penalty='l1', random_state=101)
    classifier.fit(X_train, y_train)
    print ('Out-of-sample accuracy: %0.3f' % 
                                    classifier.score(X_test, y_test)) Out: Out-of-sample accuracy: 0.933

样本外精度比使用贪婪方法获得的精度更高。 秘密是在初始化LogisticRegression类时分配的penalty='l1'C值。 由于C是基于 L1 的选择的主要成分,因此正确选择它非常重要。 这可以通过使用网格搜索和交叉验证来完成,但是通过正则化获得变量选择的方法更简单,更有效:稳定性选择。

稳定性选择即使在默认值下也可以成功使用 L1 正则化(尽管您可能需要更改它们才能改善结果),因为它通过随机选择训练数据集中的一部分来二次采样(即通过使用来重新计算正则化过程多次)来验证其结果。

结果不包括通常将其系数估计为零的所有变量。 仅当变量的系数为非零时,该变量才被认为对数据集和特征集变化稳定,这是要包含在模型中的重要内容(因此,其名称为“稳定性选择”)。

让我们通过实现选择方法(通过使用我们之前使用的数据集)进行测试:

In: from sklearn.linear_model import RandomizedLogisticRegression
    selector = RandomizedLogisticRegression(n_resampling=300, 
                                            random_state=101)
    selector.fit(X_train, y_train)
    print ('Variables selected: %i' % sum(selector.get_support()!=0))
    X_train_s = selector.transform(X_train)
    X_test_s = selector.transform(X_test)
    classifier.fit(X_train_s, y_train)
    print ('Out-of-sample accuracy: %0.3f' % 
           classifier.score(X_test_s, y_test)) 

Out: Variables selected: 3
     Out-of-sample accuracy: 0.933

实际上,仅通过使用RandomizedLogisticRegression类的默认参数,我们获得了与基于 L1 的选择相似的结果。

该算法工作正常。 它是可靠的并且开箱即用(没有参数可调整,除非您想降低C值以加快速度)。 我们只是建议您将n_resampling参数设置为较大的数目,以便您的计算机可以在合理的时间内处理稳定性选择。

如果要对回归问题求助于相同的算法,则应改用RandomizedLasso类。 让我们看看如何使用它。 首先,我们创建一个足以解决回归问题的数据集。 为简单起见,我们将使用 100 个样本,10 个特征的观察矩阵; 信息特征的数量为4

然后,我们可以让RandomizezLasso通过打印得分来找出最重要的特征(信息量最大的特征)。 请注意,所得分数是浮点数:

In: from sklearn.linear_model import RandomizedLasso
    from sklearn.datasets import make_regression
    X, y = make_regression(n_samples=100, n_features=10, 
                           n_informative=4, 
                           random_state=101) 
    rlasso = RandomizedLasso()
    rlasso.fit(X, y)
    list(enumerate(rlasso.scores_))

Out: [(0, 1.0),
      (1, 0.0),
      (3, 0.0),
      (4, 0.0),
      (5, 1.0),
      (6, 0.0),
      (7, 0.0),
      (8, 1.0),
      (9, 0.0)]

不出所料,权重非零的特征数为4。 选择它们,因为它们是进行任何进一步分析的最有用的信息。 也就是说,演示该方法的有效性,您可以在大多数特征选择情况下安全地应用该方法,以便快速选择适用于逻辑或线性回归模型以及其他线性模型的有用特征。

在管道中包装所有内容

作为最后一个主题,我们将讨论如何将到目前为止已经看到的转换和选择操作包装到一个命令中,该命令将把您的数据从源到机器学习算法。

将所有数据操作包装到一个命令中可以带来一些好处:

  • 因为流水线迫使您依赖函数进行操作(每个步骤都是一个函数),所以您的代码变得清晰且逻辑上更合理。
  • 您以与训练数据完全相同的方式对待测试数据,而无需重复代码或过程中可能出现任何错误。
  • 您可以轻松地在已设计的所有数据管道上网格搜索最佳参数,而不仅仅是在机器学习超参数上。

我们根据需要构建的数据流来区分两种包装器:串行或并行。

串行处理意味着您的转换步骤彼此依赖,因此必须按一定顺序执行。 对于串行处理,Scikit-learn 提供了Pipeline类,可以在pipeline模块中找到该类。

另一方面,并​​行处理意味着您所有的转换都仅取自相同数据,并且可以通过单独的过程轻松执行,这些过程的结果将在最后收集在一起。 Scikit-learn 还具有用于并行处理的类FeatureUnion,该类再次位于pipeline模块中。 FeatureUnion有趣的方面是它也可以并行化任何串行管线。

将特征组合在一起并链接转换

弄清楚FeatureUnionPipeline如何工作的最佳方法是什么? 只要回想一下 Scikit-learn API 的工作方式:首先,实例化一个类,然后将其拟合到一些数据,然后根据先前的拟合来转换相同的数据(或某些不同的数据)。 您只需提供包含步骤名称和要执行的命令的元组来指示管道,而不是与脚本一起执行。 根据顺序,这些操作将由您的 Python 线程执行或分配给多个处理器上的不同线程。

在我们的示例中,我们试图复制我们之前的示例,通过稳定性选择来构建逻辑回归分类器。 首先,我们在此基础上添加一些无监督的学习和特征创建。 我们首先通过创建训练和测试数据集来设置问题:

In: import numpy as np 
    from sklearn.model_selection import train_test_split 
    from sklearn.datasets import make_classification 
    from sklearn.linear_model import LogisticRegression 
    from sklearn.pipeline import Pipeline 
    from sklearn.pipeline import FeatureUnion 
    X, y = make_classification(n_samples=100, n_features=100, 
                               n_informative=5,  
                               n_redundant=2, random_state=101) 
    X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                        test_size=0.30, 
                                                        random_state=101) 
    classifier = LogisticRegression(C=0.1, penalty='l1', random_state=101) 

这样做之后,我们指示并行执行 PCA,KernelPCA和两个自定义转换器-一个仅按原样传递特征,而另一个按逆向传递。 您可以期望transformer_list中的每个元素都适合,应用转换以及所有结果按列堆叠在一起,但是仅当执行transform方法时(这是一个懒惰的执行;定义FeatureUnion不会触发任何执行)。

您还会发现使用make_pipelinemake_union命令获得相同结果很有用。 实际上,这些命令可以生成FeatureUnionPipeline类,可以将它们设置为输出。 值得一提的是,它们不需要您命名步骤,因为该功能将自动完成命名:

In: from sklearn.decomposition import PCA 
    from sklearn.decomposition import KernelPCA 
    from sklearn.preprocessing import FunctionTransformer 

    def identity(x): 
        return x 

    def inverse(x): 
        return 1.0 / x 

    parallel = FeatureUnion(transformer_list=[ 
               ('pca', PCA()), 
               ('kernelpca', KernelPCA()), 
               ('inverse', FunctionTransformer(inverse)), 
               ('original',FunctionTransformer(identity))], n_jobs=1) 

请注意,我们已经将n_jobs设置为1,从而完全避免了多重处理。 这是因为负责在 Scikit-learn 上进行多核并行处理的joblib包无法与 Windows 上运行的 Jupyter 笔记本上的自定义函数一起正常使用。 如果您使用的是 macOS 或 Linux,则可以安全地将n_jobs设置为多个工作程序,或将问题上的所有多核资源都设置为(-1)。 但是,在 Windows 上运行时,除非您没有使用自定义函数而是从包中选择它们,或者您在将__name__变量设置为__main__的脚本中运行代码,否则肯定会遇到一些问题。 在本章的“构建自定义评分函数”部分的结尾,我们已经在技术上更详细地讨论了这个问题。 请同时参阅该部分提示中的建议,以获取有关该问题的更多见解。

定义并行操作后,我们可以继续准备完整的管道:

In: from sklearn.preprocessing import RobustScaler 
    from sklearn.linear_model import RandomizedLogisticRegression 
    from sklearn.feature_selection import RFECV 
    selector = RandomizedLogisticRegression(n_resampling=300, 
                                            random_state=101, 
                                            n_jobs=1) 
    pipeline = Pipeline(steps=[('parallel_transformations', parallel), 
                               ('random_selection', selector), 
                               ('logistic_reg', classifier)]) 

拥有完整的转换和学习流水线的一大优势是可以控制其所有参数。 我们可以在管道上测试网格搜索,以便找到超参数的最佳配置:

In: from sklearn import model_selection 
    search_dict = {'logistic_reg__C':[10,1,0.1], 'logistic_reg__penalty':  
                   ['l1','l2']} 
    search_func = model_selection.GridSearchCV(estimator=pipeline,  
    param_grid =search_dict, scoring='accuracy', n_jobs=1,  
    iid=False, refit=True, cv=10) 
    search_func.fit(X_train,y_train) 
    print (search_func.best_estimator_) 
    print (search_func.best_params_) 
    print (search_func.best_score_) 

在定义参数网格搜索时,可以通过以下方式引用管道的不同部分:编写管道名称,添加两个下划线以及要调整的参数名称。 例如,对逻辑回归的C超参数起作用,需要将其命名为'logistic_reg__C'。 如果参数嵌套在多个管道中,则只需将它们全部命名,并用双下划线分隔,就好像您导航到磁盘目录一样。

由于使用双下划线来构造管道的步骤和超参数的层次结构,因此在命名管道的步骤时不能使用它。

作为最后的步骤,我们仅使用结果搜索对测试集进行预测。 完成此操作后,Python 将执行完整的管道,并使用网格搜索设置的超参数,并为您提供结果。 您不必担心将在训练上完成的工作复制到测试集中; 管道中的一组指令将始终确保数据处理操作的一致性和可重复性:

In: from sklearn.metrics import classification_report 
    print (classification_report(y_test, search_func.predict(X_test)))  

Out:              precision    recall  f1-score   support 

               0       0.94      0.94      0.94        17 
               1       0.92      0.92      0.92        13  

     avg / total       0.93      0.93      0.93        30 

构建自定义转换函数

就像您将注意到的那样,在我们的示例中,我们使用了两个自定义转换函数,一个标识和一个逆函数,以使原始特征与转换后的特征保持一致并使特征逆。 自定义转换可以帮助您处理针对您的问题的特定解决方案,并且您还将发现它们很有用,因为它们可以通过过滤不必要或错误的值来充当过滤器。

您可以通过应用sklearn.preprocessing中的FunctionTransformer函数来创建自定义转换,该函数使用fittransform方法将任何函数转换为 Scikit-learn 类。 从头开始创建转换可能有助于您清楚其工作方式。

首先,您必须创建一个类。 让我们看一个过滤某些列的示例,该列先前是您从数据集中定义的:

In: from sklearn.base import BaseEstimator, TransformerMixin

 class filtering(BaseEstimator, TransformerMixin): 
        def __init__(self, columns): 
            self.columns = columns 

        def fit(self, X, y=None): 
            return self 

        def transform(self, X): 
            if len(self.columns) == 0: 
                return X 
            else: 
                return X[:,self.columns] 

使用__init__方法,您可以定义参数以实例化该类。 在这种情况下,您只需记录一个列表,其中包含要过滤的列的位置。 然后,您必须为该类准备fittransform方法。

在我们的示例中,fit方法仅返回自身。 在不同情况下,使用fit方法可能很有用,以便跟踪稍后要应用于测试集的训练集的特征(例如,特征的均值和方差, 最大值和最小值,依此类推)。

您想对数据执行的实际操作是在transform方法中执行的。

您可能还记得,由于 Scikit-learn 在内部使用 NumPy 数组进行操作,因此将转换后的数据视为 NumPy 数组非常重要。

定义了类后,可以根据需要将其包装在PipelineFeatureUnion中。 在我们的示例中,我们只是通过选择训练集的前五个特征并对其进行 PCA 转换来创建管道:

In: ff = filtering([1,2,3])
 ff.fit_transform(X_train)

Out: array([[ 0.78503915,  0.84999568, -0.63974955], 
            [-2.4481912 , -0.38522917, -0.14586868], 
            [-0.6506899 ,  1.71846072, -1.14010846], 
            ... 

总结

在本章中,我们通过应用许多高级数据操作(从 EDA 和特征创建到降维和离群值检测)从数据中提取了有意义的含义。

更重要的是,我们借助许多示例开始开发我们的数据管道。 这是通过将训练/交叉验证/测试设置封装到我们的假设中来实现的,该假设以各种活动表示-从数据选择和转换到学习算法及其最佳超参数的选择。

在下一章中,我们将深入研究 Scikit-learn 包提供的主要机器学习算法,例如线性模型,支持向量机,树的集合以及无监督的聚类技术等。

四、机器学习

在说明了数据科学项目中的所有数据准备步骤之后,我们终于到达了学习阶段,在此阶段应用了学习算法。 为了向您介绍 Scikit-learn 和其他 Python 包中现成的最有效的机器学习工具,我们准备了所有主要算法系列的简要介绍。 我们使用超参数示例和技巧来完成此过程,以确保获得最佳结果。

在本章中,我们将介绍以下主题:

  • 线性和逻辑回归
  • 朴素贝叶斯
  • K 最近邻居(KNN)
  • 支持向量机(SVM)
  • 解决方案包
  • 装袋和提升分类器
  • 基于随机梯度的大数据分类与回归
  • 使用 K 均值和 DBSCAN 的无监督聚类

神经网络和深度学习将在下一章中介绍。

准备工具和数据集

如前几章所述,用于机器学习的 Python 包是 Scikit-learn。 在本章中,我们还将使用 XGboost,LightGBM 和 Catboost:您将在相关部分中找到说明。

法国计算机科学与自动化研究所 Inria 开发的 Scikit-learn 的使用动机是多种多样的。 在这一点上,有必要提及使用 Scikit-learn 成功实现数据科学项目的最重要原因:

  • 跨模型的一致 API(fitpredicttransformpartial_fit)自然有助于正确实现对以 NumPy 数组组织的数据进行处理的数据科学过程
  • 完整的经过测试的可扩展经典机器学习模型供您选择,提供了许多核心实现,用于从 RAM 内存中无法容纳的数据中学习
  • 一群杰出的贡献者(Andreas Mueller,Olivier Grisel,Fabian Pedregosa,Gael Varoquaux,Gilles Loupe,Peter Prettenhofer 等)为我们带来了稳定的发展,并增加了许多新产品。
  • 包含许多示例的大量文档,可使用help命令在线或在线查阅

在本章中,我们将 Scikit-learn 的机器学习算法应用于一些示例数据集。 我们将分解非常有启发性但太常用的鸢尾和波士顿数据集,以证明将机器学习应用于更现实的数据集。 我们从以下示例中选择了有趣的示例:

为了让您拥有这样的数据集,而不必每次都要测试示例时都不必依赖互联网连接,建议您下载它们并将其存储在硬盘上。 因此,我们准备了一些脚本来自动下载数据集,这些脚本将精确地放置在您使用 Python 的目录中,从而使数据访问更加容易:

In: import pickle
    import urllib
    import ssl
    ssl._create_default_https_context = ssl._create_unverified_context
    from sklearn.datasets import fetch_mldata
    from sklearn.datasets import load_svmlight_file
    from sklearn.datasets import fetch_covtype
    from sklearn.datasets import fetch_20newsgroups
    mnist = fetch_mldata("MNIST original")
    pickle.dump(mnist, open("mnist.pickle", "wb"))
    target_page = 
'http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary/ijcnn1.bz2'
    with urllib.request.urlopen(target_page) as response:
 with open('ijcnn1.bz2','wb') as W: W.write(response.read())    target_page =
'http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/regression/cadata'
    cadata = load_svmlight_file(urllib.request.urlopen(target_page))
    pickle.dump(cadata, open("cadata.pickle", "wb")) 

    covertype_dataset = fetch_covtype(random_state=101, shuffle=True)
    pickle.dump(covertype_dataset, open(
                                   "covertype_dataset.pickle", "wb"))   

    newsgroups_dataset = fetch_20newsgroups(shuffle=True, 
              remove=('headers', 'footers', 'quotes'), random_state=6)

    pickle.dump(newsgroups_dataset, open(
                                    "newsgroups_dataset.pickle", "wb"))

如果下载程序的任何部分对您不起作用,我们将为您直接下载数据集。 获取压缩的 zip 包后,您要做的就是将其数据解压缩到当前工作的 Python 目录中,您可以通过使用以下命令在 Python 界面(Jupyter 笔记本电脑或任何 Python IDE)上运行来找到该目录:

In: import os
    print ("Current directory is: "%s"" % (os.getcwd()))

您可以使用其他开源代码测试本书中的所有算法,并可以随意使用数据集。 Google 提供了一个搜索引擎,可以在这个页面上为您的实验寻找合适的数据:您只需要询问搜索引擎想要的内容即可。

线性和逻辑回归

线性回归和逻辑回归是分别可用于线性预测目标值或目标类别的两种方法。 让我们从线性回归预测目标值的示例开始。

在本节中,我们将再次使用波士顿数据集,其中包含 506 个样本,13 个特征(所有实数)和一个(实数)数字目标(这使其非常适合回归问题)。 我们将使用训练/测试拆分交叉验证来将我们的数据集分为两部分,以测试我们的方法(在本示例中,我们的数据集的 80% 用于训练,而 20% 的数据在测试集中):

In: from sklearn.datasets import load_boston
    boston = load_boston()
    from sklearn.model_selection import train_test_split
    X_train, X_test, Y_train, Y_test = train_test_split(boston.data, 
                                  boston.target, test_size=0.2, random_state=0) 

现在已加载数据集,并已创建训练/测试对。 在接下来的几个步骤中,我们将训练回归变量并将其拟合到训练集中,并预测测试数据集中的目标变量。 然后,我们将使用 MAE 分数来衡量回归任务的准确率(如第 3 章,“数据管道”中所述)。 至于评分函数,我们决定了平均绝对误差,以与误差本身的大小成正比地惩罚误差(使用更常见的均方误差会更多地强调较大的误差,因为误差是平方的):

In: from sklearn.linear_model import LinearRegression
    regr = LinearRegression()
    regr.fit(X_train, Y_train)
    Y_pred = regr.predict(X_test)

    from sklearn.metrics import mean_absolute_error
    print ("MAE", mean_absolute_error(Y_test, Y_pred)) Out: MAE 3.84281058945

伟大的! 我们以最简单的方式实现了我们的目标。 现在让我们看一下训练系统所需的时间:

In: %timeit regr.fit(X_train, y_train) Out: 544 µs ± 37.4 µs per loop 
     (mean ± std. dev. of 7 runs, 1000 loops each)

那真的很快! 结果当然不是那么好(如果您在本书前面的第 1 章,“第一步”)。 但是,线性回归在表现与训练速度和简单性之间提供了很好的权衡。 现在,让我们看一下该算法的内幕。 为什么这么快却不那么准确? 答案在某种程度上是可以预期的-这是因为它是一种非常简单的线性方法。

让我们简要地对此技术进行数学解释。 让我们将X(i)命名为第i个样本(实际上是具有数字特征的行向量),并将Y(i)作为目标。 线性回归的目标是找到一个良好的权重(列)向量W,该向量最适合于与观察向量相乘时近似目标值,即X(i) * W ≈ Y(i)(请注意,这是点积)。W应该相同,并且对每个观察结果都是最佳的。 因此,解决以下等式变得容易:

W可以借助矩阵求逆(或更可能是伪求逆,这是一种计算有效的方法)和点积的帮助容易地找到。 这就是线性回归如此之快的原因。 请注意,这只是一个简单的解释-实际方法添加了另一个虚拟特征来补偿过程的偏差。 但是,这并没有很大程度地改变回归算法的复杂性。

我们现在进行逻辑回归。 尽管顾名思义,它是一个分类器,而不是回归器。 它只能用于仅处理两个类的分类问题(二分类)。 通常,目标标签是布尔值; 也就是说,它们的值为True/False或 0/1(指示是否存在预期结果)。 在我们的示例中,我们继续使用相同的数据集。 目标是猜测房屋价值是高于还是低于我们感兴趣的阈值的平均值。本质上,我们从回归问题转向二元分类问题,因为现在我们的目标是猜测一个示例成为一个小组的一部分的可能性有多大。 我们开始使用以下命令来准备数据集:

In: import numpy as np
    avg_price_house = np.average(boston.target)
    high_priced_idx = (Y_train >= avg_price_house)
    Y_train[high_priced_idx] = 1
    Y_train[np.logical_not(high_priced_idx)] = 0
    Y_train = Y_train.astype(np.int8)
    high_priced_idx = (Y_test >= avg_price_house)
    Y_test[high_priced_idx] = 1
    Y_test[np.logical_not(high_priced_idx)] = 0
    Y_test = Y_test.astype(np.int8)

现在我们将训练并应用分类器。 要衡量其表现,我们只需打印分类报告:

In: from sklearn.linear_model import LogisticRegression
    clf = LogisticRegression()
    clf.fit(X_train, Y_train)
    Y_pred = clf.predict(X_test)
    from sklearn.metrics import classification_report
    print (classification_report(Y_test, Y_pred))

 Out:
 precision    recall  f1-score   support

          0       0.81      0.92      0.86        61
          1       0.85      0.68      0.76        41

avg / total       0.83      0.82      0.82       102

根据LogisticRegression分类器的优化过程,此命令的输出可以在您的计算机上更改(未设置种子以提高结果的可复制性)。

precisionrecall值超过80百分比。 对于一个非常简单的方法来说,这已经是一个很好的结果。 训练速度也令人印象深刻。 感谢 Jupyter 笔记本,我们可以在表现和速度方面将算法与更高级的分类器进行比较:

In: %timeit clf.fit(X_train, y_train)

Out: 2.75 ms ± 120 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

什么是逻辑回归的内幕? 人们可以想象的最简单的分类器(除均值外)是线性回归变量,后跟一个硬阈值:

y_pred[i] = sign(X[i] * W)

在此,如果a大于或等于零,则sign(a) = 1,否则为 0。

为了降低阈值的难度并预测属于某个类别的可能性,逻辑回归求助于logit函数。 它的输出是一个(0 到 1)实数(只能通过舍入获得 0.0 和 1.0;否则, logit函数将趋向于它们),这表示观察值属于 1 类的可能性。 使用公式,结果如下:

在上面的公式中,您具有:logistic(α) = exp(α) / (1 + exp(α))

为什么logistic函数而不是其他函数? 好吧,因为它在大多数实际情况下都很好用。 在其余情况下,如果您对其结果不完全满意,则可能需要尝试其他一些非线性函数(不过,合适的函数种类有限)。

朴素贝叶斯

朴素贝叶斯是用于概率二进制和多类分类的非常常见的分类器。 给定特征向量,它利用贝叶斯规则来预测每个类别的概率。 它通常用于文本分类,因为它对大量数据(即具有许多特征的数据集)非常有效,并且具有一致的先验概率,可有效处理维数问题的诅咒。

朴素贝叶斯分类器分为三种: 他们每个人都对这些特征有很强的假设(假设)。 如果要处理真实/连续数据,则高斯朴素贝叶斯分类器会假定特征是从高斯过程生成的(也就是说,它们是正态分布的)。 或者,如果要处理事件模型,其中可以使用多项式分布来建模事件(在这种情况下,特征是计数器或频率),则需要使用“多项朴素贝叶斯”分类器。 最后,如果您所有的特征都是独立且布尔的,并且可以安全地假设它们是伯努利过程的结果,则可以使用伯努利朴素贝叶斯分类器。

现在让我们尝试高斯朴素贝叶斯分类器的应用示例。 此外,本章结尾给出了文本分类的示例。 您可以通过简单地将示例的 SGDClassifier 替换为 MultinomialNB 来与朴素贝叶斯一起测试它。

在以下示例中,假设特征为高斯特征,我们将使用鸢尾花数据集:

In: from sklearn import datasets
    iris = datasets.load_iris()
    from sklearn.model_selection import train_test_split
    X_train, X_test, Y_train, Y_test = train_test_split(iris.data, 
iris.target, test_size=0.2, random_state=0) In: from sklearn.naive_bayes import GaussianNB
    clf = GaussianNB()
    clf.fit(X_train, Y_train)
    Y_pred = clf.predict(X_test) In: from sklearn.metrics import classification_report
    print (classification_report(Y_test, Y_pred)) Out:
             precision    recall  f1-score   support

          0       1.00      1.00      1.00        11
          1       0.93      1.00      0.96        13
          2       1.00      0.83      0.91         6

avg / total       0.97      0.97      0.97        30 In: %timeit clf.fit(X_train, y_train) 

Out: 685 µs ± 9.86 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

生成的模型似乎具有良好的表现和较高的训练速度,尽管我们不要忘记我们的数据集也很小。 现在,让我们看看它如何处理另一个多类问题。

分类器的目的是预测特征向量属于Ck类的可能性。 在该示例中,存在三个类别(setosaversicolorvirginica)。 因此,我们需要计算所有类别的成员资格概率; 为了使说明简单,我们将其命名为123。 因此,对于观察i,朴素贝叶斯分类器的目标是计算以下内容:

在此, X(i)是特征的向量(在示例中,它由四个实数组成),其成分为[X(i, 0), X(i, 1), X(i, 2), X(i, 3)]

使用贝叶斯规则,它将变为以下内容:

我们可以描述相同的公式,如下所示:

后验概率是类别的先验概率乘以可能性,然后除以证据。

从概率论出发,我们知道联合概率可以表示为以下形式(简化问题):

然后,乘法的第二个因子可以重写如下(条件概率):

然后,您可以使用条件概率定义来表示乘法的第二个成员。 最后,您将得到一个很长的乘法:

朴素的假设是,当与每个类别相关时,每个特征都被认为有条件地独立于其他特征。 因此,概率可以简单地相乘。 公式如下:

因此,总结一下数学,以选择最佳类别,使用以下公式:

这是一种简化,因为除去了证据概率(贝叶斯规则的分母),因为所有类别的事件概率都相同。

从前面的公式中,您可以了解学习阶段为何如此之快的原因,因为它只是对事件的计数。

请注意,对于此分类器,不存在相应的回归变量,但是您可以通过对它进行分类来实现对连续目标变量的建模,即将其转换为类(例如,我们房屋价格的低,平均和高值) 问题)。

K 最近邻居

K 最近邻居或简称为 KNN,属于基于实例的学习类别,也称为惰性分类器。 这是最简单的分类方法之一,因为在我们要分类的情况下,仅通过查看训练集中的 K 个最接近的示例(按照欧几里得距离或某种其他距离)来完成分类。 然后,在给出 K 个相似示例的情况下,选择最受欢迎的目标(多数投票)作为分类标签。 该算法必须使用两个参数:邻域基数(K)和评估相似度的度量(尽管最常用的是欧几里德距离或 L2,并且是大多数实现的默认参数)。

让我们看一个例子。 我们将使用大型数据集MNIST手写数字。 稍后我们将解释为什么我们决定在示例中使用此数据集。 我们打算仅使用其中的一小部分(1,000 个样本)来保持合理的计算时间,并且我们将观察值进行混洗以获得更好的结果(尽管如此,您的最终输出可能与我们的最终输出略有不同):

In: from sklearn.utils import shuffle
    from sklearn.datasets import fetch_mldata
    from sklearn.model_selection import train_test_split

    import pickle
    mnist = pickle.load(open( "mnist.pickle", "rb" ))
    mnist.data, mnist.target = shuffle(mnist.data, mnist.target)
 # We reduce the dataset size, otherwise it'll take too much time to run    mnist.data = mnist.data[:1000]
    mnist.target = mnist.target[:1000]
    X_train, X_test, y_train, y_test = train_test_split(mnist.data,
                               mnist.target, test_size=0.8, random_state=0)  In: from sklearn.neighbors import KNeighborsClassifier    # KNN: K=10, default measure of distance (euclidean)
    clf = KNeighborsClassifier(3)
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)  In: from sklearn.metrics import classification_report    print (classification_report(y_test, y_pred)) Out:
              precision    recall  f1-score   support

        0.0       0.79      0.91      0.85        82
        1.0       0.62      0.98      0.76        86
        2.0       0.88      0.68      0.76        77
        3.0       0.71      0.83      0.77        69
        4.0       0.68      0.88      0.77        91
        5.0       0.69      0.66      0.67        56
        6.0       0.93      0.86      0.89        90
        7.0       0.91      0.85      0.88       102
        8.0       0.91      0.41      0.57        73
        9.0       0.79      0.50      0.61        74

avg / total       0.80      0.77      0.76       800

该数据集的表现不是很高。 但是,请考虑一下分类器必须处理十个不同的类。 现在,让我们检查分类器进行训练和预测所需的时间:

In: %timeit clf.fit(X_train, y_train)
Out: 1.18 ms ± 119 µs per loop (mean ± std. dev. of 7 runs, 
     1000 loops each)
In: %timeit clf.predict(X_test)
Out: 179 ms ± 1.68 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

训练速度异常出色。 现在考虑算法。 训练阶段只是将数据复制到该算法以后将要使用的某种数据结构中,而没有别的(这就是它被称为惰性学习器的原因)。 相反,预测速度与您在训练步骤中拥有的样本数量以及构成它的特征数量(实际上就是元素的特征矩阵数量)有关。 在我们看到的所有其他算法中,预测速度与数据集中训练案例的数量无关。 总而言之,我们可以说 KNN 非常适合小型数据集,但绝对不是处理大数据时要使用的算法。

关于该分类算法的最后一句话-您还可以尝试类似的回归器KNeighborsRegressor,其工作方式相同。 它的算法几乎相同,除了预测值是邻域的 K 目标值的平均值。

非线性算法

支持向量机SVM)是一种强大的高级监督学习技术,用于分类和回归,可以自动拟合线性和非线性模型。

与其他机器学习算法相比,SVM 算法具有很多优势:

  • 它们可以处理大多数监督问题,例如回归,分类和异常检测(无论如何,它们实际上最擅长于二元分类)。
  • 它们可以很好地处理嘈杂的数据和异常值。 由于它们仅与某些特定示例(支持向量)一起使用,因此它们往往不太适合。
  • 尽管与其他机器学习算法一样,SVM 可以从降维和特征选择中获得好处,但它们与呈现更多特征而不是示例的数据集配合得很好。
  • 至于缺点,我们必须提到这些:
  • 它们仅提供估计值,而不提供概率,除非您通过普拉特定标进行了一些耗时且计算量大的概率校准
  • 它们与示例数量呈超线性比例关系(因此,它们无法处理非常大的数据集)

Scikit-learn 提供了一个基于 LIBSVM 的实现,LIBSVM 是一个完整的 SVM 分类和回归实现的库,而 LIBLINEAR 是一个可扩展的库,适用于大型数据集的线性分类,尤其是任何基于稀疏文本的数据集。 这两个库都是在国立台湾大学开发的,并且都使用 C++ 和 C API 编写,可以与其他语言接口。 这两个库都经过了广泛的测试(免费,它们已在其他开源机器学习工具包中使用),并且早已被证明既快速又可靠。 C API 很好地解释了它们在 Python Scikit-learn 下最佳运行的两个棘手需求:

  • LIBSVM 在运行时需要为核操作保留一些内存。 cache_size参数用于设置核缓存的大小,以兆字节为单位指定。 尽管默认值为 200,但建议根据可用资源将其提高到 500 或 1000。
  • 他们都期望 C 顺序的 NumPy ndarray或 SciPy sparse.csr_matrix(行优化的稀疏矩阵类型),最好是float64类型。 如果 Python 包装器以不同的数据结构接收它们,则它将必须以适当的格式复制数据,从而减慢训练过程并消耗更多的 RAM 内存。

LIBSVM 或 LIBLINEAR 都不提供能够处理大型数据集的实现。 SGDClassifierSGDRegressor是 Scikit-learn 类,即使数据太大而无法容纳到内存中,它们也可以在合理的计算时间内生成解决方案。 在以下有关处理大数据的段落中将讨论它们。

支持分类的 SVM

Scikit-learn 提供的 SVM 分类的实现如下所示:

目的超参数
sklearn.svm.SVC用于二进制和多类线性和核分类的 LIBSVM 实现C,核,度和伽马
sklearn.svm.NuSVC.SVC版本相同nu,核,度和gamma
sklearn.svm.OneClassSVM无监督检测异常值nu,核,度和gamma
sklearn.svm.LinearSVC基于 LIBLINEAR; 它是一个二进制和多类线性分类器罚款,损失和C

作为使用 SVM 进行分类的示例,我们将同时使用带有线性核和 RBF 核的 SVC(RBF 代表径向基函数,这是有效的非线性函数)。 取而代之的是,将LinearSVC用于表示大量观测值的复杂问题(由于三次复杂性的提高,标准 SVC 在处理 10,000 多个观测值时效果不佳;相反,LinearSVC可以线性缩放)。

对于我们的第一个分类示例(二进制),我们将使用 IJCNN'01 神经网络竞赛的数据集。 它是由 10 缸内燃机的物理系统产生的 50,000 个样本的时间序列。 我们的目标是二进制:正常发动机点火或不点火。 我们将使用本章开头的脚本从 LIBSVM 网站检索的数据集。 数据文件为 LIBSVM 格式,并由 Bzip2 压缩。 我们使用 Scikit-learn 的load_svmlight_file函数对其进行操作:

In: from sklearn.datasets import load_svmlight_file
    X_train, y_train = load_svmlight_file('ijcnn1.bz2')
    first_rows = 2500
    X_train, y_train = X_train[:first_rows,:], y_train[:first_rows]

为了举例说明,我们将观测值的数量从 25,000 限制为 2,500。 可用特征的数量为 22。此外,由于它已经与 SVM 要求兼容,并且已经在 0 到 1 之间范围内重新缩放了特征,因此我们不会对其进行预处理。

In: import numpy as np
    from sklearn.model_selection import cross_val_score
    from sklearn.svm import SVC
    hypothesis = SVC(kernel='rbf', random_state=101)
    scores = cross_val_score(hypothesis, X_train, y_train, 
                             cv=5, scoring='accuracy')
    print ("SVC with rbf kernel -> cross validation accuracy: \
            mean = %0.3f std = %0.3f" % (np.mean(scores), np.std(scores))) Out: SVC with rbf kernel -> cross validation accuracy: 
     mean = 0.910 std = 0.001

在我们的示例中,我们测试了带有 RBF 核的 SVC。 所有其他参数均保持默认值。 您可以尝试将first_rows修改为更大的值(最大 25,000),并验证算法扩展到观察数量增加的程度。 跟踪计算时间,您会注意到缩放比例不是线性的。 也就是说,计算时间将与数据大小成比例增加。 关于 SVM 可伸缩性,有趣的是,看到这种算法在遇到多类问题和大量情况时的行为。 我们将使用的 Covertype 数据集以美国 30x30 米的大片森林为例。 收集与它们有关的数据以预测每个斑块(覆盖类型)的优势树种。 这是一个多类分类问题(可以预测为七个covertypes)。 每个样本都具有 54 个特征,并且有超过 580,000 个示例(但出于性能原因,我们将仅处理 25,000 个此类示例)。 而且,类是不平衡的,大多数示例都有两种树。

这是可用于加载先前准备的数据集的脚本:

In: import pickle
    covertype_dataset = pickle.load(open("covertype_dataset.pickle", "rb"))
    covertype_X = covertype_dataset.data[:25000,:]
    covertype_y = covertype_dataset.target[:25000] -1  

使用此脚本,您可以了解要预测的示例,特征和目标:

In: import numpy as np
    covertypes = ['Spruce/Fir', 'Lodgepole Pine', 'Ponderosa Pine', 
                  'Cottonwood/Willow', 'Aspen', 'Douglas-fir', 'Krummholz']
    print ('original dataset:', covertype_dataset.data.shape)
    print ('sub-sample:', covertype_X.shape)
    print('target freq:', list(zip(covertypes,np.bincount(covertype_y)))) Out: original dataset: (581012, 54)
     sub-sample: (25000, 54)
     target freq: [('Spruce/Fir', 9107), ('Lodgepole Pine', 12122), 
     ('Ponderosa Pine', 1583), ('Cottonwood/Willow', 120), ('Aspen', 412),   
     ('Douglas-fir', 779), ('Krummholz', 877)] 

假设我们认为,由于我们有七个类别,因此我们需要训练七个不同的分类器,重点是针对一个类别与另一个类别进行预测(在多类别问题中,LinearSVC的默认行为是“剩余”)。 这样,每个交叉验证测试都将有 175,000 个数据点(因此,如果cv = 3,则必须重复三遍)。 考虑到有 54 个变量,这对许多算法来说都是一个挑战,但是LinearSVC可以演示如何在合理的时间内处理它:

In: from sklearn.cross_validation import cross_val_score, StratifiedKFold
    from sklearn.svm import LinearSVC
    hypothesis = LinearSVC(dual=False, class_weight='balanced')
    cv_strata = StratifiedKFold(covertype_y, n_folds=3, 
                                shuffle=True, random_state=101)
    scores = cross_val_score(hypothesis, covertype_X, covertype_y, 
                             cv=cv_strata, scoring='accuracy')
    print ("LinearSVC -> cross validation accuracy: \
           mean = %0.3f std = %0.3f" % (np.mean(scores), np.std(scores))) Out: LinearSVC -> cross validation accuracy: mean = 0.645 std = 0.007  

得出的精度为0.65,这是一个很好的结果。 但是,它肯定会留下进一步改进的空间。 另一方面,这个问题似乎是一个非线性的问题,尽管将 SVC 与非线性核一起使用会导致观察过程很多,因此训练过程非常漫长。 在下面的示例中,我们将通过使用其他非线性算法来重现此问题,以检查是否可以提高LinearSVC获得的分数。

支持向量机回归

至于回归,下面显示了 Scikit-learn 提出的 SVM 算法:

目的超参数
sklearn.svm.SVRLIBSVM 实现回归C,核,度,伽玛和epsilon
sklearn.svm.NuSVR.SVR相同nuC,核,度和伽玛

为了提供回归示例,我们决定使用加利福尼亚州房屋的房地产价格数据集(与之前看到的波士​​顿房屋价格数据集略有不同的问题):

In: import pickle
    X_train, y_train = pickle.load(open( "cadata.pickle", "rb" ))
    from sklearn.preprocessing import scale
    first_rows = 2000
    X_train = scale(X_train[:first_rows,:].toarray())
    y_train = y_train[:first_rows]/10**4.0 

由于性能原因,将数据集中的案例减少为2,000。 对特征进行了缩放,以避免原始变量的不同比例产生影响。 此外,目标变量除以1,000以使其在千美元值中更具可读性:

In: import numpy as np
    from sklearn.cross_validation import cross_val_score
    from sklearn.svm import SVR
    hypothesis = SVR()
    scores = cross_val_score(hypothesis, X_train, y_train, cv=3, 
                             scoring='neg_mean_absolute_error')
    print ("SVR -> cross validation accuracy: mean = %0.3f \
    std = %0.3f" % (np.mean(scores), np.std(scores))) Out: SVR -> cross validation accuracy: mean = -4.618 std = 0.347 

选择的误差是平均绝对误差,它由sklearn类报告为负数(但实际上要解释为不带符号;负号只是 Scikit-learn 的内部函数使用的计算技巧) 。

调整 SVM

在我们开始处理超参数(根据实现通常是不同的参数集)之前,在使用 SVM 算法时有两个方面需要澄清。

首先是关于 SVM 对不同规模和大量变量的敏感性。 与基于线性组合的其他学习算法类似,具有不同比例的变量会使该算法被具有较大范围或方差的特征所控制。 而且,极高或极低的数目可能在学习算法的优化过程中引起问题。 建议以有限的时间间隔缩放所有数据,例如[0, +1],这是使用稀疏数组时的必要选择。 实际上,希望保留零个条目。 否则,数据将变得密集,从而消耗更多的内存。 您也可以将数据缩放到[-1, +1]间隔。 或者,您可以将它们标准化为零均值和单位方差。 您可以从预处理模块中使用MinMaxScalerStandardScaler工具类,方法是先将它们拟合到训练数据上,然后转换训练和测试集。

第二个方面是关于不平衡的阶级。 该算法倾向于偏爱频繁类。 除了重采样或下采样(将多数类减少为较小的相同数目)之外,一种解决方案是根据类的频率加权C惩罚参数(低值将对类进行更多的惩罚,高值将对类进行更多的惩罚) 。 对于不同的实现,有两种方法可以实现此目的。 首先,SVC 中有class_weight参数(可以将其设置为关键字balanced,或提供包含每个类的特定值的字典)。 然后,在SVCNuSVCSVRNuSVROneClassSVM.fit()方法中还有sample_weight参数(它需要一维数组作为输入,其中每个位置是指每个训练示例的权重)。

处理了比例和类的平衡之后,您可以使用 Sklearn 中model_selection模块中的GridSearchCV详尽搜索其他参数的最佳设置。 尽管 SVM 可以使用默认参数很好地工作,但是它们通常不是最佳的,并且您需要使用交叉验证来测试各种值组合以找到最佳参数。

根据其重要性,您必须设置以下参数:

  • C:惩罚值。 减小它会使余量更大,从而忽略了更多噪声,同时也使该模型更具通用性。 通常可以在np.logspace(-3, 3, 7)的范围内考虑最佳值。
  • kernel:可以将 SVM 的非线性主力设置为linearpolyrbfsigmoid或自定义核(供专家使用!)。 当然,最常用的是rbf
  • degree:它与kernel='poly'一起使用,表示多项式展开的维数。 相反,它被其他核忽略。 通常,将其值设置为 2 到 5 效果最佳。
  • gamma'rbf''poly''sigmoid'的系数。 高值倾向于以更好的方式拟合数据,但可能导致一些过拟合。 直观地,我们可以将伽马想象为单个示例对模型的影响。 较低的值使每个示例的影响都很大。 由于必须考虑许多点,因此 SVM 曲线倾向于采用受局部点影响较小的形状,其结果将是病态的轮廓曲线。 相反,较高的gamma值会使曲线更多地考虑点的局部排列方式。 许多小气泡会阐明结果,这些小气泡通常会表示局部点所产生的影响。 该超参数的建议网格搜索范围为np.logspace(-3, 3, 7)
  • nu:对于使用nuSVRnuSVC进行回归和分类,此参数将对没有可靠分类的训练点进行近似估计,即,错误分类的点和边界内或边界上的正确点。 它应该在[0, 1]的范围内,因为它是相对于您的训练集的比例。 最后,它以C的形式发挥作用,高比例扩大了边距。
  • epsilon:此参数通过定义epsilon大范围来指定 SVR 将接受多少误差,在该范围内,与该点的真实值无关的惩罚。 建议的搜索范围是np.insert(np.logspace(-4, 2, 7),0,[0])
  • penaltylossdual:对于LinearSVC,这些参数接受('l1', 'squared_hinge', False), ('l2', 'hinge', True), ('l2', ' squared_hinge', True)('l2', 'squared_hinge', False)组合。 ('l2', 'hinge', True)组合类似于SVC(kernel = 'linear')学习器。

例如,我们将再次加载 IJCNN'01 数据集,并通过寻找更好的度数Cgamma值来尝试提高初始精度 0.91。 为了节省时间,我们将使用RandomizedSearchCV类将精度提高到0.989(交叉验证估计值):

In: from sklearn.svm import SVC
    from sklearn.model_selection import RandomizedSearchCV
    X_train, y_train = load_svmlight_file('ijcnn1.bz2')
    first_rows = 2500
    X_train, y_train = X_train[:first_rows,:], y_train[:first_rows]
    hypothesis = SVC(kernel='rbf', random_state=101)
    search_dict = {'C': [0.01, 0.1, 1, 10, 100], 
                   'gamma': [0.1, 0.01, 0.001, 0.0001]}
    search_func = RandomizedSearchCV(estimator=hypothesis, 
                                     param_distributions=search_dict,
                                     n_iter=10, scoring='accuracy', 
 n_jobs=-1, iid=True, refit=True, 
                                     cv=5, random_state=101)    search_func.fit(X_train, y_train)
    print ('Best parameters %s' % search_func.best_params_)
    print ('Cross validation accuracy: mean = %0.3f' %  
            search_func.best_score_) Out: Best parameters {'C': 100, 'gamma': 0.1} 
 Cross validation accuracy: mean = 0.989

策略集

到目前为止,我们已经看到越来越复杂的单一学习算法。 集成表示一种有效的替代方法,因为它们通过组合或链接基于不同数据样本和算法设置的模型结果来达到更好的预测准确率。 集成策略将自己分为两个分支。 根据使用的方法,他们通过以下方式将预测组合在一起:

  • 平均算法:这些算法通过平均各种并行估计量的结果来进行预测。 估计量的变化可进一步划分为四个系列:粘贴,装袋,子空间和补丁。
  • 增强算法:这些算法通过使用顺序聚合估计量的加权平均值进行预测。

在研究分类和回归的一些示例之前,我们将为您提供重新加载 Covertype 数据集的必要步骤,Covertype 数据集是在处理线性 SVC 之前我们开始探讨的多类分类问题:

In: import pickle
    covertype_dataset = pickle.load(open("covertype_dataset.pickle", "rb"))
    print (covertype_dataset.DESCR)
    covertype_X = covertype_dataset.data[:15000,:]
    covertype_y = covertype_dataset.target[:15000]
    covertypes = ['Spruce/Fir', 'Lodgepole Pine', 'Ponderosa Pine', 
                  'Cottonwood/Willow', 'Aspen', 'Douglas-fir', 'Krummholz']

随机样本的粘贴

粘贴是我们将讨论的第一类平均集成。 在粘贴过程中,使用从数据中获取的小样本来构建一定数量的估计器(使用不替换的样本)。 最后,将结果汇总起来,通过对结果进行平均(在回归的情况下)或在处理分类时采用投票率最高的类别来获得估计值。 当处理非常大的数据时(例如无法容纳到内存中的情况),粘贴非常有用,因为它只允许处理计算机的可用 RAM 和计算资源可管理的那些数据部分。

作为一种方法,随机森林算法的创建者 Leo Breiman 首先设计了这种策略。 尽管可以通过使用可用的装袋算法(下段主题BaggingClassifierBaggingRegressor,并将其bootstrap设置为)轻松实现,但 Scikit-learn 包中没有使用粘贴的特定算法。 Falsemax_features设为 1.0。

弱分类器的装袋

装袋用于样本的方式与粘贴类似,但是可以替换。 同样,在理论上由 Leo Breiman 阐述,装袋是在特定的 Scikit-learn 类中进行回归的,而在分类中进行。 您只需要决定要用于训练的算法即可。 将其插入BaggingClassifierBaggingRegressor中以解决回归问题,并设置足够多的估计量(因此要设置大量的样本):

In: import numpy as np
    from sklearn.model_selection import cross_val_score
    from sklearn.ensemble import BaggingClassifier
    from sklearn.neighbors import KNeighborsClassifier
    hypothesis = BaggingClassifier(KNeighborsClassifier(n_neighbors=1), 
                                   max_samples=0.7, max_features=0.7,  
                                   n_estimators=100)
    scores = cross_val_score(hypothesis, covertype_X, covertype_y, cv=3, 
                                      scoring='accuracy', n_jobs=-1)
    print ("BaggingClassifier -> cross validation accuracy: mean = %0.3f 
    std = %0.3f" % (np.mean(scores), np.std(scores))) Out: BaggingClassifier -> cross validation accuracy: 
     mean = 0.795 std = 0.001  

预测器较弱是用于装袋的估计器的不错选择。 在分类或预测方面学习能力较弱的人,由于其简单性或估计的偏见,其执行效果不佳(刚好超过数据问题的机会基准)。 朴素贝叶斯和 K 最近邻就是很好的例子。 使用弱学习器并将其组合在一起的好处是,与复杂算法相比,它们可以更快地得到训练。 尽管预测能力较弱,但结合起来使用时,它们通常比更复杂的单个算法具有可比甚至更好的预测表现。

随机子空间和随机补丁

在随机子空间中,由于特征的随机子集,估计量会有所不同。 同样,可以通过调整BaggingClassifierBaggingRegressor的参数,并将max_features设置为小于 1.0 的数字来实现这样的解决方案,该数字表示要为集合的每个模型随机选择的特征的百分比。

取而代之的是,在随机补丁中,估计器建立在样本和特征的子集上。

现在让我们在表中检查粘贴,装袋,随机子空间和随机补丁的不同特征,这些特征是使用 Scikit-learn 中的BaggingClassifierBaggingRegressor实现的:

组装目的超参数
粘贴使用子样本构建了许多模型(在不替换小于原始数据集的样本的情况下进行抽样)bootstrap=False``max_samples <1.0``max_features=1.0
装袋使用自举案例的随机选择构建了许多模型(通过替换原始样本的相同大小进行采样)bootstrap=True``max_samples = 1.0``max_features=1.0
随机子空间这与装袋相同,但是在选择每个模型时也会对特征进行采样bootstrap=True``max_samples = 1.0``max_features<1.0
随机补丁这与装袋相同,但是在选择每个模型时也会对特征进行采样bootstrap=False``max_samples <1.0``max_features<1.0

max_featuresmax_samples必须小于 1.0 时,可以将它们设置为(0, 1)范围内的任何值,并且可以通过网格搜索测试最佳值。 根据我们的经验,如果您需要限制或加快搜索速度,最有效的值是 0.7 到 0.9。

随机森林和多余的树木

Leo Breiman 和 Adele Cutler 最初是在“随机森林”算法的核心中设计出这个主意的,而该算法的名称至今仍是他们的商标(尽管该算法是开源的)。 随机森林在 Scikit-learn 中实现为RandomForestClassifier/RandomForestRegressor

随机森林的工作方式与 Leo Breiman 所设计的装袋相似,但它只能使用二叉拆分决策树进行操作,而二叉决策树会不断发展壮大。 此外,它使用自举对每个模型中要使用的案例进行采样。 并且,随着树的生长,在分支的每个分割处,也将随机绘制要用于分割处的变量集。 最后,这是该算法的核心秘密,因为它使由于不同样本和拆分时所考虑的变量而彼此不同的树融合在一起。 与众不同,它们也不相关。 这是有益的,因为当结果汇总时,可以排除很多差异,因为平均而言,分布两侧的极值趋于彼此平衡。 换句话说,装袋算法保证了预测中的一定程度的多样性,从而允许制定可能永远不会遇到单个学习器(例如决策树)的规则。

在 Scikit-learn 中以ExtraTreesClassifier/ExtraTreesRegressor类表示的极端树是一种随机性更高的随机森林,其估计值的方差较小,但代价是估计量的偏差更大。 无论如何,在 CPU 效率方面,与随机森林相比,极端树可以提供相当大的提速,因此从示例和特征上来说,当您使用大型数据集时,极端树是理想的选择。 产生更高偏差但速度更快的原因是在极端树中构建拆分的方式。 随机森林会从要考虑拆分树的分支的采样特征中仔细搜索最佳值,以分配给每个分支,而在极端树中,这是随机决定的。 因此,尽管随机选择的分割可能不是最有效的分割(因此存在偏差),但无需进行大量计算。

让我们看看这两种算法在预测的准确率和执行时间方面如何与 Covertype 森林问题进行比较。 为此,我们将在 Jupyter 笔记本电脑的单元格中使用神奇的%%time单元格,以测量计算表现:

In: import numpy as np
    from sklearn.model_selection import cross_val_score
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.ensemble import ExtraTreesClassifier  

In: %%time
    hypothesis = RandomForestClassifier(n_estimators=100, random_state=101)
    scores = cross_val_score(hypothesis, covertype_X, covertype_y, 
                             cv=3, scoring='accuracy', n_jobs=-1)
    print ("RandomForestClassifier -> cross validation accuracy: \
            mean = %0.3f std = %0.3f" % (np.mean(scores), np.std(scores))) Out: RandomForestClassifier -> cross validation accuracy: 
     mean = 0.809 std = 0.009
     Wall time: 7.01 s In: %%time
    hypothesis = ExtraTreesClassifier(n_estimators=100, random_state=101)
    scores = cross_val_score(hypothesis, covertype_X, covertype_y, cv=3, 
                                            scoring='accuracy', n_jobs=-1)
    print ("ExtraTreesClassifier -> cross validation accuracy: mean = %0.3f 
    std = %0.3f" % (np.mean(scores), np.std(scores))) Out: ExtraTreesClassifier -> cross validation accuracy: 
     mean = 0.821 std = 0.009
     Wall time: 6.48 s

对于这两种算法,应设置的关键超参数如下:

  • max_features:这是每次拆分中可以确定算法表现的采样特征数量。 数字越小,速度越快,但偏差越大。
  • min_samples_leaf:这使您可以确定树木的深度。 较大的数字会减少方差并增加偏差。
  • bootstrap:这是一个布尔值,允许进行引导。
  • n_estimators:这是树的数量(请记住,树越多越好,但这要以计算成本为基础)。

实际上,随机森林和极端树都是并行算法。 不要忘记设置适当的n_jobs数量以加快执行速度。 在分类时,他们决定投票最多的阶层(多数投票); 回归时,它们只是对结果值求平均值。 作为示例,我们提出一个基于加利福尼亚房价数据集的回归示例:

In: import pickle
    from sklearn.preprocessing import scale
    X_train, y_train = pickle.load(open( "cadata.pickle", "rb" ))
    first_rows = 2000 In: import numpy as np
    from sklearn.ensemble import RandomForestRegressor
    X_train = scale(X_train[:first_rows,:].toarray())
    y_train = y_train[:first_rows]/10**4.
    hypothesis = RandomForestRegressor(n_estimators=300, random_state=101)
    scores = cross_val_score(hypothesis, X_train, y_train, cv=3, 
                             scoring='neg_mean_absolute_error', n_jobs=-1)
    print ("RandomForestClassifier -> cross validation accuracy: mean = %0.3f 
    std = %0.3f" % (np.mean(scores), np.std(scores))) Out: RandomForestClassifier -> cross validation accuracy: 
     mean = -4.642 std = 0.514

估计整体概率

随机森林具有很大的优势,它们被认为是您应该尝试对数据进行研究以找出可以获得哪种结果的第一种算法。 这是因为随机森林没有太多要固定的超参数,并且开箱即用地工作得很好。 他们自然可以解决多类问题。 此外,随机森林提供了一种方法来估计变量对您的见解或特征选择的重要性,并且它们有助于估计示例之间的相似性,因为相似的情况应最终出现在集合中许多树的同一末端。

但是,在分类问题中,该算法缺乏预测结果概率的能力(除非使用CalibratedClassifierCV使用 Scikit-learn 中提供的概率校准进行校准)。 在分类问题中,通常不足以预测响应标签。 我们还需要与之相关的概率(概率为真;这是预测的可信度)。 这对于多类问题特别有用,因为正确的答案可能是第二大或第三大可能的答案(因此,概率提供了答案的等级)。

但是,当需要使用“随机森林”来估计响应类别的概率时,该算法将仅报告示例相对于该集合本身中所有树的数量被分类为该类别的次数。 这样的比率实际上并不对应于正确的概率,但是它是一个有偏的比率(预测的概率仅与真实的比率相关;它不能以数字上正确的方式表示出来)。

为了帮助随机森林和受类似情况影响的其他算法(如朴素贝叶斯或线性 SVM)发出正确的响应概率,在 Scikit-learn 中引入了CalibratedClassifierCV包装器类。

CalibrateClassifierCV使用两种方法将机器学习算法的响应重新映射为概率:普拉特定标和等渗回归(后者是表现较好的非参数方法,条件是您有足够的示例,即至少有 1,000 个)。 这两种方法都是第二级模型,旨在仅对算法的原始响应和预期概率之间的链接进行建模。 通过将原始概率分布与校准概率分布进行比较,可以绘制结果。

例如,在这里我们使用CalibratedClassifierCV来修正 Covertype 问题:

In: import pandas as pd
    import matplotlib.pyplot as plt
    from sklearn.calibration import CalibratedClassifierCV
    from sklearn.calibration import calibration_curve
    hypothesis = RandomForestClassifier(n_estimators=100, random_state=101)
    calibration = CalibratedClassifierCV(hypothesis, method='sigmoid', 
                                         cv=5)
    covertype_X = covertype_dataset.data[:15000,:]
    covertype_y = covertype_dataset.target[:15000]
    covertype_test_X = covertype_dataset.data[15000:25000,:]
    covertype_test_y = covertype_dataset.target[15000:25000]

为了评估校准的行为,我们准备了一个由 10,000 个示例组成的测试集,这些示例不用于训练。 我们的校准模型将基于 Platt 的模型(method='sigmoid'),并使用五个交叉验证折叠来调整校准:

In: hypothesis.fit(covertype_X,covertype_y)
    calibration.fit(covertype_X,covertype_y)
    prob_raw = hypothesis.predict_proba(covertype_test_X)
    prob_cal = calibration.predict_proba(covertype_test_X)

拟合原始模型和校准模型后,我们估计概率,现在将它们绘制在散点图中以突出显示差异。 在对美国黄松的估计概率进行预测之后,似乎已经对原始的随机森林概率(实际投票百分比)进行了重新缩放,使其类似于对数曲线。 现在,我们尝试编写一些代码,并探讨校准为概率输出带来的变化类型:

In: %matplotlib inline
    tree_kind = covertypes.index('Ponderosa Pine')
    probs = pd.DataFrame(list(zip(prob_raw[:,tree_kind], 
                                  prob_cal[:,tree_kind])), 
                         columns=['raw','calibrted'])
    plot = probs.plot(kind='scatter', x=0, y=1, s=64, 
                      c='blue', edgecolors='white')

校准虽然不会改变模型的表现,但通过重塑概率输出可以帮助您获得与训练数据更相符的概率。 在下图中,您可以通过添加一些非线性作为校正来观察校准程序如何修改了原始概率:

模型序列—— AdaBoost

AdaBoost 是基于梯度下降优化方法的增强算法。 它适合数据重新加权版本上的一系列弱学习器(最初是树桩,即单级决策树)。 根据案例的可预测性分配权重。 较难处理的案件的权重更高。 想法是,树首先学习简单的示例,然后将更多的精力集中在困难的示例上。 最后,对弱学习器的顺序进行加权以最大程度地提高整体表现:

In: import numpy as np
    from sklearn.ensemble import AdaBoostClassifier
    hypothesis = AdaBoostClassifier(n_estimators=300, random_state=101)
    scores = cross_val_score(hypothesis, covertype_X, covertype_y, cv=3, 
                             scoring='accuracy', n_jobs=-1)
    print ("Adaboost -> cross validation accuracy: mean = %0.3f 
    std = %0.3f" % (np.mean(scores), np.std(scores)))
    Out: Adaboost -> cross validation accuracy: mean = 0.610 std = 0.014

梯度树增强(GTB)

梯度提升是增强的另一个改进版本。 像 AdaBoost 一样,它基于梯度下降函数。 尽管该算法的特征在于估计的方差增加,对数据中的噪声更敏感(这两个问题都可以通过使用子采样来减弱),但已证明是该算法中最熟练的算法之一。 由于非并行操作。

为了演示 GTB 的表现,我们将再次尝试检查是否可以改善 Covertype 数据集的预测表现,在说明线性 SVM 和集成算法时已经进行了检查:

In: import pickle
    covertype_dataset = pickle.load(open("covertype_dataset.pickle", "rb"))
    covertype_X = covertype_dataset.data[:15000,:]
    covertype_y = covertype_dataset.target[:15000] -1 
    covertype_val_X = covertype_dataset.data[15000:20000,:]
    covertype_val_y = covertype_dataset.target[15000:20000] -1
    covertype_test_X = covertype_dataset.data[20000:25000,:]
    covertype_test_y = covertype_dataset.target[20000:25000] -1  

加载数据后,将训练样本大小限制为 15000 个观察值,以实现合理的训练效果。 我们还提取了由 5,000 个示例组成的验证样本和由另外 5,000 个示例组成的测试样本。 现在,我们开始训练模型:

In: import numpy as np
    from sklearn.model_selection import cross_val_score, StratifiedKFold
    from sklearn.ensemble import GradientBoostingClassifier
    hypothesis = GradientBoostingClassifier(max_depth=5, 
                                            n_estimators=50, 
                                            random_state=101)
    hypothesis.fit(covertype_X, covertype_y) In: from sklearn.metrics import accuracy_score
    print ("GradientBoostingClassifier -> test accuracy:", 
             accuracy_score(covertype_test_y, 
                            hypothesis.predict(covertype_test_X))) Out: GradientBoostingClassifier -> test accuracy: 0.8202

为了从GradientBoostingClassifierGradientBoostingRegression获得最佳表现,您必须进行以下调整:

  • n_estimators:超过估计量,会增加方差。 无论如何,如果估计量不足,则该算法将遭受高偏差。 正确的数字不是先验已知的,必须通过交叉验证测试各种配置来启发式地找到。
  • max_depth:它增加了方差和复杂性。
  • subsample:使用 0.9 到 0.7 的值可以有效减少估计的方差。
  • learning_rate:较小的值可以改善训练过程中的优化,尽管这将需要更多的估计量来收敛,因此需要更多的计算时间。
  • in_samples_leaf:这可以减少由于噪声数据导致的方差,从而保留对极少数情况的过拟合。

除了深度学习,梯度提升实际上是最先进的机器学习算法。 自从 Adaboost 和 Jerome Friedman 开发的以下梯度提升实现以来,出现了各种算法实现,最近的是 XGBoost,LightGBM 和 CatBoost。 在以下各段中,我们将探索这些新解决方案,并使用 Forest Covertype 数据在道路上对其进行测试。

XGBoost

XGBoost 代表极限梯度提升,这是一个不属于 Scikit-learn 的开源​​项目,尽管最近它已通过 scikit-Learn 包装器接口进行了扩展,该接口使用基于 XGBoost 的模型进行渲染,并已集成到您的计算机的数据管道中。

XGBoost 源代码可从 GitHub 上获得; 可以在这个页面上找到其文档和一些教程。

XGBoost 算法在 Kaggle 和 KDD-cup 2015 等数据科学竞赛中获得了动力和普及。作者(Tianqui Chen,Tong He,Carlos Guestrin)在有关该算法的论文中报告,2015 年期间在 Kaggle 上进行了 29 个挑战,其中有 17 个获奖解决方案使用 XGBoost 作为独立解决方案或作为多个不同模型集合的一部分。

在他们的论文《XGBoost:可扩展树增强系统》中(可在这个页面中找到),作者报告说,每个团队也都使用 XGBoost 在最近的 KDD 杯 2015 的前十名中排名第二。

除了在准确率和计算效率上都取得了成功的成就外,从不同的角度来看,XGBoost 还是可扩展的解决方案。 XGBoost 代表了新一代 GBM 算法,这要归功于对初始树增强 GBM 算法的重要调整:

  • 稀疏感知算法; 它可以利用稀疏矩阵,既节省内存(不需要密集矩阵),又节省计算时间(以特殊方式处理零值)。
  • 近似树学习(加权分位数草绘),其结果相似,但所需时间比可能的分支切割的经典完整探索要少得多。
  • 一台机器上的并行计算(在搜索最佳分割阶段使用多线程),以及在多个机器上进行类似分布的计算。
  • 利用称为列块的数据存储解决方案,在一台计算机上进行核外计算。 这样可以按列在磁盘上排列数据,从而通过优化算法(适用于列向量)期望从磁盘上提取数据,从而节省了时间。
  • XGBoost 还可以有效地处理丢失的数据。 基于标准决策树的其他树集合要求首先使用诸如负数之类的非标度值来估计缺失数据,以便开发出适当的树分支以处理缺失值。

相反,XGBoost 首先适合所有非缺失值。 在为变量创建分支之后,它决定哪个分支更适合丢失值,以最大程度地减少预测误差。 这样的方法可以使两棵树都更加紧凑,并提供有效的插补策略,从而提高预测能力。

从实际的角度来看,XGBoost 具有与 Scikit-learn 的 GBT 几乎相同的参数。 关键参数如下:

  • eta:相当于 Scikit-learn 的 GTB 中的学习率。 它影响算法学习的速度,从而影响需要多少棵树。 较高的值有助于更好地融合学习过程,但代价是要花费更多的训练时间和更多的树木。
  • gamma:这是树开发中的停止标准,因为它表示在树的叶节点上进行进一步分区所需的最小损失减少。 更高的值使学习更加保守。
  • min_child_weight:这些代表树的叶子节点上存在的最小权重(示例)。 较高的值可防止过拟合和树的复杂性。
  • max_depth:树的最大深度。
  • subsample:训练数据中示例的一部分,将在每次迭代中使用。
  • colsample_bytree:在每次迭代中使用的特征分数。
  • colsample_bylevel:在每个分支分割中使用的特征分数(如在随机森林中)。

在我们如何应用 XGBoost 的示例中,我们首先回顾如何通过部分切片包含完整数据集的初始 NumPy 数组来上载 Covertype 数据集并将其分为训练集,验证集和测试集:

In: from sklearn.datasets import fetch_covtype
 from sklearn.model_selection import cross_val_score, StratifiedKFold covertype_dataset = fetch_covtype(random_state=101, shuffle=True) covertype_dataset.target = covertype_dataset.target.astype(int) covertype_X = covertype_dataset.data[:15000,:] covertype_y = covertype_dataset.target[:15000] -1 covertype_val_X = covertype_dataset.data[15000:20000,:] covertype_val_y = covertype_dataset.target[15000:20000] -1 covertype_test_X = covertype_dataset.data[20000:25000,:] covertype_test_y = covertype_dataset.target[20000:25000] -1

加载数据后,我们首先通过设置目标(如multi:softprob来定义超参数,但是 XGBoost 为回归,分类,多类和排名提供了其他选择),然后设置了一些前述参数。

当拟合数据时,可以对算法给出进一步的指示。 在我们的案例中,我们将eval_metric设置为针对多类问题的准确率('merror'),并提供了eval_set,这是 XGBoost 在训练过程中必须通过计算评估指标来监控的验证集。 如果训练在 25 个回合中没有提高评估指标(由early_stopping_rounds定义),则训练将在达到先前定义的估计器数量(n_estimators)之前停止。 这种方法称为“早期停止”,它源自神经网络训练,有效地避免了训练阶段的过拟合:

有关参数和评估指标的完整列表,请参见这个页面。 在这里,我们开始导入包,设置其参数并将其适合我们的问题:

In: import xgboost as xgb
 hypothesis = xgb.XGBClassifier(objective= "multi:softprob", max_depth = 24, 
                                   gamma=0.1, 
                                   subsample = 0.90,                                   learning_rate=0.01,
                                   n_estimators = 500, 
                                   nthread=-1)
 hypothesis.fit(covertype_X, covertype_y,                   eval_set=[(covertype_val_X, covertype_val_y)], 
                   eval_metric='merror', early_stopping_rounds=25, 
                   verbose=False)

为了获得预测,我们只使用与 Scikit-learn API 相同的方法:predictpredict_proba。 打印准确率揭示了到目前为止,XGBoost 算法的长期拟合实际上是如何带来最佳测试结果的。 对混淆矩阵的检查表明,只有白杨树类型很难预测:

In: from sklearn.metrics import accuracy_score, confusion_matrix
 print ('test accuracy:', accuracy_score(covertype_test_y,           hypothesis.predict(covertype_test_X)))
 print (confusion_matrix(covertype_test_y,hypothesis.predict(covertype_test_X))) Out: test accuracy: 0.848 
     [[1512   288     0     0     0     2    18]
 [ 215  2197    18     0     7    11     0] [   0    17   261     4     0    19     0] [   0     0     4    20     0     3     0] [   1    54     3     0    19     0     0] [   0    16    42     0     0    86     0] [  37     1     0     0     0     0   145]]

LightGBM

当您的数据集包含大量案例或变量时,即使 XGBoost 是用 C++ 编译的,训练起来也确实需要很长时间。 因此,尽管 XGBoost 取得了成功,但仍有空间在 2017 年 1 月推出另一种算法(XGBoost 的首次出现是在 2015 年 3 月)。 它是高性能的 LightGBM,能够分发和快速处理大量数据,并且由 Microsoft 的团队作为一个开源项目开发。

这是它的 GitHub 页面。 并且,这里是说明该算法背后思想的学术论文

LightGBM 基于决策树以及 XGBoost,但遵循不同的策略。 XGBoost 使用决策树对变量进行拆分,并在该变量上探索不同的切分(逐级树增长策略),而 LightGBM 则专注于拆分并从那里进行拆分,以实现更好的拟合(这就是叶子级别的树木生长策略)。 这使得 LightGBM 可以首先快速获得数据的良好拟合度,并生成与 XGBoost 相比的替代解决方案(如果您希望将这两个解决方案进行混合(即平均),则可以很好地解决这一问题,以减少估计值的方差) )。

从算法上讲,XGBoost 仔细研究了广度优先搜索BFS),而 LightGBM 则是深度优先搜索DFS)。

这是该算法的其他亮点:

  1. 由于采用逐叶策略,因此树木更为复杂,从而导致预测的准确率更高,但过拟合的风险也更高; 因此,这对于小型数据集(使用具有 10,000 多个示例的数据集)尤其无效。
  2. 在较大的数据集上速度更快。
  3. 它可以利用并行化和 GPU 使用率; 因此,它可以扩展到更大的问题(实际上,它仍然是 GBM,一种顺序算法;并行化的是决策树的“查找最佳拆分”部分)。
  4. 这是内存节约的方法,因为它不会按原样存储和处理连续变量,但是会将它们变成离散的值箱(基于直方图的算法)。

调整 LightGBM 可能会令人望而生畏,需要修复一百多个参数(您可以在此处找到所有这些参数), 但是,实际上,您只需调整几个即可获得出色的结果。 LightGBM 中的参数在以下方面有所不同:

  • 核心参数,指定要对数据完成的任务
  • 控制参数,决定决策树的行为
  • 度量参数,无视您的误差度量(除了用于分类和回归的经典误差之外,实际上还有很多可供选择的列表)
  • IO 参数,主要决定输入的处理方式

这是每个类别的主要参数的快速概述。

至于核心参数,您可以通过以下操作来选择关键选项:

  • task:您要用模型完成的任务; 可以是trainpredictconvert_model(将其作为一系列if-else语句获取),重新装配(用于使用新数据更新模型)。
  • application:默认情况下,预期模型为回归模型,但可以为regressionbinarymulticlass和其他许多模型(也可以作为lambdarank用于对搜索引擎优化等任务进行排名 )。
  • boosting:LightGBM 可以使用不同的算法进行学习迭代。 默认值为gbdt(单个决策树),但它可以为rf(随机森林),darts(下降点符合多个加性回归树)或goss(基于梯度的单边采样)。
  • device:默认情况下为cpu,但是如果系统上有可用的gpu,则使用gpu

IO 参数定义如何加载(甚至由模型存储)数据:

  • max_bin:用于存储特征值的最大仓数(越多,处理数字变量时的近似值越小,但更多的内存和计算时间)
  • categorical_feature:类别特征的索引
  • ignore_column:要忽略的特征的索引
  • save_binary:如果将数据以二进制格式保存在磁盘上以加快加载和保存内存的速度

最后,通过设置控制参数,您可以更具体地决定模型如何从数据中学习:

  • num_boost_round:要执行的增强迭代次数。
  • learning_rate:在生成的模型的构建中,每次增强迭代权重的速率。
  • num_leaves:一棵树上的最大叶子数,默认为 31。
  • max_depth:一棵树可以达到的最大深度。
  • min_data_in_leaf:要创建的叶子的最小示例数。
  • bagging_fraction:在每次迭代中随机使用的数据比例。
  • feature_fraction:当增强为rf时,此参数指示要为分割随机考虑的全部特征的比例。
  • early_stopping_round:固定此参数,如果您的模型在一定回合中没有改善,它将停止训练。 它有助于减少过拟合和训练时间。
  • lambda_l1lambda_l2:正则化参数,范围为 0 到 1(最大值)。
  • min_gain_to_split:此参数指示在树上创建拆分的最小增益。 通过不发展分裂对模型的贡献不大,它限制了树的复杂性。
  • max_cat_group:在处理具有高基数(大量类别)的类别变量时,此参数通过聚合次要变量来限制变量可以具有的类别数目。 此参数的默认值为 64。
  • is_unbalance:对于二分类中的不平衡数据集,将其设置为True可使算法针对不平衡类进行调整。
  • scale_pos_weight:对于二分类中的不平衡数据集,它也为正类别设置权重。

实际上,我们仅引用了 LightGBM 模型的所有可能参数的一小部分,但引用了最重要和最重要的参数。 浏览文档,您可以找到更多适合您特定情况和项目的参数。

我们如何调整所有这些参数? 实际上,您可以有效地进行一些操作。 如果要实现更快的计算,只需使用save_binary并设置一个较小的max_bin即可。 您还可以使用数量较少的bagging_fractionfeature_fraction来减少训练集的大小并加快学习过程(以增加解决方案的差异为代价,因为它将从更少的数据中学习)。

如果要通过误差度量获得更高的精度,则应改用较大的max_bin(这意味着在使用数字变量时精度更高),应使用较小的learning_rate和较大的num_iterations(这是必需的,因为算法会收敛),并使用较大的num_leaves(尽管可能会导致过拟合)。

在过拟合的情况下,您可以尝试设置lambda_l1lambda_l2min_gain_to_split并获得更多的正则化。 您也可以尝试max_depth避免生长过深的树木。

在我们的示例中,我们承担与之前相同的任务,以对 Forest Covertype 数据集进行分类。 我们首先导入必要的包。

接下来,下一步是为该增强算法设置参数以使其正常工作。 我们定义目标('multiclass'),设置较低的学习率(0.01),并允许其分支几乎完全像随机森林一样散布:其树的最大深度设置为 128,并且结果数 leaves 是 256。为此,我们还对情况和特征都进行了随机抽样(每次将其中的 90% 装袋):

In: import lightgbm as lgb
 import numpy as np params = {'task': 'train', 'boosting_type': 'gbdt', 'objective': 'multiclass', 'num_class':len(np.unique(covertype_y)), 'metric': 'multi_logloss', 'learning_rate': 0.01, 'max_depth': 128, 'num_leaves': 256, 'feature_fraction': 0.9, 'bagging_fraction': 0.9, 'bagging_freq': 10}

然后,我们使用 LightGBM 包中的Dataset命令设置数据集进行训练,验证和测试:

In: train_data = lgb.Dataset(data=covertype_X, label=covertype_y)
 val_data = lgb.Dataset(data=covertype_val_X, label=covertype_val_y)

最后,我们通过提供先前设置的参数,确定最大迭代次数为 2500,设置验证集,并要求误差大于 25 的迭代不能改善验证,要求提早停止训练,从而设置训练实例( 这将使我们避免由于太多的迭代而导致的过拟合,也就是说,增加了树的数量):

In: bst = lgb.train(params,
 train_data, num_boost_round=2500, valid_sets=val_data, verbose_eval=500, early_stopping_rounds=25)

过了一会儿,训练停止指出对 0.40 和 851 迭代的验证所产生的对数损失是最好的选择。 进行训练,直到 25 轮验证分数没有提高:

Out: Early stopping, best iteration is:[851]       
     valid_0's multi_logloss: 0.400478

除了使用验证集,我们还可以通过交叉验证来测试最佳迭代次数,即在同一训练集上:

In: lgb_cv = lgb.cv(params,
 train_data, num_boost_round=2500, nfold=3, shuffle=True, stratified=True, verbose_eval=500, early_stopping_rounds=25) nround = lgb_cv['multi_logloss-mean'].index(np.min(lgb_cv[ 'multi_logloss-mean'])) print("Best number of rounds: %i" % nround) Out: cv_agg's multi_logloss: 0.468806 + 0.0124661
 Best number of rounds: 782

结果并不像验证集那样出色,但是回合的数量与我们之前的发现并不遥远。 无论如何,我们将在提早停车的情况下使用首列训练。 首先,我们使用预测方法获得最佳类别的概率,并获得最佳迭代,然后选择概率最高的类别作为预测。

这样做之后,我们将检查准确率并绘制混淆矩阵。 获得的分数类似于 XGBoost,但获得的训练时间较短:

In: y_probs = bst.predict(covertype_test_X, 
          num_iteration=bst.best_iteration)
 y_preds = np.argmax(y_probs, axis=1) from sklearn.metrics import accuracy_score, confusion_matrix
    print('test accuracy:', accuracy_score(covertype_test_y, y_preds)) print(confusion_matrix(covertype_test_y, y_preds)) Out: test accuracy: 0.8444
 [[1495 309     0    0    0    2   14]      [ 221 2196   17    0    5    9    0] 
      [   0   20  258    5    0   18    0] 
      [   0   0     3   19    0    5    0] 
      [   1   51    4    0   21    0    0] 
      [   0   14   43    0    0   87    0] 
      [  36   1     0    0    0    0  146]]

CatBoost

2017 年 7 月,另一种有趣的 GBM 算法由俄罗斯搜索引擎 Yandex 公开:它是 CatBoost,其名称来自两个词 Category 和 Boosting。 实际上,它的最强之处是能够处理分类变量,该变量实际上通过采用“单热编码”和“均值编码”的混合策略来充分利用大多数关系数据库中的信息(一种通过分配变量来表达分类级别的方法) 适当的数值来解决手头的问题;稍后再讨论)。

正如《CatBoost:具有分类特征的梯度提升支持》所解释的,因为其他 GBM 解决方案都通过一次热编码来处理分类变量(就数据矩阵的内存打印而言而言非常昂贵)或通过将任意数字代码分配给分类级别(至多一种不精确的方法,需要大的分支才能生效),CatBoost 的解决方法有所不同。

您可以为算法提供分类变量的索引,并设置一个one_hot_max_size参数,告诉 CatBoost 使用单热编码来处理分类变量(如果变量的级别小于或等于该级别)。 如果变量具有更多分类级别,从而超过了one_hot_max_size参数,则算法将以与均值编码不太相同的方式对它们进行编码,如下所示:

  1. 排列示例级别。
  2. 根据损失函数将级别转换为整数,以使其最小化。
  3. 根据对目标的随机排序,根据对级别标签进行计数,将级别号转换为浮点数值(更多详细信息请参见这个页面(带有简单示例)。

CatBoost 用来编码分类变量的想法并不是什么新鲜事物,但它已经成为一种特征工程,已经被广泛使用,主要是在 Kaggle 的数据科学竞赛中使用。 均值编码(也称为似然编码,影响编码或目标编码)只是将标签与目标变量的关联关系转换为数字的一种方法。 如果您进行回归分析,则可以根据该级别的平均目标值来转换标签; 如果是分类,则只是给定该标签的目标分类的概率(目标概率,取决于每个类别值)。 它可能看起来像是一个简单而明智的特征工程技巧,但实际上,它具有副作用,主要是在过拟合方面,因为您正在将来自目标的信息纳入预测变量。

有几种经验方法可以限制过拟合并利用将分类变量作为数字变量进行处理。 最好了解更多信息,实际上是 Coursera 的视频,因为没有关于它的正式论文。 我们的建议是谨慎使用此技巧。

CatBoost 除了具有 R 和 Python API 并在 GBM 领域中以与 XGBoost 和 LightGBM 竞争的程度相同之外,还得益于 GPU 和多 GPU 支持(您可以在这个页面上查看性能比较。它也是一个完全开源的项目,您可以从其 GitHub 存储库中阅读以下所有代码

即使在 CatBoost 的情况下,参数列表也非常庞大,尽管详细,但位于这个页面。 对于简单的应用,您只需要调整以下关键参数:

  • one_hot_max_size:目标编码任何分类变量的阈值
  • iterations:迭代次数
  • od_wait:评估指标未改善时要等待的迭代次数
  • learning_rate:学习率
  • depth:树的深度
  • l2_leaf_reg:正则化系数
  • random_strengthbagging_temperature控制随机装袋

我们首先导入所有必需的包和函数:

  1. 由于 CatBoost 在处理分类变量时表现出色,因此我们必须重建 Forest Covertype 数据集,因为其所有分类变量都已经被热编码。 因此,我们只需重建它们并重新创建数据集即可:
In: import numpy as np
 from sklearn.datasets import fetch_covtype from catboost import CatBoostClassifier, Pool covertype_dataset = fetch_covtype(random_state=101, 
                                      shuffle=True)
    label = covertype_dataset.target.astype(int) - 1 wilderness_area = 
    np.argmax(covertype_dataset.data[:,10:(10+4)], 
              axis=1) soil_type = np.argmax(
                   covertype_dataset.data[:,(10+4):(10+4+40)], 
                   axis=1) data = (covertype_dataset.data[:,:10],  
            wilderness_area.reshape(-1,1),            soil_type.reshape(-1,1))
 data = np.hstack(data)
  1. 创建它之后,我们像以前一样选择训练,验证和测试部分:
In: covertype_train = Pool(data[:15000,:], 
                           label[:15000], [10, 11])
 covertype_val = Pool(data[15000:20000,:], 
                         label[15000:20000], [10, 11]) covertype_test = Pool(data[20000:25000,:], 
                          None, [10, 11]) covertype_test_y = label[20000:25000]
  1. 现在是时候设置CatBoostClassifier了。 我们决定采用较低的学习率(0.05)和大量的迭代,最大树深度为 8(CatBoost 的实际最大值为 16),针对MultiClass进行优化(对数丢失),但同时监控训练和验证的准确率:
In: model = CatBoostClassifier(iterations=4000,
 learning_rate=0.05, depth=8, custom_loss = 'Accuracy', eval_metric = 'Accuracy', use_best_model=True, loss_function='MultiClass')
  1. 然后,我们开始训练,关闭详细程度,但允许在样本内,更重要的是在样本外,直观地表示训练及其结果:
In: model.fit(covertype_train, eval_set=covertype_val, 
              verbose=False, plot=True)

这是可以为在 CoverType 数据集上训练的模型获得什么样的可视化效果的示例:

  1. 训练后,我们只需预测类别及其相关概率:
In: preds_class = model.predict(covertype_test) 
    preds_proba = model.predict_proba(covertype_test)
  1. 准确率评估指出,结果等效于 XGBoost(对 848 而言为 0.847),并且混淆矩阵看起来更清晰,指出此算法可以完成更好的分类工作:
In: from sklearn.metrics import accuracy_score, confusion_matrix 
    print('test accuracy:', accuracy_score(covertype_test_y, 
                                           preds_class))
 print(confusion_matrix(covertype_test_y, preds_class))
 Out: test accuracy:  0.847 [[1482  320    0    0    0    0   18]      [ 213 2199   12    0   10   12    2] 
      [   0   13  260    5    0   23    0] 
      [   0    0    6   18    0    3    0] 
      [   2   40    5    0   30    0    0] 
      [   0   16   33    1    0   94    0] 
      [  31    0    0    0    0    0  152]]

处理大数据

大数据将数据科学项目置于四个角度:数量(数据量),速度,多样性和准确率(您的数据是否真正代表了它应该是什么,还是受到一些偏见,失真或错误的影响?)。 Scikit-learn 包提供了一系列类和函数,可以帮助您有效地处理如此大的数据,以至于无法完全容纳在标准计算机的内存中。

在向您提供大数据解决方案概述之前,我们必须创建或导入一些数据集,以便使您更好地了解不同算法的可伸缩性和性能。 这将需要约 1.5 GB 的硬盘空间,实验后将释放该硬盘空间。 (本身不是大数据-如今,很难找到内存少于 4 GB 的计算机-但是,甚至没有玩具数据集,它也应该提供一些想法)。

创建一些大数据集作为示例

作为大数据分析的典型示例,我们将使用互联网上的一些文本数据,并利用可用的fetch_20newsgroups,其中包含 11,314 个帖子的数据,每个帖子平均约 206 个单词,出现在 20 个不同的新闻组中:

In: import numpy as np
    from sklearn.datasets import fetch_20newsgroups
    newsgroups_dataset = fetch_20newsgroups(shuffle=True, 
                         remove=('headers', 'footers', 'quotes'), 
                         random_state=6)
    print ('Posts inside the data: %s' % np.shape(newsgroups_dataset.data))
    print ('Average number of words for post: %0.0f' % 
           np.mean([len(text.split(' ')) for text in 
           newsgroups_dataset.data])) Out: Posts inside the data: 11314
     Average number of words for post: 206

相反,为了得出通用分类示例,我们将创建三个合成数据集,其中包含 100,000 至 1000 万个案例。 您可以根据计算机的资源来创建和使用其中任何一个。 我们将始终在实验中引用最大的一个:

In: from sklearn.datasets import make_classification
    X,y = make_classification(n_samples=10**5, n_features=5, 
                              n_informative=3, random_state=101)
    D = np.c_[y,X]
    np.savetxt('large_dataset_10__5.csv', D, delimiter=",") 
    # the saved file should be around 14,6 MB
    del(D, X, y)
    X,y = make_classification(n_samples=10**6, n_features=5, 
                              n_informative=3, random_state=101)
    D = np.c_[y,X]
    np.savetxt('large_dataset_10__6.csv', D, delimiter=",") 
    # the saved file should be around 146 MB
    del(D, X, y)
    X,y = make_classification(n_samples=10**7, n_features=5, 
                            n_informative=3, random_state=101)
    D = np.c_[y,X]
    np.savetxt('large_dataset_10__7.csv', D, delimiter=",") 
    #the saved file should be around 1,46 GB
    del(D, X, y)

创建并使用任何数据集后,可以通过以下命令将它们从磁盘中删除:

In: import os
    os.remove('large_dataset_10__5.csv')
    os.remove('large_dataset_10__6.csv')
    os.remove('large_dataset_10__7.csv')

体积可扩展

在不将太多兆字节(或千兆字节)数据加载到内存中的情况下管理大量数据的技巧是一次仅使用部分示例来增量更新算法的参数,然后对以下数据块重复更新,直到机器学习器至少对所有观察进行了详细阐述。

借助.partial_fit()方法,这在 Scikit-learn 中是可能的,该方法已可用于一定数量的有监督和无监督算法。 通过使用.partial_fit()方法并提供一些基本信息(例如,对于分类,您应该事先知道要预测的类的数量),即使您只有一个案例或几个观察结果,也可以立即开始拟合模型 。

该方法称为incremental learning。 您逐步输入到学习算法中的数据块称为批量。 增量学习的关键点如下:

  • 批量大小
  • 数据预处理
  • 相同示例的传递次数
  • 验证和参数微调

批量大小通常取决于您的可用内存。 原理是数据块越大越好,因为随着数据样本的大小增加,数据样本将获得更多的数据分布代表。 此外,数据预处理也具有挑战性。 增量学习算法可以很好地处理[-1, +1][0, +1]范围内的数据(例如,多项式贝叶斯不接受负值)。 但是,要缩放到如此精确的范围,您需要事先知道每个变量的范围。 或者,您必须执行以下操作之一:一次传递所有数据,记录最小值和最大值,或者从第一批数据中导出它们,并修剪超出初始最大值和最小值的以下观察值。

解决此问题的一种更可靠的方法是使用 Sigmoid 归一化,将所有可能值范围限制在 0 到 1 之间。

通过次数可能会成为问题。 实际上,当您多次传递相同的示例时,可以帮助将预测系数收敛到最佳解决方案。 如果您通过太多相同的观察结果,该算法将趋于过拟合; 也就是说,它将过多地适应重复多次的数据。 一些算法(例如 SGD 系列)对您建议的要学习的示例的顺序也非常敏感。 因此,您必须设置其随机播放选项(shuffle = True)或在学习开始之前随机播放文件行,请记住,为提高效率,为学习建议的行顺序应该是随意的。

验证是一个批量流,可以通过两种方式实现:

  • 逐步验证; 也就是说,先测试模型如何预测新到达的数据块,然后再将它们传递给训练。
  • 从每个块中拿出一些观察结果。 后者也是为网格搜索或其他优化保留样本的最佳方法。

在我们的示例中,我们将对数损失(类似于逻辑回归)委托给SGDClassifier,以学习在给定10 ** 7个观察值的情况下如何预测二进制结果:

In: from sklearn.linear_model import SGDClassifier
    from sklearn.preprocessing import MinMaxScaler
    import pandas as pd
    streaming = pd.read_csv('large_dataset_10__7.csv', 
                         header=None, chunksize=10000)
    learner = SGDClassifier(loss='log', max_iter=100)
    minmax_scaler = MinMaxScaler(feature_range=(0, 1))
    cumulative_accuracy = list()
 for n,chunk in enumerate(streaming): if n == 0: minmax_scaler.fit(chunk.iloc[:,1:].values) X = minmax_scaler.transform(chunk.iloc[:,1:].values) X[X>1] = 1 X[X<0] = 0        y = chunk.iloc[:,0]
 if n > 8: cumulative_accuracy.append(learner.score(X,y)) learner.partial_fit(X,y,classes=np.unique(y)) print ('Progressive validation mean accuracy %0.3f' %
           np.mean(cumulative_accuracy)) Out: Progressive validation mean accuracy 0.660

首先,pandas read_csv允许我们通过读取 10,000 个观测值的批量来遍历文件(可以根据您的计算资源来增加或减少数目)。

我们使用MinMaxScaler来记录第一批中每个变量的范围。 对于以下批次,我们将使用以下规则:如果超过[0, +1]的限制之一,则将它们修剪到最接近的限制。 否则,我们可以使用MinMaxScalerpartial_fit方法,并在使用模型学习时了解特征的边界。 使用MinMaxScaler时,唯一需要注意的注意事项是离群值,因为离群值可以将数值转换压缩到[0, +1]间隔的一部分。

最终,从第十批开始,我们将在使用新算法更新训练之前,在每个新接收的批上记录学习算法的准确率。 最后,对累计的准确率得分进行平均,以提供整体表现评估。

速度

各种算法都使用增量学习来工作。 对于分类,我们将回忆以下内容:

  • sklearn.naive_bayes.MultinomialNB
  • sklearn.naive_bayes.BernoulliNB
  • sklearn.linear_model.Perceptron
  • sklearn.linear_model.SGDClassifier
  • sklearn.linear_model.PassiveAggressiveClassifier

对于回归,我们将回忆以下内容:

  • sklearn.linear_model.SGDRegressor
  • sklearn.linear_model.PassiveAggressiveRegressor

至于速度,它们在速度上都是可比的。 您可以使用以下脚本自行尝试:

In: from sklearn.naive_bayes import MultinomialNB
    from sklearn.naive_bayes import BernoulliNB
    from sklearn.linear_model import Perceptron
    from sklearn.linear_model import SGDClassifier
    from sklearn.linear_model import PassiveAggressiveClassifier
    import pandas as pd
    from datetime import datetime
    classifiers = {'SGDClassifier hinge loss' : SGDClassifier(loss='hinge',
                                            random_state=101, max_iter=10), 
                   'SGDClassifier log loss' : SGDClassifier(loss='log',
                                            random_state=101, max_iter=10),
                   'Perceptron' : Perceptron(random_state=101,max_iter=10),
                   'BernoulliNB' : BernoulliNB(),
              'PassiveAggressiveClassifier' : PassiveAggressiveClassifier(
                                             random_state=101, max_iter=10)
                   }
    large_dataset = 'large_dataset_10__6.csv'
    for algorithm in classifiers:
        start = datetime.now()
        minmax_scaler = MinMaxScaler(feature_range=(0, 1))
        streaming = pd.read_csv(large_dataset, header=None, chunksize=100)
        learner = classifiers[algorithm]
        cumulative_accuracy = list()
        for n,chunk in enumerate(streaming):
            y = chunk.iloc[:,0]
            X = chunk.iloc[:,1:]
            if n > 50 :
                cumulative_accuracy.append(learner.score(X,y))
            learner.partial_fit(X,y,classes=np.unique(y))
        elapsed_time = datetime.now() - start
        print (algorithm + ' : mean accuracy %0.3f in %s secs'
          % (np.mean(cumulative_accuracy),elapsed_time.total_seconds())) Out: BernoulliNB : mean accuracy 0.734 in 41.101 secs
     Perceptron : mean accuracy 0.616 in 37.479 secs
     SGDClassifier hinge loss : mean accuracy 0.712 in 38.43 secs
     SGDClassifier log loss : mean accuracy 0.716 in 39.618 secs
     PassiveAggressiveClassifier : mean accuracy 0.625 in 40.622 secs

作为一般说明,请记住较小的批量速度较慢,因为这意味着从数据库或文件访问更多磁盘,这始终是瓶颈。

处理多样性

多样性是大数据的另一个典型特征。 当我们处理文本数据或很大的分类变量(例如,在程序化广告中存储网站名称的变量)时,尤其如此。 当您从大量示例中学到的知识以及展开类别或单词时,您会看到每个都是合适的排他变量。 您可能会发现难以应对多样化的挑战以及大型数据流的不可预测性。 Scikit-learn 为您提供了一种简单而又快速的方法来实现哈希技巧,并且完全忘记了预先定义刚性变量结构的问题。

哈希技巧使用哈希函数和稀疏矩阵来节省时间,资源和麻烦。 哈希函数是以确定性的方式映射它们收到的任何输入的函数。 不管您给它们提供数字还是字符串,它们都将始终为您提供一定范围内的整数。 相反,稀疏矩阵是仅记录不为零的值的数组,因为其行和列的任何组合的默认值为零。 因此,哈希技巧限制了所有可能的输入。 以前是否未在相应的输入稀疏矩阵上看到某个范围或位置没有关系,该稀疏矩阵的值不为 0。

除了 Python 中的内置哈希函数外,hashlib。 有趣的是,Scikit-learn 在许多函数和方法中也大量使用了哈希函数,并且可以使用 MurmurHash 32可以在针对开发人员的工具中找到它; 只需将其导入并直接使用即可:

In: from sklearn.utils import murmurhash3_32
 print (murmurhash3_32("something", seed=0, positive=True))

例如,如果您输入的是Python,则诸如abs(hash('Python'))之类的哈希命令可以将其转换为整数 539294296,然后将值 1 分配给 539294296 列索引处的单元格。 哈希函数是一种非常快速便捷的方法,可以在给定相同输入的情况下始终定位相同的列索引。 仅使用绝对值可确保每个索引仅对应于数组中的一列(负索引仅从最后一列开始,因此在 Python 中,数组的每一列都可以由正数和负数表示)。

下面的示例使用HashingVectorizer类,这是一个方便的类,该类自动获取文档,分离单词,并借助哈希技巧将其转换为输入矩阵。 该脚本旨在根据新闻组中现有帖子上使用的单词来学习为什么在 20 个不同的新闻组中发布帖​​子:

In: import pandas as pd
    from sklearn.linear_model import SGDClassifier
    from sklearn.feature_extraction.text import HashingVectorizer
    def streaming():
        for response, item in zip(newsgroups_dataset.target,        
                                  newsgroups_dataset.data):
            yield response, item
    hashing_trick = HashingVectorizer(stop_words='english', norm = 'l2')
    learner = SGDClassifier(random_state=101, max_iter=10)
    texts = list()
    targets = list()
    for n, (target, text) in enumerate(streaming()):
        texts.append(text)
        targets.append(target)
        if n % 1000 == 0 and n >0:
            learning_chunk = hashing_trick.transform(texts)
        if n > 1000:
            last_validation_score = learner.score(learning_chunk, targets),
            learner.partial_fit(learning_chunk, targets, 
                                classes=[k for k in range(20)])
            texts, targets = list(), list()
    print ('Last validation score: %0.3f' % last_validation_score) Out: Last validation score: 0.710

此时,无论您输入什么文本,预测算法都将始终通过指出一个类来回答。 在我们的案例中,它指出了适合在该帖子上显示的newsgroup。 让我们使用分类广告中的文字来尝试这种算法:

In: New_text = ["A 2014 red Toyota Prius v Five with fewer than 14K" + 
                "miles. Powered by a reliable 1.8L four cylinder " +
                "hybrid engine that averages 44mpg in the city and " +
                "40mpg on the highway."]
    text_vector = hashing_trick.transform(New_text)
    print (np.shape(text_vector), type(text_vector))
    print ('Predicted newsgroup: %s' %  
           newsgroups_dataset.target_names[learner.predict(text_vector)]) Out: (1, 1048576) <class 'scipy.sparse.csr.csr_matrix'>
     Predicted newsgroup: rec.autos

当然,您可以更改New_text变量,并发现您的文本最有可能在新闻组中显示的位置。 请注意,HashingVectorizer类已将文本转换为csr_matrix(这是一种非常有效的稀疏矩阵)以节省内存,该数据集具有大约一百万列。

随机梯度下降(SGD)概述

我们将通过对 SGD 系列的快速概述来完成本章中的有关大数据学习的这一部分,其中包括SGDClassifier(用于分类)和SGDRegressor(用于回归)。

像其他分类器一样,可以通过使用.fit()方法(将内存中的数据集逐行传递到学习算法)或以前基于批次的.partial_fit()方法进行拟合。 在后一种情况下,如果要分类,则必须使用class参数声明预测的类。 它可以接受一个列表,其中包含在训练阶段应该满足的所有类代码。

当损失参数设置为loss时,SGDClassifier可以充当逻辑回归。 如果将损失设置为hinge,它将转换为线性 SVC。 它也可以采取其他损失函数的形式,甚至可以采用损失函数进行回归。

SGDRegressor使用squared_loss损失参数模拟线性回归。 取而代之的是,Huber 损失将平方损失转换为一定距离epsilon上的线性损失(另一固定参数)。 它也可以使用epsilon_insensitive损失函数或稍有不同的squared_epsilon_insensitive(会更严重地影响异常值)充当线性 SVR。

与在机器学习的其他情况下一样,无法先验估计数据科学问题上不同损失函数的表现。 无论如何,请考虑到如果要进行分类并且需要估计类概率,则只能将选择限制为logmodified_huber

需要调整以使该算法最适合您的数据的关键参数如下:

  • n_iter:数据上的迭代次数。 根据经验,通过次数越多,算法的优化效果越好。 但是,如果通过次数过多,则存在过度装配的较高风险。 根据经验,SGD 在看到10 ** 6个示例后趋于收敛到一个稳定的解。 根据您的示例,相应地设置迭代次数。
  • penalty:您必须选择 l1,l2 或 Elasticnet,它们都是不同的正则化策略,以避免由于过度参数化而导致过拟合(使用过多不必要的参数导致的记忆远大于对模式的学习)。 简而言之,l1 倾向于将无用系数减小为零,l2 只是将其衰减,而 Elasticnet 是 l1 和 l2 策略的混合。
  • alpha:这是正则项的乘数; alpha越高,正则化越多。 我们建议您通过执行10 ** -710 ** -1的网格搜索来找到最佳的alpha值。
  • l1_ratio:l1 比率用于弹性网惩罚。 建议值或 0.15 通常会非常有效。
  • learning_rate:这设置每个示例对系数的影响程度。 通常,对于分类器而言,它是最佳选择;对于回归而言,invscaling是最佳选择。 如果要使用invscaling进行分类,则必须设置eta0power_t (invscaling = eta0 / (t**power_t))。 使用invscaling,您可以从较低的学习率开始,尽管它会降低得较慢,但学习率低于最佳率。
  • epsilon:如果您的损失是huberepsilon_insensitivesquared_epsilon_insensitive,则应使用此选项。
  • shuffle:如果为True,则该算法将打乱训练数据的顺序,以提高学习的通用性。

窥探自然语言处理(NLP)

本节与机器学习不是严格相关的,但它包含自然语言处理领域的一些机器学习结果。 Python 有许多处理文本数据的包,用于文本处理的功能最强大且最完整的工具箱之一是 NLTK,即自然语言工具箱。

适用于 Python 社区的其他 NLP 工具包是 gensimspaCy

在以下各节中,我们将探讨 NLTK 核心功能。 我们将使用英语。 对于其他语言,您首先需要下载语言语料库(请注意,有时语言没有用于 NLTK 的免费开源语料库)。

请访问 NLTK 数据的官方网站,以使用多种语言来访问语料库和词法资源,从而可以与 NLTK 一起使用。

单词分词

分词是将文本拆分为单词的动作。 块空白似乎很容易,但并非如此,因为文本包含标点和收缩。 让我们从一个例子开始:

In: my_text = "The coolest job in the next 10 years will be " +\
              "statisticians. People think I'm joking, but " +\
              "who would've guessed that computer engineers " +\
              "would've been the coolest job of the 1990s?"
    simple_tokens = my_text.split(' ')
    print (simple_tokens)Out: ['The', 'coolest', 'job', 'in', 'the', 'next', '10', 'years', 'will', 
      'be', 'statisticians.', 'People', 'think', "I'm", 'joking,', 'but', 
      'who', "would've", 'guessed', 'that', 'computer', 'engineers',
      "would've", 'been', 'the', 'coolest', 'job', 'of', 'the', '1990s?']

在这里,您可以立即看到有问题。 以下标记包含多个单词:statisticians.(带有最后一个句点),I'm(两个单词),would've1990s?(带有最后一个问号)。 现在让我们看一下 NLTK 在此任务中的表现如何(当然,在幕后,该算法比简单的空白分块器还要复杂):

In: import nltk
    nltk_tokens = nltk.word_tokenize(my_text)
    print (nltk_tokens)
 Out: ['The', 'coolest', 'job', 'in', 'the', 'next', '10', 'years', 
      'will', 'be', 'statisticians', '.', 'People', 'think', 'I', 
      "'m", 'joking', ',', 'but', 'who', 'would', "'ve", 'guessed', 
      'that', 'computer', 'engineers', 'would', "'ve", 'been', 'the', 
      'coolest', 'job', 'of', 'the', '1990s', '?']

在执行此调用或其他一些 NLTK 包调用时,如果出现错误消息"Resource u'tokenizers/punkt/english.pickle' not found.",只需在控制台上键入nltk.download()并选择下载所有内容或浏览触发警告的丢失资源。

在这里,质量更好,并且每个令牌都与文本中的一个单词相关联。

注意.,?也是令牌。

还存在一个句子标记器(请参见nltk.tokenize.punkt模块),但是在数据科学中很少使用。

此外,除了通用英语标记器外,NLTK 还包含许多其他标记器,可在不同的上下文中使用。 例如,如果您正在处理推文,则TweetTokenizer对解析类似推文的文档非常有用。 最有用的选项是删除句柄,缩短连续字符并正确分词标签。 这是一个例子:

In: 
from nltk.tokenize import TweetTokenizer  
tt = TweetTokenizer(strip_handles=True, reduce_len=True) 
tweet = '@mate: I loooooooove this city!!!!!!! #love #foreverhere'
tt.tokenize(tweet) Out: [':', 'I', 'looove', 'this', 'city', '!', '!', '!', '#love', 
      '#foreverhere']

词干提取

词干提取是减少单词的变形形式并将单词带入其核心概念的动作。 例如,isbeaream后面的概念相同。 同样,gogoes以及tabletables后面的概念相同。 推导每个单词的词根概念的操作称为词干提取。 在 NLTK 中,您可以选择要使用的词干提取(有几种获取词根的方法)。 我们将向您展示其中一个,让 Jupyter 笔记本中的其他内容与本书的这一部分相关联:

In: from nltk.stem import *
    stemmer = LancasterStemmer()
    print ([stemmer.stem(word) for word in nltk_tokens]) Out: ['the', 'coolest', 'job', 'in', 'the', 'next', '10', 'year', 
      'wil', 'be', 'stat', '.', 'peopl', 'think', 'i', "'m", 'jok', 
      ',', 'but', 'who', 'would', "'ve", 'guess', 'that', 'comput', 
      'engin', 'would', "'ve", 'been', 'the', 'coolest', 'job', 
      'of', 'the', '1990s', '?']

在示例中,我们使用了 Lancaster 词干提取器,它是功能最强大且最新的算法之一。 检查结果,您将立即看到它们全部为小写,并且statistician与它的根stat相关联。 做得好!

单词标记

标记POS 标记是单词(或令牌)与其词性标记POS 标签)。 标记后,您知道动词,形容词,名词等在句子中的位置(和位置)。 即使在这种情况下,NLTK 也会使此复杂操作变得非常容易:

In: import nltk
    print (nltk.pos_tag(nltk_tokens)) Out: [('The', 'DT'), ('coolest', 'NN'), ('job', 'NN'), ('in', 'IN'), 
      ('the', 'DT'), ('next', 'JJ'), ('10', 'CD'), ('years', 'NNS'), 
      ('will', 'MD'), ('be', 'VB'), ('statisticians', 'NNS'), ('.', '.'), 
      ('People', 'NNS'), ('think', 'VBP'), ('I', 'PRP'), ("'m", 'VBP'), 
      ('joking', 'VBG'), (',', ','), ('but', 'CC'), ('who', 'WP'), 
      ('would', 'MD'), ("'ve", 'VB'), ('guessed', 'VBN'), ('that', 'IN'), 
      ('computer', 'NN'), ('engineers', 'NNS'), ('would', 'MD'), 
      ("'ve", 'VB'),  ('been', 'VBN'), ('the', 'DT'), ('coolest', 'NN'), 
      ('job', 'NN'), ('of', 'IN'), ('the', 'DT'), ('1990s', 'CD'), 
      ('?', '.')]

使用 NLTK 的语法,您将认识到The令牌表示确定符(DT),coolestjob表示名词(NN),in表示连词,依此类推。 关联非常详细; 对于动词,有六个可能的标记,如下所示:

  • takeVB(动词,基本形式)
  • tookVBD(动词,过去时)
  • takingVBG(动词,动名词)
  • takenVBN(动词,过去分词)
  • takeVBP(动词,单数现在时)
  • takesVBZ(动词,第三人称单数现在时)

如果您需要更详细的句子视图,则可能需要使用解析树标记器来理解其语法结构。 由于此操作非常适合逐句分析,因此在数据科学中很少使用此操作。

命名实体识别(NER)

NER 的目标是识别与人员,组织和位置相关的令牌。 让我们用一个例子来进一步解释它:

In: import nltk
    text = "Elvis Aaron Presley was an American singer and actor. Born in \
            Tupelo, Mississippi, when Presley was 13 years old he and his \ 
            family relocated to Memphis, Tennessee."
    chunks = nltk.ne_chunk(nltk.pos_tag(nltk.word_tokenize(text)))
    print (chunks) Out:  (S
      (PERSON Elvis/NNP)
      (PERSON Aaron/NNP Presley/NNP)
      was/VBD
      an/DT
      (GPE American/JJ)
      singer/NN
      and/CC
      actor/NN
      ./.
      Born/NNP
      in/IN
      (GPE Tupelo/NNP)
      ,/,
      (GPE Mississippi/NNP)
      ,/,
      when/WRB
      (PERSON Presley/NNP)
      was/VBD
      13/CD
      years/NNS
      old/JJ
      he/PRP
      and/CC
      his/PRP$
      family/NN
      relocated/VBD
      to/TO
      (GPE Memphis/NNP)
      ,/,
      (GPE Tennessee/NNP)
      ./.)

对猫王的维基百科页面的摘录进行了分析和 NER 处理。 此处列出了 NER 认可的一些实体:

  • 埃尔维斯·亚伦·普雷斯利PERSON(人物)
  • 美国人GPE(地缘政治实体)
  • 密西西比州图珀洛GPE(地缘政治实体)
  • 田纳西州孟菲斯GPE(地缘政治实体)

停用词

停用词是文本中信息最少的片(或标记),因为它们是最常见的词(例如,theitisasnot)。 停用词通常会被删除。 而且,如果将其删除,则与在特征选择阶段中发生的情况完全相同,处理所需的时间更少,内存也更少。 此外,有时它更准确。 删除停用词会降低文本的整体熵,从而使其中的任何信号更明显,更易于在特征中表示。

在 Scikit-learn 中也可以找到英语停用词列表。 有关其他语言的停用词,请查看 NLTK:

In: from sklearn.feature_extraction import text
    stop_words = text.ENGLISH_STOP_WORDS
    print (stop_words) Out: frozenset(['all', 'six', 'less', 'being', 'indeed', 'over', 'move',  
                'anyway', 'four', 'not', 'own', 'through', 'yourselves', 
                'fify', 'where', 'mill', 'only', 'find', 'before', 'one', 
                'whose', 'system', 'how', ... 
In: from nltk.corpus import stopwords
    print(stopwords.words('english'))
Out: ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 
      'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 
      'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 
      'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what',  
      'which', 'who', 'whom', 'this', 'that', 'these', ...
In: print(stopwords.words('german'))
Out: ['aber', 'alle', 'allem', 'allen', 'aller', 'alles', 'als', 'also', 
      'am', 'an', 'ander', 'andere', 'anderem', 'anderen', 'anderer', 
      'anderes', 'anderm', 'andern', 'anderr', 'anders', 'auch', 
      'auf', 'au', ...

完整的数据科学示例——文本分类

现在,这是一个完整的示例,可让您将每个文本放在正确的类别中。 我们将使用20newsgroup数据集,该数据集已在第 1 章,“第一步”中引入。 为了使事情更真实,并防止分类器过拟合数据,我们将删除电子邮件标头,页脚(例如签名)和引号。 另外,在这种情况下,目标是在两个相似的类别之间进行分类:sci.medsci.space。 我们将使用准确率度量来评估分类:

In: import nltk
    from sklearn.datasets import fetch_20newsgroups
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.linear_model import SGDClassifier
    from sklearn.metrics import accuracy_score
    from sklearn.datasets import fetch_20newsgroups
    import numpy as np
    categories = ['sci.med', 'sci.space']
    to_remove = ('headers', 'footers', 'quotes')
    twenty_sci_news_train = fetch_20newsgroups(subset='train', 
                          remove=to_remove, categories=categories)
    twenty_sci_news_test = fetch_20newsgroups(subset='test', 
                         remove=to_remove, categories=categories)

让我们从使用TfIdf预处理文本数据的最简单方法开始。 请记住,Tfidf是文档中单词的频率乘以所有文档中其频率的倒数。 高分表示该单词在当前文档中已被多次使用,但在其他文档中很少见(也就是说,它是文档的关键字):

In: tf_vect = TfidfVectorizer()
    X_train = tf_vect.fit_transform(twenty_sci_news_train.data)
    X_test = tf_vect.transform(twenty_sci_news_test.data)
    y_train = twenty_sci_news_train.target
    y_test = twenty_sci_news_test.target

现在,让我们使用线性分类器(SGDClassifier)来执行分类任务。 最后要做的是打印出分类精度:

In: clf = SGDClassifier()
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    print ("Accuracy=", accuracy_score(y_test, y_pred)) Out: Accuracy= 0.878481012658

精确度为87.8,是非常好的结果。 整个程序由少于 20 行代码组成。 现在,让我们看看是否可以做得更好。 在本章中,我们学习了停用词的删除,分词和词干提取。 让我们看看我们是否通过使用它们来获得准确率:

In: def clean_and_stem_text(text):
 tokens = nltk.word_tokenize(text.lower())        clean_tokens = [word for word in tokens if word not in stop_words]
        stem_tokens = [stemmer.stem(token) for token in clean_tokens]
        return " ".join(stem_tokens)
    cleaned_docs_train = [clean_and_stem_text(text) for text in  
                          twenty_sci_news_train.data]
    cleaned_docs_test = [clean_and_stem_text(text) for text in 
                         twenty_sci_news_test.data]

clean_and_stem_text函数基本上将小写,分词,词干提取和重构数据集中的每个文档。 最后,我们将应用与先前示例相同的预处理(Tfidf)和分类器(SGDClassifier):

In: X1_train = tf_vect.fit_transform(cleaned_docs_train)
    X1_test = tf_vect.transform(cleaned_docs_test)
    clf.fit(X1_train, y_train)
    y1_pred = clf.predict(X1_test)
    print ("Accuracy=", accuracy_score(y_test, y1_pred)) Out: Accuracy= 0.893670886076

此处理需要更多时间,但我们获得了约 1.5% 的精度。 对Tfidf参数的精确调整和对分类器参数的交叉验证选择最终将使准确率提高到 90% 以上。 到目前为止,我们对这种表现感到满意,但是您可以尝试突破这一障碍。

无监督学习概述

到目前为止,在所有方法中,每个样本或观察值都有其自己的目标标签或值。 在其他一些情况下,数据集是未标记的,并且要提取数据结构,您需要一种无监督的方法。 在本节中,我们将介绍两种执行聚类的方法,因为它们是无监督学习中最常用的方法之一。

请记住,经常将术语聚类无监督学习视为同义词,尽管实际上,无监督学习的含义更大。

K 均值

我们将介绍的第一种方法称为 K 均值,尽管存在不可避免的缺点,但它是最常用的聚类算法。 在信号处理中,K 均值等效于向量量化,即,选择最佳码字(从给定的码本中选择) 近似输入的观察值(或单词)。

您必须为算法提供 K 参数,即群集数。 有时,这可能是一个限制,因为您必须首先研究当前数据集的正确 K。

K 均值迭代 EM期望最大化)方法。 在第一阶段,它将每个训练点分配给最近的聚类质心; 在第二阶段中,它将聚类质心移动到分配给它的点的质心(以减少变形)。 重心的初始位置是随机的。 因此,您可能需要多次运行算法,以免找到局部最小值。

这就是算法背后的理论。 现在,让我们在实践中看到它。 在本节中,我们使用两个二维虚拟数据集,这些数据集将说明发生了什么情况。 两个数据集均由 2,000 个样本组成,因此您也可以对处理时间有所了解。

现在,让我们创建人工数据集,然后通过图表示它们:

In: %matplotlib inline
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn import datasets
    N_samples = 2000
    dataset_1 = np.array(datasets.make_circles(n_samples=N_samples, 
                         noise=0.05, factor=0.3)[0])
    dataset_2 = np.array(datasets.make_blobs(n_samples=N_samples, 
                         centers=4, cluster_std=0.4, random_state=0)    
    plt.scatter(dataset_1[:,0], dataset_1[:,1], c=labels_1, 
                alpha=0.8, s=64, edgecolors='white')
   plt.show()

这是我们创建的第一个数据集,由点的同心圆环组成(这是一个非常棘手的问题,因为所表示的群集是非球形的):

In: plt.scatter(dataset_2[:,0], dataset_2[:,1], alpha=0.8, s=64, 
                c='blue', edgecolors='white')
    plt.show()

这是第二个,由点分开的气泡组成:

现在是时候应用 K 均值了。 在这种情况下,我们将设置K=2。 让我们看看结果:

In: from sklearn.cluster import KMeans
    K_dataset_1 = 2
    km_1 = KMeans(n_clusters=K_dataset_1)
    labels_1 = km_1.fit(dataset*1).labels*     plt.scatter(dataset_1[:,0], dataset_1[:,1], c=labels_1, 
                alpha=0.8, s=64, edgecolors='white')
    plt.scatter(km_1.cluster_centers_[:,0], km_1.cluster_centers_[:,1], 
                s=200, c=np.unique(labels_1), edgecolors='black')
    plt.show()

这是获取关于此问题的结果:

如您所见,K 均值在此数据集上的表现不佳,因为它期望球形数据群集。 对于此数据集,应在使用 K 均值之前应用核 PCA。

现在,让我们看看它如何处理球面集群数据。 在这种情况下,基于对问题的了解和轮廓系数,我们将设置K=4

In: K_dataset_2 = 4
    km_2 = KMeans(n_clusters=K_dataset_2)
    labels_2 = km_2.fit(dataset*2).labels*     plt.scatter(dataset_2[:,0], dataset_2[:,1], c=labels_2, 
                alpha=0.8, s=64, edgecolors='white')
    plt.scatter(km_2.cluster_centers_[:,0], km_2.cluster_centers_[:,1], 
          marker='s', s=100, c=np.unique(labels_2), edgecolors='black')
    plt.show()

我们在此问题上得到的结果要好得多:

正如预期的那样,绘制的结果很好。 质心和聚类正是我们在查看未标记数据集时所想到的。 现在,我们将检查是否有其他聚类方法可以帮助我们解决非球形聚类问题。

在实际情况下,您可以考虑使用轮廓系数来了解群集的定义良好程度。 它是组内一致性的评估指标,适用于各种聚类结果,甚至适用于监督学习中的类别结构。 您可以在这个页面上了解有关剪影系数的更多信息。

DBSCAN ——一种基于密度的聚类技术

现在,我们将向您介绍 DBSCAN,这是一种基于密度的聚类技术。 这是一种非常简单的技术。 它选择一个随机点; 如果该点位于密集区域(如果它的邻居数超过 N 个),则它将开始扩展群集,包括所有邻居以及邻居的邻居,直到达到不再有邻居的点。

如果该点不在密集区域中,则将其分类为噪声。 然后,随机选择另一个未标记的点,然后过程重新开始。 该技术对非球形群集非常有用,但与球形群集同样有效。 输入只是邻域半径(eps参数,即被视为邻居的两个点之间的最大距离),输出是每个点的聚类成员资格标签。

请注意,由值 1 标记的点被 DBSCAN 分类为噪声。

让我们看一下先前介绍的数据集的示例:

In: from sklearn.cluster import DBSCAN
    dbs_1 = DBSCAN(eps=0.25)
    labels_1 = dbs_1.fit(dataset*1).labels*     plt.scatter(dataset_1[:,0], dataset_1[:,1], c=labels_1, 
                alpha=0.8, s=64, edgecolors='white')
    plt.show()

现在,可以通过 DBSCAN 算法正确定位群集:

结果现在是完美的。 没有将点分类为噪音(标签集中仅显示01标签):

In: np.unique(labels_1) Out: array([0, 1])

现在让我们转到另一个数据集:

In: dbs_2 = DBSCAN(eps=0.5)
    labels_2 = dbs_2.fit(dataset*2).labels*     plt.scatter(dataset_2[:,0], dataset_2[:,1], c=labels_2, 
                alpha=0.8, s=64, edgecolors='white')
    plt.show() In: np.unique(labels_2) Out: array([-1,  0,  1,  2,  3])

为 DBSCAN 选择最佳设置花费了一些时间,在这种情况下,已检测到四个聚类,并将几个点归类为噪声(因为标签集包含 -1):

在本节的最后,最后一个重要说明是,在 K 均值和 DBSCAN 的基本介绍中,我们一直使用欧几里得距离,因为它是这些函数中的默认距离度量(尽管如果您认为合适的话,其他距离度量也可以使用)。 在实际情况下使用此距离时,请记住您必须对每个特征进行归一化(z 归一化),以便每个特征对最终失真的贡献均相等。 如果未对数据集进行规范化,则具有更大支持的特征将对输出标签具有更大的决策权,而这正是我们所不希望的。

潜在狄利克雷分布(LDA)

对于文本,可以使用潜在狄利克雷分布LDA 作为流行的无监督算法,该算法可用于理解文档集合中的一组常见单词。

请注意,另一种算法,线性判别分析,也具有相同的首字母缩写词,但是这两种算法是完全不相关的。

LDA 的目的是从一组文档中提取同类单词或主题集。 该算法的数学原理非常先进。 在这里,我们将看到它的一个实际概念。

让我们从一个示例开始,解释为什么 LDA 如此流行以及为什么其他无监督的方法在处理文本时不够好。 例如,K 均值和 DBSCAN 为每个样本提供了一个艰难的决定,将每个点置于不相交的分区中。 相反,文档常常描述一起涵盖的主题(想想莎士比亚的书;它们很好地融合了悲剧,浪漫史和冒险经历)。 在文本文件上,任何艰难的决定几乎肯定是错误的。 相反,LDA 提供构成文档的主题的混合输出,以及指示在文档中代表了多少主题的指示。

让我们用一个例子来解释它是如何工作的。 我们将在两个类别上训练算法; 汽车和药品,来自 20 个新闻组的数据集(在本章前面的较早段落“准备工具和数据集”中,我们已经使用了相同的数据集):

In: import nltk
    Import gensim
    from sklearn.datasets import fetch_20newsgroups

    def tokenize(text):
 return [token.lower() \
                for token in gensim.utils.simple_preprocess(text) \ if token not in gensim.parsing.preprocessing.STOPWORDS]    text_dataset=fetch_20newsgroups(categories=['rec.autos','sci.med'],
 random_state=101, remove=('headers', 'footers', 'quotes')) documents = text_dataset.data print("Document count:", len(documents)) Out: Document count: 1188

构成数据集的 1,188 个文档中的每个文档都是一个字符串。 例如,第一个文档包含以下文本:

In: documents[0] 
Out: 'nI have a new doctor who gave me a prescription today for something called nSeptra DS. He said it may cause GI problems and I have a sensitive stomach nto begin with. Anybody ever taken this antibiotic. Any good?  Suggestions nfor avoiding an upset stomach?  Other tips?n'

这份文件绝对是关于医学的。 无论如何,算法没有什么真正重要的。 现在,让我们分词并创建一个包含数据集中所有单词的字典。 请注意,令牌化操作还会删除停用词并将每个词都用小写字母表示:

In: processed_docs = [tokenize(doc) for doc in documents]
    word_dic = gensim.corpora.Dictionary(processed_docs)
    print("Num tokens:", len(word_dic)) **Out:** **Num tokens: 16161**

在数据集中,有超过 16,000 个不同的词。 现在是时候过滤太常见的单词和太稀有的单词了。 在这一步中,我们将使单词出现至少 10 次,且不超过文档的 20%。 至此,我们有了每个文档的“单词袋”(或 BoW)表示形式; 也就是说,每个文档都表示为字典,其中包含每个单词在文本中出现的次数。 文本中每个单词的绝对位置都会丢失,就像将文档中的所有单词都放在袋子中一样。 结果,并非基于此方法在特征中捕获文本中的所有信号,但在大多数情况下,足以建立有效的模型:

In: word_dic.filter_extremes(no_below=10, no_above=0.2)
    bow = [word_dic.doc2bow(doc) for doc in processed_docs]

最后,这是 LDA 的核心类。 在此示例中,我们指示 LDA 在数据集中只有两个主题。 我们还提供了其他参数来使算法收敛(如果不收敛,您将收到 Python 解释器的警告)。 请注意,此算法可在计算机上的许多 CPU 上使用,以加快处理速度。 如果它不起作用,请使用具有相同参数的单进程类gensim.models.ldamodel.LdaModel

In: lda_model = gensim.models.LdaMulticore(bow, num_topics=2, 
                                           id2word=word_dic, passes=10, 
                                           iterations=500)

最后,几分钟后,模型被训练。 要查看单词和主题之间的关联,请运行以下代码:

In: lda_model.print_topics(-1)  
Out: [(0, '0.011*edu + 0.008*com + 0.007*health + 0.007*medical + 
       0.007*new + 0.007*use + 0.006*people + 0.005*time + 
       0.005*years + 0.005*patients'), (1, '0.018*car + 0.008*good + 
       0.008*think + 0.008*cars + 0.007*msg + 0.006*time + 
       0.006*people + 0.006*water + 0.005*food + 0.005*engine')]

如您所见,该算法遍历了所有文档,并了解到主要主题是汽车和医学。 请注意,该算法并未提供主题的简称,而是其组成(数字是每个主题中每个单词的权重,从高到低排列)。 另外,请注意,两个主题中都出现了一些单词; 它们是模棱两可的词,可以在两种意义上使用。

最后,让我们看看该算法如何在看不见的文档上工作。 为了使事情变得容易,让我们创建一个包含两个主题的句子,例如“我已经给医生看了我的新车。 他喜欢它的大轮子!”。然后,在为这个新文档创建了词袋表示法之后,LDA 将产生两个分数,每个主题一个分数:

In: new_doc = "I've shown the doctor my new car. He loved its big wheels!"
    bow_doc = word_dic.doc2bow(tokenize(new_doc))
    for index, score in sorted(lda_model[bow_doc], key=lambda tup: 
    -1*tup[1]):
    print("Score: {}t Topic: {}".format(score,  
         lda_model.print_topic(index, 5))) Out: Score: 0.5047402389474193   Topic: 0.011*edu + 0.008*com +  
            0.007*health + 0.007*medical + 0.007*new
     Score: 0.49525976105258074  Topic: 0.018*car + 0.008*good + 
            0.008*think + 0.008*cars + 0.007*msg

两个主题的得分都在 0.5 和 0.5 左右,这意味着句子包含了主题carmedicine的良好平衡。 我们在这里显示的只是两个主题的示例; 但是由于执行库 Gensim,相同的实现也可以在几小时内分配整个英语维基百科的进程。

Word2Vec 算法提供了与 LDA 不同的方法,Word2Vec 算法是一种用于在向量中嵌入单词的最新模型。 与 LDA 相比,Word2Vec 跟踪句子中单词的位置,并且此附加上下文有助于更好地消除某些单词的歧义。 Word2Vec 使用类似于深度学习的方法进行训练,但是 Gensim 库提供的实现使训练和使用变得非常容易。 请注意,虽然 LDA 旨在理解文档中的主题,但 Word2Vec 在单词级别起作用,并试图了解低维空间中单词之间的语义关系(即,为每个单词创建一个 N 维向量) 。 让我们看一个例子,使事情变得清楚。

我们将使用电影评论数据集来训练 Word2Vec 模型。 只需通过将组成语料库的句子传递给 Word2Vec 构造器,并最终传递可以并行执行训练任务的工作器数量即可完成训练:

In: from gensim.models import Word2Vec
    from nltk.corpus import movie_reviews
    w2v = Word2Vec(movie_reviews.sents(), workers=4)
    w2v.init_sims(replace=True)

最后一行代码只是冻结了模型,不允许进行任何其他更新。 这也带来了另一个非常受欢迎的好处:减少对象的内存指纹。

表示单词的向量可视化可能很复杂; 因此,让我们看一些相似性(即,低维子空间中的相似向量)。 在这里,我们将要求模型提供与单词housecountryside最相似的前五个单词(以及相似性得分)。 这只是一个例子。 可以为输入语料库中包含的所有单词检索相似的单词:

In: w2v.wv.most_similar('house', topn=5) 
Out: [('apartment', 0.8799251317977905),  ('body', 0.8719735145568848), ('hotel', 0.8618944883346558), ('head', 0.848749041557312), ('boat', 0.8469674587249756)] 
In: w2v.vw.most_similar('countryside', topn=5) 
Out: [('motorcycle', 0.9531803131103516),  ('marches', 0.9499938488006592), ('rural', 0.9467764496803284), ('shuttle', 0.9466159343719482), ('mining', 0.9461280107498169)]

Word2Vec 如何做到这一点? 简单地说,在低维向量空间中具有相似性得分。 实际上,要查看每个单词的向量表示,请执行以下操作:

In: w2v.wv['countryside'] 
Out: array([-0.09412272,  0.07695948, -0.14981066,  0.04894404, 
            -0.03712097, -0.17099065, -0.0379245 , -0.05336253,  
             0.06084964, -0.01273731, -0.03949985, -0.06456301, 
            -0.03289359, -0.06889232,   0.02217194, ...

数组由 100 个维度组成; 您可以在训练模型时通过设置size参数来增加或减少它。 默认值为 100。

在我们先前使用的most_similar方法中,您还可以指定要使用的否定词(即,减去相似的词)。 一个经典的例子是找到与womanking类似的词,而没有queen。 毫不奇怪,最高结果是man

In: w2v.wv.most_similar(positive=['woman', 'king'], negative=['queen'], 
                        topn=3) 
Out: [('man', 0.8440324068069458),  ('girl', 0.7671926021575928), ('child', 0.7635241746902466)]

由于向量表示,该模型还提供了在一组相似单词中识别不匹配单词的方法。 也就是说,与上下文不匹配的单词(在这种情况下,上下文是卧室):

In: w2v.wv.doesnt_match(['bed', 'pillow', 'cake', 'mattress']) Out: 'cake'    

最后,所有前面的方法都基于相似度分数。 该模型还提供了单词之间相似度的原始分数。 这是womangirl以及womanboy相似性得分的示例。 第一个相似度更高,但第二个相似度不为零,这是因为我们在谈论人的事实将两个词联系在一起:

In: w2v.wv.similarity('woman', 'girl'), w2v.similarity('woman', 'boy') 
Out: (0.90198267746062233, 0.82372486297773828)

总结

在本章中,我们介绍了机器学习的要点。 我们从一些简单但仍然非常有效的分类器开始(线性和逻辑回归器,朴素贝叶斯和 K 最近邻)。 然后,我们继续使用更高级的(SVM)。 我们介绍了如何将弱分类器组合在一起(集合,随机森林,梯度树增强),并介绍了三个很棒的梯度提升分类器:XGboost,LightGBM 和 CatBoost。 最后,我们窥视了大数据,集群和 NLP 中使用的算法。

在下一章中,我们将向您介绍 Matplotlib 可视化的基础知识,如何使用 Pandas 操作 EDA 并通过 Seaborn 实现漂亮的可视化,以及如何设置 Web 服务器以按需提供信息。