盘一盘 Python 系列特别篇 - Sklearn (0.22)

2019-12-25 14:49:08 浏览数 (1)

在〖机器学习之 Sklearn〗一贴中,我们已经介绍过 Sklearn,它全称是 Scikit-learn,是基于 Python 语言的机器学习工具。

在 2019 年 12 月 3 日,Sklearn 已经更新到版本 0.22,里面添加了若干功能,这也是本帖的内容。

首先在 Anaconda 的提示窗中输入以下代码,来安装更新当前的 Sklearn,如果代码运行时报错就以管理员身份打开提示窗。

代码语言:javascript复制
pip install --upgrade scikit-learn

检查一下 Sklearn 的版本确定已经更新到 0.22。

代码语言:javascript复制
import sklearn
print( sklearn.__version__ )
代码语言:javascript复制
0.22

在添加的众多功能中,我觉得以下几个算是比较有用的。

  • 一行画出 ROC-AUC 图
  • 实现堆积法 (stacking)
  • 为任何模型估计特征重要性
  • 用 k-近邻法来填充缺失值

首先加载下面例子共用的包。

1

ROC-AUC

首先介绍一下接受者操作特征 (ROC)。

接受者操作特征

ROC 是 receiver operating characteristic 的简称,直译为「接受者操作特征」。「ROC 曲线」非常类似「PR 曲线」,但图的横轴纵轴并不是查准率和查全率。「ROC 曲线」反映在不同分类阈值上,真正类率 (true positive rate, TPR) 和假正类率 (false positive rate, FPR) 的关系。

  • TPR 是「真正类」和所有正类 (真正类 假负类) 的比率,真正类率 = 查全率
  • FPR 是「假正类」和所有负类 (假正类 真负类) 的比率,假正类率 = 1- 真负类率 = 1 - 特异率 (specificity)

一般来说,阈值越高

  • 越不容易预测出正类,TPR 下降 ( TPR 和阈值成递减关系)
  • 越容易预测出负类,(1- FPR) 上升 ( FPR 和阈值成递减关系)

阈值越低

  • 越容易预测出正类,TPR 上升 ( TPR 和阈值成递减关系)
  • 越不容易预测出负类,(1- FPR) 下降 ( FPR 和阈值成递减关系)

因此 TPR 和 FPR 是单调递增关系。

「PR 曲线」和「ROC 曲线」对比图见下,后者和横轴之间的面积叫AUC,是 area under the curve 的简称。

AUC 将所有可能分类阈值的评估标准浓缩成一个数值,根据 AUC 大小,我们得出

如何计算 AUC 和计算 PR 曲线下的面积一样的,把横坐标和纵坐标代表的变量弄对就可以了,如下图。

如何确定这些 TPRi 和 FPRi (i = 0,1,...,5) 不是一件容易讲清的事,我试试,先看一个二分类预测类别以及预测正类概率的表 (按照预测概率降序排序,其中正类 P 和负类 N 都有 10 个)。

第一个点:当阈值 = 0.9,那么第 1 个样本预测为 P,后 19 个样本预测为 N,这时

  • TPR = 真正类/全部正类 = 1/10 =0.1
  • FPR = 1 - 真负类/全部负类 = 1 - 10/10 =0
  • 阈值 0.9 → (0, 0.1)

第二个点:当阈值 = 0.8,那么第 1, 2 个样本预测为 P,后 18 个样本预测为 N,这时

  • TPR = 真正类/全部正类 = 2/10 =0.2
  • FPR = 1 - 真负类/全部负类 = 1 - 10/10 =0
  • 阈值 0.8 → (0, 0.2)

...

第四个点:当阈值 = 0.6,那么前 4 个样本预测为 P,后 16 个样本预测为 N,这时

  • TPR = 真正类/全部正类 = 3/10 =0.3
  • FPR = 1 - 真负类/全部负类 = 1 - 9/10 =0.1
  • 阈值 0.8 → (0.1, 0.3)

...

最后一个点:当阈值 = 0.1,那么全部样本预测为 P,零样本预测为 N,这时

  • TPR = 真正类/全部正类 = 10/10 =1
  • FPR = 1 - 真负类/全部负类 = 1 - 0/10 =1
  • 阈值 0.8 → (1, 1)

因此可画出下图右半部分,即 ROC 曲线,再根据横坐标纵坐标上的 FPR 和 TPR 计算 AUC。

AUC 越大,分类器的质量越好。

在 Scikit-learn 里,还记得有三种方式引入数据吗?

  1. 用 load_dataname 来加载小数据
  2. 用 fetch_dataname 来下载大数据
  3. 用 make_dataname 来构造随机数据

这里我们用第三种:

用支持向量机分类器 svc 和随机森林分类器 rfc 来训练一下。

一行画 ROC 图需要引入 plot_roc_curve 包。

代码语言:javascript复制
from sklearn.metrics import plot_roc_curve

再运行下面一行代码,需要传进三个参数:估计器 svc,特征 x_test,标签 y_test。

代码语言:javascript复制
plot_roc_curve( svc, X_test, y_test );

在版本 v0.22 之前,要画出上面这图需要写好多行代码:

所以现在这个 plot_roc_curve 包真的方便,不过其实在 Scikit-plot 里,早就有类似的函数了,见〖机学可视化之 Scikit-Plot〗一贴。那个函数叫做 plot_roc 用到的参数有 3 个:

  • y_test:测试集真实标签
  • y_prob:测试集预测标签的概率
  • figsize:图片大小

我猜想 v0.22 是借鉴了 Scikit-plot 里 plot_roc 函数的写法得到了 plot_roc_curve。

此外,plot_roc_curve 函数还可以画出不同估计器得到的 ROC 曲线。只需要将 svc 模型下的 ROC 图中的坐标系传到 rfc 模型下的 ROC 图中的 ax 参数中。代码如下:

2

Stacking

首先介绍一下堆积法 (stacking)。

堆积法

堆积法实际上借用交叉验证思想来训练一级分类器,解释如下图:

训练一级分类器 – 首先将训练数据分为 3 份:D1, D2, D3,h1 在 D1 和 D2 上训练,h2 在 D1 和 D3 上训练,h3 在 D2 和 D3 上训练。

新训练数据 – 包含:h1 在 D3 上的产出,h2 在 D2 上的产出,h3 在 D1 上的产出。

训练二级分类器 – 在新训练数据和对应的标签上训练出第二级分类器 H。

接着我们拿手写数字 (MNIST) 数据举例。

代码语言:javascript复制
from sklearn.datasets import fetch_openml

下面也是 v0.22 的一个特功能 (但我觉得没什么太大用):可以从 openML 返回数据帧的值,需要将 as_frame 参数设置为 True。再用操作符 . 来获取 X 和 y。,代码如下:

代码语言:javascript复制
X_y = fetch_openml( 'mnist_784', 
                    version=1,
                    as_frame=True )
X, y = X_y.data, X_y.target

其实我觉得原来将 return_X_y 参数设置为 True,一次性的把 X 和 y 都返回出来更简便,代码如下:

代码语言:javascript复制
X, y = fetch_openml( 'mnist_784',
                      version=1, 
                      return_X_y=True )

按 80:20 划分训练集和测试集,在标准化照片像素使得值在 0-1 之间。

接下来重头戏来了,用 StackingClassifier 作为元估计器(meta-estimators),来集成两个子估计器(base-estimator),我们用了随机森林分类器 rfc 和梯度提升分类器 gbc 作为一级分类器,之后用对率回归分类器作为二级分类器。

代码非常简单,如下:

代码通用格式为 (其中 FE 代表一级估计器,BE 代表,SE 代表)

FE = [('BE1', BE1),

('BE2', BE2),

...,

('BEn', BEn)]

clf = StackingClassifier(

estimators = FE

final_estimators = SE)

在 Scikit-learn 里没有实现 StackingClassifier,我们只能用 mlxtend 里面的模型。现在在版本 0.22 里不需要这么做了。

代码语言:javascript复制
from mlxtend.classifier import StackingClassifier

比较子估计器元估计器的在测试集上的表现。

代码语言:javascript复制
rfc.fit(X_train, y_train)
gbc.fit(X_train, y_train)
clf.fit(X_train, y_train)
rfc.score(X_test, y_test)
gbc.score(X_test, y_test)
clc.score(X_test, y_test)
代码语言:javascript复制
0.9482142857142857
0.8391428571428572
1.0

集成分类器的得分比随机森林分类器和梯度提升分类器都高,几乎完全分类正确测试集了。堆积法的效果还真不错。

3

Feature Importance

首先介绍一下如何用置换检验 (permutation test) 来计算特征重要性 (feature importance)。

置换检验计算特征重要性

核心思想是“如果某个特征是重要特征,那么加入一些随机噪声模型性能会下降”。

做法是把所有数据在特征上的值重新随机排列,此做法被称为置换检验。这样可以保证随机打乱的数据分布和原数据接近一致。下图展示了在特征“性格”上随机排列后的数据样貌,随机排列将“好坏坏好坏坏好好”排成“坏坏好坏好坏坏好”。

在置换检验后,特征的重要性可看成是模型“在原数据的性能”和“在特征数据置换后的性能”的差距,有

接着我们拿鸢尾花 (iris) 数据举例。

首先按 80:20 划分训练集和测试集。

用随机森林训练,并在每个特征维度上做 10 次置换操作,注意 n_repeats 设置为 10。

打印出 result,得到重要性的均值标准差10 组具体值

这种数据形式最适合用箱形图 (box plot) 展示,均值是用来决定哪个特征最重要的,在箱形图中用一条线表示 (通常这条线指的中位数)。

根据上图,我们得出花瓣长度 (petal length) 特征最重要,而花萼长度 (sepal length) 特征最不重要。当特征多时可用该方法来选择特征。

这个 permutation_importance 函数可以用在任何估计器上,再用一个支持向量机 svc。

根据上图,我们得出同样结论,花瓣长度特征最重要,花萼长度特征最不重要,虽然具体特征重要性均值和标准差不同,但在判断特征重要性的大方向还是一致的。

4

KNN Imputation

缺失数据的处理方式通常有三种:删除 (delete)、推算 (impute) 和归类(categorize)。下面举例用的数据如下:

删除法

删除数据最简单,有两种方式:

  • 删除行 (数据点)
  • 删除列 (特征)

删除法的优点

  • 操作简单
  • 可以用在任何模型比如决策树、线性回归等等

删除法的缺点

  • 删除的数据可能包含重要信息
  • 不知道删除行好还是删除列好
  • 对缺失数据的测试集没用

推算法

根据特征值是分类型或数值变量,两种方式:

  1. 用众数来推算分类型
  2. 用平均数来推算数值

特征“性格”的特征值是个分类型变量,因此计数未缺失数据得到 2 个好和 7 个坏,根据众数原则应该将缺失数据用“坏”来填充。特征“收入”的特征值是个数值型变量,根据平均数原则算出未缺失数据的均值 20.4 万来填充。

推算法的优点

  • 操作简单
  • 可以用在任何模型比如决策树和线性回归等等
  • 对缺失数据的测试集有用,运用同样的规则 (众数分类型变量,平均数数值型变量)

推算法的缺点是可能会造成系统型误差。

系统性误差有真实案例。在华盛顿的银行里申请贷款,根据当地法律,申请人是不允许填年龄的。如果整合所有美国申请人资料,发现所有来自华盛顿的数据缺失年龄那一栏。假如按照“平均化数值型变量”规则算出均值 41岁,那么把所有华盛顿申请者的年龄填为 41 岁是不合理的。

归类法

归类的核心思想是把缺失 (unknown) 也当作是一种特征值。下图举例用决策树将“收入缺失”和“收入低”归纳成同一类。

这时缺失值是实实在在的一个类别了。

用 KNN 填充缺失值

这里介绍的填充缺失值的方法是用 k-近邻 (k-nearest neighbor, KNN) 来估算缺失值的,即在每个特征下,缺失值都是使用在训练集中找到 k 个最近邻居的平均值估算的。

代码如下 (引入 sklearn.impute 里面的 KNNImputer):

代码语言:javascript复制
[[1. 2. 4.]
 [3. 4. 3.]
 [5. 6. 5.]
 [7. 8. 9.]]

结果是合理的。X 的每一列代表一个特征,原始的 X 为

代码语言:javascript复制
[[1.  2. nan]
 [3.  4. 3. ]
 [nan 6. 5. ]
 [7.  8. 9. ]]

在第一列中,离 nan 最近的 2 个邻居是 3 和 7,它们平均数是 5。在第四列中,离 nan 最近的 2 个邻居是 3 和 5,它们平均数是 4。总结图如下:

5

总结

回顾上面介绍的四个新填功能:

I. 一行画出 ROC-AUC 图,代码用

代码语言:javascript复制
from sklearn.metrics import plot_roc_curve

II. 实现堆积法,代码用

代码语言:javascript复制
from sklearn.ensemble import StackingClassifier

III. 为任何模型估计特征重要性,代码用

代码语言:javascript复制
from sklearn.inspection import permutation_importance

IV. 用 k-近邻法来填充缺失值,代码用

代码语言:javascript复制
from sklearn.impute import KNNImputer

Stay Tuned!

0 人点赞