Python机器学习(原书第3版)
上QQ阅读APP看书,第一时间看更新

4.5 选择有意义的特征

如果发现模型在训练数据集上的表现远比在测试数据集要好,那么很有可能发生了过拟合现象。正如在第3章中所讨论的那样,过拟合意味着模型在拟合参数的过程中,对训练数据集中某些特征的适应性过强,但却不能很好地泛化新数据,因此模型的方差比较大。过拟合的原因是,与给定的训练数据相比,我们的模型太过复杂。降低泛化误差的常见解决方案如下:

  • 收集更多的训练数据。
  • 通过正则化引入对复杂性的惩罚。
  • 选择参数较少的简单模型。
  • 降低数据的维数。

收集更多的训练数据通常不太适用。在第6章中,我们将介绍一种实用技术,帮助判断提供更多的训练数据是否对我们有所帮助。在下面的章节中,我们将学习如何通过特征选择来实现正则化和降维,从而采用需要较少参数的简单模型来拟合数据,进而减少过拟合的机会。

4.5.1 L1和L2正则化对模型复杂度的惩罚

回顾第3章,L2正则化是通过惩罚权重大的个体来降低模型复杂度的一种方法。我们定义权重向量w的L2范数如下:

096-01

另外一种降低模型复杂度的方法是L1正则化

096-02

这里,我们只是用权重绝对值之和替代了权重平方之和。与L2正则化相比,L1正则化通常会产生大部分特征权重为0的稀疏特征向量。如果高维数据集的样本包含许多不相关的特征,特别是不相关特征数量大于样本数量时,稀疏性很有实有价值。从这个意义上说,L1正则化可以理解为一种特征选择技术。

4.5.2 L2正则化的几何解释

如上节所述,L2正则化为代价函数增加了惩罚项,与未正则化的代价函数所训练的模型相比,L2还能有效地抑制极端权重值。

为了更好地理解L1正则化对数据进行稀疏化,我们首先来看一下正则化的几何解释。我们绘制权重系数为w1和w2的凸代价函数的等高线。

这里,我们用第2章中Adaline所采用的代价函数——误差平方和(SSE)作为代价函数,因为它是球形的,比逻辑回归的代价函数更容易绘制。在后续内容中,我们还将再次使用此代价函数。请记住,我们的目标是寻找一个权重系数的组合,能够最小化训练数据的代价函数,如图4-4所示(椭圆中心点)。

097-01

图 4-4

现在,我们可以把正则化看作是在代价函数中加入惩罚项来促进较小的权重,换句话说,惩罚较大的权重。因此,通过正则化参数λ增加正则化的强度,使得权重趋于零,从而降低模型对训练数据的依赖性。图4-5说明了L2惩罚项的概念。

097-02

图 4-5

用阴影表示的球来表示二次的L2正则项。在这里,我们的权重系数不能超出正则化的区域——权重系数的组合不能落在阴影区域之外。另外,我们仍然希望最小化代价函数。在惩罚的约束下,尽最大的可能确定L2球与无惩罚项的代价函数等高线的交叉点。正则化参数λ越大,含惩罚项的代价函数增速就越快,导致L2球变窄。如果正则化参数λ趋于无穷大,那么权重系数将快速变为0,即成为L2球的圆心。总之,我们的目标是最小化无惩罚项的代价函数与惩罚项的总和,可以将其理解为增加偏置并采用更简单的模型来降低在训练数据不足的情况下拟合模型的方差。

4.5.3 L1正则化的稀疏解决方案

现在,我们来讨论L1正则化和稀疏性。L1正则化的主要概念与我们前面讨论的类似。然而,由于L1惩罚项是权重系数绝对值的和(记住L2是二次项),因此可以用菱形区域来表示,如图4-6所示。

097-03

图 4-6

从图4-6中我们可以看到,代价函数等高线与L1菱形在w1=0处相交。由于L1正则化系统的边界是尖锐的,因此这个交点更可能是最优的代价函数的椭圆与L1菱形边界的交点位于坐标轴上,从而促进了稀疏性。

008-01

L1正则化与稀疏性

关于L1正则化可能导致稀疏性的数学细节超出了本书的范围。如果你有兴趣,可以在The Elements of Statistical Learning的3.4节中发现对L2和L1正则化的极好解释,该书由Trevor Hastie、Robert Tibshirani和Jerome Friedman撰写,由Springer于2009年出版。

对于scikit-learn中支持L1正则化的正则化模型,我们可以简单地将参数penalty设置为'l1'来获得稀疏解:

098-01

请注意,我们还需要选择其他不同的优化算法(例如solver='liblinear'),因为'lbfgs'目前还不支持L1正则化的损失优化。将L1正则化逻辑回归应用于标准的Wine数据,将得出以下稀疏解:

098-02

训练和测试准确率(100%)表明模型在这两个数据集上的表现都很好。当我们通过lr.intercept_属性访问截距项时,可以看到数组返回三个值:

098-03

由于通过一对其余(OvR)方法在多元分类数据集上拟合LogisticRegression对象,第一个截距值属于拟合类1而不是类2和3的模型,第二个截距值属于拟合类2而不是类1和3的模型,第三个截距值属于拟合类3而不是类1和2的模型:

098-04

通过访问lr.coef_属性,我们获得包含三行权重系数的权重数组,每一权重向量对应一个分类。每行由13个权重组成,我们用每个权重乘以13维葡萄酒数据集中的特征值来计算净输入:

099-02

008-01

访问scikit-learn估计器的偏差和权重参数

在scikit-learn中,w0对应intercept_,当j>0时,wj值对应coef_

由于L1正则化是一种特征选择方法,我们只训练了一个对数据集中潜在不相关特征处理能力强的模型。严格地说,前面例子中的权重向量不一定稀疏,因为它们所包含的非零项更多。然而,可以通过进一步增加正则化强度来增强稀疏性(更多零项),即选择较小的C参数值。

在本章的最后一个正则化示例中,我们将改变正则化强度并绘制正则化的路径,即不同特征在不同正则化强度下的权重系数:

099-03

执行上述代码产生图4-7所示的路径图,这为解释L1正则化行为提供了更进一步的认识。正如我们所看到的,在一个强大的正则化参数(C<0.01)作用下,惩罚项使得所有的特征权重都为零,C是正则化参数λ的逆。

100-01

图 4-7

4.5.4 序列特征选择算法

另外一种降低模型复杂度并避免过拟合的方法是通过特征选择进行降维,这对未正则化的模型特别有用。主要有两类降维技术:特征选择特征提取。通过特征选择可以从原始特征中选择子集,而特征提取则是从特征集中提取信息以构造新的特征子空间。

在本节,我们将看到一些经典的特征选择算法。第5章将讨论不同的特征提取技术以把数据集压缩到低维特征子空间。

序列特征选择算法属于贪婪搜索算法,用于把初始的d维特征空间降低到k维特征子空间(k<d)。特征选择算法的动机是自动选择与问题最相关的特征子集,提高模型的计算效率,或者通过去除不相关的特征或噪声降低模型的泛化误差,这对不支持正则化的算法很有效。

经典的序列特征选择算法是序列后向选择(seguehtial baekward selection,SBS),其目的是在分类器性能衰减最小的约束下,降低初始特征子空间的维数,以提高计算效率。在某些情况下,SBS甚至可以在模型面临过拟合问题时提高模型的预测能力。

008-01

贪婪搜索算法

贪婪算法在组合搜索问题的每个阶段都做出局部最优选择,通常会得到待解决问题的次优解,而穷举搜索算法则评估所有可能的组合,并保证找到最优解。然而,在实践中,穷举搜索往往在计算上不可行,而贪婪算法则可以找到不太复杂且计算效率更高的解决方案。

SBS算法背后的理念非常简单:它顺序地从完整的特征子集中移除特征,直到新特征子空间包含需要的特征数量。为了确定每个阶段要删除哪个特征,需要定义期待最小化的标准衡量函数(criterion function)J。

可以简单地把标准衡量函数计算的标准值定义为在去除特定特征前后分类器的性能差异。每个阶段要去除的特征可以定义为该标准值最大的特征;或者更直观地说,每个阶段去除性能损失最小的那个特征。基于前面SBS的定义,我们可以总结出四个简单的步骤来描述该算法:

1)用k=d初始化算法,d为特征空间Xd的维数。

2)确定最大化标准x-=argmax J(Xk-x)的特征x-,其中xXk

3)从特征集中去除特征x-:Xk-1=Xk-x-;k=k-1。

4)如果k等于期望的特征数,则停止;否则,跳转到步骤2。

008-01

序数列特征算法资源

读者可以在文献(Comparative Study of Techniques for Large-Scale Feature Selection, F. Ferri, P. Pudil, M. Hatef, and J. Kittler, pages 403-413, 1994)中发现对几种序列特征算法的详细评价。

不幸的是,scikit-learn尚未实现SBS算法。但是它很简单,我们可以用Python实现:

101-01
102-01

前面的实现定义了参数k_features,指定了想要返回的理想特征数目。在默认情况下,我们调用scikit-learn的accuracy_score对模型在特征子空间的性能(分类估计器)进行评估。

fit方法的while循环中,我们对由itertools.combination函数创建的特征子集循环地进行评估删减,直至特征子集达到预期维度。在每次迭代中,我们根据内部创建的测试数据集X_test收集每个最优子集的准确率得分,并将其存储在列表self.scores_中。稍后我们将用这些分数来评估。最终特征子集的列号被赋给self.indices_,我们可以用它调用transform方法,返回包括选定特征列在内的新数据数组。注意,fit方法并未具体计算判断标准,而是简单地去除了没有包含在最优特征子集中的特征。

现在,让我们看看实现的SBS应用于scikit-learn的KNN分类器的效果:

102-02

虽然SBS实现已经在fit函数内将数据集划分成测试数据集和训练数据集,但是仍然将训练数据集X_train输入到算法中。SBS的fit方法将为测试(验证)和训练创建新子集,这就是为什么该测试数据集也被称为验证数据集。这也是一种避免原始测试数据集成为训练数据的必要方法。

请记住,SBS算法收集每个阶段最优特征子集的得分,下面进入代码实现中更为精彩的部分,绘制出在验证数据集上计算的KNN分类器的分类准确率。示例代码如下:

102-03

从图4-8可以看到,可能是由于维数降低(维数诅咒,在第3章中,我们在介绍KNN算法时曾经讨论过),KNN分类器通过减少特征数提高了在验证数据集上的准确率。此外,还可以看到当k={3,7,8,9,10,11,12}时,分类器的准确率达到100%。

103-01

图 4-8

为了满足好奇心,让我们来看看在验证数据集上产生如此良好性能的最小特征子集(k=3)究竟是个什么样子:

103-02

用前面的代码可以得到存储在sbs.subsets_属性第11位的三个特征子集的列号,以及从pandas Wine DataFrame的列索引返回的相应特征名。

我们接着评估该KNN分类器在原始测试数据集上的性能:

103-03

前面的代码用完整的特征集在训练数据集上取得了大约97%的准确率,在测试数据集上获得大约96%的准确率,这表明模型已经可以很好地泛化到新数据集。现在用选定的三个特征子集来看一下KNN的性能:

103-04

如果采用葡萄酒数据集中少于四分之一的原始特征,测试数据集的预测准确率就会略有下降。这可能表明三个特征所提供的差异信息并不比原始数据集少。然而,我们必须记住葡萄酒数据集是一个小数据集,非常容易受随机性的影响,即如何将数据集划分成训练数据集和测试数据集,以及如何进一步将训练数据集划分为训练数据集和验证数据集。

虽然减少特征数量并没有提高KNN模型的性能,但是缩小了数据集的规模,在实际应用中可能会涉及昂贵的数据收集。此外,通过大量地减少特征的数量,我们可以得到更简单的模型,这些模型也更加容易解释。

008-01

用scikit-learn实现特征选择算法

scikit-learn有许多其他的特征选择算法,包括基于特征权重的递归后向消除法(recursive backward elimination)、基于树的根据重要性选择特征的方法以及单变量统计检验方法。对不同特征选择方法的全面讨论超出了本书的范围,但是可以从网站http://scikit-learn.org/stable/modules/feature_selection.html找到优秀的总结与例证。

此外,我实现了几种不同的序列特征选择方法,这与前面实现的简单SBS相关。可以从网站http://rasbt.github.io/mlxtend/user_guide/feature_selection/SequentialFeatureSelector/找到这些Python实现的mlxtend软件包。