4.5 选择有意义的特征
如果一个模型在训练数据集上的表现比在测试数据集上好很多,这意味着模型过拟合(overfitting)于训练数据。过拟合是指模型参数对于训练数据集的特定观测值拟合得非常接近,但训练数据集的分布与真实数据并不一致——我们称之为模型具有较高的方差。产生过拟合的原因是建立在给定训练数据集上的模型过于复杂,而常用的降低泛化误差的方案有:
1)收集更多的训练数据
2)通过正则化引入罚项
3)选择一个参数相对较少的简单模型
4)降低数据的维度
一般来说,收集更多的训练数据不太适用。在下一章中,我们将探讨一种更为有用的技术,以验证收集更多训练数据是否能够对解决过拟合问题有所帮助。而在本章的后续内容中,我们将讨论正则化和特征选择降维这两种常用的减少过拟合问题的方法。
4.5.1 使用L1正则化满足数据稀疏化
回顾一下第3章的内容,L2正则化是通过对大的权重增加罚项以降低模型复杂度的一种方法,其中,权重向量w的L2范数如下:
另外一种降低模型复杂度的方法则与L1正则化相关:
在此,我们只是简单地将权重的平方和用权重绝对值的和来替代。与L2正则化不同,L1正则化可生成稀疏的特征向量,且大多数的权值为0。当高维数据集中包含许多不相关的特征,尤其是不相关的特征数量大于样本数量时,权重的稀疏化处理能够发挥很大的作用。从这个角度来看,L1正则化可以被视作一种特征选择技术。
为了更好地理解L1正则化如何对数据进行稀疏化,我们先退一步,来看一下正则化的几何解释。我们先绘制权重系数w1和w2的凸代价函数的等高线。在此,我们将使用第2章中Adaline算法所采用的代价函数-误差平方和(sum of the squared errors,SSE)作为代价函数,因为它的图像是对称的,且比逻辑斯谛回归的代价函数更易于绘制。在后续内容中,我们还将再次使用此代价函数。请记住,我们的目标是找到一个权重系数的组合,能够使得训练数据的代价函数最小,如下图所示(椭圆中心的圆点):
至此,我们可以将正则化看作是在代价函数中增加一个罚项,以对小的权重做出激励,换句话说,我们对大的权重做出了惩罚。
这样,通过正则化参数λ来增加正则化项的强度,使得权重向0收缩,就降低了模型对训练数据的依赖程度。我们使用下图来阐述L2罚项的概念。
我们使用阴影表示的球代表二次的L2正则化项。在此,我们的权重系数不能超出正则化的区域——权重的组合不能落在阴影以外的区域。另一方面,我们仍然希望能够使得代价函数最小化。在罚项的约束下,最好的选择就是L2球与不含有罚项的代价函数等高线的切点。正则化参数λ的值越大,含有罚项的代价函数增长得就越快,L2的球就会越小。例如,如果增大正则化参数的值,使之趋向于无穷大,权重系数将趋近与0,即图像上所示的L2球的圆心。总结一下这个例子的主要内容:我们的目标是使得代价函数和罚项之和最小,这可以理解为通过增加偏差使得模型趋向于简单,以降低在训练数据不足的情况下拟合得到的模型的方差。
现在来讨论L1正则化与稀疏问题。L1正则化的主要理念与我们之前所讨论的相类似。不过,由于L1的罚项是权重系数绝对值的和(请记住L2罚项是二次的),我们可以将其表示为菱形区域,如下图所示:
在上图中,代价函数等高线与L1菱形在w1=0处相交。由于L1的边界不是圆滑的,这个交点更有可能是最优的——也就是说,椭圆形的代价函数边界与L1菱形边界的交点位于坐标轴上,这也就使得模型更加稀疏。L1正则化如何导致数据稀疏的数学论证已超出了本书的范围。如果你感兴趣,请参阅Trevor Hastie、Robert Tibshirani和Jerome Friedman的著作The Elements of Statistical Learning中的3.4节,里面有关于L2与L1正则化的精彩论述。
对于scikit-learn中支持L1的正则化模型,我们可以通过将penalty参数设定为‘l1’来进行简单的数据稀疏处理:
将其应用于经过标准化处理的葡萄酒数据,经过L1正则化的逻辑斯谛回归模型可以产生如下稀疏化结果:
训练和测试的精确度(均为98%)显示此模型未出现过拟合。通过lr.intercept_属性得到截距项后,可以看到代码返回了包含三个数值的数组:
由于我们在多类别分类数据集上使用了LogisticRegression对象,它默认使用一对多(One-vs-Rest,OvR)的方法。其中,第一个截距项为类别1相对于类别2、3的匹配结果,第二个截距项为类别2相对于类别1、3的匹配结果,第三个截距项则为类别3相对于类别1、2的匹配结果。
通过lr.coef_属性得到的权重数组包含三个权重系数向量,每一个权重向量对应一个分类。每一个向量包含13个权重值,通过与13维的葡萄酒数据集中的特征数据相乘来计算模型的净输入:
我们注意到,权重向量是稀疏的,这意味着只有少数几个非零项。在L1正则化特征选择的作用下,我们训练了一个模型,该模型对数据集上可能存在的不相关特征是鲁棒的。
最后,我们来绘制一下正则化效果的图,它展示了将权重系数(正则化参数)应用于多个特征上时所产生的不同的正则化效果:
通过下图,我们能对L1正则化有个更深入的认识。可以看到,在强的正则化参数(C<0.1)作用下,罚项使得所有的特征权重都趋近于0,这里,C是正则化参数的倒数。
4.5.2 序列特征选择算法
另外一种降低模型复杂度从而解决过拟合问题的方法是通过特征选择进行降维(dimensionality reduction),该方法对未经正则化处理的模型特别有效。降维技术主要分为两个大类:特征选择和特征提取。通过特征选择,我们可以选出原始特征的一个子集。而在特征提取中,通过对现有的特征信息进行推演,构造出一个新的特征子空间。在本节,我们将着眼于一些经典的特征选择算法。第5章会介绍几种特征抽取技术,以将数据集压缩到低维的特征子空间。
序列特征选择算法系一种贪婪搜索算法,用于将原始的d维特征空间压缩到一个k维特征子空间,其中k<d。使用特征选择算法出于以下考虑:能够剔除不相关特征或噪声,自动选出与问题最相关的特征子集,从而提高计算效率或是降低模型的泛化误差。这在模型不支持正则化时尤为有效。一个经典的序列特征选择算法是序列后向选择算法(Sequential Backward Selection,SBS),其目的是在分类性能衰减最小的约束下,降低原始特征空间上的数据维度,以提高计算效率。在某些情况下,SBS甚至可以在模型面临过拟合问题时提高模型的预测能力。
与穷举搜索算法相比,贪心算法可以在组合搜索的各阶段找到一个局部最优选择,进而得到待解决问题的次优解。不过在实际应用中,由于巨大的计算成本,往往使得穷举搜索算法方法不可行,而贪心算法则可以找到不那么复杂,且计算效率更高的解决方案。
SBS算法背后的理念非常简单:SBS依次从特征集合中删除某些特征,直到新的特征子空间包含指定数量的特征。为了确定每一步中所需删除的特征,我们定义一个需要最小化的标准衡量函数J。该函数的计算准则是:比较判定分类器的性能在删除某个特定特征前后的差异。由此,每一步中待删除的特征,就是那些能够使得标准衡量函数值尽可能大的特征,或者更直观地说:每一步中特征被删除后,所引起的模型性能损失最小。基于上述对SBS的定义,我们可以将算法总结为四个简单的步骤:
1)设k=d进行算法初始化,其中d是特征空间Xd的维度。
2)定义x-为满足标准x-=argmaxJ(Xk-x)最大化的特征,其中x∈Xk。
3)将特征x-从特征集中删除:Xk-1=Xk-x-,k=k-1。
4)如果k等于目标特征数量,算法终止,否则跳转到第2步。
如果想更多了解序列特征选择算法的相关问题,请参阅文献:Comparative Study of Techniques for Large Scale Feature Selection,F.Ferri,P.Pudil,M.Hatef,and J.Kittler.Comparative study of techniques for large-scale feature selection.Pattern Recognition in Practice IV,pages 403——413,1994。
遗憾的是,scikit-learn中并没有实现SBS算法。不过它相当简单,我们可以使用Python来实现:
在前面的实现中,我们使用参数k_features来指定需返回的特征数量。默认情况下,我们使用scikit-learn中的accuracy_score去衡量分类器的模型和评估器在特征空间上的性能。在fit方法的while循环中,通过itertools.combination函数创建的特征子集循环地进行评估和删减,直到特征子集达到预期维度。在每次迭代中,基于内部测试数据集X_test创建的self.scors_列表存储了最优特征子集的准确度分值。后续我们将使用这些分值来对结果做出评价。最终特征子集的列标被赋值给self.indices_,我们可以通过transform方法返回由选定特征列构成的一个新数组。请注意,我们没有在fit方法中明确地计算评价标准,而只是简单地删除了那些没有包含在最优特征子集中的特征。
现在,来看一下我们实现的SBS应用于scikit-learn中KNN分类器的效果:
在SBS的实现中,已经在fit函数内将数据集划分为测试数据集和训练数据集,我们依旧将训练数据集X_train输入到算法中。SBS的fit方法将建立一个新的训练子集用于测试(验证)和训练,这也是为什么这里的测试数据集被称为验证数据集的原因。这也是一种避免原始测试数据集变成训练数据集的必要方法。
我们的SBS算法在每一步中都存储了最优特征子集的分值,下面进入代码实现中更为精彩的部分:绘制出KNN分类器的分类准确率,准确率数值是在验证数据集上计算得出的。代码如下:
通过下图可以看到:当我们减少了特征的数量后,KNN分类器在验证数据集上的准确率提高了,这可能归因于在第3章中介绍KNN算法时讨论过的“维度灾难”。此外,图中还显示,当k={5,6,7,8,9,10}时,算法可以达到百分之百的准确率。
为了满足一下自己的好奇心,现在让我们看一下是哪五个特征在验证数据集上有如此好的表现:
使用上述代码,我们从sb.subsets_的第9列中获取了五个特征子集的列标,并通过以pandas的DataFrame格式存储的葡萄酒数据对应的索引中提取到了相应的特征名称。
下面我们验证一下KNN分类器在原始测试集上的表现:
在上述代码中,我们在训练数据集上使用了所有的特征并得到了大约为98.4%的准确率。不过,在测试数据集上的准确率稍低(约为94.4%),此指标暗示模型稍有过拟合。现在让我们在选定的五个特征集上看一下KNN的性能:
当特征数量不及葡萄酒数据集原始特征数量的一半时,在测试数据集上的预测准确率提高了两个百分点。同时,通过测试数据集(约为96.3%)和训练数据集(约为96.0%)上准确率之间的微小差别可以发现,过拟合现象得以缓解。
scikit-learn中的特征选择算法
scikti-learn提供了许多其他的特征选择算法,包括:基于特征权重的递归后向消除算法(recursive backward elimination based on feature weights)、基于特征重要性的特征选择树方法(tree-based methods to select features by importance),以及单变量统计测试(univariate statistical tests)。对特征选择方法进行更深入的讨论已经超出了本书的范围,对这些算法的总结性描述及其示例请参见链接:http://scikit-learn.org/stable/modules/feature_selection.html。