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

87 阅读51分钟

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

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

译者:飞龙

协议:CC BY-NC-SA 4.0

第十三章:线性回归

13.0 引言

线性回归是我们工具箱中最简单的监督学习算法之一。如果您曾经在大学里修过入门统计课程,很可能您最后学到的主题就是线性回归。线性回归及其扩展在当目标向量是定量值(例如房价、年龄)时继续是一种常见且有用的预测方法。在本章中,我们将涵盖多种线性回归方法(及其扩展)来创建性能良好的预测模型。

13.1 拟合一条线

问题

您希望训练一个能够表示特征和目标向量之间线性关系的模型。

解决方案

使用线性回归(在 scikit-learn 中,LinearRegression):

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

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

# Create linear regression
regression = LinearRegression()

# Fit the linear regression
model = regression.fit(features, target)

讨论

线性回归假设特征与目标向量之间的关系大致是线性的。也就是说,特征对目标向量的效果(也称为系数权重参数)是恒定的。为了解释起见,在我们的解决方案中,我们只使用了三个特征来训练我们的模型。这意味着我们的线性模型将是:

y ^ = β ^ 0 + β ^ 1 x 1 + β ^ 2 x 2 + β ^ 3 x 3 + ϵ

这里,y ^ 是我们的目标,xi 是单个特征的数据,β ^ 1,β^ 2和β^ 3是通过拟合模型确定的系数,ϵ是误差。在拟合模型后,我们可以查看每个参数的值。例如,β ^ 0,也称为偏差截距,可以使用intercept_查看:

# View the intercept
model.intercept_
-0.009650118178816669

coef_显示了β^ 1和β^ 2:

# View the feature coefficients
model.coef_
array([1.95531234e-02, 4.42087450e+01, 5.81494563e+01])

在我们的数据集中,目标值是一个随机生成的连续变量:

# First value in the target vector
target[0]
-20.870747595269407

使用predict方法,我们可以根据输入特征预测输出:

# Predict the target value of the first observation
model.predict(features)[0]
-20.861927709296808

不错!我们的模型只偏离了约 0.01!

线性回归的主要优势在于其可解释性,这在很大程度上是因为模型的系数是目标向量一单位变化的影响。我们模型的第一个特征的系数约为~–0.02,这意味着我们在第一个特征每增加一个单位时目标的变化。

使用score函数,我们还可以看到我们的模型在数据上的表现:

# Print the score of the model on the training data
print(model.score(features, target))
0.9999901732607787

scikit learn 中线性回归的默认得分是 R²,范围从 0.0(最差)到 1.0(最好)。正如我们在这个例子中所看到的,我们非常接近完美值 1.0。然而值得注意的是,我们是在模型已经见过的数据(训练数据)上评估该模型,而通常我们会在一个独立的测试数据集上进行评估。尽管如此,在实际情况下,这样高的分数对我们的模型是个好兆头。

13.2 处理交互效应

问题

你有一个特征,其对目标变量的影响取决于另一个特征。

解决方案

创建一个交互项来捕获这种依赖关系,使用 scikit-learn 的 PolynomialFeatures

# Load libraries
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.datasets import make_regression

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

# Create interaction term
interaction = PolynomialFeatures(
    degree=3, include_bias=False, interaction_only=True)
features_interaction = interaction.fit_transform(features)

# Create linear regression
regression = LinearRegression()

# Fit the linear regression
model = regression.fit(features_interaction, target)

讨论

有时,一个特征对目标变量的影响至少部分依赖于另一个特征。例如,想象一个简单的基于咖啡的例子,我们有两个二进制特征——是否加糖(sugar)和是否搅拌(stirred)——我们想预测咖啡是否甜。仅仅加糖(sugar=1, stirred=0)不会使咖啡变甜(所有的糖都在底部!),仅仅搅拌咖啡而不加糖(sugar=0, stirred=1)也不会使其变甜。实际上,是将糖放入咖啡并搅拌(sugar=1, stirred=1)才能使咖啡变甜。sugarstirred 对甜味的影响是相互依赖的。在这种情况下,我们称 sugarstirred 之间存在交互效应

我们可以通过包含一个新特征来考虑交互效应,该特征由交互特征的相应值的乘积组成:

y ^ = β ^ 0 + β ^ 1 x 1 + β ^ 2 x 2 + β ^ 3 x 1 x 2 + ϵ

其中 x1 和 x2 分别是 sugarstirred 的值,x1x2 表示两者之间的交互作用。

在我们的解决方案中,我们使用了一个只包含两个特征的数据集。以下是每个特征的第一个观察值:

# View the feature values for first observation
features[0]
array([0.0465673 , 0.80186103])

要创建一个交互项,我们只需为每个观察值将这两个值相乘:

# Import library
import numpy as np

# For each observation, multiply the values of the first and second feature
interaction_term = np.multiply(features[:, 0], features[:, 1])

我们可以看到第一次观察的交互项:

# View interaction term for first observation
interaction_term[0]
0.037340501965846186

然而,虽然我们经常有充分的理由相信两个特征之间存在交互作用,但有时我们也没有。在这些情况下,使用 scikit-learn 的 PolynomialFeatures 为所有特征组合创建交互项会很有用。然后,我们可以使用模型选择策略来识别产生最佳模型的特征组合和交互项。

要使用PolynomialFeatures创建交互项,我们需要设置三个重要的参数。最重要的是,interaction_only=True告诉PolynomialFeatures仅返回交互项(而不是多项式特征,我们将在 Recipe 13.3 中讨论)。默认情况下,PolynomialFeatures会添加一个名为bias的包含 1 的特征。我们可以通过include_bias=False来防止这种情况发生。最后,degree参数确定从中创建交互项的特征的最大数量(以防我们想要创建的交互项是三个特征的组合)。我们可以通过检查我们的解决方案中PolynomialFeatures的输出,看看第一个观察值的特征值和交互项值是否与我们手动计算的版本匹配:

# View the values of the first observation
features_interaction[0]
array([0.0465673 , 0.80186103, 0.0373405 ])

13.3 拟合非线性关系

问题

您希望对非线性关系进行建模。

解决方案

通过在线性回归模型中包含多项式特征来创建多项式回归:

# Load library
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.datasets import make_regression

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

# Create polynomial features x² and x³
polynomial = PolynomialFeatures(degree=3, include_bias=False)
features_polynomial = polynomial.fit_transform(features)

# Create linear regression
regression = LinearRegression()

# Fit the linear regression
model = regression.fit(features_polynomial, target)

讨论

到目前为止,我们只讨论了建模线性关系。线性关系的一个例子是建筑物的层数与建筑物的高度之间的关系。在线性回归中,我们假设层数和建筑物高度的影响大致是恒定的,这意味着一个 20 层的建筑物大致会比一个 10 层的建筑物高出两倍,而一个 5 层的建筑物大致会比一个 10 层的建筑物高出两倍。然而,许多感兴趣的关系并不严格是线性的。

我们经常希望建模非线性关系,例如学生学习时间与她在考试中得分之间的关系。直觉上,我们可以想象,对于一个小时的学习和没有学习的学生之间的考试成绩差异很大。然而,在学习时间增加到 99 小时和 100 小时之间时,学生的考试成绩差异就会变得很小。随着学习小时数的增加,一个小时的学习对学生考试成绩的影响逐渐减小。

多项式回归是线性回归的扩展,允许我们建模非线性关系。要创建多项式回归,将我们在 Recipe 13.1 中使用的线性函数转换为多项式函数:

y ^ = β ^ 0 + β ^ 1 x 1 + ϵ

通过添加多项式特征将线性回归模型扩展为多项式函数:

y ^ = β ^ 0 + β ^ 1 x 1 + β ^ 2 x 1 2 + . . . + β ^ d x 1 d + ϵ

其中d是多项式的次数。我们如何能够对非线性函数使用线性回归?答案是我们不改变线性回归拟合模型的方式,而只是添加多项式特征。也就是说,线性回归并不“知道”x2是x的二次转换,它只是将其视为另一个变量。

可能需要更实际的描述。为了建模非线性关系,我们可以创建将现有特征 x 提升到某个幂次的新特征: x2、x3 等。我们添加的这些新特征越多,模型创建的“线”就越灵活。为了更加明确,想象我们想要创建一个三次多项式。为了简单起见,我们将专注于数据集中的第一个观察值:

# View first observation
features[0]
array([-0.61175641])

要创建一个多项式特征,我们将第一个观察值的值提升到二次方,x 1 2:

# View first observation raised to the second power, x²
features[0]**2
array([0.37424591])

这将是我们的新功能。然后,我们还将第一个观察值的值提升到三次方,x 1 3:

# View first observation raised to the third power, x³
features[0]**3
array([-0.22894734])

通过在我们的特征矩阵中包含所有三个特征(x、x2 和 x3)并运行线性回归,我们进行了多项式回归:

# View the first observation's values for x, x², and x³
features_polynomial[0]
array([-0.61175641,  0.37424591, -0.22894734])

PolynomialFeatures 有两个重要参数。首先,degree 确定多项式特征的最大次数。例如,degree=3 会生成 x2 和 x3。其次,默认情况下 PolynomialFeatures 包括一个只包含 1 的特征(称为偏差)。我们可以通过设置 include_bias=False 来删除它。

13.4 通过正则化减少方差

问题

您希望减少线性回归模型的方差。

解决方案

使用包含收缩惩罚(也称为正则化)的学习算法,例如岭回归和拉索回归:

# Load libraries
from sklearn.linear_model import Ridge
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_regression

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

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create ridge regression with an alpha value
regression = Ridge(alpha=0.5)

# Fit the linear regression
model = regression.fit(features_standardized, target)

讨论

在标准线性回归中,模型训练以最小化真实值(yi)与预测值(y^ i)目标值或残差平方和(RSS)之间的平方误差:

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

正则化回归学习者类似,除了它们试图最小化 RSS 系数值总大小的某种惩罚,称为收缩惩罚,因为它试图“收缩”模型。线性回归的两种常见类型的正则化学习者是岭回归和拉索。唯一的形式上的区别是使用的收缩惩罚类型。在岭回归中,收缩惩罚是一个调整超参数,乘以所有系数的平方和:

RSS + α ∑ j=1 p β ^ j 2

其中β^ j是第j个p特征的系数,α是一个超参数(接下来会讨论)。Lasso则类似,只是收缩惩罚是一个调整的超参数,乘以所有系数的绝对值的和:

1 2n RSS + α ∑ j=1 p β ^ j

其中n是观察数。那么我们应该使用哪一个?作为一个非常一般的经验法则,岭回归通常比 lasso 产生稍微更好的预测,但 lasso(我们将在 Recipe 13.5 中讨论原因)产生更可解释的模型。如果我们希望在岭回归和 lasso 的惩罚函数之间取得平衡,我们可以使用弹性网,它只是一个包含两种惩罚的回归模型。无论我们使用哪一个,岭回归和 lasso 回归都可以通过将系数值包括在我们试图最小化的损失函数中来对大或复杂的模型进行惩罚。

超参数α让我们控制对系数的惩罚程度,较高的α值会创建更简单的模型。理想的α值应像其他超参数一样进行调整。在 scikit-learn 中,可以使用alpha参数设置α。

scikit-learn 包含一个RidgeCV方法,允许我们选择理想的α值:

# Load library
from sklearn.linear_model import RidgeCV

# Create ridge regression with three alpha values
regr_cv = RidgeCV(alphas=[0.1, 1.0, 10.0])

# Fit the linear regression
model_cv = regr_cv.fit(features_standardized, target)

# View coefficients
model_cv.coef_
array([1.29223201e-02, 4.40972291e+01, 5.38979372e+01])

我们可以轻松查看最佳模型的α值:

# View alpha
model_cv.alpha_
0.1

最后一点:因为在线性回归中系数的值部分由特征的尺度确定,在正则化模型中所有系数都被合并在一起,因此在训练之前必须确保对特征进行标准化。

13.5 使用 Lasso 回归减少特征

问题

您希望通过减少特征来简化您的线性回归模型。

解决方案

使用 lasso 回归:

# Load library
from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_regression

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

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create lasso regression with alpha value
regression = Lasso(alpha=0.5)

# Fit the linear regression
model = regression.fit(features_standardized, target)

讨论

lasso 回归惩罚的一个有趣特征是它可以将模型的系数收缩到零,有效减少模型中的特征数。例如,在我们的解决方案中,我们将alpha设置为0.5,我们可以看到许多系数为 0,意味着它们对应的特征未在模型中使用:

# View coefficients
model.coef_
array([-0\.        , 43.58618393, 53.39523724])

然而,如果我们将α增加到一个更高的值,我们会看到几乎没有特征被使用:

# Create lasso regression with a high alpha
regression_a10 = Lasso(alpha=10)
model_a10 = regression_a10.fit(features_standardized, target)
model_a10.coef_
array([-0\.        , 32.92181899, 42.73086731])

这种效果的实际好处在于,我们可以在特征矩阵中包含 100 个特征,然后通过调整 lasso 的 α 超参数,生成仅使用最重要的 10 个特征之一的模型(例如)。这使得我们能够在提升模型的可解释性的同时减少方差(因为更少的特征更容易解释)。

第十四章:树和森林

14.0 引言

基于树的学习算法是一类广泛且流行的非参数化监督方法,既适用于分类又适用于回归。基于树的学习器的基础是决策树,其中一系列决策规则(例如,“如果一个人的信用评分大于 720…​”)被链接起来。结果看起来略像一个倒置的树形,顶部是第一个决策规则,下面是后续的决策规则分支开展。在决策树中,每个决策规则出现在一个决策节点,规则创建通向新节点的分支。末端没有决策规则的分支称为叶节点

树模型之所以受欢迎的一个原因是它们的可解释性。事实上,决策树可以以完整形式绘制出来(参见配方 14.3),以创建一个高度直观的模型。从这个基本的树系统中产生了多种扩展,从随机森林到堆叠。在本章中,我们将讨论如何训练、处理、调整、可视化和评估多种基于树的模型。

14.1 训练决策树分类器

问题

你需要使用决策树训练一个分类器。

解决方案

使用 scikit-learn 的 DecisionTreeClassifier

# Load libraries
from sklearn.tree import DecisionTreeClassifier
from sklearn import datasets

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

# Create decision tree classifier object
decisiontree = DecisionTreeClassifier(random_state=0)

# Train model
model = decisiontree.fit(features, target)

讨论

决策树学习器试图找到一个决策规则,在节点处产生最大的不纯度减少。虽然有多种不纯度的测量方法,但默认情况下,DecisionTreeClassifier 使用基尼不纯度:

G ( t ) = 1 - ∑ i=1 c p i 2

其中 G(t) 是节点 t 处的基尼不纯度,pi 是节点 t 处类别 c 的观察比例。这个找到减少不纯度的决策规则并创建分裂的过程会递归重复,直到所有叶节点是纯净的(即只包含一个类别)或达到某个任意的截止点。

在 scikit-learn 中,DecisionTreeClassifier 的操作类似于其他学习方法;在使用 fit 训练模型之后,我们可以使用模型预测观察的类别:

# Make new observation
observation = [[ 5,  4,  3,  2]]

# Predict observation's class
model.predict(observation)
array([1])

我们还可以看到观察的预测类别概率:

# View predicted class probabilities for the three classes
model.predict_proba(observation)
array([[0., 1., 0.]])

最后,如果我们想使用不同的不纯度测量,我们可以使用 criterion 参数:

# Create decision tree classifier object using entropy
decisiontree_entropy = DecisionTreeClassifier(
    criterion='entropy', random_state=0)

# Train model
model_entropy = decisiontree_entropy.fit(features, target)

参见

14.2 训练决策树回归器

问题

你需要使用决策树训练一个回归模型。

解决方案

使用 scikit-learn 的 DecisionTreeRegressor

# Load libraries
from sklearn.tree import DecisionTreeRegressor
from sklearn import datasets

# Load data with only two features
diabetes = datasets.load_diabetes()
features = diabetes.data
target = diabetes.target

# Create decision tree regressor object
decisiontree = DecisionTreeRegressor(random_state=0)

# Train model
model = decisiontree.fit(features, target)

讨论

决策树回归与决策树分类类似;但是,它不是减少基尼不纯度或熵,而是默认情况下测量潜在分裂如何减少均方误差(MSE):

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

其中 yi 是目标的真实值,而 y ¯ i 是平均值。在 scikit-learn 中,可以使用 DecisionTreeRegressor 进行决策树回归。一旦我们训练好一个决策树,就可以用它来预测观测值的目标值:

# Make new observation
observation = [features[0]]

# Predict observation's value
model.predict(observation)
array([151.])

就像使用 DecisionTreeClassifier 一样,我们可以使用 criterion 参数来选择所需的分裂质量测量。例如,我们可以构建一个树,其分裂减少平均绝对误差(MAE):

# Create decision tree classifier object using MAE
decisiontree_mae = DecisionTreeRegressor(criterion="absolute_error",
  random_state=0)

# Train model
model_mae = decisiontree_mae.fit(features, target)

参见

14.3 可视化决策树模型

问题

需要可视化由决策树学习算法创建的模型。

解决方案

将决策树模型导出为 DOT 格式,然后进行可视化:

# Load libraries
import pydotplus
from sklearn.tree import DecisionTreeClassifier
from sklearn import datasets
from IPython.display import Image
from sklearn import tree

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

# Create decision tree classifier object
decisiontree = DecisionTreeClassifier(random_state=0)

# Train model
model = decisiontree.fit(features, target)

# Create DOT data
dot_data = tree.export_graphviz(decisiontree,
                                out_file=None,
                                feature_names=iris.feature_names,
                                class_names=iris.target_names)

# Draw graph
graph = pydotplus.graph_from_dot_data(dot_data)

# Show graph
Image(graph.create_png())

mpc2 14in01

讨论

决策树分类器的一个优点是,我们可以可视化整个训练好的模型,使决策树成为机器学习中最具可解释性的模型之一。在我们的解决方案中,我们将训练好的模型导出为 DOT 格式(一种图形描述语言),然后用它来绘制图形。

如果我们看根节点,我们可以看到决策规则是,如果花瓣宽度小于或等于 0.8 厘米,则进入左分支;否则,进入右分支。我们还可以看到基尼不纯度指数(0.667)、观测数量(150)、每个类中的观测数量([50,50,50])以及如果我们在该节点停止,观测将被预测为的类别(setosa)。我们还可以看到在该节点,学习者发现单个决策规则(花瓣宽度(厘米)<= 0.8)能够完美识别所有 setosa 类观测。此外,再增加一个相同特征的决策规则(花瓣宽度(厘米)<= 1.75),决策树能够正确分类 150 个观测中的 144 个。这使得花瓣宽度成为非常重要的特征!

如果我们想在其他应用程序或报告中使用决策树,可以将可视化导出为 PDF 或 PNG 图像:

# Create PDF
graph.write_pdf("iris.pdf")
True
# Create PNG
graph.write_png("iris.png")
True

虽然这个解决方案可视化了决策树分类器,但同样可以轻松用于可视化决策树回归器。

注意:macOS 用户可能需要安装 Graphviz 的可执行文件才能运行上述代码。可以使用 Homebrew 命令 brew install graphviz 完成安装。有关 Homebrew 安装说明,请访问 Homebrew 的网站。

参见

14.4 训练随机森林分类器

问题

要使用随机决策树“森林”训练分类模型。

解决方案

使用 scikit-learn 的 RandomForestClassifier 训练随机森林分类模型。

# Load libraries
from sklearn.ensemble import RandomForestClassifier
from sklearn import datasets

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

# Create random forest classifier object
randomforest = RandomForestClassifier(random_state=0, n_jobs=-1)

# Train model
model = randomforest.fit(features, target)

讨论

决策树的一个常见问题是它们往往过度拟合训练数据。这促使了一种称为随机森林的集成学习方法的广泛使用。在随机森林中,训练许多决策树,但每棵树只接收一个自举样本的观测集(即使用替换的原始观测数量的随机样本),并且在确定最佳分裂时,每个节点只考虑特征的一个子集。这些随机化决策树的森林(因此得名)投票以确定预测类别。

通过将这个解决方案与配方 14.1 进行比较,我们可以看到 scikit-learn 的RandomForestClassifierDecisionTreeClassifier类似:

# Make new observation
observation = [[ 5,  4,  3,  2]]

# Predict observation's class
model.predict(observation)
array([1])

RandomForestClassifier也使用与DecisionTreeClassifier许多相同的参数。例如,我们可以改变用于分裂质量的测量:

# Create random forest classifier object using entropy
randomforest_entropy = RandomForestClassifier(
    criterion="entropy", random_state=0)

# Train model
model_entropy = randomforest_entropy.fit(features, target)

然而,作为一组森林而不是单个决策树,RandomForestClassifier有一些参数是随机森林特有的或特别重要的。首先,max_features参数确定在每个节点考虑的最大特征数,并接受多个参数,包括整数(特征数量)、浮点数(特征百分比)和sqrt(特征数量的平方根)。默认情况下,max_features设置为auto,与sqrt相同。其次,bootstrap参数允许我们设置是否使用替换创建考虑树的观测子集(默认设置)或不使用替换。第三,n_estimators设置森林中包含的决策树数量。最后,虽然不特定于随机森林分类器,因为我们实际上在训练许多决策树模型,通常通过设置n_jobs=-1来使用所有可用核心是很有用的。

参见

14.5 训练随机森林回归器

问题

你希望使用“森林”中的随机决策树来训练回归模型。

解决方案

使用 scikit-learn 的RandomForestRegressor训练随机森林回归模型:

# Load libraries
from sklearn.ensemble import RandomForestRegressor
from sklearn import datasets

# Load data with only two features
diabetes = datasets.load_diabetes()
features = diabetes.data
target = diabetes.target

# Create random forest regressor object
randomforest = RandomForestRegressor(random_state=0, n_jobs=-1)

# Train model
model = randomforest.fit(features, target)

讨论

就像我们可以制作一组决策树分类器的森林一样,我们也可以制作一组决策树回归器,其中每棵树都使用一个自举样本集合,并且在每个节点处,决策规则只考虑特征的一个子集。与RandomForestClassifier一样,我们有一些重要的参数:

max_features

设置在每个节点考虑的最大特征数的最大值。默认为p个特征,其中p是总特征数。

bootstrap

设置是否使用替换采样。默认为True

n_estimators

设置要构建的决策树数量。默认为10

参见

14.6 使用袋外错误评估随机森林

问题

您需要在不使用交叉验证的情况下评估随机森林模型。

解决方案

计算模型的袋外得分:

# Load libraries
from sklearn.ensemble import RandomForestClassifier
from sklearn import datasets

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

# Create random forest classifier object
randomforest = RandomForestClassifier(
    random_state=0, n_estimators=1000, oob_score=True, n_jobs=-1)

# Train model
model = randomforest.fit(features, target)

# View out-of-bag-error
randomforest.oob_score_
0.9533333333333334

讨论

在随机森林中,每棵决策树使用自举子样本集进行训练。这意味着对于每棵树,都有一个单独的未用于训练该树的观察子集。这些称为袋外观察。我们可以使用袋外观察作为测试集来评估我们的随机森林的性能。

对于每个观察值,学习算法将观察的真实值与未使用该观察训练的树的预测进行比较。计算总体分数并提供随机森林性能的单一度量。袋外得分估计是交叉验证的一种替代方法。

在 scikit-learn 中,我们可以通过在随机森林对象(即 RandomForestClassifier)中设置 oob_score=True 来计算随机森林的袋外得分。可以使用 oob_score_ 属性来检索得分。

14.7 随机森林中重要特征的识别

问题

您需要知道随机森林模型中哪些特征最重要。

解决方案

通过检查模型的 feature_importances_ 属性计算和可视化每个特征的重要性:

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

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

# Create random forest classifier object
randomforest = RandomForestClassifier(random_state=0, n_jobs=-1)

# Train model
model = randomforest.fit(features, target)

# Calculate feature importances
importances = model.feature_importances_

# Sort feature importances in descending order
indices = np.argsort(importances)[::-1]

# Rearrange feature names so they match the sorted feature importances
names = [iris.feature_names[i] for i in indices]

# Create plot
plt.figure()

# Create plot title
plt.title("Feature Importance")

# Add bars
plt.bar(range(features.shape[1]), importances[indices])

# Add feature names as x-axis labels
plt.xticks(range(features.shape[1]), names, rotation=90)

# Show plot
plt.show()

mpc2 14in02

讨论

决策树的一个主要优点是可解释性。具体而言,我们可以可视化整个模型(参见 Recipe 14.3)。然而,随机森林模型由数十、数百甚至数千棵决策树组成。这使得对随机森林模型进行简单直观的可视化变得不切实际。尽管如此,还有另一种选择:我们可以比较(和可视化)每个特征的相对重要性。

在 Recipe 14.3 中,我们可视化了一个决策树分类器模型,并看到基于花瓣宽度的决策规则能够正确分类许多观察结果。直观地说,这意味着花瓣宽度在我们的分类器中是一个重要特征。更正式地说,具有分裂平均不纯度(例如分类器中的基尼不纯度或熵以及回归器中的方差)更大的特征被认为更重要。

但是,有两件事情需要注意关于特征重要性。首先,scikit-learn 要求我们将名义分类特征拆分为多个二进制特征。这会使得该特征的重要性分布在所有二进制特征上,即使原始的名义分类特征非常重要,也会使得每个特征看起来不重要。其次,如果两个特征高度相关,一个特征将会获得大部分重要性,使另一个特征看起来不太重要,这对解释有影响如果不考虑。

在 scikit-learn 中,分类和回归决策树以及随机森林可以使用 feature_importances_ 方法报告每个特征的相对重要性:

# View feature importances
model.feature_importances_
array([0.09090795, 0.02453104, 0.46044474, 0.42411627])

数值越高,特征越重要(所有重要性得分总和为 1)。通过绘制这些值,我们可以为我们的随机森林模型增加可解释性。

14.8 在随机森林中选择重要特征

问题

您需要对随机森林进行特征选择。

解决方案

确定重要特征并仅使用最重要的特征重新训练模型:

# Load libraries
from sklearn.ensemble import RandomForestClassifier
from sklearn import datasets
from sklearn.feature_selection import SelectFromModel

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

# Create random forest classifier
randomforest = RandomForestClassifier(random_state=0, n_jobs=-1)

# Create object that selects features with importance greater
# than or equal to a threshold
selector = SelectFromModel(randomforest, threshold=0.3)

# Create new feature matrix using selector
features_important = selector.fit_transform(features, target)

# Train random forest using most important features
model = randomforest.fit(features_important, target)

讨论

有些情况下,我们可能希望减少模型中特征的数量。例如,我们可能希望减少模型的方差,或者我们可能希望通过只包含最重要的特征来提高可解释性。

在 scikit-learn 中,我们可以使用一个简单的两阶段工作流程来创建一个具有减少特征的模型。首先,我们使用所有特征训练一个随机森林模型。然后,我们使用这个模型来识别最重要的特征。接下来,我们创建一个只包含这些特征的新特征矩阵。在我们的解决方案中,我们使用 SelectFromModel 方法创建一个包含重要性大于或等于某个 threshold 值的特征的特征矩阵。最后,我们使用这些特征创建一个新模型。

我们必须注意这种方法的两个限制。首先,已经进行了一次独热编码的名义分类特征会导致特征重要性在二元特征中被稀释。其次,高度相关特征的特征重要性将有效地分配给一个特征,而不是均匀分布在两个特征之间。

另请参阅

14.9 处理不平衡类别

问题

您有一个目标向量,其中包含高度不平衡的类别,并希望训练一个随机森林模型。

解决方案

使用 class_weight="balanced" 训练决策树或随机森林模型:

# Load libraries
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn import datasets

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

# Make class highly imbalanced by removing first 40 observations
features = features[40:,:]
target = target[40:]

# Create target vector indicating if class 0, otherwise 1
target = np.where((target == 0), 0, 1)

# Create random forest classifier object
randomforest = RandomForestClassifier(
    random_state=0, n_jobs=-1, class_weight="balanced")

# Train model
model = randomforest.fit(features, target)

讨论

在实际进行机器学习时,不平衡类别是一个常见问题。如果不加以解决,不平衡类别的存在会降低模型的性能。我们将在预处理过程中讨论如何处理不平衡类别 Recipe 17.5。然而,在 scikit-learn 中的许多学习算法都具有用于纠正不平衡类别的内置方法。我们可以使用 class_weight 参数将 RandomForestClassifier 设置为纠正不平衡类别。如果提供了一个字典,其形式为类别名称和所需权重(例如 {"male": 0.2, "female": 0.8}),RandomForestClassifier 将相应地加权类别。然而,更常见的参数是 balanced,其中类别的权重自动与其在数据中出现的频率成反比:

w j = n kn j

其中wj是类j的权重,n是观察次数,nj是类j中的观察次数,k是总类数。例如,在我们的解决方案中,有 2 个类别(k),110 次观察(n),分别有 10 和 100 次观察在每个类中(nj)。如果我们使用class_weight="balanced"来加权类别,则较小的类别被加权更多:

# Calculate weight for small class
110/(2*10)
5.5

而较大的类别被加权更少:

# Calculate weight for large class
110/(2*100)
0.55

14.10 控制树的大小

问题

您希望手动确定决策树的结构和大小。

解决方案

在 scikit-learn 基于树的学习算法中使用树结构参数:

# Load libraries
from sklearn.tree import DecisionTreeClassifier
from sklearn import datasets

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

# Create decision tree classifier object
decisiontree = DecisionTreeClassifier(random_state=0,
                                      max_depth=None,
                                      min_samples_split=2,
                                      min_samples_leaf=1,
                                      min_weight_fraction_leaf=0,
                                      max_leaf_nodes=None,
                                      min_impurity_decrease=0)

# Train model
model = decisiontree.fit(features, target)

讨论

scikit-learn 的基于树的学习算法有多种控制决策树大小的技术。这些通过参数访问:

max_depth

树的最大深度。如果为None,则树会生长直到所有叶子节点都是纯净的。如果是整数,则树被“修剪”到该深度。

min_samples_split

在一个节点上分裂之前所需的最小观察次数。如果提供整数作为参数,则确定原始最小值,如果提供浮点数,则最小值是总观察次数的百分比。

min_samples_leaf

在叶子节点上所需的最小观察次数。与min_samples_split相同的参数。

max_leaf_nodes

最大叶子节点数。

min_impurity_split

在执行分割之前所需的最小不纯度减少。

虽然知道这些参数存在是有用的,但最可能我们只会使用max_depthmin_impurity_split,因为更浅的树(有时称为树桩)是更简单的模型,因此具有较低的方差。

14.11 通过增强提高性能

问题

您需要比决策树或随机森林性能更好的模型。

解决方案

使用AdaBoostClassifierAdaBoostRegressor训练增强模型:

# Load libraries
from sklearn.ensemble import AdaBoostClassifier
from sklearn import datasets

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

# Create adaboost tree classifier object
adaboost = AdaBoostClassifier(random_state=0)

# Train model
model = adaboost.fit(features, target)

讨论

在随机森林中,随机化决策树的集合预测目标向量。另一种常用且通常更强大的方法称为增强。在增强的一种形式中称为 AdaBoost,我们迭代地训练一系列弱模型(通常是浅决策树,有时称为树桩),每次迭代都优先考虑前一个模型预测错误的观察结果。更具体地,在 AdaBoost 中:

  1. 为每个观察值 xi 分配初始权重值 w i = 1 n,其中 n 是数据中观察总数。

  2. 在数据上训练一个“弱”模型。

  3. 对于每个观察:

    1. 如果弱模型正确预测 xi,则 wi 减少。

    2. 如果弱模型错误预测 xi,则 wi 增加。

  4. 训练一个新的弱模型,其中具有较大 wi 的观察值优先考虑。

  5. 重复步骤 4 和 5,直到数据完全预测或训练了预设数量的弱模型。

结果是一个聚合模型,个体弱模型专注于更难(从预测角度)的观察。在 scikit-learn 中,我们可以使用 AdaBoostClassifierAdaBoostRegressor 实现 AdaBoost。最重要的参数是 base_estimatorn_estimatorslearning_rateloss

base_estimator

base_estimator 是用于训练弱模型的学习算法。与 AdaBoost 一起使用的最常见的学习器是决策树,默认参数。

n_estimators

n_estimators 是要迭代训练的模型数量。

learning_rate

learning_rate 是每个模型对权重的贡献,默认为 1。减小学习率意味着权重将略微增加或减少,迫使模型训练速度变慢(但有时会导致更好的性能分数)。

loss

loss 仅适用于 AdaBoostRegressor,设置更新权重时使用的损失函数。默认为线性损失函数,但可以更改为 squareexponential

参见

14.12 训练 XGBoost 模型

问题

您需要训练具有高预测能力的基于树的模型。

解决方案

使用 xgboost Python 库:

# Load libraries
import xgboost as xgb
from sklearn import datasets, preprocessing
from sklearn.metrics import classification_report
from numpy import argmax

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

# Create dataset
xgb_train = xgb.DMatrix(features, label=target)

# Define parameters
param = {
    'objective': 'multi:softprob',
    'num_class': 3
}

# Train model
gbm = xgb.train(param, xgb_train)

# Get predictions
predictions = argmax(gbm.predict(xgb_train), axis=1)

# Get a classification report
print(classification_report(target, predictions))
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        50
           1       1.00      0.96      0.98        50
           2       0.96      1.00      0.98        50

    accuracy                           0.99       150
   macro avg       0.99      0.99      0.99       150
weighted avg       0.99      0.99      0.99       150

讨论

XGBoost(即极端梯度提升)是机器学习领域中非常流行的梯度提升算法。尽管它不总是基于树的模型,但经常应用于决策树集成中。由于在机器学习竞赛网站 Kaggle 上取得了广泛成功,XGBoost 因其卓越的性能提升而备受青睐,已成为提高性能的可靠算法,超越了典型随机森林或梯度增强机器的性能。

尽管 XGBoost 因计算密集而闻名,但过去几年的计算性能优化(例如 GPU 支持)显著简化了与 XGBoost 的快速迭代,它仍然是在统计性能要求时的常见选择算法。

另请参阅

14.13 使用 LightGBM 提升实时性能

问题

您需要训练一个在计算上优化的基于梯度提升树的模型。

解决方案

使用梯度提升机库 lightgbm

# Load libraries
import lightgbm as lgb
from sklearn import datasets, preprocessing
from sklearn.metrics import classification_report
from numpy import argmax

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

# Create dataset
lgb_train = lgb.Dataset(features, target)

# Define parameters
params = {
    'objective': 'multiclass',
    'num_class': 3,
    'verbose': -1,
}

# Train model
gbm = lgb.train(params, lgb_train)

# Get predictions
predictions = argmax(gbm.predict(features), axis=1)

# Get a classification report
print(classification_report(target, predictions))
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        50
           1       1.00      1.00      1.00        50
           2       1.00      1.00      1.00        50

    accuracy                           1.00       150
   macro avg       1.00      1.00      1.00       150
weighted avg       1.00      1.00      1.00       150

讨论

lightgbm 库用于梯度提升机,在训练时间、推断和 GPU 支持方面都经过高度优化。由于其计算效率高,常用于生产环境和大规模设置中。尽管 scikit-learn 模型通常更易于使用,但某些库如 lightgbm 在受限于大数据或严格的模型训练/服务时间时会更实用。

另请参阅

第十五章:K 近邻算法

15.0 简介

k-最近邻(KNN)分类器是监督机器学习中最简单但最常用的分类器之一。KNN 通常被认为是一种惰性学习器;它不会技术上训练一个模型来进行预测。相反,一个观测值被预测为与 k 个最近观测值中最大比例的类相同。

例如,如果一个具有未知类的观测值被一个类为 1 的观测值所包围,则该观测值将被分类为类 1。在本章中,我们将探讨如何使用 scikit-learn 创建和使用 KNN 分类器。

15.1 寻找一个观测值的最近邻居

问题

您需要找到一个观测值的 k 个最近邻居。

解决方案

使用 scikit-learn 的 NearestNeighbors

# Load libraries
from sklearn import datasets
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import StandardScaler

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

# Create standardizer
standardizer = StandardScaler()

# Standardize features
features_standardized = standardizer.fit_transform(features)

# Two nearest neighbors
nearest_neighbors = NearestNeighbors(n_neighbors=2).fit(features_standardized)

# Create an observation
new_observation = [ 1,  1,  1,  1]

# Find distances and indices of the observation's nearest neighbors
distances, indices = nearest_neighbors.kneighbors([new_observation])

# View the nearest neighbors
features_standardized[indices]
array([[[1.03800476, 0.55861082, 1.10378283, 1.18556721],
        [0.79566902, 0.32841405, 0.76275827, 1.05393502]]])

讨论

在我们的解决方案中,我们使用了鸢尾花数据集。我们创建了一个观测值,new_observation,具有一些值,然后找到了最接近我们观测值的两个观测值。 indices 包含了最接近我们数据集中的观测值的位置,所以 X[indices] 显示了这些观测值的值。直观地,距离可以被看作是相似性的度量,因此两个最接近的观测值是与我们创建的花最相似的两朵花。

我们如何衡量距离?scikit-learn 提供了多种距离度量方式,d,包括欧几里得距离:

d euclidean = ∑ i=1 n (x i -y i ) 2

和曼哈顿距离:

d manhattan = ∑ i=1 n x i - y i

默认情况下,NearestNeighbors 使用闵可夫斯基距离:

d minkowski = ∑ i=1 n x i -y i p 1/p

其中xi和yi是我们计算距离的两个观测值。闵可夫斯基距离包括一个超参数p,其中p=1 是曼哈顿距离,p=2 是欧几里得距离,等等。在 scikit-learn 中,默认情况下p=2。

我们可以使用 metric 参数设置距离度量:

# Find two nearest neighbors based on Euclidean distance
nearestneighbors_euclidean = NearestNeighbors(
    n_neighbors=2, metric='euclidean').fit(features_standardized)

我们创建的 distance 变量包含了到两个最近邻居的实际距离测量:

# View distances
distances
array([[0.49140089, 0.74294782]])

此外,我们可以使用 kneighbors_graph 创建一个矩阵,指示每个观测值的最近邻居:

# Find each observation's three nearest neighbors
# based on Euclidean distance (including itself)
nearestneighbors_euclidean = NearestNeighbors(
    n_neighbors=3, metric="euclidean").fit(features_standardized)

# List of lists indicating each observation's three nearest neighbors
# (including itself)
nearest_neighbors_with_self = nearestneighbors_euclidean.kneighbors_graph(
    features_standardized).toarray()

# Remove 1s marking an observation is a nearest neighbor to itself
for i, x in enumerate(nearest_neighbors_with_self):
    x[i] = 0

# View first observation's two nearest neighbors
nearest_neighbors_with_self[0]
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 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., 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., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

当我们寻找最近邻居或使用基于距离的任何学习算法时,重要的是要转换特征,使它们处于相同的尺度上。这是因为距离度量将所有特征都视为处于相同的尺度上,但如果一个特征是以百万美元计算的,而第二个特征是以百分比计算的,那么计算出的距离将偏向于前者。在我们的解决方案中,我们通过使用 StandardScaler 对特征进行了标准化,以解决这个潜在的问题。

15.2 创建 K 近邻分类器

问题

给定一个未知类别的观测值,你需要根据其邻居的类别预测其类别。

解答

如果数据集不太大,使用 KNeighborsClassifier

# Load libraries
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn import datasets

# Load data
iris = datasets.load_iris()
X = iris.data
y = iris.target

# Create standardizer
standardizer = StandardScaler()

# Standardize features
X_std = standardizer.fit_transform(X)

# Train a KNN classifier with 5 neighbors
knn = KNeighborsClassifier(n_neighbors=5, n_jobs=-1).fit(X_std, y)

# Create two observations
new_observations = [[ 0.75,  0.75,  0.75,  0.75],
                    [ 1,  1,  1,  1]]

# Predict the class of two observations
knn.predict(new_observations)
array([1, 2])

讨论

在 KNN 中,给定一个观测值,xu,其目标类别未知,算法首先基于某种距离度量(例如欧几里得距离)确定最近的 k 个观测值(有时称为 xu 的 邻域),然后这些 k 个观测值基于它们的类别“投票”,获胜的类别就是 xu 的预测类别。更正式地,某个类别 j 的概率 xu 为:

1 k ∑ i∈ν I ( y i = j )

其中 ν 是 k 个观测值在 xu 的邻域中,yi 是第 i 个观测值的类别,I 是一个指示函数(即,1 为真,0 其他)。在 scikit-learn 中,我们可以使用 predict_proba 查看这些概率:

# View probability that each observation is one of three classes
knn.predict_proba(new_observations)
array([[0\. , 0.6, 0.4],
       [0\. , 0\. , 1\. ]])

概率最高的类别成为预测类别。例如,在前面的输出中,第一观测值应该是类别 1 (Pr = 0.6),而第二观测值应该是类别 2 (Pr = 1),这正是我们所看到的:

knn.predict(new_observations)
array([1, 2])

KNeighborsClassifier 包含许多重要参数需要考虑。首先,metric 设置使用的距离度量。其次,n_jobs 决定使用计算机的多少核心。因为做出预测需要计算一个点与数据中每个点的距离,推荐使用多个核心。第三,algorithm 设置计算最近邻的方法。虽然算法之间存在实际差异,默认情况下 KNeighborsClassifier 尝试自动选择最佳算法,因此通常不需要担心这个参数。第四,默认情况下 KNeighborsClassifier 的工作方式与我们之前描述的相同,每个邻域中的观测值获得一个投票;然而,如果我们将 weights 参数设置为 distance,则更靠近的观测值的投票比更远的观测值更重要。直观上这很有道理,因为更相似的邻居可能会告诉我们更多关于一个观测值类别的信息。

最后,由于距离计算将所有特征视为在相同尺度上,因此在使用 KNN 分类器之前,标准化特征是很重要的。

15.3 确定最佳邻域大小

问题

你想在 k 最近邻分类器中选择最佳的 k 值。

解答

使用模型选择技术如 GridSearchCV

# Load libraries
from sklearn.neighbors import KNeighborsClassifier
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.model_selection import GridSearchCV

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

# Create standardizer
standardizer = StandardScaler()

# Create a KNN classifier
knn = KNeighborsClassifier(n_neighbors=5, n_jobs=-1)

# Create a pipeline
pipe = Pipeline([("standardizer", standardizer), ("knn", knn)])

# Create space of candidate values
search_space = [{"knn__n_neighbors": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}]

# Create grid search
classifier = GridSearchCV(
    pipe, search_space, cv=5, verbose=0).fit(features_standardized, target)

讨论

k 的大小在 KNN 分类器中有着真实的影响。在机器学习中,我们试图在偏差和方差之间找到一个平衡点,k 的值在其中的显性展示是极其重要的。如果 k = n,其中 n 是观测数量,那么我们具有高偏差但低方差。如果 k = 1,我们将具有低偏差但高方差。最佳模型将通过找到能平衡这种偏差-方差权衡的k的值来获得。在我们的解决方案中,我们使用GridSearchCV对具有不同k值的 KNN 分类器进行了五折交叉验证。完成后,我们可以看到产生最佳模型的k值:

# Best neighborhood size (k)
classifier.best_estimator_.get_params()["knn__n_neighbors"]
6

15.4 创建基于半径的最近邻分类器

问题

给定一个未知类的观测值,您需要基于一定距离内所有观测值的类来预测其类别。

解决方案

使用 RadiusNeighborsClassifier

# Load libraries
from sklearn.neighbors import RadiusNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn import datasets

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

# Create standardizer
standardizer = StandardScaler()

# Standardize features
features_standardized = standardizer.fit_transform(features)

# Train a radius neighbors classifier
rnn = RadiusNeighborsClassifier(
    radius=.5, n_jobs=-1).fit(features_standardized, target)

# Create two observations
new_observations = [[ 1,  1,  1,  1]]

# Predict the class of two observations
rnn.predict(new_observations)
array([2])

讨论

在 KNN 分类中,一个观测的类别是根据其 k 个邻居的类别预测的。一种不太常见的技术是基于半径的最近邻(RNN)分类器,其中一个观测的类别是根据给定半径 r 内所有观测的类别预测的。

在 scikit-learn 中,RadiusNeighborsClassifierKNeighbors​Classi⁠fier非常相似,只有两个参数例外。首先,在RadiusNeighbors​Clas⁠sifier中,我们需要指定用于确定观测是否为邻居的固定区域半径使用radius。除非有设置radius到某个值的实质性原因,否则最好在模型选择过程中像其他超参数一样进行调整。第二个有用的参数是outlier_label,它指示如果半径内没有观测值,则给出一个观测的标签—这本身可以是一个用于识别异常值的有用工具。

15.5 寻找近似最近邻

问题

您希望在低延迟下获取大数据的最近邻:

解决方案

使用 近似最近邻(ANN)搜索,使用 Facebook 的 faiss 库:

# Load libraries
import faiss
import numpy as np
from sklearn import datasets
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import StandardScaler

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

# Create standardizer
standardizer = StandardScaler()

# Standardize features
features_standardized = standardizer.fit_transform(features)

# Set faiss parameters
n_features = features_standardized.shape[1]
nlist = 3
k = 2

# Create an IVF index
quantizer = faiss.IndexFlatIP(n_features)
index = faiss.IndexIVFFlat(quantizer, n_features, nlist)

# Train the index and add feature vectors
index.train(features_standardized)
index.add(features_standardized)

# Create an observation
new_observation = np.array([[ 1,  1,  1,  1]])

# Search the index for the 2 nearest neighbors
distances, indices = index.search(new_observation, k)

# Show the feature vectors for the two nearest neighbors
np.array([list(features_standardized[i]) for i in indices[0]])
array([[1.03800476, 0.55861082, 1.10378283, 1.18556721],
       [0.79566902, 0.32841405, 0.76275827, 1.05393502]])

讨论

KNN 是在小数据集中找到最相似观测的一个很好的方法。然而,随着数据集的增大,计算任意观测与数据集中所有其他点之间距离所需的时间也会增加。大规模的 ML 系统如搜索或推荐引擎通常使用某种形式的向量相似度测量来检索相似的观测。但在实时规模中,我们需要在不到 100 毫秒内获得结果,KNN 变得不可行。

ANN 通过牺牲精确最近邻搜索的一些质量以换取速度来帮助我们克服这个问题。换句话说,虽然 ANN 搜索的前 10 个最近邻的顺序和项可能与精确 KNN 搜索的前 10 个结果不匹配,但我们能更快地得到这前 10 个最近邻。

在这个例子中,我们使用了一种名为倒排文件索引(IVF)的 ANN 方法。这种方法通过使用聚类来限制最近邻搜索的范围。IVF 使用 Voronoi 镶嵌将我们的搜索空间划分为多个不同的区域(或聚类)。当我们去查找最近邻时,我们访问了有限数量的聚类以找到相似的观测值,而不是对数据集中的每一个点进行比较。

如何从数据中创建 Voronoi 镶嵌最好通过简单的数据可视化。例如,取随机数据的散点图在二维中可视化,如图 15-1 所示。

mpc2 1501

图 15-1. 一组随机生成的二维数据的散点图

使用 Voronoi 镶嵌,我们可以创建多个子空间,每个子空间只包含我们想要搜索的总观测的一个小子集,如图 15-2 所示。

mpc2 1502

图 15-2. 将随机生成的二维数据分割成多个不同子空间

Faiss库中,nlist参数允许我们定义要创建的聚类数。还可以在查询时使用一个额外的参数nprobe来定义要搜索的聚类数,以检索给定观测值的最近邻。增加nlistnprobe都可以提高邻居的质量,但会增加计算成本,从而导致 IVF 索引的运行时间更长。减少这些参数会产生相反的效果,您的代码将运行得更快,但可能返回质量较低的结果。

注意,此示例返回与本章第一个配方完全相同的输出。这是因为我们处理的是非常小的数据,并且仅使用了三个聚类,这使得我们的 ANN 结果与我们的 KNN 结果没有显著差异。

参见

15.6 评估近似最近邻

问题

您想看看您的 ANN 与精确最近邻(KNN)的比较情况:

解决方案

计算 ANN 相对于 KNN 的 Recall @k 最近邻

# Load libraries
import faiss
import numpy as np
from sklearn import datasets
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import StandardScaler

# Number of nearest neighbors
k = 10

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

# Create standardizer
standardizer = StandardScaler()

# Standardize features
features_standardized = standardizer.fit_transform(features)

# Create KNN with 10 NN
nearest_neighbors = NearestNeighbors(n_neighbors=k).fit(features_standardized)

# Set faiss parameters
n_features = features_standardized.shape[1]
nlist = 3

# Create an IVF index
quantizer = faiss.IndexFlatIP(n_features)
index = faiss.IndexIVFFlat(quantizer, n_features, nlist)

# Train the index and add feature vectors
index.train(features_standardized)
index.add(features_standardized)
index.nprobe = 1

# Create an observation
new_observation = np.array([[ 1,  1,  1,  1]])

# Find distances and indices of the observation's exact nearest neighbors
knn_distances, knn_indices = nearest_neighbors.kneighbors(new_observation)

# Search the index for the two nearest neighbors
ivf_distances, ivf_indices = index.search(new_observation, k)

# Get the set overlap
recalled_items = set(list(knn_indices[0])) & set(list(ivf_indices[0]))

# Print the recall
print(f"Recall @k={k}: {len(recalled_items)/k * 100}%")
Recall @k=10: 100.0%

讨论

Recall @k 最简单的定义是 ANN 在某个k个最近邻处返回的项目数,这些项目同时也出现在相同k个精确最近邻中,除以k。在这个例子中,在 10 个最近邻处,我们有 100%的召回率,这意味着我们的 ANN 返回的索引与我们的 KNN 在 k=10 时是相同的(尽管不一定是相同的顺序)。

在评估 ANN 与精确最近邻时,Recall 是一个常用的度量标准。

参见

第十六章:Logistic 回归

16.0 引言

尽管其名称中带有“回归”,逻辑回归 实际上是一种广泛使用的监督分类技术。逻辑回归(及其扩展,如多项式逻辑回归)是一种直接、被理解的方法,用于预测观察值属于某个类别的概率。在本章中,我们将涵盖在 scikit-learn 中使用逻辑回归训练各种分类器的过程。

16.1 训练一个二元分类器

问题

您需要训练一个简单的分类器模型。

解决方案

使用 LogisticRegression 在 scikit-learn 中训练一个逻辑回归模型:

# Load libraries
from sklearn.linear_model import LogisticRegression
from sklearn import datasets
from sklearn.preprocessing import StandardScaler

# Load data with only two classes
iris = datasets.load_iris()
features = iris.data[:100,:]
target = iris.target[:100]

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create logistic regression object
logistic_regression = LogisticRegression(random_state=0)

# Train model
model = logistic_regression.fit(features_standardized, target)

讨论

尽管其名称中带有“回归”,逻辑回归实际上是一种广泛使用的二元分类器(即目标向量只能取两个值)。在逻辑回归中,线性模型(例如β[0] + β[1]x)包含在逻辑(也称为 sigmoid)函数中,11+e -z,使得:

P ( y i = 1 ∣ X ) = 1 1+e -(β 0 +β 1 x)

其中P(y i =1∣X)是第i个观察目标值yi为类别 1 的概率;X是训练数据;β0和β1是待学习的参数;e是自然常数。逻辑函数的效果是将函数的输出值限制在 0 到 1 之间,因此可以解释为概率。如果P(y i =1∣X)大于 0.5,则预测为类别 1;否则,预测为类别 0。

在 scikit-learn 中,我们可以使用 LogisticRegression 训练一个逻辑回归模型。一旦训练完成,我们可以使用该模型预测新观察的类别:

# Create new observation
new_observation = [[.5, .5, .5, .5]]

# Predict class
model.predict(new_observation)
array([1])

在这个例子中,我们的观察被预测为类别 1。此外,我们可以看到观察为每个类的成员的概率:

# View predicted probabilities
model.predict_proba(new_observation)
array([[0.17738424, 0.82261576]])

我们的观察有 17.7%的机会属于类别 0,82.2%的机会属于类别 1。

16.2 训练一个多类分类器

问题

如果超过两个类别,则需要训练一个分类器模型。

解决方案

使用 LogisticRegression 在 scikit-learn 中训练一个逻辑回归,使用一对多或多项式方法:

# Load libraries
from sklearn.linear_model import LogisticRegression
from sklearn import datasets
from sklearn.preprocessing import StandardScaler

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

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create one-vs-rest logistic regression object
logistic_regression = LogisticRegression(random_state=0, multi_class="ovr")

# Train model
model = logistic_regression.fit(features_standardized, target)

讨论

单独来看,逻辑回归只是二元分类器,意味着它不能处理目标向量超过两个类。然而,逻辑回归的两个巧妙扩展可以做到。首先,在 一对多 逻辑回归(OvR)中,为每个预测的类别训练一个单独的模型,无论观察结果是否属于该类(从而将其转化为二元分类问题)。它假设每个分类问题(例如,类别 0 或非类别 0)是独立的。

或者,多项式逻辑回归(MLR)中,我们在 配方 16.1 中看到的逻辑函数被 softmax 函数取代:

P ( y i = k ∣ X ) = e β k x i ∑ j=1 K e β j x i

其中 P(y i =k∣X) 是第 i 个观察目标值 y i 属于类别 k 的概率,K 是总类别数。MLR 的一个实际优势是,使用 predict_proba 方法预测的概率更可靠(即更好地校准)。

当使用 LogisticRegression 时,我们可以选择我们想要的两种技术之一,OvR (ovr) 是默认参数。我们可以通过设置参数为 multinomial 切换到 MLR。

16.3 通过正则化减少方差

问题

你需要减少逻辑回归模型的方差。

解决方案

调整正则化强度超参数 C

# Load libraries
from sklearn.linear_model import LogisticRegressionCV
from sklearn import datasets
from sklearn.preprocessing import StandardScaler

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

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create decision tree regression object
logistic_regression = LogisticRegressionCV(
    penalty='l2', Cs=10, random_state=0, n_jobs=-1)

# Train model
model = logistic_regression.fit(features_standardized, target)

讨论

正则化 是一种惩罚复杂模型以减少其方差的方法。具体来说,是向我们试图最小化的损失函数中添加一个惩罚项,通常是 L1 和 L2 惩罚。在 L1 惩罚中:

α ∑ j=1 p β ^ j

其中 β^ j 是正在学习的第 j 个特征的参数,α 是表示正则化强度的超参数。使用 L2 惩罚时:

α ∑ j=1 p β ^ j 2

较高的 α 值增加了较大参数值的惩罚(即更复杂的模型)。scikit-learn 遵循使用 C 而不是 α 的常见方法,其中 C 是正则化强度的倒数:C=1 α。为了在使用逻辑回归时减少方差,我们可以将 C 视为一个超参数,用于调整以找到创建最佳模型的 C 的值。在 scikit-learn 中,我们可以使用 LogisticRegressionCV 类来高效地调整 C。LogisticRegressionCV 的参数 Cs 可以接受一个值范围供 C 搜索(如果提供一个浮点数列表作为参数),或者如果提供一个整数,则会在对数尺度的 -10,000 到 10,000 之间生成相应数量的候选值列表。

不幸的是,LogisticRegressionCV 不允许我们在不同的惩罚项上搜索。为了做到这一点,我们必须使用在 第十二章 讨论的效率较低的模型选择技术。

16.4 在非常大的数据上训练分类器

问题

您需要在非常大的数据集上训练一个简单的分类器模型。

解决方案

使用 stochastic average gradient(SAG)求解器在 scikit-learn 中训练逻辑回归:

# Load libraries
from sklearn.linear_model import LogisticRegression
from sklearn import datasets
from sklearn.preprocessing import StandardScaler

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

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create logistic regression object
logistic_regression = LogisticRegression(random_state=0, solver="sag")

# Train model
model = logistic_regression.fit(features_standardized, target)

讨论

scikit-learn 的 LogisticRegression 提供了一些训练逻辑回归的技术,称为 solvers。大多数情况下,scikit-learn 会自动为我们选择最佳的求解器,或者警告我们无法使用某个求解器来做某事。然而,有一个特定的情况我们应该注意。

尽管详细解释超出了本书的范围(更多信息请参见 Mark Schmidt 在本章 “参见” 部分的幻灯片),随机平均梯度下降使我们能够在数据非常大时比其他求解器更快地训练模型。然而,它对特征缩放非常敏感,因此标准化我们的特征特别重要。我们可以通过设置 solver="sag" 来让我们的学习算法使用这个求解器。

参见

16.5 处理不平衡的类

问题

您需要训练一个简单的分类器模型。

解决方案

使用 scikit-learn 中的 LogisticRegression 训练逻辑回归模型:

# Load libraries
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn import datasets
from sklearn.preprocessing import StandardScaler

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

# Make class highly imbalanced by removing first 40 observations
features = features[40:,:]
target = target[40:]

# Create target vector indicating if class 0, otherwise 1
target = np.where((target == 0), 0, 1)

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create decision tree regression object
logistic_regression = LogisticRegression(random_state=0, class_weight="balanced")

# Train model
model = logistic_regression.fit(features_standardized, target)

讨论

就像 scikit-learn 中许多其他学习算法一样,LogisticRegression 自带处理不平衡类别的方法。如果我们的类别高度不平衡,在预处理过程中没有处理它,我们可以使用 class_weight 参数来加权这些类别,以确保每个类别的混合平衡。具体地,balanced 参数将自动根据其频率的倒数加权类别:

w j = n kn j

其中 wj 是类别 j 的权重,n 是观测数量,nj 是类别 j 中的观测数量,k 是总类别数量。

第十七章:支持向量机

17.0 引言

要理解支持向量机,我们必须了解超平面。形式上,超平面n - 1维空间中的一个* n * -维子空间。尽管听起来复杂,但实际上相当简单。例如,如果我们想要划分一个二维空间,我们会使用一维超平面(即,一条线)。如果我们想要划分一个三维空间,我们会使用二维超平面(即,一张平面或一张床单)。超平面只是将该概念推广到n维空间的一种方式。

支持向量机通过找到在训练数据中最大化类之间间隔的超平面来对数据进行分类。在一个二维示例中,我们可以将超平面看作是分开两个类的最宽的直线“带”(即,具有间隔的线)。

在本章中,我们将涵盖在各种情况下训练支持向量机,并深入了解如何扩展该方法以解决常见问题。

17.1 训练线性分类器

问题

您需要训练一个模型来对观察结果进行分类。

解决方案

使用支持向量分类器(SVC)找到最大化类之间间隔的超平面:

# Load libraries
from sklearn.svm import LinearSVC
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
import numpy as np

# Load data with only two classes and two features
iris = datasets.load_iris()
features = iris.data[:100,:2]
target = iris.target[:100]

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create support vector classifier
svc = LinearSVC(C=1.0)

# Train model
model = svc.fit(features_standardized, target)

讨论

scikit-learn 的LinearSVC实现了一个简单的 SVC。为了理解 SVC 正在做什么,让我们绘制出数据和超平面的图像。虽然 SVC 在高维度下工作得很好,但在我们的解决方案中,我们只加载了两个特征并取了一部分观察结果,使得数据只包含两个类。这将让我们可以可视化模型。回想一下,当我们只有两个维度时,SVC 试图找到具有最大间隔的超平面—​一条线—​来分离类。在下面的代码中,我们在二维空间中绘制了两个类,然后绘制了超平面:

# Load library
from matplotlib import pyplot as plt

# Plot data points and color using their class
color = ["black" if c == 0 else "lightgrey" for c in target]
plt.scatter(features_standardized[:,0], features_standardized[:,1], c=color)

# Create the hyperplane
w = svc.coef_[0]
a = -w[0] / w[1]
xx = np.linspace(-2.5, 2.5)
yy = a * xx - (svc.intercept_[0]) / w[1]

# Plot the hyperplane
plt.plot(xx, yy)
plt.axis("off"), plt.show();

mpc2 17in01

在这个可视化中,所有类 0 的观察结果都是黑色的,而类 1 的观察结果是浅灰色的。超平面是决策边界,决定了如何对新的观察结果进行分类。具体来说,线上方的任何观察结果将被分类为类 0,而线下方的任何观察结果将被分类为类 1。我们可以通过在可视化的左上角创建一个新的观察结果来证明这一点,这意味着它应该被预测为类 0:

# Create new observation
new_observation = [[ -2,  3]]

# Predict class of new observation
svc.predict(new_observation)
array([0])

关于 SVC 有几点需要注意。首先,为了可视化的目的,我们将示例限制为二元示例(即,只有两个类);但是,SVC 可以很好地处理多类问题。其次,正如我们的可视化所示,超平面在定义上是线性的(即,不是曲线的)。在这个示例中这是可以的,因为数据是线性可分的,意味着有一个能够完美分离两个类的超平面。不幸的是,在现实世界中,这种情况很少见。

更典型地,我们将无法完全分开类别。在这些情况下,支持向量分类器在最大化超平面间隔和最小化误分类之间存在平衡。在 SVC 中,后者由超参数 C 控制。C 是 SVC 学习器的参数,是对误分类数据点的惩罚。当 C 较小时,分类器可以接受误分类的数据点(高偏差但低方差)。当 C 较大时,分类器对误分类数据点进行严格惩罚,因此竭尽全力避免任何误分类数据点(低偏差但高方差)。

在 scikit-learn 中,C 是由参数 C 确定的,默认为 C=1.0。我们应该将 C 视为我们学习算法的超参数,通过模型选择技术在 第十二章 中进行调优。

17.2 使用核处理线性不可分类

问题

你需要训练一个支持向量分类器,但你的类别是线性不可分的。

解决方案

使用核函数训练支持向量机的扩展,以创建非线性决策边界:

# Load libraries
from sklearn.svm import SVC
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
import numpy as np

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

# Generate two features
features = np.random.randn(200, 2)

# Use an XOR gate (you don't need to know what this is) to generate
# linearly inseparable classes
target_xor = np.logical_xor(features[:, 0] > 0, features[:, 1] > 0)
target = np.where(target_xor, 0, 1)

# Create a support vector machine with a radial basis function kernel
svc = SVC(kernel="rbf", random_state=0, gamma=1, C=1)

# Train the classifier
model = svc.fit(features, target)

讨论

对支持向量机的全面解释超出了本书的范围。但是,简短的解释可能有助于理解支持向量机和核。出于最好在其他地方学习的原因,支持向量分类器可以表示为:

f ( x ) = β 0 + ∑ iϵS α i K ( x i , x i ' )

其中 β0 是偏差,S 是所有支持向量观测的集合,α 是待学习的模型参数,( x i , x i ' ) 是两个支持向量观测 xi 和 x i ' 的对。最重要的是,K 是一个核函数,用于比较 xi 和 x i ' 之间的相似性。如果你不理解核函数也不用担心。对于我们的目的,只需意识到:(1)K 决定了用于分离我们类别的超平面类型,(2)我们通过使用不同的核函数创建不同的超平面。例如,如果我们想要一个类似于我们在 配方 17.1 中创建的基本线性超平面,我们可以使用线性核:

K ( x i , x i ' ) = ∑ j=1 p x ij x i ' j

其中 p 是特征数。然而,如果我们想要一个非线性决策边界,我们可以将线性核替换为多项式核:

K ( x i , x i ' ) = (r+γ∑ j=1 p x ij x i ' j ) d

其中d是多项式核函数的阶数。或者,我们可以使用支持向量机中最常见的核函数之一,径向基函数核

K ( x i , x i ' ) = e (-γ∑ j=1 p (x ij x i ' j ) 2 )

其中γ是一个超参数,必须大于零。上述解释的主要观点是,如果我们有线性不可分的数据,我们可以用替代核函数替换线性核函数,从而创建非线性超平面决策边界。

我们可以通过可视化一个简单的例子来理解核函数的直觉。这个函数基于 Sebastian Raschka 的一个函数,绘制了二维空间的观测和决策边界超平面。您不需要理解这个函数的工作原理;我在这里包含它,以便您自己进行实验:

# Plot observations and decision boundary hyperplane
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt

def plot_decision_regions(X, y, classifier):
    cmap = ListedColormap(("red", "blue"))
    xx1, xx2 = np.meshgrid(np.arange(-3, 3, 0.02), np.arange(-3, 3, 0.02))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    plt.contourf(xx1, xx2, Z, alpha=0.1, cmap=cmap)

    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
                    alpha=0.8, c=cmap(idx),
                    marker="+", label=cl)

在我们的解决方案中,我们有包含两个特征(即两个维度)和一个目标向量的数据。重要的是,这些类别被分配得线性不可分。也就是说,我们无法画出一条直线来分隔这两类数据。首先,让我们创建一个带有线性核函数的支持向量机分类器:

# Create support vector classifier with a linear kernel
svc_linear = SVC(kernel="linear", random_state=0, C=1)

# Train model
svc_linear.fit(features, target)
SVC(C=1, kernel='linear', random_state=0)

接下来,由于我们只有两个特征,我们是在二维空间中工作,可以可视化观测、它们的类别以及我们模型的线性超平面:

# Plot observations and hyperplane
plot_decision_regions(features, target, classifier=svc_linear)
plt.axis("off"), plt.show();

mpc2 17in02

我们可以看到,我们的线性超平面在分隔这两类数据时表现非常糟糕!现在,让我们将线性核函数替换为径向基函数核,并用它来训练一个新模型:

# Create a support vector machine with a radial basis function kernel
svc = SVC(kernel="rbf", random_state=0, gamma=1, C=1)

# Train the classifier
model = svc.fit(features, target)

然后可视化观测和超平面:

# Plot observations and hyperplane
plot_decision_regions(features, target, classifier=svc)
plt.axis("off"), plt.show();

mpc2 17in03

通过使用径向基函数核,我们可以创建一个决策边界,它能够比线性核函数更好地分离这两类数据。这就是支持向量机中使用核函数的动机。

在 scikit-learn 中,我们可以通过使用kernel参数来选择要使用的核函数。选择核函数后,我们需要指定适当的核选项,如多项式核中的阶数(使用degree参数)和径向基函数核中的γ值(使用gamma参数)。我们还需要设置惩罚参数C。在训练模型时,大多数情况下,我们应该将所有这些视为超参数,并使用模型选择技术来确定它们值的组合,以生成性能最佳的模型。

17.3 创建预测概率

问题

您需要知道观测的预测类别概率。

解决方案

当使用 scikit-learn 的SVC时,设置probability=True,训练模型,然后使用predict_proba来查看校准概率:

# Load libraries
from sklearn.svm import SVC
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
import numpy as np

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

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create support vector classifier object
svc = SVC(kernel="linear", probability=True, random_state=0)

# Train classifier
model = svc.fit(features_standardized, target)

# Create new observation
new_observation = [[.4, .4, .4, .4]]

# View predicted probabilities
model.predict_proba(new_observation)
array([[0.00541761, 0.97348825, 0.02109414]])

讨论

我们讨论过的许多监督学习算法使用概率估计来预测类别。例如,在 k 最近邻算法中,一个观测的k个邻居的类别被视为投票,以创建该观测属于该类别的概率。然后预测具有最高概率的类别。支持向量机使用超平面来创建决策区域,并不会自然输出一个观测属于某个类别的概率估计。但是,事实上我们可以在一些条件下输出校准的类别概率。在具有两个类别的支持向量机中,可以使用Platt 缩放,首先训练支持向量机,然后训练一个单独的交叉验证逻辑回归,将支持向量机的输出映射到概率:

P ( y = 1 ∣ x ) = 1 1+e (A×f(x)+B)

其中A和B是参数向量,f(x)是第i个观测到超平面的有符号距离。当我们有超过两个类别时,会使用 Platt 缩放的扩展。

更实际地说,创建预测概率有两个主要问题。首先,因为我们使用交叉验证训练第二个模型,生成预测概率可能会显著增加训练模型的时间。其次,因为预测概率是使用交叉验证创建的,它们可能并不总是与预测类别匹配。也就是说,一个观测可能被预测为类别 1,但其预测的概率小于 0.5。

在 scikit-learn 中,必须在训练模型时生成预测概率。我们可以通过将SVCprobability设置为True来实现这一点。模型训练完成后,可以使用predict_proba输出每个类别的估计概率。

17.4 识别支持向量

问题

需要确定哪些观测是决策超平面的支持向量。

解决方案

训练模型,然后使用support_vectors_

# Load libraries
from sklearn.svm import SVC
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
import numpy as np

# Load data with only two classes
iris = datasets.load_iris()
features = iris.data[:100,:]
target = iris.target[:100]

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create support vector classifier object
svc = SVC(kernel="linear", random_state=0)

# Train classifier
model = svc.fit(features_standardized, target)

# View support vectors
model.support_vectors_
array([[-0.5810659 ,  0.42196824, -0.80497402, -0.50860702],
       [-1.52079513, -1.67737625, -1.08231219, -0.86427627],
       [-0.89430898, -1.4674418 ,  0.30437864,  0.38056609],
       [-0.5810659 , -1.25750735,  0.09637501,  0.55840072]])

讨论

支持向量机得名于这个事实:决策超平面由相对较少的被称为支持向量的观测决定。直观地说,可以将超平面看作由这些支持向量“支持”。因此,这些支持向量对我们的模型非常重要。例如,如果从数据中删除一个非支持向量的观测,模型不会改变;但是,如果删除一个支持向量,超平面将不会具有最大间隔。

在我们训练了支持向量机之后,scikit-learn 提供了多种选项来识别支持向量。在我们的解决方案中,我们使用了support_vectors_来输出我们模型中四个支持向量的实际观测特征。或者,我们可以使用support_查看支持向量的索引:

model.support_
array([23, 41, 57, 98], dtype=int32)

最后,我们可以使用n_support_来查找属于每个类的支持向量的数量:

model.n_support_
array([2, 2], dtype=int32)

17.5 处理不平衡类

问题

在存在类不平衡的情况下,您需要训练支持向量机分类器。

解决方案

使用class_weight增加误分类较小类别的惩罚:

# Load libraries
from sklearn.svm import SVC
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
import numpy as np

# Load data with only two classes
iris = datasets.load_iris()
features = iris.data[:100,:]
target = iris.target[:100]

# Make class highly imbalanced by removing first 40 observations
features = features[40:,:]
target = target[40:]

# Create target vector indicating if class 0, otherwise 1
target = np.where((target == 0), 0, 1)

# Standardize features
scaler = StandardScaler()
features_standardized = scaler.fit_transform(features)

# Create support vector classifier
svc = SVC(kernel="linear", class_weight="balanced", C=1.0, random_state=0)

# Train classifier
model = svc.fit(features_standardized, target)

讨论

在支持向量机中,C是一个超参数,用于确定误分类观察的惩罚。处理支持向量机中的不平衡类的一种方法是通过按类别加权C,使得:

C k = C × w j

其中C是错误分类的惩罚,wj是与类j频率成反比的权重,Ck是类k的C值。总体思路是增加对误分类少数类的惩罚,以防止它们被多数类“淹没”。

在 scikit-learn 中,当使用SVC时,可以通过设置class_weight="balanced"来自动设置Ck的值。balanced参数会自动加权各个类别,以便:

w j = n kn j

其中wj是类j的权重,n是观察次数,nj是类j中的观察次数,k是总类数。