Python-机器学习秘籍第二版-四-

116 阅读58分钟

Python 机器学习秘籍第二版(四)

原文:annas-archive.org/md5/343c5e6c97737f77853e89eacb95df75

译者:飞龙

协议:CC BY-NC-SA 4.0

第十章:使用特征选择进行降维

10.0 Introduction

在第九章中,我们讨论了如何通过创建具有(理想情况下)类似能力的新特征来降低特征矩阵的维度。这称为特征提取。在本章中,我们将介绍一种替代方法:选择高质量、信息丰富的特征并丢弃不太有用的特征。这称为特征选择

有三种特征选择方法:过滤、包装和嵌入。过滤方法通过检查特征的统计属性选择最佳特征。我们明确设置统计量的阈值或手动选择要保留的特征数的方法是通过过滤进行特征选择的示例。包装方法使用试错法找到产生质量预测模型的特征子集。包装方法通常是最有效的,因为它们通过实际试验而非简单的假设来找到最佳结果。最后,嵌入方法在学习算法的培训过程中选择最佳特征子集作为其延伸部分。

理想情况下,我们会在本章节中描述所有三种方法。然而,由于嵌入方法与特定的学习算法紧密相连,要在深入探讨算法本身之前解释它们是困难的。因此,在本章中,我们仅涵盖过滤和包装特征选择方法,将嵌入方法的讨论留到那些深入讨论这些学习算法的章节中。

10.1 数值特征方差阈值法

Problem

您有一组数值特征,并希望过滤掉那些方差低(即可能包含较少信息)的特征。

Solution

选择方差高于给定阈值的特征子集:

# Load libraries
from sklearn import datasets
from sklearn.feature_selection import VarianceThreshold

# Import some data to play with
iris = datasets.load_iris()

# Create features and target
features = iris.data
target = iris.target

# Create thresholder
thresholder = VarianceThreshold(threshold=.5)

# Create high variance feature matrix
features_high_variance = thresholder.fit_transform(features)

# View high variance feature matrix
features_high_variance[0:3]
array([[ 5.1,  1.4,  0.2],
       [ 4.9,  1.4,  0.2],
       [ 4.7,  1.3,  0.2]])

Discussion

方差阈值法(VT)是一种通过过滤进行特征选择的示例,也是特征选择的最基本方法之一。其动机是低方差特征可能不太有趣(并且不太有用),而高方差特征可能更有趣。VT 首先计算每个特征的方差:

V a r ( x ) = 1 n ∑ i=1 n (x i -μ) 2

其中 x 是特征向量,xi 是单个特征值,μ 是该特征的平均值。接下来,它删除所有方差未达到该阈值的特征。

在使用 VT 时要牢记两点。首先,方差未居中;即,它位于特征本身的平方单位中。因此,当特征集包含不同单位时(例如,一个特征以年为单位,而另一个特征以美元为单位),VT 将无法正常工作。其次,方差阈值是手动选择的,因此我们必须凭借自己的判断来选择一个合适的值(或者使用第十二章中描述的模型选择技术)。我们可以使用 variances_ 查看每个特征的方差:

# View variances
thresholder.fit(features).variances_
array([0.68112222, 0.18871289, 3.09550267, 0.57713289])

最后,如果特征已经标准化(均值为零,方差为单位),那么很显然 VT 将无法正确工作:

# Load library
from sklearn.preprocessing import StandardScaler

# Standardize feature matrix
scaler = StandardScaler()
features_std = scaler.fit_transform(features)

# Caculate variance of each feature
selector = VarianceThreshold()
selector.fit(features_std).variances_
array([1., 1., 1., 1.])

10.2 二进制特征方差的阈值处理

问题

您拥有一组二进制分类特征,并希望过滤掉方差低的特征(即可能包含少量信息)。

解决方案

选择一个伯努利随机变量方差高于给定阈值的特征子集:

# Load library
from sklearn.feature_selection import VarianceThreshold

# Create feature matrix with:
# Feature 0: 80% class 0
# Feature 1: 80% class 1
# Feature 2: 60% class 0, 40% class 1
features = [[0, 1, 0],
            [0, 1, 1],
            [0, 1, 0],
            [0, 1, 1],
            [1, 0, 0]]

# Run threshold by variance
thresholder = VarianceThreshold(threshold=(.75 * (1 - .75)))
thresholder.fit_transform(features)
array([[0],
       [1],
       [0],
       [1],
       [0]])

讨论

与数值特征类似,选择高信息二分类特征并过滤掉信息较少的策略之一是检查它们的方差。在二进制特征(即伯努利随机变量)中,方差计算如下:

Var ( x ) = p ( 1 - p )

其中 p 是类 1 观察值的比例。因此,通过设置 p,我们可以移除大多数观察值为一类的特征。

10.3 处理高度相关的特征

问题

您有一个特征矩阵,并怀疑某些特征之间高度相关。

解决方案

使用相关性矩阵检查高度相关特征。如果存在高度相关的特征,请考虑删除其中一个:

# Load libraries
import pandas as pd
import numpy as np

# Create feature matrix with two highly correlated features
features = np.array([[1, 1, 1],
                     [2, 2, 0],
                     [3, 3, 1],
                     [4, 4, 0],
                     [5, 5, 1],
                     [6, 6, 0],
                     [7, 7, 1],
                     [8, 7, 0],
                     [9, 7, 1]])

# Convert feature matrix into DataFrame
dataframe = pd.DataFrame(features)

# Create correlation matrix
corr_matrix = dataframe.corr().abs()

# Select upper triangle of correlation matrix
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape),
                          k=1).astype(bool))

# Find index of feature columns with correlation greater than 0.95
to_drop = [column for column in upper.columns if any(upper[column] > 0.95)]

# Drop features
dataframe.drop(dataframe.columns[to_drop], axis=1).head(3)
02
011
120
231

讨论

在机器学习中,我们经常遇到的一个问题是高度相关的特征。如果两个特征高度相关,那么它们所包含的信息非常相似,同时包含这两个特征很可能是多余的。对于像线性回归这样简单的模型,如果不移除这些特征,则违反了线性回归的假设,并可能导致人为膨胀的 R-squared 值。解决高度相关特征的方法很简单:从特征集中删除其中一个特征。通过设置相关性阈值来移除高度相关特征是另一种筛选的例子。

在我们的解决方案中,首先我们创建了所有特征的相关性矩阵:

# Correlation matrix
dataframe.corr()
012
01.0000000.9761030.000000
10.9761031.000000-0.034503
20.000000-0.0345031.000000

接着,我们查看相关性矩阵的上三角来识别高度相关特征的成对:

# Upper triangle of correlation matrix
upper
012
0NaN0.9761030.000000
1NaNNaN0.034503
2NaNNaNNaN

其次,我们从这些成对特征中移除一个特征。

10.4 删除分类中无关紧要的特征

问题

您有一个分类目标向量,并希望删除无信息的特征。

解决方案

如果特征是分类的,请计算每个特征与目标向量之间的卡方统计量(χ 2):

# Load libraries
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2, f_classif

# Load data
iris = load_iris()
features = iris.data
target = iris.target

# Convert to categorical data by converting data to integers
features = features.astype(int)

# Select two features with highest chi-squared statistics
chi2_selector = SelectKBest(chi2, k=2)
features_kbest = chi2_selector.fit_transform(features, target)

# Show results
print("Original number of features:", features.shape[1])
print("Reduced number of features:", features_kbest.shape[1])
Original number of features: 4
Reduced number of features: 2

如果特征是数量的,请计算每个特征与目标向量之间的 ANOVA F 值:

# Select two features with highest F-values
fvalue_selector = SelectKBest(f_classif, k=2)
features_kbest = fvalue_selector.fit_transform(features, target)

# Show results
print("Original number of features:", features.shape[1])
print("Reduced number of features:", features_kbest.shape[1])
Original number of features: 4
Reduced number of features: 2

而不是选择特定数量的特征,我们可以使用SelectPercentile来选择顶部n百分比的特征:

# Load library
from sklearn.feature_selection import SelectPercentile

# Select top 75% of features with highest F-values
fvalue_selector = SelectPercentile(f_classif, percentile=75)
features_kbest = fvalue_selector.fit_transform(features, target)

# Show results
print("Original number of features:", features.shape[1])
print("Reduced number of features:", features_kbest.shape[1])
Original number of features: 4
Reduced number of features: 3

讨论

卡方统计检验两个分类向量的独立性。也就是说,统计量是类别特征中每个类别的观察次数与如果该特征与目标向量独立(即没有关系)时预期的观察次数之间的差异:

χ 2 = ∑ i=1 n (O i -E i ) 2 E i

其中Oi是类别i中观察到的观测次数,Ei是类别i中预期的观测次数。

卡方统计量是一个单一的数字,它告诉您观察计数和在整体人群中如果没有任何关系时预期计数之间的差异有多大。通过计算特征和目标向量之间的卡方统计量,我们可以得到两者之间独立性的度量。如果目标与特征变量无关,那么对我们来说它是无关紧要的,因为它不包含我们可以用于分类的信息。另一方面,如果两个特征高度依赖,它们可能对训练我们的模型非常有信息性。

要在特征选择中使用卡方,我们计算每个特征与目标向量之间的卡方统计量,然后选择具有最佳卡方统计量的特征。在 scikit-learn 中,我们可以使用SelectKBest来选择它们。参数k确定我们想要保留的特征数,并过滤掉信息最少的特征。

需要注意的是,卡方统计只能在两个分类向量之间计算。因此,特征选择的卡方要求目标向量和特征都是分类的。然而,如果我们有一个数值特征,我们可以通过首先将定量特征转换为分类特征来使用卡方技术。最后,为了使用我们的卡方方法,所有值都需要是非负的。

或者,如果我们有一个数值特征,我们可以使用f_classif来计算 ANOVA F 值统计量和每个特征以及目标向量的相关性。F 值分数检查如果我们按照目标向量对数值特征进行分组,每个组的平均值是否显著不同。例如,如果我们有一个二进制目标向量,性别和一个定量特征,测试分数,F 值将告诉我们男性的平均测试分数是否与女性的平均测试分数不同。如果不是,则测试分数对我们预测性别没有帮助,因此该特征是无关的。

10.5 递归消除特征

问题

你想要自动选择保留的最佳特征。

解决方案

使用 scikit-learn 的RFECV进行递归特征消除(RFE),使用交叉验证(CV)。也就是说,使用包装器特征选择方法,重复训练模型,每次删除一个特征,直到模型性能(例如准确性)变差。剩下的特征就是最好的:

# Load libraries
import warnings
from sklearn.datasets import make_regression
from sklearn.feature_selection import RFECV
from sklearn import datasets, linear_model

# Suppress an annoying but harmless warning
warnings.filterwarnings(action="ignore", module="scipy",
                        message="^internal gelsd")

# Generate features matrix, target vector, and the true coefficients
features, target = make_regression(n_samples = 10000,
                                   n_features = 100,
                                   n_informative = 2,
                                   random_state = 1)

# Create a linear regression
ols = linear_model.LinearRegression()

# Recursively eliminate features
rfecv = RFECV(estimator=ols, step=1, scoring="neg_mean_squared_error")
rfecv.fit(features, target)
rfecv.transform(features)
array([[ 0.00850799,  0.7031277 ,  1.52821875],
       [-1.07500204,  2.56148527, -0.44567768],
       [ 1.37940721, -1.77039484, -0.74675125],
       ...,
       [-0.80331656, -1.60648007,  0.52231601],
       [ 0.39508844, -1.34564911,  0.4228057 ],
       [-0.55383035,  0.82880112,  1.73232647]])

一旦我们进行了 RFE,我们就可以看到我们应该保留的特征数量:

# Number of best features
rfecv.n_features_
3

我们还可以看到哪些特征应该保留:

# Which categories are best
rfecv.support_
array([False, False, False, False, False,  True, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False,  True, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False,  True, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False])

我们甚至可以查看特征的排名:

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

讨论

这可能是本书到目前为止最复杂的配方,结合了一些我们尚未详细讨论的主题。然而,直觉足够简单,我们可以在这里解释它,而不是推迟到以后的章节。RFE 背后的想法是重复训练模型,每次更新该模型的权重系数。第一次训练模型时,我们包括所有特征。然后,我们找到具有最小参数的特征(请注意,这假设特征已经重新缩放或标准化),意味着它不太重要,并从特征集中删除该特征。

那么显而易见的问题是:我们应该保留多少特征?我们可以(假设性地)重复此循环,直到我们只剩下一个特征。更好的方法要求我们包括一个新概念叫交叉验证。我们将在下一章详细讨论 CV,但这里是一般的想法。

给定包含(1)我们想要预测的目标和(2)特征矩阵的数据,首先我们将数据分为两组:一个训练集和一个测试集。其次,我们使用训练集训练我们的模型。第三,我们假装不知道测试集的目标,并将我们的模型应用于其特征以预测测试集的值。最后,我们将我们预测的目标值与真实的目标值进行比较,以评估我们的模型。

我们可以使用 CV 找到在 RFE 期间保留的最佳特征数。具体而言,在带有 CV 的 RFE 中,每次迭代后我们使用交叉验证评估我们的模型。如果 CV 显示在我们消除一个特征后模型改善了,那么我们继续下一个循环。然而,如果 CV 显示在我们消除一个特征后模型变差了,我们将该特征重新放回特征集,并选择这些特征作为最佳特征。

在 scikit-learn 中,使用RFECV实现了带有多个重要参数的 RFE 与 CV。estimator参数确定我们想要训练的模型类型(例如线性回归),step参数在每个循环中设置要删除的特征数量或比例,scoring参数设置我们在交叉验证期间用于评估模型质量的度量标准。

参见

第十一章:模型评估

11.0 介绍

在本章中,我们将探讨评估通过我们的学习算法创建的模型质量的策略。在讨论如何创建它们之前讨论模型评估可能看起来很奇怪,但我们的疯狂之中有一种方法。模型的实用性取决于其预测的质量,因此,从根本上说,我们的目标不是创建模型(这很容易),而是创建高质量的模型(这很难)。因此,在探索多种学习算法之前,让我们首先了解如何评估它们产生的模型。

11.1 交叉验证模型

问题

您希望评估您的分类模型在未预料到的数据上的泛化能力。

解决方案

创建一个管道,对数据进行预处理,训练模型,然后使用交叉验证进行评估:

# Load libraries
from sklearn import datasets
from sklearn import metrics
from sklearn.model_selection import KFold, cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

# Load digits dataset
digits = datasets.load_digits()

# Create features matrix
features = digits.data

# Create target vector
target = digits.target

# Create standardizer
standardizer = StandardScaler()

# Create logistic regression object
logit = LogisticRegression()

# Create a pipeline that standardizes, then runs logistic regression
pipeline = make_pipeline(standardizer, logit)

# Create k-fold cross-validation
kf = KFold(n_splits=5, shuffle=True, random_state=0)

# Conduct k-fold cross-validation
cv_results = cross_val_score(pipeline, # Pipeline
                             features, # Feature matrix
                             target, # Target vector
                             cv=kf, # Performance metric
                             scoring="accuracy", # Loss function
                             n_jobs=-1) # Use all CPU cores

# Calculate mean
cv_results.mean()
0.969958217270195

讨论

乍一看,评估监督学习模型似乎很简单:训练一个模型,然后使用某种性能指标(准确率、平方误差等)计算其表现。然而,这种方法基本上是有缺陷的。如果我们使用我们的数据训练一个模型,然后评估它在该数据上的表现,我们并没有达到我们的预期目标。我们的目标不是评估模型在训练数据上的表现,而是评估它在从未见过的数据上的表现(例如新客户、新犯罪案件、新图像)。因此,我们的评估方法应该帮助我们理解模型在从未见过的数据上进行预测的能力有多好。

一种策略可能是留出一部分数据用于测试。这被称为验证(或留出法)。在验证中,我们的观察(特征和目标)被分成两个集合,传统上称为训练集测试集。我们拿出测试集并将其放在一边,假装我们以前从未见过它。接下来,我们使用训练集训练我们的模型,使用特征和目标向量来教模型如何做出最佳预测。最后,我们通过评估模型在测试集上的表现来模拟从未见过的外部数据。然而,验证方法有两个主要弱点。首先,模型的性能可能高度依赖于被选择为测试集的少数观察结果。其次,模型不是在所有可用数据上进行训练,也没有在所有可用数据上进行评估。

克服这些弱点的更好策略被称为k 折交叉验证(KFCV)。在 KFCV 中,我们将数据分成k个部分,称为折叠。然后,模型使用k-1个折叠组成的一个训练集进行训练,然后最后一个折叠被用作测试集。我们重复这个过程k次,每次使用不同的折叠作为测试集。然后对每个k次迭代中模型的表现进行平均,以产生一个总体测量。

在我们的解决方案中,我们使用五折交叉验证并将评估分数输出到 cv_results 中:

# View score for all 5 folds
cv_results
array([0.96111111, 0.96388889, 0.98050139, 0.97214485, 0.97214485])

在使用 KFCV 时有三个重要的注意事项。首先,KFCV 假设每个观察结果都是独立生成的(即数据是独立同分布的[IID])。如果数据是 IID 的,将观察结果随机分配到 fold 时进行洗牌是一个好主意。在 scikit-learn 中,我们可以设置 shuffle=True 来执行洗牌。

其次,在使用 KFCV 评估分类器时,通常有利于每个 fold 中大致包含来自不同目标类的观察结果的相同百分比(称为分层 k 折)。例如,如果我们的目标向量包含性别信息,并且观察结果中有 80% 是男性,那么每个 fold 将包含 80% 的男性和 20% 的女性观察结果。在 scikit-learn 中,我们可以通过将 KFold 类替换为 StratifiedKFold 来执行分层 k 折交叉验证。

最后,在使用验证集或交叉验证时,重要的是基于训练集预处理数据,然后将这些转换应用到训练集和测试集。例如,当我们对标准化对象 standardizer 进行 fit 操作时,我们仅计算训练集的均值和方差。然后,我们使用 transform 将该转换应用于训练集和测试集:

# Import library
from sklearn.model_selection import train_test_split

# Create training and test sets
features_train, features_test, target_train, target_test = train_test_split(
    features, target, test_size=0.1, random_state=1)

# Fit standardizer to training set
standardizer.fit(features_train)

# Apply to both training and test sets which can then be used to train models
features_train_std = standardizer.transform(features_train)
features_test_std = standardizer.transform(features_test)

这样做的原因是因为我们假设测试集是未知数据。如果我们使用观察结果来同时拟合训练集和测试集的预处理器,测试集中的部分信息就会泄漏到训练集中。对于任何预处理步骤,如特征选择,都适用这一规则。

scikit-learn 的 pipeline 包使得在使用交叉验证技术时变得更加简单。我们首先创建一个管道来预处理数据(例如 standardizer),然后训练一个模型(逻辑回归,logit):

# Create a pipeline
pipeline = make_pipeline(standardizer, logit)

然后,我们使用该管道运行 KFCV,scikit 会为我们完成所有工作:

# Do k-fold cross-validation
cv_results = cross_val_score(pipeline, # Pipeline
                             features, # Feature matrix
                             target, # Target vector
                             cv=kf, # Performance metric
                             scoring="accuracy", # Loss function
                             n_jobs=-1) # Use all CPU cores

cross_val_score 带有三个参数,我们还没有讨论过,但值得注意:

cv

cv 确定了我们的交叉验证技术。K 折是目前最常用的,但还有其他方法,例如留一法交叉验证,其中 fold 的数量 k 等于数据集中的数据点数量。

scoring

scoring 定义了成功的度量标准,本章的其他示例中讨论了其中一些。

n_jobs=-1

n_jobs=-1 告诉 scikit-learn 使用所有可用的核心。例如,如果您的计算机有四个核心(笔记本电脑上常见的数量),那么 scikit-learn 将同时使用所有四个核心来加速操作。

一个小提示:当运行其中一些示例时,您可能会看到一个警告,提示“ConvergenceWarning: lbfgs failed to converge.” 这些示例中使用的配置旨在防止这种情况发生,但如果仍然发生,您可以暂时忽略它。我们将在本书后面深入研究具体类型的模型时解决此类问题。

参见

11.2 创建一个基线回归模型

问题

您想要一个简单的基线回归模型,以便与您训练的其他模型进行比较。

解决方案

使用 scikit-learn 的DummyRegressor创建一个简单的基线模型:

# Load libraries
from sklearn.datasets import load_wine
from sklearn.dummy import DummyRegressor
from sklearn.model_selection import train_test_split

# Load data
wine = load_wine()

# Create features
features, target = wine.data, wine.target

# Make test and training split
features_train, features_test, target_train, target_test = train_test_split(
    features, target, random_state=0)

# Create a dummy regressor
dummy = DummyRegressor(strategy='mean')

# "Train" dummy regressor
dummy.fit(features_train, target_train)

# Get R-squared score
dummy.score(features_test, target_test)
-0.0480213580840978

为了比较,我们训练我们的模型并评估性能分数:

# Load library
from sklearn.linear_model import LinearRegression

# Train simple linear regression model
ols = LinearRegression()
ols.fit(features_train, target_train)

# Get R-squared score
ols.score(features_test, target_test)
0.804353263176954

讨论

DummyRegressor 允许我们创建一个非常简单的模型,我们可以用作基线,以与我们训练的任何其他模型进行比较。这通常可以用来模拟产品或系统中的“天真”现有预测过程。例如,产品可能最初被硬编码为假设所有新用户在第一个月内都会花费 100 美元,而不考虑其特征。如果我们将这种假设编码到基线模型中,我们就能够通过比较虚拟模型的score与训练模型的分数来明确说明使用机器学习方法的好处。

DummyRegressor 使用strategy参数来设置预测方法,包括在训练集中使用平均值或中位数。此外,如果我们将strategy设置为constant并使用constant参数,我们可以设置虚拟回归器来预测每个观测的某个常数值:

# Create dummy regressor that predicts 1s for everything
clf = DummyRegressor(strategy='constant', constant=1)
clf.fit(features_train, target_train)

# Evaluate score
clf.score(features_test, target_test)
-0.06299212598425186

关于score的一个小注意事项。默认情况下,score返回确定系数(R-squared,R 2)得分:

R 2 = 1 - ∑ i (y i -y ^ i ) 2 ∑ i (y i -y ¯) 2

其中y i是目标观测的真实值,y^ i是预测值,而y¯是目标向量的平均值。

R 2越接近 1,目标向量中方差被特征解释的程度就越高。

11.3 创建一个基准分类模型

问题

您想要一个简单的基线分类器来与您的模型进行比较。

解决方案

使用 scikit-learn 的DummyClassifier

# Load libraries
from sklearn.datasets import load_iris
from sklearn.dummy import DummyClassifier
from sklearn.model_selection import train_test_split

# Load data
iris = load_iris()

# Create target vector and feature matrix
features, target = iris.data, iris.target

# Split into training and test set
features_train, features_test, target_train, target_test = train_test_split(
   features, target, random_state=0)

# Create dummy classifier
dummy = DummyClassifier(strategy='uniform', random_state=1)

# "Train" model
dummy.fit(features_train, target_train)

# Get accuracy score
dummy.score(features_test, target_test)
0.42105263157894735

通过比较基线分类器和我们训练的分类器,我们可以看到改进:

# Load library
from sklearn.ensemble import RandomForestClassifier

# Create classifier
classifier = RandomForestClassifier()

# Train model
classifier.fit(features_train, target_train)

# Get accuracy score
classifier.score(features_test, target_test)
0.9736842105263158

讨论

一个分类器性能的常见测量是它比随机猜测好多少。scikit-learn 的DummyClassifier使得这种比较变得容易。strategy参数提供了多种生成值的选项。有两种特别有用的策略。首先,stratified按训练集目标向量的类比例生成预测(例如,如果训练数据中有 20%的观察结果是女性,则DummyClassifier将 20%的时间预测为女性)。其次,uniform将在不同类别之间以均匀随机方式生成预测。例如,如果观察结果中有 20%是女性,80%是男性,则uniform会生成 50%女性和 50%男性的预测。

参见

11.4 评估二元分类器预测

问题

给定一个训练好的分类模型,你想评估其质量。

解决方案

使用 scikit-learn 的cross_val_score进行交叉验证,同时使用scoring参数来定义一系列性能指标,包括准确度、精确度、召回率和F[1]准确度是一种常见的性能指标。它简单地表示预测正确的观察比例:

A c c u r a c y = TP+TN TP+TN+FP+FN

其中:

TP

真阳性数量。这些是属于阳性类别(患病、购买产品等)并且我们预测正确的观察结果。

TN

真阴性数量。这些是属于阴性类别(未患病、未购买产品等)并且我们预测正确的观察结果。

FP

假阳性数量,也称为I 型错误。这些是被预测为阳性类别但实际上属于阴性类别的观察结果。

FN

假阴性数量,也称为II 型错误。这些是被预测为阴性类别但实际上属于阳性类别的观察结果。

我们可以通过设置scoring="accuracy"来在三折(默认折数)交叉验证中测量准确度:

# Load libraries
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification

# Generate features matrix and target vector
X, y = make_classification(n_samples = 10000,
                           n_features = 3,
                           n_informative = 3,
                           n_redundant = 0,
                           n_classes = 2,
                           random_state = 1)

# Create logistic regression
logit = LogisticRegression()

# Cross-validate model using accuracy
cross_val_score(logit, X, y, scoring="accuracy")
array([0.9555, 0.95  , 0.9585, 0.9555, 0.956 ])

准确率的吸引力在于它有一个直观和简单的英文解释:被正确预测的观测的比例。然而,在现实世界中,通常我们的数据有不平衡的类别(例如,99.9%的观测属于类别 1,只有 0.1%属于类别 2)。当面对不平衡的类别时,准确率会遇到一个悖论,即模型准确率很高,但缺乏预测能力。例如,想象我们试图预测一个在人群中发生率为 0.1%的非常罕见的癌症的存在。在训练完我们的模型后,我们发现准确率为 95%。然而,99.9%的人没有这种癌症:如果我们简单地创建一个“预测”没有人有这种癌症的模型,我们的天真模型将更准确,但显然它不能预测任何事情。因此,我们常常有动机使用其他指标,如精确度、召回率和*F[1]*分数。

精确度是每个被预测为正类的观测中实际为正类的比例。我们可以将其看作是我们预测中的噪声测量——也就是说,我们在预测某事是正的时候有多大可能是对的。精确度高的模型是悲观的,因为他们仅在非常肯定的情况下预测某个观测属于正类。形式上,精确度是:

Precision = TP TP+FP

# Cross-validate model using precision
cross_val_score(logit, X, y, scoring="precision")
array([0.95963673, 0.94820717, 0.9635996 , 0.96149949, 0.96060606])

召回率是每个真正正例中被正确预测的比例。召回率衡量了模型识别正类观测的能力。召回率高的模型是乐观的,因为他们在预测某个观测属于正类时的门槛很低:

Recall = TP TP+FN

# Cross-validate model using recall
cross_val_score(logit, X, y, scoring="recall")
array([0.951, 0.952, 0.953, 0.949, 0.951])

如果这是你第一次遇到精确度和召回率,如果需要一些时间才能完全理解它们,那是可以理解的。这是准确率的一个缺点;精确度和召回率不太直观。几乎总是我们希望在精确度和召回率之间达到某种平衡,而这种角色由*F[1]*分数扮演。F[1]分数是调和平均数(一种用于比率的平均数):

F 1 = 2 × Precision × Recall Precision + Recall

这个分数是正预测中实现的正确性的一种度量——也就是说,标记为正的观测中有多少实际上是正的:

# Cross-validate model using F1
cross_val_score(logit, X, y, scoring="f1")
array([0.95529884, 0.9500998 , 0.95827049, 0.95520886, 0.95577889])

讨论

作为评估指标,准确率具有一些有价值的特性,特别是它的直观性。然而,更好的指标通常涉及使用一定平衡的精确度和召回率——也就是说,我们模型的乐观和悲观之间存在一种权衡。*F[1]*代表着召回率和精确度之间的平衡,其中两者的相对贡献是相等的。

作为使用cross_val_score的替代方案,如果我们已经有了真实的 y 值和预测的 y 值,我们可以直接计算准确率和召回率等指标:

# Load libraries
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# Create training and test split
X_train, X_test, y_train, y_test = train_test_split(X,
                                                    y,
                                                    test_size=0.1,
                                                    random_state=1)

# Predict values for training target vector
y_hat = logit.fit(X_train, y_train).predict(X_test)

# Calculate accuracy
accuracy_score(y_test, y_hat)
0.947

参见

11.5 评估二元分类器阈值

问题

你想评估一个二元分类器和各种概率阈值。

解决方案

使用接收者操作特征曲线(ROC 曲线)来评估二元分类器的质量。在 scikit-learn 中,我们可以使用roc_curve来计算每个阈值下的真正例和假正例,然后绘制它们:

# Load libraries
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.model_selection import train_test_split

# Create feature matrix and target vector
features, target = make_classification(n_samples=10000,
                                       n_features=10,
                                       n_classes=2,
                                       n_informative=3,
                                       random_state=3)

# Split into training and test sets
features_train, features_test, target_train, target_test = train_test_split(
    features, target, test_size=0.1, random_state=1)

# Create classifier
logit = LogisticRegression()

# Train model
logit.fit(features_train, target_train)

# Get predicted probabilities
target_probabilities = logit.predict_proba(features_test)[:,1]

# Create true and false positive rates
false_positive_rate, true_positive_rate, threshold = roc_curve(
  target_test,
  target_probabilities
)

# Plot ROC curve
plt.title("Receiver Operating Characteristic")
plt.plot(false_positive_rate, true_positive_rate)
plt.plot([0, 1], ls="--")
plt.plot([0, 0], [1, 0] , c=".7"), plt.plot([1, 1] , c=".7")
plt.ylabel("True Positive Rate")
plt.xlabel("False Positive Rate")
plt.show()

mpc2 11in01

讨论

接收者操作特征曲线是评估二元分类器质量的常见方法。ROC 在每个概率阈值(即观测被预测为一类的概率)下比较真正例和假正例的存在。通过绘制 ROC 曲线,我们可以看到模型的表现。一个完全正确预测每个观测的分类器将看起来像前一图中 ROC 输出的实线浅灰色线,立即向顶部直线上升。预测随机的分类器将出现为对角线。模型越好,它距实线越接近。

到目前为止,我们只根据它们预测的值来检查模型。然而,在许多学习算法中,这些预测的值是基于概率估计的。也就是说,每个观测都被赋予属于每个类别的显式概率。在我们的解决方案中,我们可以使用predict_proba来查看第一个观测的预测概率:

# Get predicted probabilities
logit.predict_proba(features_test)[0:1]
array([[0.86891533, 0.13108467]])

我们可以使用classes_来查看类别:

logit.classes_
array([0, 1])

在这个例子中,第一个观测有约 87%的概率属于负类(0),13%的概率属于正类(1)。默认情况下,scikit-learn 预测如果概率大于 0.5,则观测属于正类(称为阈值)。然而,我们经常希望明确地偏置我们的模型以使用不同的阈值出于实质性原因,而不是中间地带。例如,如果一个假阳性对我们的公司造成很高的成本,我们可能更喜欢一个概率阈值较高的模型。我们未能预测一些正例,但当观测被预测为正例时,我们可以非常确信预测是正确的。这种权衡体现在真正例率(TPR)和假正例率(FPR)中。TPR 是正确预测为真的观测数除以所有真正的正例观测数:

TPR = TP TP+FN

FPR 是错误预测的正例数除以所有真负例观测数:

FPR = FP FP+TN

ROC 曲线代表每个概率阈值下的相应 TPR 和 FPR。例如,在我们的解决方案中,大约 0.50 的阈值具有约 0.83 的 TPR 和约 0.16 的 FPR:

print("Threshold:", threshold[124])
print("True Positive Rate:", true_positive_rate[124])
print("False Positive Rate:", false_positive_rate[124])
Threshold: 0.5008252732632008
True Positive Rate: 0.8346938775510204
False Positive Rate: 0.1607843137254902

然而,如果我们将阈值提高到约 80%(即,在模型预测观测为正类之前,增加其必须确定的程度),TPR 显著下降,但 FPR 也是如此:

print("Threshold:", threshold[49])
print("True Positive Rate:", true_positive_rate[49])
print("False Positive Rate:", false_positive_rate[49])
Threshold: 0.8058575028551827
True Positive Rate: 0.5653061224489796
False Positive Rate: 0.052941176470588235

这是因为我们对预测为正类有更高要求,导致模型未能识别出一些正例(较低的 TPR),但也减少了负面观测被预测为正面的噪声(较低的 FPR)。

除了能够可视化 TPR 和 FPR 之间的权衡之外,ROC 曲线还可以用作模型的一般度量。模型越好,曲线越高,因此曲线下面积也越大。因此,通常计算 ROC 曲线下面积(AUC ROC)来判断模型在所有可能阈值下的总体质量。AUC ROC 越接近 1,模型越好。在 scikit-learn 中,我们可以使用roc_auc_score来计算 AUC ROC:

# Calculate area under curve
roc_auc_score(target_test, target_probabilities)
0.9073389355742297

参见

11.6 评估多类分类器预测

问题

您有一个预测三个或更多类别的模型,并希望评估模型的性能。

解决方案

使用能够处理两个以上类别的评估指标进行交叉验证:

# Load libraries
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification

# Generate features matrix and target vector
features, target = make_classification(n_samples = 10000,
                           n_features = 3,
                           n_informative = 3,
                           n_redundant = 0,
                           n_classes = 3,
                           random_state = 1)

# Create logistic regression
logit = LogisticRegression()

# Cross-validate model using accuracy
cross_val_score(logit, features, target, scoring='accuracy')
array([0.841 , 0.829 , 0.8265, 0.8155, 0.82  ])

讨论

当我们有均衡的类别(即目标向量中每个类别的观测值数量大致相等)时,准确率就像在二分类设置中一样,是一种简单且可解释的评估指标选择。准确率是正确预测数量除以观测数量,无论是在多分类还是二分类设置中都同样有效。然而,当我们有不平衡的类别(这是一个常见情况)时,我们应该倾向于使用其他评估指标。

许多 scikit-learn 内置的度量是用于评估二元分类器的。然而,许多这些度量可以扩展用于当我们有两个以上类别时的情况。精确率、召回率和*F[1]*分数是我们在之前的配方中已经详细介绍过的有用度量。虽然它们都是最初设计用于二元分类器的,但我们可以将它们应用于多类别设置,通过将我们的数据视为一组二元类别来处理。这样做使我们能够将度量应用于每个类别,就好像它是数据中唯一的类别一样,然后通过对所有类别的评估分数进行平均来聚合它们:

# Cross-validate model using macro averaged F1 score
cross_val_score(logit, features, target, scoring='f1_macro')
array([0.84061272, 0.82895312, 0.82625661, 0.81515121, 0.81992692])

这段代码中,macro 指的是用于计算类别评估分数平均值的方法。选项包括macroweightedmicro

macro

计算每个类别的度量分数的均值,每个类别的权重相等。

weighted

计算每个类别的度量分数的均值,权重为数据中每个类别的大小。

micro

计算每个观测-类别组合的度量分数的均值。

11.7 可视化分类器的性能

问题

给定测试数据的预测类别和真实类别,您希望直观比较模型的质量。

解决方案

使用混淆矩阵,比较预测类别和真实类别:

# Load libraries
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
import pandas as pd

# Load data
iris = datasets.load_iris()

# Create features matrix
features = iris.data

# Create target vector
target = iris.target

# Create list of target class names
class_names = iris.target_names

# Create training and test set
features_train, features_test, target_train, target_test = train_test_split(
    features, target, random_state=2)

# Create logistic regression
classifier = LogisticRegression()

# Train model and make predictions
target_predicted = classifier.fit(features_train,
    target_train).predict(features_test)

# Create confusion matrix
matrix = confusion_matrix(target_test, target_predicted)

# Create pandas dataframe
dataframe = pd.DataFrame(matrix, index=class_names, columns=class_names)

# Create heatmap
sns.heatmap(dataframe, annot=True, cbar=None, cmap="Blues")
plt.title("Confusion Matrix"), plt.tight_layout()
plt.ylabel("True Class"), plt.xlabel("Predicted Class")
plt.show()

mpc2 11in02

讨论

混淆矩阵是分类器性能的一种简单有效的可视化方式。混淆矩阵的主要优势之一是它们的可解释性。矩阵的每一列(通常可视化为热图)代表预测类别,而每一行显示真实类别。结果是每个单元格都是预测和真实类别的一种可能组合。这可能最好通过一个例子来解释。在解决方案中,左上角的单元格是预测为Iris setosa(由列表示)的观察数量,它们实际上是Iris setosa(由行表示)。这意味着模型准确地预测了所有Iris setosa的花。然而,该模型在预测Iris virginica时并不那么成功。右下角的单元格表示模型成功预测了十一个观察结果为Iris virginica,但(向上查看一个单元格)预测了一个实际上是Iris versicolor的花为virginica

关于混淆矩阵有三点值得注意。首先,一个完美的模型会在对角线上有数值,其他地方都是零。一个糟糕的模型会使观察计数均匀地分布在单元格周围。其次,混淆矩阵让我们不仅能看到模型错在哪里,还能看到它错在哪里。也就是说,我们可以看到误分类的模式。例如,我们的模型很容易区分Iris virginicaIris setosa,但在分类Iris virginicaIris versicolor时稍微困难一些。最后,混淆矩阵适用于任意数量的类别(尽管如果目标向量中有一百万个类别,混淆矩阵的可视化可能会难以阅读)。

参见

11.8 评估回归模型

问题

您想要评估回归模型的性能。

解决方案

使用 均方误差(MSE):

# Load libraries
from sklearn.datasets import make_regression
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LinearRegression

# Generate features matrix, target vector
features, target = make_regression(n_samples = 100,
                                   n_features = 3,
                                   n_informative = 3,
                                   n_targets = 1,
                                   noise = 50,
                                   coef = False,
                                   random_state = 1)

# Create a linear regression object
ols = LinearRegression()

# Cross-validate the linear regression using (negative) MSE
cross_val_score(ols, features, target, scoring='neg_mean_squared_error')
array([-1974.65337976, -2004.54137625, -3935.19355723, -1060.04361386,
       -1598.74104702])

另一个常见的回归指标是确定系数,

# Cross-validate the linear regression using R-squared
cross_val_score(ols, features, target, scoring='r2')
array([0.8622399 , 0.85838075, 0.74723548, 0.91354743, 0.84469331])

讨论

MSE 是回归模型中最常见的评估指标之一。形式上,MSE 是:

MSE = 1 n ∑ i=1 n (y ^i-y i ) 2

其中 n 是观察次数,yi 是我们试图预测的目标的真实值,对于观察 i,y ^i 是模型对 yi 的预测值。均方误差(MSE)是所有预测值与真实值之间距离的平方和的度量。MSE 值越高,总体平方误差越大,因此模型越糟糕。平方误差项的数学优势包括强制所有误差值为正,但一个常常未被意识到的影响是,平方会比许多小误差更严厉地惩罚少量大误差,即使这些误差的绝对值相同。例如,想象两个模型,A 和 B,每个模型有两个观察:

  • 模型 A 的误差为 0 和 10,因此其 MSE 为 0² + 10² = 100

  • 模型 B 每个误差为 5,因此其 MSE 为 5² + 5² = 50

两个模型的总误差都是 10;然而,MSE 认为模型 A(MSE = 100)比模型 B(MSE = 50)更差。在实践中,这种影响很少成问题(实际上理论上有益),并且 MSE 作为评估指标运行得非常好。

一个重要的注释:在 scikit-learn 中,默认情况下,scoring 参数假定更高的值优于较低的值。然而,对于 MSE,情况并非如此,较高的值意味着模型较差。因此,scikit-learn 使用 neg_mean_squared_error 参数来观察 MSE。

一种常见的替代回归评估指标是我们在 Recipe 11.2 中使用的默认指标 R2,它衡量模型解释的目标向量方差量。

R 2 = 1 - ∑ i=1 n (y i -y ^ i ) 2 ∑ i=1 n (y i -y ¯) 2

其中 y i 是第 i 个观察的真实目标值,y^ i 是第 i 个观察的预测值,y ¯ 是目标向量的均值。当 R2 接近 1.0 时,模型越好。

参见

11.9 评估聚类模型

问题

您已经使用了无监督学习算法来对数据进行聚类。现在您想知道它的表现如何。

解决方案

使用 轮廓系数 来衡量聚类的质量(请注意,这不是衡量预测性能的指标):

# Load libraries
import numpy as np
from sklearn.metrics import silhouette_score
from sklearn import datasets
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs

# Generate features matrix
features, _ = make_blobs(n_samples = 1000,
                         n_features = 10,
                         centers = 2,
                         cluster_std = 0.5,
                         shuffle = True,
                         random_state = 1)

# Cluster data using k-means to predict classes
model = KMeans(n_clusters=2, random_state=1).fit(features)

# Get predicted classes
target_predicted = model.labels_

# Evaluate model
silhouette_score(features, target_predicted)
0.8916265564072141

讨论

监督模型评估比较预测(例如类别或定量值)与目标向量中对应的真实值。然而,使用聚类方法的最常见动机是你的数据没有目标向量。许多聚类评估指标需要一个目标向量,但是当你有一个可用的目标向量时,再次使用聚类这样的无监督学习方法可能会不必要地束手无策。

如果我们没有目标向量,我们无法评估预测与真实值之间的情况,但是我们可以评估簇本身的特性。直观地,我们可以想象“好”的簇在同一簇内的观察之间有非常小的距离(即密集的簇),而在不同簇之间有很大的距离(即分离良好的簇)。轮廓系数提供了一个单一值,同时衡量了这两个特性。形式上,第i个观察的轮廓系数为:

s i = b i -a i max(a i ,b i )

其中si是观察的轮廓系数,ai是i与同一类别所有观察之间的平均距离,bi是i与不同类别最接近的簇中所有观察之间的平均距离。silhouette_score返回的值是所有观察的平均轮廓系数。轮廓系数的范围在-1 到 1 之间,1 表示密集且分离良好的簇。

参见

11.10 创建自定义评估度量

问题

您希望使用您创建的度量来评估一个模型。

解决方案

创建度量作为一个函数,并使用 scikit-learn 的make_scorer将其转换为评分器函数:

# Load libraries
from sklearn.metrics import make_scorer, r2_score
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Ridge
from sklearn.datasets import make_regression

# Generate features matrix and target vector
features, target = make_regression(n_samples = 100,
                                   n_features = 3,
                                   random_state = 1)

# Create training set and test set
features_train, features_test, target_train, target_test = train_test_split(
     features, target, test_size=0.10, random_state=1)

# Create custom metric
def custom_metric(target_test, target_predicted):
    # Calculate R-squared score
    r2 = r2_score(target_test, target_predicted)
    # Return R-squared score
    return r2

# Make scorer and define that higher scores are better
score = make_scorer(custom_metric, greater_is_better=True)

# Create ridge regression object
classifier = Ridge()

# Train ridge regression model
model = classifier.fit(features_train, target_train)

# Apply custom scorer
score(model, features_test, target_test)
0.9997906102882058

讨论

虽然 scikit-learn 有许多内置的度量指标来评估模型性能,但通常定义我们自己的度量也很有用。scikit-learn 通过使用make_scorer使这变得简单。首先,我们定义一个接受两个参数(真实目标向量和我们的预测值)并输出某个分数的函数。其次,我们使用make_scorer创建一个评分器对象,确保指定高或低分数是可取的(使用greater_is_better参数)。

在解决方案中,自定义度量(custom_metric)只是一个玩具示例,因为它简单地包装了一个用于计算分数的内置度量。在实际情况中,我们将用我们想要的任何自定义度量替换custom_metric函数。然而,我们可以通过将结果与 scikit-learn 的r2_score内置方法进行比较,看到计算的自定义度量确实有效:

# Predict values
target_predicted = model.predict(features_test)

# Calculate R-squared score
r2_score(target_test, target_predicted)
0.9997906102882058

参见

11.11 可视化训练集大小的效果

问题

你想要评估训练集中观测数量对某些指标(准确率、F[1] 等)的影响。

解决方案

绘制准确性与训练集大小的图表:

# Load libraries
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import learning_curve

# Load data
digits = load_digits()

# Create feature matrix and target vector
features, target = digits.data, digits.target

# Create CV training and test scores for various training set sizes
train_sizes, train_scores, test_scores = learning_curve(# Classifier
                                                        RandomForestClassifier(),
                                                        # Feature matrix
                                                        features,
                                                        # Target vector
                                                        target,
                                                        # Number of folds
                                                        cv=10,
                                                        # Performance metric
                                                        scoring='accuracy',
                                                        # Use all computer cores
                                                        n_jobs=-1,
                                                        # Sizes of 50
                                                        # Training set
                                                       train_sizes=np.linspace(
                                                       0.01,
                                                       1.0,
                                                       50))

# Create means and standard deviations of training set scores
train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)

# Create means and standard deviations of test set scores
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)

# Draw lines
plt.plot(train_sizes, train_mean, '--', color="#111111",  label="Training score")
plt.plot(train_sizes, test_mean, color="#111111", label="Cross-validation score")

# Draw bands
plt.fill_between(train_sizes, train_mean - train_std,
                 train_mean + train_std, color="#DDDDDD")
plt.fill_between(train_sizes, test_mean - test_std,
                 test_mean + test_std, color="#DDDDDD")

# Create plot
plt.title("Learning Curve")
plt.xlabel("Training Set Size"), plt.ylabel("Accuracy Score"),
plt.legend(loc="best")
plt.tight_layout()
plt.show()

mpc2 11in03

讨论

学习曲线 可视化模型在训练集和交叉验证中随着训练集观测数量增加而表现的性能(例如准确率、召回率)。它们通常用于确定我们的学习算法是否会从收集额外的训练数据中受益。

在我们的解决方案中,我们绘制了随机森林分类器在 50 个不同训练集大小上的准确性,范围从观测数据的 1%到 100%。交叉验证模型的逐渐增加的准确性得分告诉我们,我们可能会从额外的观测中受益(尽管在实践中这可能并不可行)。

参见

11.12 创建评估指标的文本报告

问题

你想要一个分类器性能的快速描述。

解决方案

使用 scikit-learn 的 classification_report

# Load libraries
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

# Load data
iris = datasets.load_iris()

# Create features matrix
features = iris.data

# Create target vector
target = iris.target

# Create list of target class names
class_names = iris.target_names

# Create training and test set
features_train, features_test, target_train, target_test = train_test_split(
    features, target, random_state=0)

# Create logistic regression
classifier = LogisticRegression()

# Train model and make predictions
model = classifier.fit(features_train, target_train)
target_predicted = model.predict(features_test)

# Create a classification report
print(classification_report(target_test,
                            target_predicted,
                            target_names=class_names))
              precision    recall  f1-score   support

      setosa       1.00      1.00      1.00        16
  versicolor       1.00      0.91      0.95        11
   virginica       0.92      1.00      0.96        11

    accuracy                           0.97        38
   macro avg       0.97      0.97      0.97        38
weighted avg       0.98      0.97      0.97        38

讨论

classification_report 提供了一个快速查看一些常见评估指标(包括精确度、召回率和 F[1] 分数,详见 Recipe 11.4)的方法。支持是每个类别中的观测数量。

参见

11.13 可视化超参数值效果

问题

你想要了解模型在某些超参数值变化时的性能变化。

解决方案

绘制超参数与模型准确性的图表(验证曲线):

# Load libraries
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import validation_curve

# Load data
digits = load_digits()

# Create feature matrix and target vector
features, target = digits.data, digits.target

# Create range of values for parameter
param_range = np.arange(1, 250, 2)

# Calculate accuracy on training and test set using range of parameter values
train_scores, test_scores = validation_curve(
    # Classifier
    RandomForestClassifier(),
    # Feature matrix
    features,
    # Target vector
    target,
    # Hyperparameter to examine
    param_name="n_estimators",
    # Range of hyperparameter's values
    param_range=param_range,
    # Number of folds
    cv=3,
    # Performance metric
    scoring="accuracy",
    # Use all computer cores
    n_jobs=-1)

# Calculate mean and standard deviation for training set scores
train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)

# Calculate mean and standard deviation for test set scores
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)

# Plot mean accuracy scores for training and test sets
plt.plot(param_range, train_mean, label="Training score", color="black")
plt.plot(param_range, test_mean, label="Cross-validation score",
         color="dimgrey")

# Plot accuracy bands for training and test sets
plt.fill_between(param_range, train_mean - train_std,
                 train_mean + train_std, color="gray")
plt.fill_between(param_range, test_mean - test_std,
                 test_mean + test_std, color="gainsboro")

# Create plot
plt.title("Validation Curve With Random Forest")
plt.xlabel("Number Of Trees")
plt.ylabel("Accuracy Score")
plt.tight_layout()
plt.legend(loc="best")
plt.show()

mpc2 11in04

讨论

大多数训练算法(包括本书涵盖的许多算法)在开始训练过程之前必须选择的超参数。例如,随机森林分类器 创建一个“森林”由决策树组成,每棵树对观测的预测类进行投票。随机森林分类器的一个超参数是森林中的树的数量。通常在模型选择过程中选择超参数值(参见 第十二章)。然而,偶尔可视化模型性能随着超参数值的变化而变化是有用的。在我们的解决方案中,我们绘制了随机森林分类器在训练集和交叉验证中随着树的数量增加而准确性的变化。当我们有少量树时,训练和交叉验证分数都很低,表明模型欠拟合。随着树的数量增加到 250,两者的准确性趋于稳定,表明在训练大量森林的计算成本上可能没有太多价值。

在 scikit-learn 中,我们可以使用 validation_curve 计算验证曲线,其中包含三个重要参数:

param_name

要变化的超参数名称

param_range

要使用的超参数的值

scoring

评估模型的评估指标

参见

第十二章:模型选择

12.0 引言

在机器学习中,我们使用训练算法通过最小化某个损失函数来学习模型的参数。然而,许多学习算法(例如支持向量分类器和随机森林)有额外的 超参数,由用户定义,并影响模型学习其参数的方式。正如我们在本书的前面提到的,参数(有时也称为模型权重)是模型在训练过程中学习的内容,而超参数是我们手动提供的(用户提供的)内容。

例如,随机森林是决策树的集合(因此有 森林 一词);然而,森林中决策树的数量并非由算法学习,而必须在拟合之前设置好。这通常被称为 超参数调优超参数优化模型选择。此外,我们可能希望尝试多个学习算法(例如尝试支持向量分类器和随机森林,看哪种学习方法产生最佳模型)。

尽管在这个领域术语广泛变化,但在本书中,我们将选择最佳学习算法及其最佳超参数称为模型选择。原因很简单:想象我们有数据,并且想要训练一个支持向量分类器,有 10 个候选超参数值,以及一个随机森林分类器,有 10 个候选超参数值。结果是我们尝试从一组 20 个候选模型中选择最佳模型。在本章中,我们将介绍有效地从候选集中选择最佳模型的技术。

在本章中,我们将提到特定的超参数,比如 C(正则化强度的倒数)。如果你不知道超参数是什么,不要担心。我们将在后面的章节中介绍它们。相反,只需将超参数视为在开始训练之前必须选择的学习算法的设置。通常,找到能够产生最佳性能的模型和相关超参数是实验的结果——尝试各种可能性并找出最佳的那个。

12.1 使用穷举搜索选择最佳模型

问题

你想通过搜索一系列超参数来选择最佳模型。

解决方案

使用 scikit-learn 的 GridSearchCV

# Load libraries
import numpy as np
from sklearn import linear_model, datasets
from sklearn.model_selection import GridSearchCV

# Load data
iris = datasets.load_iris()
features = iris.data
target = iris.target

# Create logistic regression
logistic = linear_model.LogisticRegression(max_iter=500, solver='liblinear')

# Create range of candidate penalty hyperparameter values
penalty = ['l1','l2']

# Create range of candidate regularization hyperparameter values
C = np.logspace(0, 4, 10)

# Create dictionary of hyperparameter candidates
hyperparameters = dict(C=C, penalty=penalty)

# Create grid search
gridsearch = GridSearchCV(logistic, hyperparameters, cv=5, verbose=0)

# Fit grid search
best_model = gridsearch.fit(features, target)

# Show the best model
print(best_model.best_estimator_)
LogisticRegression(C=7.742636826811269, max_iter=500, penalty='l1',
                   solver='liblinear')

讨论

GridSearchCV 是一种使用交叉验证进行模型选择的蛮力方法。具体来说,用户定义一个或多个超参数可能的值集合,然后 GridSearchCV 使用每个值和/或值组合来训练模型。选择具有最佳性能得分的模型作为最佳模型。

例如,在我们的解决方案中,我们使用逻辑回归作为我们的学习算法,并调整了两个超参数:C 和正则化惩罚。我们还指定了另外两个参数,解算器和最大迭代次数。如果您不知道这些术语的含义也没关系;我们将在接下来的几章中详细讨论它们。只需意识到 C 和正则化惩罚可以取一系列值,这些值在训练之前必须指定。对于 C,我们定义了 10 个可能的值:

np.logspace(0, 4, 10)
array([1.00000000e+00, 2.78255940e+00, 7.74263683e+00, 2.15443469e+01,
       5.99484250e+01, 1.66810054e+02, 4.64158883e+02, 1.29154967e+03,
       3.59381366e+03, 1.00000000e+04])

类似地,我们定义了两个正则化惩罚的可能值:['l1', 'l2']。对于每个 C 和正则化惩罚值的组合,我们训练模型并使用 k 折交叉验证进行评估。在我们的解决方案中,C 有 10 个可能的值,正则化惩罚有 2 个可能的值,并且使用 5 折交叉验证。它们创建了 10 × 2 × 5 = 100 个候选模型,其中选择最佳模型。

一旦完成GridSearchCV,我们可以看到最佳模型的超参数:

# View best hyperparameters
print('Best Penalty:', best_model.best_estimator_.get_params()['penalty'])
print('Best C:', best_model.best_estimator_.get_params()['C'])
Best Penalty: l1
Best C: 7.742636826811269

默认情况下,确定了最佳超参数后,GridSearchCV会在整个数据集上重新训练一个模型(而不是留出一个折用于交叉验证)。我们可以像对待其他 scikit-learn 模型一样使用该模型来预测值:

# Predict target vector
best_model.predict(features)
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

GridSearchCV的一个参数值得注意:verbose。虽然大多数情况下不需要,但在长时间搜索过程中,接收到搜索正在进行中的指示可能会让人放心。verbose参数确定了搜索过程中输出消息的数量,0表示没有输出,而13表示额外的输出消息。

参见

12.2 使用随机搜索选择最佳模型

问题

您希望选择最佳模型的计算成本较低的方法。

解决方案

使用 scikit-learn 的RandomizedSearchCV

# Load libraries
from scipy.stats import uniform
from sklearn import linear_model, datasets
from sklearn.model_selection import RandomizedSearchCV

# Load data
iris = datasets.load_iris()
features = iris.data
target = iris.target

# Create logistic regression
logistic = linear_model.LogisticRegression(max_iter=500, solver='liblinear')

# Create range of candidate regularization penalty hyperparameter values
penalty = ['l1', 'l2']

# Create distribution of candidate regularization hyperparameter values
C = uniform(loc=0, scale=4)

# Create hyperparameter options
hyperparameters = dict(C=C, penalty=penalty)

# Create randomized search
randomizedsearch = RandomizedSearchCV(
    logistic, hyperparameters, random_state=1, n_iter=100, cv=5, verbose=0,
    n_jobs=-1)

# Fit randomized search
best_model = randomizedsearch.fit(features, target)

# Print best model
print(best_model.best_estimator_)
LogisticRegression(C=1.668088018810296, max_iter=500, penalty='l1',
                   solver='liblinear')

讨论

在 Recipe 12.1 中,我们使用GridSearchCV在用户定义的一组超参数值上搜索最佳模型,根据评分函数。比GridSearchCV的蛮力搜索更高效的方法是从用户提供的分布(例如正态分布、均匀分布)中随机组合一定数量的超参数值进行搜索。scikit-learn 使用RandomizedSearchCV实现了这种随机搜索技术。

使用RandomizedSearchCV,如果我们指定一个分布,scikit-learn 将从该分布中随机抽样且不重复地抽取超参数值。例如,这里我们从范围为 0 到 4 的均匀分布中随机抽取 10 个值作为一般概念的示例:

# Define a uniform distribution between 0 and 4, sample 10 values
uniform(loc=0, scale=4).rvs(10)
array([3.95211699, 0.30693116, 2.88237794, 3.00392864, 0.43964702,
       1.46670526, 0.27841863, 2.56541664, 2.66475584, 0.79611958])

或者,如果我们指定一个值列表,例如两个正则化惩罚超参数值['l1', 'l2']RandomizedSearchCV将从列表中进行带替换的随机抽样。

就像GridSearchCV一样,我们可以看到最佳模型的超参数值:

# View best hyperparameters
print('Best Penalty:', best_model.best_estimator_.get_params()['penalty'])
print('Best C:', best_model.best_estimator_.get_params()['C'])
Best Penalty: l1
Best C: 1.668088018810296

就像使用GridSearchCV一样,在完成搜索后,RandomizedSearchCV会使用最佳超参数在整个数据集上拟合一个新模型。我们可以像使用 scikit-learn 中的任何其他模型一样使用这个模型;例如,进行预测:

# Predict target vector
best_model.predict(features)
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2,
       2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

超参数组合的采样数量(即训练的候选模型数量)由n_iter(迭代次数)设置指定。值得注意的是,RandomizedSearchCV并不比GridSearchCV更快,但通常在较短时间内通过测试更少的组合来实现与GridSearchCV可比较的性能。

参见

12.3 从多个学习算法中选择最佳模型

问题

通过在一系列学习算法及其相应的超参数上进行搜索,您可以选择最佳模型。

解决方案

创建一个包含候选学习算法及其超参数的字典,作为GridSearchCV的搜索空间:

# Load libraries
import numpy as np
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline

# Set random seed
np.random.seed(0)

# Load data
iris = datasets.load_iris()
features = iris.data
target = iris.target

# Create a pipeline
pipe = Pipeline([("classifier", RandomForestClassifier())])

# Create dictionary with candidate learning algorithms and their hyperparameters
search_space = [{"classifier": [LogisticRegression(max_iter=500,
       solver='liblinear')],
                 "classifier__penalty": ['l1', 'l2'],
                 "classifier__C": np.logspace(0, 4, 10)},
                {"classifier": [RandomForestClassifier()],
                 "classifier__n_estimators": [10, 100, 1000],
                 "classifier__max_features": [1, 2, 3]}]

# Create grid search
gridsearch = GridSearchCV(pipe, search_space, cv=5, verbose=0)

# Fit grid search
best_model = gridsearch.fit(features, target)

# Print best model
print(best_model.best_estimator_)
Pipeline(steps=[('classifier',
                 LogisticRegression(C=7.742636826811269, max_iter=500,
                                    penalty='l1', solver='liblinear'))])

讨论

在前两个示例中,我们通过搜索学习算法的可能超参数值来找到最佳模型。但是,如果我们不确定要使用哪种学习算法怎么办?scikit-learn 允许我们将学习算法作为搜索空间的一部分。在我们的解决方案中,我们定义了一个搜索空间,其中包含两个学习算法:逻辑回归和随机森林分类器。每个学习算法都有自己的超参数,并且我们使用classifier__[*hyperparameter name*]的格式定义其候选值。例如,对于我们的逻辑回归,为了定义可能的正则化超参数空间C的可能值集合以及潜在的正则化惩罚类型penalty,我们创建一个字典:

{'classifier': [LogisticRegression(max_iter=500, solver='liblinear')],
 'classifier__penalty': ['l1', 'l2'],
 'classifier__C': np.logspace(0, 4, 10)}

我们也可以为随机森林的超参数创建一个类似的字典:

{'classifier': [RandomForestClassifier()],
 'classifier__n_estimators': [10, 100, 1000],
 'classifier__max_features': [1, 2, 3]}

完成搜索后,我们可以使用 best_estimator_ 查看最佳模型的学习算法和超参数:

# View best model
print(best_model.best_estimator_.get_params()["classifier"])
LogisticRegression(C=7.742636826811269, max_iter=500, penalty='l1',
                   solver='liblinear')

就像前面两个示例一样,一旦我们完成了模型选择搜索,我们就可以像使用任何其他 scikit-learn 模型一样使用这个最佳模型:

# Predict target vector
best_model.predict(features)
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

12.4 在预处理时选择最佳模型

问题

您希望在模型选择过程中包含一个预处理步骤。

解决方案

创建一个包含预处理步骤及其任何参数的管道:

# Load libraries
import numpy as np
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# Set random seed
np.random.seed(0)

# Load data
iris = datasets.load_iris()
features = iris.data
target = iris.target

# Create a preprocessing object that includes StandardScaler features and PCA
preprocess = FeatureUnion([("std", StandardScaler()), ("pca", PCA())])

# Create a pipeline
pipe = Pipeline([("preprocess", preprocess),
                 ("classifier", LogisticRegression(max_iter=1000,
                     solver='liblinear'))])

# Create space of candidate values
search_space = [{"preprocess__pca__n_components": [1, 2, 3],
                 "classifier__penalty": ["l1", "l2"],
                 "classifier__C": np.logspace(0, 4, 10)}]

# Create grid search
clf = GridSearchCV(pipe, search_space, cv=5, verbose=0, n_jobs=-1)

# Fit grid search
best_model = clf.fit(features, target)

# Print best model
print(best_model.best_estimator_)
Pipeline(steps=[('preprocess',
                 FeatureUnion(transformer_list=[('std', StandardScaler()),
                                                ('pca', PCA(n_components=1))])),
                ('classifier',
                 LogisticRegression(C=7.742636826811269, max_iter=1000,
                                    penalty='l1', solver='liblinear'))])

讨论

在使用数据训练模型之前,我们通常需要对数据进行预处理。在进行模型选择时,我们必须小心处理预处理。首先,GridSearchCV使用交叉验证确定哪个模型性能最高。然而,在交叉验证中,我们实际上是在假装保留为测试集的折叠是不可见的,因此不是拟合任何预处理步骤的一部分(例如,缩放或标准化)。因此,我们不能对数据进行预处理然后运行GridSearchCV。相反,预处理步骤必须成为GridSearchCV采取的操作集的一部分。

这可能看起来很复杂,但 scikit-learn 让它变得简单。FeatureUnion允许我们正确地组合多个预处理操作。在我们的解决方案中,我们使用FeatureUnion来组合两个预处理步骤:标准化特征值(StandardScaler)和主成分分析(PCA)。该对象称为preprocess,包含我们的两个预处理步骤。然后,我们将preprocess包含在一个管道中与我们的学习算法一起。结果是,这使我们能够将拟合、转换和训练模型与超参数组合的正确(而令人困惑的)处理外包给 scikit-learn。

第二,一些预处理方法有它们自己的参数,通常需要用户提供。例如,使用 PCA 进行降维需要用户定义要使用的主成分数量,以产生转换后的特征集。理想情况下,我们会选择产生在某个评估测试指标下性能最佳模型的组件数量。

幸运的是,scikit-learn 使这变得容易。当我们在搜索空间中包含候选组件值时,它们被视为要搜索的任何其他超参数。在我们的解决方案中,我们在搜索空间中定义了features__pca__n_components': [1, 2, 3],以指示我们要发现一个、两个或三个主成分是否产生最佳模型。

在模型选择完成后,我们可以查看产生最佳模型的预处理值。例如,我们可以查看最佳的主成分数量:

# View best n_components
best_model.best_estimator_.get_params()['preprocess__pca__n_components']
1

12.5 使用并行化加快模型选择速度

问题

需要加快模型选择速度。

解决方案

通过设置n_jobs=-1来利用机器中的所有核心,从而使您能够同时训练多个模型:

# Load libraries
import numpy as np
from sklearn import linear_model, datasets
from sklearn.model_selection import GridSearchCV

# Load data
iris = datasets.load_iris()
features = iris.data
target = iris.target

# Create logistic regression
logistic = linear_model.LogisticRegression(max_iter=500, solver='liblinear')

# Create range of candidate regularization penalty hyperparameter values
penalty = ["l1", "l2"]

# Create range of candidate values for C
C = np.logspace(0, 4, 1000)

# Create hyperparameter options
hyperparameters = dict(C=C, penalty=penalty)

# Create grid search
gridsearch = GridSearchCV(logistic, hyperparameters, cv=5, n_jobs=-1, verbose=1)

# Fit grid search
best_model = gridsearch.fit(features, target)

# Print best model
print(best_model.best_estimator_)
Fitting 5 folds for each of 2000 candidates, totalling 10000 fits
LogisticRegression(C=5.926151812475554, max_iter=500, penalty='l1',
                   solver='liblinear')

讨论

在本章的示例中,我们将候选模型的数量保持较少,以使代码迅速完整。但是,在现实世界中,我们可能有成千上万甚至成千上万个模型要训练。因此,找到最佳模型可能需要花费很多小时。

为了加快这一过程,scikit-learn 允许我们同时训练多个模型。不深入技术细节,scikit-learn 可以同时训练多达机器上的核心数量的模型。现代大多数笔记本电脑至少有四个核心,因此(假设您当前使用的是笔记本电脑),我们可以同时训练四个模型。这将大大增加我们模型选择过程的速度。参数 n_jobs 定义了并行训练的模型数量。

在我们的解决方案中,我们将 n_jobs 设置为 -1,这告诉 scikit-learn 使用 所有 核心。然而,默认情况下 n_jobs 被设置为 1,这意味着它只使用一个核心。为了演示这一点,如果我们像在解决方案中一样运行相同的 GridSearchCV,但使用 n_jobs=1,我们可以看到找到最佳模型要花费显著更长的时间(确切时间取决于您的计算机):

# Create grid search using one core
clf = GridSearchCV(logistic, hyperparameters, cv=5, n_jobs=1, verbose=1)

# Fit grid search
best_model = clf.fit(features, target)

# Print best model
print(best_model.best_estimator_)
Fitting 5 folds for each of 2000 candidates, totalling 10000 fits
LogisticRegression(C=5.926151812475554, max_iter=500, penalty='l1',
                   solver='liblinear')

12.6 使用算法特定方法加速模型选择

问题

您需要加速模型选择,但不使用额外的计算资源。

解决方案

如果您正在使用一些特定的学习算法,请使用 scikit-learn 的模型特定的交叉验证超参数调整,例如 LogisticRegressionCV

# Load libraries
from sklearn import linear_model, datasets

# Load data
iris = datasets.load_iris()
features = iris.data
target = iris.target

# Create cross-validated logistic regression
logit = linear_model.LogisticRegressionCV(Cs=100, max_iter=500,
       solver='liblinear')

# Train model
logit.fit(features, target)

# Print model
print(logit)
LogisticRegressionCV(Cs=100, max_iter=500, solver='liblinear')

讨论

有时候学习算法的特性使我们能够比蛮力或随机模型搜索方法显著更快地搜索最佳超参数。在 scikit-learn 中,许多学习算法(例如岭回归、套索回归和弹性网络回归)都有一种特定于算法的交叉验证方法,以利用这一点。例如,LogisticRegression 用于进行标准的逻辑回归分类器,而 LogisticRegressionCV 实现了一个高效的交叉验证逻辑回归分类器,可以识别超参数 C 的最佳值。

scikit-learn 的 LogisticRegressionCV 方法包括参数 Cs。如果提供了一个列表,Cs 包含要从中选择的候选超参数值。如果提供了一个整数,参数 Cs 将生成该数量的候选值列表。候选值从 0.0001 到 10,0000 的对数范围内抽取(这是 C 的合理值范围)。

然而,LogisticRegressionCV 的一个主要缺点是它只能搜索 C 的一系列值。在 配方 12.1 中,我们的可能超参数空间包括 C 和另一个超参数(正则化惩罚范数)。这种限制是许多 scikit-learn 模型特定的交叉验证方法的共同特点。

参见

12.7 在模型选择后评估性能

问题

您希望评估通过模型选择找到的模型的性能。

解决方案

使用嵌套交叉验证以避免偏倚评估:

# Load libraries
import numpy as np
from sklearn import linear_model, datasets
from sklearn.model_selection import GridSearchCV, cross_val_score

# Load data
iris = datasets.load_iris()
features = iris.data
target = iris.target

# Create logistic regression
logistic = linear_model.LogisticRegression(max_iter=500, solver='liblinear')

# Create range of 20 candidate values for C
C = np.logspace(0, 4, 20)

# Create hyperparameter options
hyperparameters = dict(C=C)

# Create grid search
gridsearch = GridSearchCV(logistic, hyperparameters, cv=5, n_jobs=-1, verbose=0)

# Conduct nested cross-validation and output the average score
cross_val_score(gridsearch, features, target).mean()
0.9733333333333334

讨论

在模型选择过程中的嵌套交叉验证对许多人来说是一个难以理解的概念。请记住,在 k 折交叉验证中,我们在数据的 k-1 折上训练模型,使用该模型对剩余的一折进行预测,然后评估我们的模型预测与真实值的比较。然后我们重复这个过程 k 次。

在本章描述的模型选择搜索中(即 GridSearchCVRandomizedSearchCV),我们使用交叉验证来评估哪些超参数值产生了最佳模型。然而,一个微妙且通常被低估的问题出现了:因为我们使用数据来选择最佳的超参数值,所以我们不能再使用同样的数据来评估模型的性能。解决方案是?将用于模型搜索的交叉验证包装在另一个交叉验证中!在嵌套交叉验证中,“内部”交叉验证选择最佳模型,而“外部”交叉验证提供了模型性能的无偏评估。在我们的解决方案中,内部交叉验证是我们的 GridSearchCV 对象,然后我们使用 cross_val_score 将其包装在外部交叉验证中。

如果你感到困惑,可以尝试一个简单的实验。首先,设置 verbose=1,这样我们可以看到发生了什么:

gridsearch = GridSearchCV(logistic, hyperparameters, cv=5, verbose=1)

接下来,运行 gridsearch.fit(features, target),这是我们用来找到最佳模型的内部交叉验证:

best_model = gridsearch.fit(features, target)
Fitting 5 folds for each of 20 candidates, totalling 100 fits

从输出中可以看出,内部交叉验证训练了 20 个候选模型五次,总计 100 个模型。接下来,将 clf 嵌套在一个新的交叉验证中,默认为五折:

scores = cross_val_score(gridsearch, features, target)
Fitting 5 folds for each of 20 candidates, totalling 100 fits
Fitting 5 folds for each of 20 candidates, totalling 100 fits
Fitting 5 folds for each of 20 candidates, totalling 100 fits
Fitting 5 folds for each of 20 candidates, totalling 100 fits
Fitting 5 folds for each of 20 candidates, totalling 100 fits

输出显示,内部交叉验证训练了 20 个模型五次,以找到最佳模型,然后使用外部五折交叉验证评估了该模型,总共训练了 500 个模型。