Python数据分析与挖掘——交叉验证法

交叉验证(Cross-Validation,简写为CV)主要用于建模应用中,例如PCR (主成分回归)、PLS (偏最小二乘)回归建模中。
交叉验证是在机器学习建立模型和验证模型参数时常用的办法。交叉验证,顾名思义,就是重复的使用数据,把得到的样本数据进行切分,组合为不同的训练集和测试集,用训练集来训练模型,用测试集来评估模型预测的好坏。在此基础上可以得到多组不同的训练集和测试集,某次训练集中的某样本在下次可能成为测试集中的样本,即所谓“交叉”。

那么什么时候才需要交叉验证呢?
交叉验证用在数据不是很充足的时候。比如在我日常项目里面,对于普通适中问题,如果数据样本量小于一万条,我们就会采用交叉验证来训练优化选择模型。如果样本大于一万条的话,我们一般随机的把数据分成三份,一份为训练集(Training Set),一份为验证集(Validation Set),最后一份为测试集(Test Set)。用训练集来训练模型,用验证集来评估模型预测的好坏和选择模型及其对应的参数。把最终得到的模型再用于测试集,最终决定使用哪个模型以及对应参数。

交叉验证法的思想:
在给定的建模样本中,拿出大部分样本进行建模型,留小部分样本用刚建立的模型进行预报,并求这小部分样本的预报误差,记录它们的平方加和。
这个过程一直进行,直到所有的样本都被预报了一次而且仅被预报一次。把每个样本的预报误差平方加和,称为PRESS(predicted Error Sum of Squares,预测误差平方和)。

交叉验证法的作用:

  • 交叉验证用于评估模型的预测性能,尤其是训练好的模型在新数据上的表现,可以在一定程序熵减少过拟合。
  • 交叉验证还可以从有限的数据中获取尽可能多的有效信息

常见的交叉验证方法:

  • Hold-Out Method,留出法
  • K-fold Cross Validation(记为K-CV),K折叠交叉验证
  • Leave-One-Out Cross Validation(记为LOO-CV),留一法
  • Bootstrapping,自举法

交叉验证函数

sklearn.model_selection.cross_val_score

from sklearn.model_selection import cross_val_score

cross_val_score(model, X, y=None, scoring=None, cv=None, n_jobs=1)

参数:

  • model:拟合数据的模型
  • cv : k-fold
  • scoring:打分参数 —— ‘accuracy’、 ‘f1’、 ‘precision’、 ‘recall’、 ‘roc_auc’、 'neg_log_loss’等等

交叉验证cross_val_scorescoring参数

  • 分类:accuracy(准确率)、f1、f1_micro、f1_macro(这两个用于多分类的f1_score)、precision(精确度)、recall(召回率)、roc_auc
  • 回归:neg_mean_squared_error(MSE、均方误差)、r2
  • 聚类:adjusted_rand_score、completeness_score等

简单交叉验证(简单交叉验证)

所谓的简单,是和其他交叉验证方法相对而言的。首先,我们随机的将样本数据分为两部分(比如: 70%的训练集,30%的测试集),然后用训练集来训练模型,在测试集上验证模型及参数。接着,我们再把样本打乱,重新选择训练集和测试集,继续训练数据和检验模型。最后我们选择损失函数评估最优的模型和参数。

留出法(hold-out cross validation)

在机器学习任务中,拿到数据后,我们首先会将原始数据集分为三部分训练集(Training Set)验证集(Validation Set)测试集(Test Set)。(实际划分还是两部分,只是模块会自动将训练集划分为训练集和验证集)

  • 训练集用于训练模型;
  • 验证集用于模型的参数选择配置;
  • 测试集对于模型来说是未知数据,用于评估模型的泛化能力。

在这里插入图片描述
这个方法操作简单,只需要随机将原始数据分为三组即可。
不过如果只做一次分割,它对训练集,验证集和测试机的样本比例,还有分割后数据的分布是否和原始数据集的分布相同等因素比较敏感,不同的划分会得到不同的最优模型,而且分成三个集合后,用于训练的数据更少了。于是就有人相处了k折交叉验证(k-fold cross validation),这个后面再介绍。
下面例子,一共有150条数据:

扫描二维码关注公众号,回复: 13110289 查看本文章
from sklearn import datasets

iris = datasets.load_iris()
print(iris.data.shape, iris.target.shape)

结果:

(150, 4) (150,)

用train_test_split来随机划分数据集,其中40%用于测试集,有60条数据,60%为训练集,有90条数据:

from sklearn.model_selection import train_test_split
from sklearn import datasets

iris = datasets.load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.4, random_state=0)
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)

结果:

(90, 4) (90,)
(60, 4) (60,)

用train来训练,用test来评价模型的分数。

from sklearn.model_selection import train_test_split
from sklearn import datasets
from sklearn import svm

iris = datasets.load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.4, random_state=0)
clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train)
print(clf.score(X_test, y_test) )

结果:

0.9666666666666667

此种方法的好处是处理简单,只需随机把原始数据分为两组即可,其实严格意义来说Hold-Out Method并不能算是CV,因为这种方法没有达到交叉的思想,由于是随机的将原始数据分组,所以最后验证集分类准确率的高低与原始数据的分组有很大的关系,所以这种方法得到的结果其实并不具有说服性。

k 折交叉验证(k-fold cross validation)

sklearn.model_selection.KFold
在这里插入图片描述
k折交叉验证通过对k个不同分组训练的结果进行平均来减少方差,因此模型的性能对数据的划分就不那么敏感。

  • 第一步,不重复抽样将原始数据随机分为 k 份。
  • 第二步,每一次挑选其中 1 份作为测试集,剩余 k-1 份作为训练集用于模型训练。
  • 第三步,重复第二步 k次,这样每个子集都有一次机会作为测试集,其余机会作为训练集。在每个训练集上训练后得到一个模型,用这个模型在相应的测试集上测试,计算并保存模型的评估指标。
  • 第四步,计算 k 组测试结果的平均值作为模型精度的估计,并作为当前 k 折交叉验证下模型的性能指标。

k一般取10,数据量小的是,k可以设大一点,这样训练集占整体比例就比较大,不过同时训练的模型个数也增多。数据量大的时候,k可以设置小一点。当k=m的时候,即样本总数,就变成了留一法

举例,这里直接调用了cross_val_score,这里用了5折交叉验证

from sklearn.model_selection import train_test_split
from sklearn import datasets
from sklearn import svm
from sklearn.model_selection import cross_val_score

iris = datasets.load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.4, random_state=0)
clf = svm.SVC(kernel='linear', C=1)
scores = cross_val_score(clf, iris.data, iris.target, cv=5)
print(scores)

结果:

[0.96666667 1.         0.96666667 0.96666667 1.        ]

得到最后平均分数为0.98,以及它的95%置信区间:

from sklearn.model_selection import train_test_split
from sklearn import datasets
from sklearn import svm
from sklearn.model_selection import cross_val_score

iris = datasets.load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.4, random_state=0)
clf = svm.SVC(kernel='linear', C=1)
scores = cross_val_score(clf, iris.data, iris.target, cv=5)
print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))

结果:

Accuracy: 0.98 (+/- 0.03)

我们可以直接看一下K-Fold是怎么样划分数据的:X有四个数据,把它分成2折,结构中最后一个集合是测试集,前面的是训练集,每一行为1折:

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]

把它分成4折

from sklearn.model_selection import KFold

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

结果:

[1 2 3] [0]
[0 2 3] [1]
[0 1 3] [2]
[0 1 2] [3]

同样的数据X,我们来看LeaveOneOut后是什么样子,那就是把它分成4折,结果中最后一个集合是测试集,只有一个元素,前面的是训练集,每一行为1折:

from sklearn.model_selection import LeaveOneOut

X = ["a", "b", "c", "d"]
loo = LeaveOneOut()
for train, test in loo.split(X):
    print("%s %s" % (train, test))

结果:

[1 2 3] [0]
[0 2 3] [1]
[0 1 3] [2]
[0 1 2] [3]

将原始数据分成K组(一般是均分),将每个子集数据分别做一次验证集,其余的K-1组子集数据作为训练集,这样会得到K个模型,用这K个模型最终的验证集的分类准确率的平均数作为此K-CV下分类器的性能指标。K一般大于等于2,实际操作时一般从3开始取,只有在原始数据集合数据量小的时候才会尝试取2。K-CV可以有效的避免过学习以及欠学习状态的发生,最后得到的结果也比较具有说服性。

留一法(Leave one out cross validation)

每次的测试集都只有一个样本,要进行m次训练和预测,这个方法用于训练的数据只比整体数据集少一个样本,因此最接近原始样本的分布。但是训练复杂度增加了,因为模型的数量与原始数据样本数量相同。一般在数据缺少时使用。

此外:

  • 多次 k 折交叉验证再求均值,例如:10 次 10 折交叉验证,以求更精确一点。
  • 划分时有多种方法,例如对非平衡数据可以用分层采样,就是在每一份子集中都保持和原始数据集相同的类别比例。
  • 模型训练过程的所有步骤,包括模型选择,特征选择等都是在单个折叠 fold 中独立执行的。

例子:

from sklearn.model_selection import LeaveOneOut

X = ["a", "b", "c", "d"]
loo = LeaveOneOut()
for train, test in loo.split(X):
    print("%s %s" % (train, test))

结果:

[1 2 3] [0]
[0 2 3] [1]
[0 1 3] [2]
[0 1 2] [3]

如果设原始数据有N个样本,那么LOO-CV就是N-CV,即每个样本单独作为验证集,其余的N-1个样本作为训练集,所以LOO-CV会得到N个模型,用这N个模型最终的验证集的分类准确率的平均数作为此下LOO-CV分类器的性能指标。相比于前面的K-CV,LOO-CV有两个明显的优点
每一回合中几乎所有的样本皆用于训练模型,因此最接近原始样本的分布,这样评估所得的结果比较可靠。
实验过程中没有随机因素会影响实验数据,确保实验过程是可以被复制的。
但LOO-CV的缺点则是:
计算成本高,因为需要建立的模型数量与原始数据样本数量相同,当原始数据样本数量相当多时,LOO-CV在实作上便有困难几乎就是不显示,除非每次训练分类器得到模型的速度很快,或是可以用并行化计算减少计算所需的时间。

Bootstrapping(自举法)

通过自助采样法,即在含有 m 个样本的数据集中,每次随机挑选一个样本,再放回到数据集中,再随机挑选一个样本,这样有放回地进行抽样 m 次,组成了新的数据集作为训练集。
这里会有重复多次的样本,也会有一次都没有出现的样本,原数据集中大概有 36.8% 的样本不会出现在新组数据集中。
优点是:
训练集的样本总数和原数据集一样都是 m,并且仍有约 1/3 的数据不被训练而可以作为测试集。
缺点是:
这样产生的训练集的数据分布和原数据集的不一样了,会引入估计偏差。
(由于我们的训练集有重复数据,这会改变数据的分布,因而训练结果会有估计偏差,因此,此种方法不是很常用,除非数据量真的很少,比如小于20个。)

参考

交叉验证 Cross-validation
几种交叉验证(cross validation)方式的比较
交叉验证法总结

猜你喜欢

转载自blog.csdn.net/weixin_48615832/article/details/114217724