模型评估与选择

1.经验误差与过拟合

错误率(error rate):分类错误的样本占样本总数的比例

如果在m个样本中有a个样本分类错误,则错误率为E = a/m,相应地,1-a/m称为“精度”(accuracy),即“精度= 1-错误率”。

误差(error):我们把学习器的实际预测输出与样本的真实输出之间的差异

训练误差(training error)或经验误差(empirical error):学习器在训练集上的误差

泛化误差(generalization error):在新样本上的误差

我们希望得到泛化误差小的学习器,即在新样本上能表现得很好的学习器。为达到这个目的,应该从训练样本中尽可能学出适用于所有潜在样本的“普遍规律”,这样才能在遇到新样本时做出正确的判别。

当学习器把训练样本学的“太好”了的时候,很可能已经把训练样本自身的一些特点当作了所有潜在样本都会具有的一般性质,这样就会导致泛化性能下降。这种现象在机器学习中称为“过拟合”(overfitting)。

与“过拟合”相对的是“欠拟合”(underfitting):这是指对训练样本的一般性质尚未学好。

过拟合产生的原因:由于学习能力过于强大,以至于把训练样本所包含的不太一般的特性都学到了

欠拟合产生的原因:由于学习能力低下而造成的

克服欠拟合:欠拟合比较容易克服,如在决策树学习中扩展分支、在神经网络学习中增加训练轮数等

过拟合克服:过拟合是无法彻底避免的,我们所能做到的智商“缓解”,或者说减小其风险。

在现实任务中,往往有多重学习算法可供选择,甚至对同一算法,当使用不同的参数配置时,也会产生不同的模型。那么,我们该选用哪一个学习算法、使用哪一种参数配置呢?这就是机器学习中的“模型选择”(model selection)。

理想的解决方案是对候选模型的泛化误差进行评估,然后选择泛化误差最小那个模型。

2.评估方法

使用一个“测试集”(test set)来测试学习器对新样本的判别能力,然后以测试集上的“测试误差”(test error)作为泛化误差的近似。通常我们假设测试样本也是从样本真实分布中独立同分布采样而得。同时,测试集应该尽可能与训练集互斥,即测试样本尽量不在训练集中出现、未在训练过程中使用过。

现有一个包含m个样例的数据集D=\left \{ (x_1,y_1),(x_2,y_2),\cdots ,(x_m,y_m) \right \},通过对D进行适当的处理,从中产生出训练集S和测试集T,现有如下几种做法:

2.1 留出法

“留出法”(hold-out)直接将数据集D划分为两个互斥的集合,其中一个集合作为训练集S,另一个作为测试集T,即。在S上训练出模型后,用T来评估其测试误差,作为对泛化误差的估计。

需注意的是:训练/测试集的划分要尽可能保持数据分布的一致性,避免因数据划分过程引入额外的偏差而对最终结果产生影响。如在分类任务中至少要保持样本的类别比例相似。如果从采样的角度看待数据集的划分过程,则保留类别比例的采样方式通常称为“分层采样”。

在使用留出法时,数据集D存在多种划分,一般采用若干次随机划分、重复进行试验评估后取平均值作为留出法的评估结果。

另外,我们希望评估的是用D训练出来的模型的性能,但留出法划分训练集/测试集,就会导致一个窘境:若令训练集S包含绝大多数样本,则训练出的模型可能更接近于用D训练出的模型,但由于T比较小,评估结果可能不够稳定准确;若令测试T多包含一些样本,则训练集SD差别更大了,被评估的模型与用D训练出的模型相比可能有较大差别,从而降低了评估结果的保真性。这个问题没有完美的解决方案,常见做法是将大约2/3~4/5的样本用于训练,剩余样本用于测试。

2.2 交叉验证法

“交叉验证法”(cross validation):

1.将数据集划分为k个大小相似的互斥子集,即。每个子集D_i都尽可能保持数据分布的一致性,即从D中通过分层采样得到。

2.每次用k-1个子集的并集作为训练集,余下的那个子集作为测试集;这样就可获得k 个测试结果的均值。

显然,交叉验证法评估结果的稳定性和保真性在很大程度上取决于k的取值,为强调这一点,通常把交叉验证法称为“k折交叉验证”(k-fold cross validation)。

k最常用的取值是10,其次有5,20等。

与留出法一致,将数据集D划分为k个子集同样存在多种划分方式。为减少因样本划分不同而引入的差别,k折交叉验证通常要随机使用不同的划分重复p次,最终的评估结果是这pk折交叉验证结果的均值。

假定数据集D中包含m个样本,若令k=m,则得到了交叉验证法的一个特例:留一法(leave-one-out)。

显然,留一法不受随机样本划分方式的影响,因为m个样本只有唯一的方式划分为m个子集——每个子集包含一个样本;留一法使用的训练集与初始数据集相比只少了一个样本,这就使得在绝大多数情况下,留一法被实际评估的模型与期望评估的用D训练出的模型很相似。因此,留一法的评估结果往往被认为比较准确。

然而在数据集比较大时,留一法计算量非常大,这是留一法的一大缺陷。

2.3 自助法

我们希望评估的是用D训练出的模型。但在留出法和交叉验证法中,由于保留了一部分样本用于测试,因此实际评估的模型所使用的训练集比D小,这必然会引入一些因训练样本规模不同而导致的估计偏差。

留一法受训练样本规模变化的的影响较小,但计算复杂度又太高。故现需寻找一种方法:即可减少训练样本规模不同造成的影响,同时还能比较高效地进行实践估计。

 

“自助法”(bootstrapping)是其一种有效方法,它直接以自助采样法为基础。

1. 给定包含m个样本的训练数据集D,对其进行采样产生数据集D^':每次随机从D中挑选一个样本,将其拷贝放入D^',然后再将该样本放回初始数据集D中,使得该样本在下次采样时仍有可能被采到。

2. 这个过程重复执行 m次后,得到了包含m个样本的数据集D^',这就是自助采样的结果。

显然,D中有一部分样本会在D^'中多次出现,而另一部分样本不出现。样本在m次采样中始终不被采到的概率是(1-1/m)^m,取极限得

即通过自助采样,初始数据集D中约有36.8%的样本未出现在采样数据集D^'中。于是,我们可将D^'用作训练集,D\setminus D^'用作测试集。这样,实际评估的模型与期望评估的模型都使用m个训练样本,而我们仍有数据总量约1/3的、没在训练集中出现的样本用于测试。这样的测试结果,亦称“包外估计”(out-of-bag estimate)。

自助法在数据集较小、难以有效划分训练/测试集时很有用。此外,自助法能从初始数据集中产生多个不同的训练集,这对集成学习等方法有很大的好处。

然而,自助法产生的数据集改变了初始数据集的分布,这会引入估计偏差。因此,在初始数据量足够时,留出法和交叉验证法更常用一些。

3 性能度量

衡量泛化能力的评估标准,这就是性能度量(performance measure)。

性能度量反映了任务需求,在对比不同模型的能力时,使用不同的性能度量往往会导致不同的评判结果;这意味着模型的“好坏”是相对的,什么样的模型是好的,不仅取决于算法和数据,还决定于任务需求。

在预测任务中,给定样例集,其中y_i是示例x_i 的真实标记。要评估学习器f 的性能,就要把学习器预测结果f(x)与真实标记y 进行比较。

回归任务中最常用的性能度量是“均方误差”(mean sqared error):

更一般地,对于数据分布和概率密度函数p(\cdot ),均方误差可描述为

下面介绍分类任务中常用的性能度量:

错误率:分类错误的样本数占样本总数的比例

精度:分类正确的样本数占样本总数的比例

这两种度量既适用于二分类任务,也适用于多分类任务。

在实际任务中,错误率并不能完全满足我们的需求,以西瓜为例,我们关心的是 “挑出的西瓜中有多少比例的是好瓜”,或 “所有好瓜中有多少比例被挑了出来”。

混淆矩阵:对于二分类问题,可将样例根据其真实类别与学习器预测类别的组合划分为真正例(true positive)、假正例(false positive)、真反例(true negative)、假反例(false negative)四种情形,令TP、FP、TN、FN分别表示其对应的样例数,这显然有TP+FP+TN+FN = 样例总数。

查准率P:(挑出的西瓜中有多少比例是好瓜)

查全率R:(所有好瓜中有多少比例被挑了出来)

查准率和查全率是一对矛盾的度量。一般来说,查准率高时,查全率往往低;而查全率高时,查准率往往偏低。例如,若希望将好瓜尽可能多选出来,则可通过增加选瓜的数量来实现,如果将所有西瓜都选上,那么所有的好瓜也必然都被选择上了,但这样查准率就会降低;若希望选出的瓜中好瓜比例尽可能高,则可只挑选最有把握的瓜,但这样就难免漏掉不少的好瓜,使得查全率较低。

F1值:

一些应用汇中,对查准率和查全率的重视程度有所不同,如在商品推荐系统中,为了尽可能少打扰用户,更希望推荐内容是用户感兴趣的,此时查准率更重要;而在逃犯信息检索系统中,更希望尽可能少漏掉逃犯,此时查全率更重要。

F1度量的一般形式-- F_\beta,能表达出对查准率/查全率的不同偏好:

其中\beta >0度量了查全率对查准率的相对重要性。\beta =1时退化为标准的F1\beta >1时查全率有更大的影响;\beta <1时查准率有更大的影响。

n 个二分类混淆矩阵上综合考虑查准率和查全率:

一种直接做法是先在各个混淆矩阵上分别计算出查准率和查全率,记为,再计算平均值,这样就得到“宏查准率”(macro-P)、“宏查全率”(macro-R)以及相应的“宏F1”(macro-F1):

另一种先将各混淆矩阵的对应元素进行平均,得到TP、FP、TN、FN的平均值,分别记为,再基于这些平均值计算出“微查准率”(micro-P)、“微查全率”(micro_R)和“微-F1”(micro-F1):

ROC曲线:Receiver Operating Characteristic

根据学习器的预测结果对样例进行排序,按此顺序逐个把样本作为正例进行预测,每次计算出两个重要的值,分别以他们为横、纵坐标作图,就得到了“ROC曲线”。

ROC曲线的纵轴是“真正例率”(True Positive Rate,TPR),横轴是假正例率(False Positive Rate,FPR),两者分别定义为:

显示ROC曲线的图称为“ROC图”:

AUC(Area Under ROC Curve):ROC曲线下的面积。

若一个学习器的ROC曲线被另一个学习器的曲线完全“包住”,则可断言后者的性能优于前者;若两个学习器的ROC曲线发生交叉,则难以断言孰优孰劣,此时,较为合理的判断依据是比较ROC曲线下的面积,即AUC。

AUC可通过对ROC曲线下各部分的面积求和而得。假定ROC曲线的坐标为的点按序连接而形成,AUC可估算为:

 

下面载入iris数据集,并在此数据集上训练出线性支持向量机:

>>> import numpy as np
>>> from sklearn.model_selection import train_test_split
>>> from sklearn import datasets
>>> from sklearn import svm

>>> iris = datasets.load_iris()
>>> iris.data.shape, iris.target.shape
((150, 4), (150,))

采样原数据集的40%作为测试集,从而测试(评估)分类器:

>>> X_train, X_test, y_train, y_test = train_test_split(
...     iris.data, iris.target, test_size=0.4, random_state=0)

>>> X_train.shape, y_train.shape
((90, 4), (90,))
>>> X_test.shape, y_test.shape
((60, 4), (60,))

>>> clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train)
>>> clf.score(X_test, y_test)                           
0.96...

当评价估计器设置不同超参数时,由于在训练集上,通过调整参数设置可使估计器的性能达到最佳状态,但在测试集上可能会出现过拟合情况。此时,测试集上的信息反馈最易颠覆训练好的模型,评估的指标不再有效反映出模型的泛化性能。为了解决此类问题,还应该准备另一部分被称为“validation set(验证集)”的数据集,模型训练完后在验证集上对模型进行评估。当验证集上的评估实验比较成功时,在测试集上进行最后的评估。

然而,通过将原始数据分为3个数据集合,我们就大大减少了模型学习的样本数量,并且得到的结果依赖于集合对(训练、验证)的随机选择。这个问题可通过交叉验证来解决。交叉验证需要测试集做最后的模型评估,但不再需要验证集。

k折交叉验证得出的性能指标是循环计算每个值的平均值。该方法虽然计算代价很高,但它不会浪费太多的数据,在处理样本数据较少的问题时比较有优势。

1 计算交叉验证的指标

使用交叉验证最简单的方法是在估计器和数据集上调用cross_val_score辅助函数。

下面的例子展示了如何通过分割数据,拟合模型和计算连续5次的分数(每次不同分割)来估计linear kernel支持向量机在iris数据集上的精度。

>>> from sklearn.model_selection import cross_val_score
>>> clf = svm.SVC(kernel='linear', C=1)
>>> scores = cross_val_score(clf, iris.data, iris.target, cv=5)
>>> scores                                              
array([0.96..., 1.  ..., 0.96..., 0.96..., 1.        ])

评分估计的平均得分和95%置信区间:

>>> print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))
Accuracy: 0.98 (+/- 0.03)

默认情况下,每个CV迭代计算的分数是估计器的score 方法。可以通过使用scoring参数来改变计算范数如下:

>>> from sklearn import metrics
>>> scores = cross_val_score(
...     clf, iris.data, iris.target, cv=5, scoring='f1_macro')
>>> scores                                              
array([0.96..., 1.  ..., 0.96..., 0.96..., 1.        ])

在iris数据集的情形下,样本在各个目标类别之间是平衡的,因此准确度和F1-score几乎相等。

当CV参数是一个整数时,cross_val_score默认使用KFold或StratifiedKFold策略,后者会在估计器派生ClassifierMixin时使用。

也可通过传入一个交叉验证迭代器来使用其他交叉验证策略,比如

>>> from sklearn.model_selection import ShuffleSplit
>>> n_samples = iris.data.shape[0]
>>> cv = ShuffleSplit(n_splits=5, test_size=0.3, random_state=0)
>>> cross_val_score(clf, iris.data, iris.target, cv=cv)  
array([0.977..., 0.977..., 1.  ..., 0.955..., 1.        ])

另一种使用一个可迭代生成器作为索引数据产生(train,test)划分,比如

>>> def custom_cv_2folds(X):
...     n = X.shape[0]
...     i = 1
...     while i <= 2:
...         idx = np.arange(n * (i - 1) / 2, n * i / 2, dtype=int)
...         yield idx, idx
...         i += 1
...
>>> custom_cv = custom_cv_2folds(iris.data)
>>> cross_val_score(clf, iris.data, iris.target, cv=custom_cv)
array([1.        , 0.973...])

保留数据的数据转换

正如在训练集中保留的数据上测试一个predictors (预测器)是很重要的,预处理(如标准化、特征选择)和类似的data_transformations也应该从训练集中学习,并应用于预测数据以进行预测:

>> from sklearn import preprocessing
>> X_train, X_test, y_train, y_test = train_test_split(
  ...     iris.data, iris.target, test_size=0.4, random_state=0)
>> scaler = preprocessing.StandardScaler().fit(X_train)
>> X_train_transformed = scaler.transform(X_train)
>> clf = svm.SVC(C=1).fit(X_train_transformed, y_train)
>> X_test_transformed = scaler.transform(X_test)
>> clf.score(X_test_transformed, y_test)  
  0.9333...

Pipeline可更容易地组合估计器,在交叉验证下使用如下:

>> from sklearn.pipeline import make_pipeline
>> clf = make_pipeline(preprocessing.StandardScaler(), svm.SVC(C=1))
>> cross_val_score(clf, iris.data, iris.target, cv=cv)
  ...                                                 
  array([ 0.97...,  0.93...,  0.95...])

1.1 cross_validate函数和多度量评估

cross_validate函数与cross_val_score在下面的两个方面有些不同:

1.它允许指定多个指标进行评估;

2.除了测试得分外,它还会返回一个包含训练得分、拟合次数、score-times(得分次数)的一个字段。It returns a dict containing training scores,fit-times and score-times in addition to the test score.

对于单个度量评估,其中scoring参数是一个字符串,可以调用或None,keys将是[‘test_score’,'fit_time','test_<scorer>','fit_time','score_time']

对于多度量评估,返回值是一个带有以下的keys的字典-- ['test_<scorer1_name>', 'test_<scorer2_name>', 'test_<scorer...>', 'fit_time', 'score_time']

return_train_score默认设置为True,它增加了所有scorer的训练得分keys,

还可通过设置return_estimator=True来默认在所有训练集上拟合好的估计器。

>>> from sklearn.model_selection import cross_validate
>>> from sklearn.metrics import recall_score
>>> scoring = ['precision_macro', 'recall_macro']
>>> clf = svm.SVC(kernel='linear', C=1, random_state=0)
>>> scores = cross_validate(clf, iris.data, iris.target, scoring=scoring,
...                         cv=5)
>>> sorted(scores.keys())
['fit_time', 'score_time', 'test_precision_macro', 'test_recall_macro']
>>> scores['test_recall_macro']                       
array([0.96..., 1.  ..., 0.96..., 0.96..., 1.        ])

或作为一个字典mapping得分器名称预定义或自定义的得分函数:

>>> from sklearn.metrics.scorer import make_scorer
>>> scoring = {'prec_macro': 'precision_macro',
...            'rec_macro': make_scorer(recall_score, average='macro')}
>>> scores = cross_validate(clf, iris.data, iris.target, scoring=scoring,
...                         cv=5, return_train_score=True)
>>> sorted(scores.keys())                 
['fit_time', 'score_time', 'test_prec_macro', 'test_rec_macro',
 'train_prec_macro', 'train_rec_macro']
>>> scores['train_rec_macro']                         
array([0.97..., 0.97..., 0.99..., 0.98..., 0.98...])

3.交叉验证迭代器

下面列出了一些用于生产索引标号,用于在不同的交叉验证策略中生产数据划分的工具。

3.1 循环遍历数据

假设一些数据是独立同分布的(i.i.d),所有样本来源于相同的生成过程,并假设生成过程没有记忆过去生成的样本。

3.1.2 K折

KFold将所有的样例划分为k个分组,称为折叠(如果k=n,这等价于留一策略),都具有相同的大小。如果预测函数学习时使用k-1个折叠中的数据,最后一个剩下的折叠会用于测试。

在4个样例的数据集上使用2-fold交叉验证:

>>> import numpy as np
>>> from sklearn.model_selection import KFold

>>> X = ["a", "b", "c", "d"]
>>> kf = KFold(n_splits=2)
>>> for train, test in kf.split(X):
...     print("%s  %s" % (train, test))
[2 3] [0 1]
[0 1] [2 3]

3.1.2 重复k-折交叉验证

RepeateKFold重复K-fold n次,当需要运行时可可以使用KFold n次,在每次重复中产生不同的分割。

2折K-Fold重复2次的示例:

>>> import numpy as np
>>> from sklearn.model_selection import RepeatedKFold
>>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
>>> random_state = 12883823
>>> rkf = RepeatedKFold(n_splits=2, n_repeats=2, random_state=random_state)
>>> for train, test in rkf.split(X):
...     print("%s  %s" % (train, test))
...
[2 3] [0 1]
[0 1] [2 3]
[0 2] [1 3]
[1 3] [0 2]

更多详细内容见  https://scikit-learn.org/stable/modules/cross_validation.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值