丰富的线上&线下活动,深入探索云世界
做任务,得社区积分和周边
最真实的开发者用云体验
让每位学生受益于普惠算力
让创作激发创新
资深技术专家手把手带教
遇见技术追梦人
技术交流,直击现场
海量开发者使用工具、手册,免费下载
极速、全面、稳定、安全的开源镜像
开发手册、白皮书、案例集等实战精华
为开发者定制的Chrome浏览器插件
获取葡萄酒数据集
df=pd.read_csv('your/local/path/to/wine.data',header=None)使用pandas库,我们会直接从UCI机器学习库读取开源葡萄酒数据集:
图4.4:葡萄酒数据集样本
随机将数据集分割为测试集和训练集的便捷方式是使用scikit-learn中model_selection子模块的train_test_split函数:
>>>fromsklearn.model_selectionimporttrain_test_split>>>X,y=df_wine.iloc[:,1:].values,df_wine.iloc[:,0].values>>>X_train,X_test,y_train,y_test=\...train_test_split(X,y,...test_size=0.3,...random_state=0,...stratify=y)首先我们将特征列1-13的NumPy数组形式赋值给变量X并将第一列的类标签赋值给变量y。然后我们使用train_test_split函数来随机将X和y划分为训练集和测试集。
通过设置test_size=0.3,我们将30%的葡萄酒样本赋值给X_test和y_test,剩下的70%样本分别赋值给X_train和y_train。对参数stratify提供类标签y保障了训练集和测试集具有同样类比例的原始数据。
选择合适的比例将数据集分为训练集和测试集
此外,在模型训练和评估完成后并不会丢弃所分配的测试数据,一般会对整个数据集重新训练一个分类器,因为这样可以提升模型的预测表现。虽然通常推荐这一方法,但在数据集较小及测试数据集包含异常值/离群点时会产生更差的泛化表现。并且在对整个数据集重新拟合模型后,我们就没有剩下任何独立数据用于评估表现了。
特征缩放的重要性可通过一个简单示例来讲解。假定有两个特征,一个特征的量级为1到10,另一个特征的量级为1到100,000。
有两种常用方法可将特征变成同一量级:归一化(normalization)和标准化(standardization)。这两个词在不同领域中会混用,其含义需要从上下文中获知。大多数时候,归一化指将特征重新缩放到[0,1]范围内,是最大最小值缩放(min-maxscaling)的一种特例。要对数据进行归一化,我们只需对每个特征列应用最大最小缩放,比如x(i)的新值,$x_{norm}^{(i)}$,可通过如下方式计算:
这里x(i)是具体的样本,xmin是特征列中的最小值,xmax是最大值。
最大最小值缩放已在scikit-learn中实现,可通过如下方式使用:
标准化可通过如下等式表示:
这里$\mu_x$是指定样本列的样本均值,而$\sigma_x$是对应的标准差。
下表在一个包含数字0到5的简单样本数据集上展示了两种常见特征缩放技术,即标准化和归一化的区别:
表4.1:标准化和最大最小值归一化的对比
可以通过如下示例代码手动执行表中所示的标准化和归一化:
>>>ex=np.array([0,1,2,3,4,5])>>>print('standardized:',(ex-ex.mean())/ex.std())standardized:[-1.46385011-0.87831007-0.292770020.292770020.878310071.46385011]>>>print('normalized:',(ex-ex.min())/(ex.max()-ex.min()))normalized:[0.0.20.40.60.81.]类似于MinMaxScaler类,scikit-learn也实现了标准化的类:
>>>fromsklearn.preprocessingimportStandardScaler>>>stdsc=StandardScaler()>>>X_train_std=stdsc.fit_transform(X_train)>>>X_test_std=stdsc.transform(X_test)要再次强调我们只使用StandardScaler类对训练数据进行了一次拟合,并使用这些参数来转换测试集或其它新数据点。
另一种降低模型复杂度的方法是L1正则化:
上一节中已经提到,L2正则化对损失函数添加了惩罚项,可有效形成与非正则化损失函数训练的模型相比不那么极端的权重值。
为更好理解L1正则化是如何促进稀疏性的,我们先回顾下正则化的几何解释。我们先以两个权重系数w1和w2绘制凸损失函数的轮廓。
图4.5:最小化均方差损失函数
可以把正则化看成是对损失函数添加惩罚项得到更小的权重,换句话说,我们惩罚大权重。因此通过正则化参数$\lambda$增加正则化强度,我们将权重收缩向0并降低模型对训练数据的依赖。我们通过下面的L2惩罚项图来演示这一概念:
图4.6:对损失函数应用L2正则化
下面我们来讨论L1正则化和稀疏性。L1正则化的主要概念与前面小节讨论的类似。但因为L1惩罚是权重系数绝对值之和(L2项为平方值),可通过图4.7中的菱形预算表示:
图4.7:对损失函数应用L1正则化
在上图中,我们可以看到损失函数的外围在w1=0处与L1菱形相交。因L1正则化系统的轮廓为尖角,很有可能最优点(也即损失函数椭圆与L1菱形边的交点)位于坐标轴上,这会鼓励稀疏性。
L1正则化与稀疏性
L1正则为什么会导致稀疏解的数学细节不在本书讨论范畴内。如果读者对此感兴趣,可阅读TrevorHastie,RobertTibshirani和JeromeFriedman所著《统计学习基础》(施普林格科学与商业媒体,2009年)的第3.4节中关于L2对比L1正则化的精彩讲解。
对于支持L1正则化的scikit-learn正则化模型,可以简单地设置penalty参数为'l1'来获取稀疏解:
>>>fromsklearn.linear_modelimportLogisticRegression>>>LogisticRegression(penalty='l1',...solver='liblinear',...multi_class='ovr')注意我们还需要选择不同的优化算法(如solver='liblinear'),因为'lbfgs'当前不支持L1正则化损失优化。将L1正则化逻辑回归应用于标准化的葡萄酒数据,会产生如下的稀疏解:
>>>lr=LogisticRegression(penalty='l1',...C=1.0,...solver='liblinear',...multi_class='ovr')>>>#NotethatC=1.0isthedefault.Youcanincrease>>>#ordecreaseittomaketheregularizationeffect>>>#strongerorweaker,respectively.>>>lr.fit(X_train_std,y_train)>>>print('Trainingaccuracy:',lr.score(X_train_std,y_train))Trainingaccuracy:1.0>>>print('Testaccuracy:',lr.score(X_test_std,y_test))Testaccuracy:1.0训练和测试精度(都是100%)表示我们的模型对两个数据集都非常完美。在通过lr.intercept_属性访问截距项时,可以看到数组返回三个值:
>>>lr.intercept_array([-1.26317363,-1.21537306,-2.37111954])因为我们通过一对剩余(OvR)方法对多类数据集拟合LogisticRegression对象,第一个截距属于拟合类1对类2和类3的模型,第二个值是拟合类2对类1和类3的模型截距,第二个值是拟合类3对类1和类2的模型截距:
>>>lr.coef_array([[1.24647953,0.18050894,0.74540443,-1.16301108,0.,0.,1.16243821,0.,0.,0.,0.,0.55620267,2.50890638],[-1.53919461,-0.38562247,-0.99565934,0.36390047,-0.05892612,0.,0.66710883,0.,0.,-1.9318798,1.23775092,0.,-2.23280039],[0.13557571,0.16848763,0.35710712,0.,0.,0.,-2.43804744,0.,0.,1.56388787,-0.81881015,-0.49217022,0.]])我们通过lr.coef属性访问的权重数组包含三行权重系数,每个类一个权重向量。每行包含13个权重,每个权重乘上13维葡萄酒数据集中各自的特征来计算新输入值:
访问scikit-learn评估器的偏置单元和权重参数
在scikit-learn中,intercept_对应偏置单元,coef_对应值wj。
在本章中有关正则化的最后一个例子中,我们会变化正则化强度并绘制正则化路径,不同的正则化强度使用不同特征的权重系数:
图4.8:正则化强度参数C的值的影响
减少模型复杂度及避免过拟合的另一种方式是通过特征选择降维,这对于非正则化模型尤其有用。有两种主要的降维技术:特征选择(featureselection)和特征抽取(featureextraction)。通过特征选择,我们选择原始特征的一个子集,而在特征抽取中,我们通过构建新特征子空间来获取特征信息。
经典的序列特征选择算法是序列后向选择(SBS),旨在通过分类器的最小性能衰减降低初始特征子空间的维度,提升计算效率。在某些场景中,在模型遭受过拟合时SBS甚至能提升模型的预测能力。
贪婪搜索算法
贪婪算法对组合搜索问题的每个阶段做出本地最优选择,通常会产生问题的次优解,与之对应的是穷举搜索算法,对所有可能的组合进行评估,保证会找到最优解。但在实操中,穷举搜索在算力上通常不可行,贪婪算法则是一种复杂度更低、计算效率更高的方案。
SBS算法背后的思想非常简单:SBS从全部特征子集序列删除特征,直至新特征子空间包含所需的特征数。要决定在各阶段删除哪个特征,我们需要定义一个我信希望最小化的判别函数J。
通过判别函数计算的标准可以只是删除具体特征前后的性能差值。然后每个阶段删除的特征可定义为最大化这一标准的特征;或者更简单,每个阶段我们移除在删除后产生最小性能损失的特征。根据前述对SBS的定义,可以将该算法总结为以下四步:
可在以下书中找到多种序列特征算法的详细评估:大规模特征选择技术的比较研究F.Ferri,P.Pudil,M.Hatef及J.Kittler,第403-413页,1994。
为练习我们的编码能力及具备实现自己算法的能力,我们从头使用Python实现:
在fit方法的while循环内部,由itertools.combination函数创建的特征子集进行了评估,减少至特征子集为所需维度。在每次迭代中,将最佳子集根据内部创建的测试数据集X_test的准确度打分收入列表self.scores_中。稍后我们会使用这些分数评估结果。最终特征子集的列索引赋值给self.indices_,可通过transform方法使用它返回带已选特征列的新数组。注意除了在fit方法内部显式地计算标准,我们只是删除了不在最佳性能特征子集中的特征。
下面逐步使用scikit-learn中KNN分类呃呃实现SBS:
>>>importmatplotlib.pyplotasplt>>>fromsklearn.neighborsimportKNeighborsClassifier>>>knn=KNeighborsClassifier(n_neighbors=5)>>>sbs=SBS(knn,k_features=1)>>>sbs.fit(X_train_std,y_train)虽然SBS实现已经在fit函数中将数据集分割成测试集和训练集,我们仍需将训练集X_train喂给算法。然后SBSfit会新建用于测试(验证)和训练的训练子集,这也是为什么测试集也被称为验证数据集。这一方法可防止原始测试集变成训练集的一部分。
SBS算法收集每个阶段的最佳特征子集,所以我们进入实现中更有意思的部分,并绘制KNN分类器对验证数据集所计算的分类准确度。代码如下:
图4.9:特征数对模型准确度的影响
为满足我们自己的好奇心,我们来看下对验证集产生这种好表现的最小特征子集(k=3)长什么样:
>>>k3=list(sbs.subsets_[10])>>>print(df_wine.columns[1:][k3])Index(['Alcohol','Malicacid','OD280/OD315ofdilutedwines'],dtype='object')使用上述代码,我们从sbs.subsets_属性中的第11个位置获取了三特征子集的列索引,通过葡萄酒DataFrame的列索引返回对应的特征名。
接下来,我们对原始测试集评估这个KNN分类器的表现:
>>>knn.fit(X_train_std,y_train)>>>print('Trainingaccuracy:',knn.score(X_train_std,y_train))Trainingaccuracy:0.967741935484>>>print('Testaccuracy:',knn.score(X_test_std,y_test))Testaccuracy:0.962962962963在以上代码中,我们使用了完整的特征集,对训练集得到了大约97%的准确度,对测试集得到了大约96%的准确度,表示我们的模型对新数据泛化的很好。下面我们使用所选的三特征子集来看看KNN的表现如何:
>>>knn.fit(X_train_std[:,k3],y_train)>>>print('Trainingaccuracy:',...knn.score(X_train_std[:,k3],y_train))Trainingaccuracy:0.951612903226>>>print('Testaccuracy:',...knn.score(X_test_std[:,k3],y_test))Testaccuracy:0.925925925926在使用少于四分之一的原始葡萄酒数据集特征时,测试集的预测准确度稍有下降。这可能表示这三个特征没有提供比原始数据集更少的判别信息。但我们不要忘了葡萄酒数据集是一个小数据集,很容易受随机性影响,也就是我们分割训练集和测试集的方式以及如何将测试集进一步分割成训练集和验证集。
虽然我们并没有通过减少特征数提升KNN模型的表现,但减少了数据集的大小,这对真实世界中涉及昂贵数据采集步骤的应用非常有用。同时通过减少特征数,我们得到了更简单的模型,也就更容易解释。
scikit-learn中的特征选择算法
图4.10:基于随机森林的葡萄酒数据集特征重要性
根据500棵决策树的平均杂度下降,可以总结出脯氨酸(proline)和类黄酮(flavonoid)水平、颜色强度、OD280/OD315衍射及酒精浓度是数据集中最具判别度的特征。有趣的是,图中排名较高中的两个特征也处于前面小节中所实现的SBS算法的三特征子集中(酒精浓度和稀释葡萄酒的OD280/OD315)。
但在可解释性方面,随机森林技术有一个重要的陷阱值得讲一下。如果两个或多个特征高度关联,一个特征可能会排名很高,而其它特征的信息可能不会完全捕获。如果只对模型的预测结果感觉兴趣而不关心对特征重要性值的解释就不太需要担心这个问题。
>>>fromsklearn.feature_selectionimportSelectFromModel>>>sfm=SelectFromModel(forest,threshold=0.1,prefit=True)>>>X_selected=sfm.transform(X_train)>>>print('Numberoffeaturesthatmeetthisthreshold',...'criterion:',X_selected.shape[1])Numberoffeaturesthatmeetthisthresholdcriterion:5>>>forfinrange(X_selected.shape[1]):...print("%2d)%-*s%f"%(f+1,30,...feat_labels[indices[f]],...importances[indices[f]]))1)Proline0.1854532)Flavanoids0.1747513)Colorintensity0.1439204)OD280/OD315ofdilutedwines0.1361625)Alcohol0.118529小结本章中我们学习了一些有用的技术确保可正确处理缺失数据。在将数据喂给机器学习算法之前,我们还要保证正确地编码分类变量,在本章中,我们还学习了如何将有序特征和标称特征与整数形式相映射。
下一章中,我们会学习另一个有用的降维技术:特征提取。它可以将特征压缩到更低维子空间上,而不是像特征选择中那样完全删除特征。