scikit-learn 交叉验证,评估评估器的性能(一)

sklearn 中的交叉验证方法。

0. CV

一个预测函数从所有可用数据中训练获得相关参数,再去判断同一组数据的标签是没有意义的,尽管其能够得到较高的分数。用它来判断其未曾见过的数据其分数将会明显降低,即过度拟合。为了避免这种情况,一般的做法是从所有可用数据中分出一部分作为测试数据。下图为经典的在模型训练时的交叉验证过程。

239

在 scikit-learn 中有 train_test_split 函数可以方便的将数据集分为 train 和 test。以下以 iris 数据集为例,并用一个线性SVM分类器进行训练。

1
2
3
4
5
6
7
8
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 (150,4)
# iris.target.shape (150,)

接下来从整个数据集中分出40%的数据作为测试集用来测试我们的分类器。

1
2
3
4
5
6
7
8
9
10
11
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 (90,4), y_train.shape(90,)
# X_test.shape(60,4), y_test.shape(60,)

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

当对分类器的参数进行不断调整优化时,此时对测试集的预测会越来越精准,但是也可能会发生过度拟合的问题。即测试集的模型信息会逐渐地“泄露”到训练集中。此时分类器遍不再有泛化能力。为了解决这个问题,便需要从所有可用数据集中分出另一部分作为验证集(validation set)。分类器从训练集获得参数,并在验证集上进行验证,不断调整优化。最后在测试集上评估分类器的性能。

但是,将所有可用数据分为三个部分将极大的减少训练集中的样本数量。其结果可能会依赖特定的(训练/验证)集合的划分。

而交叉验证(Cross-Validation,CV)则是该问题的一个解决方法。对所有可用数据首先还是会划分出一个测试集。剩下的训练集不会再划分出一部分作为验证集,而是将会使用一种叫做 K-fold CV 的方法进行划分。训练集将会被划分成k个相同大小的集合。对于每一个小集合都将进行如下处理:

  • 用其余k-1个小集合训练出一个模型;
  • 用该小集合来验证由此产生的模型;

k-fold 交叉验证的分数是整个循环的计算的平均值。尽管这个过程需要消耗大量的计算,但是却充分利用了有限的数据样本。

3108

1. Computing CV metrics

使用CV最简单的方法是在分类器和数据集上直接使用cross_val_score函数。

下面的例子将展示如何在 iris 数据集上评价一个线性SVM分类器的准确性。首先将数据集分组;其次训练模型;最后连续计算5次得分(每次分割不同)。

1
2
3
4
5
6
7
8
9
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)

# 5次的得分
print(scores)

# 得分均值,和其95%置信区间
print("Accuracy:%.2f(+/- %.2f)" % (scores.mean(), scores.std()*2))

在每一次CV过程中,计算分数方法默认是使用分类器自带的score方法。该方法可以通过指定 scoring 参数改变。

1
scores = cross_val_score(clf, iris.data, iris.target, cv=5, socring='f1_macro')

在Iris数据集的情况下,样本在目标类之间是平衡的,因此准确性和F1-score几乎相等。

当参数cv的值是一个整形数字时,cross_val_score 将默认使用 kfold 策略。如果分类器是从 ClassifierMixin 中派生出来的那么将使用 StratifiedKFold 策略。当然也可使用其他的 CV 迭代器,如下所示。

1
2
3
4
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)

另外也可使用自定义的方法来划分数据,如下所示。

1
2
3
4
5
6
7
8
9
10
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)

对划分数据进行数据转换

如同从训练集获得的分类器需要在测试集进行测试一样。数据的预处理也同样需要从训练集获得并同样运用到测试集中。

1
2
3
4
5
6
7
8
9
10
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 可以非常容易的将这些操作合并到CV中。

1
2
3
4
5
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.977..., 0.933..., 0.955..., 0.933..., 0.977...])