概览

**交叉验证(Cross Validation)**是机器学习领域、分类器算法等十分重要的模型性能检测方法。交叉验证是将数据集随机分为训练集和测试集,重复这一过程,直至每一个样本都做过测试集。交叉验证可以分为简单随机交叉验证、留一法、留P法、K折法。

  1. 简单随机交叉验证只划分数据一次,训练集70%、测试集30%,不是准确意义上的交叉验证;
  2. 留一法留P法都是在n个样本中选择1个或者P个样本作为测试集,而且数据集划分 C n p C_n^p Cnp,属于完全意义上的交叉验证,考虑数据集划分的各种情况,交叉验证是可重复的
  3. K折法是将数据集随机划分K组,然后依次选取其中一组作为测试集,剩余作为验证集,实现每一个样本做过测试集,在数据量大的情况下,K值越大越好,K经典取值是2,5,10.需要注意的是,每次K折交叉验证出来的结果并不一样,即K折法不具备重复试验的性质,除非实验者指定K折随机划分时的随机数;
  4. 有学者提出5×2交叉验证,即重复5次的2折交叉验证,既补充数据集的不同划分方式,又避免数据集之间太高的相关性。

上述部分交叉验证在Matlab中有3个函数可以实现这些功能,为帮助读者厘清其中的不同,选择适用的函数,本文拟对Matlab中的3个交叉验证函数做一个系统介绍。需要说明的是,Matlab中的交叉验证实质是帮助你划分好数据集,划分后,自己还需要写代码添加自己的模型。此外,本文给出自己写的留P法交叉验证代码,供读者择用。

crossvalind()

crossvalind是cross validation的缩写,该函数的输出结果有两种形式,会对后续代码书写带不变,因此本人不太喜欢这一点:
语法1:indices = crossvalind(‘KFlod’,n,k)
说明:k折法,在n个样本中将数据集平均划分k组,分组方式是用数字1至k进行标记,例如分成3组,indices结果如下:

>indices = crossvalind('KFold',10,3)
indices =
     3
     1
     1
     2
     3
     2
     3
     1
     3
     2

因此这个函数后续一般会跟有如下代码串,用于获取训练集和测试集数据。

for i=1:k
	test=(indices==k); %结果为逻辑值,每次循环选取一个组作为测试集
	train=~test; %取test的补集即为训练集
	TrainData=Data(trian,:); %提取训练集数据 
	TestData=Data(test,:); %提取测试集数据
	...%训练模型代码
end

语法2:[Train,Test] = crossvalind(‘HoldOut’,n,p)
说明:简单随机交叉验证,在n个样本中选择n*p个样本作为测试集,p只能取(0,1)之间,Train和Test是n×1的逻辑数组

语法3:[Train, Test] = crossvalind(‘LeaveMOut’,n, m)
说明:留一法和留P法,但是数据划分不完整,仅仅划分一次,并不是定义中的交叉验证

小结:crossvalind()函数使用起来给代码书写增加新的问题,比如不同交叉验证输出结果不一致需要处理,比如留一法和留P法数据划分只有一种需要补全,这些都导致crossvalind()函数不怎么实用。

cvpartition()

cvpartition()是本人比较喜欢使用的交叉验证函数,比crossvalind()函数好很多,输出结果统一,贴合原有交叉验证方法的定义。用法如下:
语法1:c = cvpartition(n,‘KFold’,k);
说明:k折法,在n个样本中将数据集平均划分k组,分组是用逻辑数组表示。
语法2:c = cvpartition(n,‘HoldOut’,p);
说明:简单随机交叉验证,在n个样本中选择n*p个样本作为测试集(0<p<1时),或者选择p个样本作为测试集(p为整数);
语法3:c = cvpartition(n,‘LeaveOut’,);
说明:留一法交叉验证,在n个样本中选择1个样本作为测试集,重复n次。

之所以很喜欢cvpartition()函数,是因为该函数将输出结果统一为cvpartition类型变量,虽然是没见过这个变量,但这个类型变量很简单,有两个关键的成员函数,很好用于交叉验证的后续代码编写。
首先看一下c是什么

>c = cvpartition(10,'HoldOut',0.2)
c = 
   Hold-out cross validation partition
   NumObservations: 10
       NumTestSets: 1
         TrainSize: 8
          TestSize: 2

>c = cvpartition(10,'KFold',4)
c = 
   K-fold cross validation partition
   NumObservations: 10
       NumTestSets: 4
         TrainSize: 8  7  7  8
          TestSize: 2  3  3  2

可以看出cvpartition类型的成员变量有交叉验证类型(eg:Hold-out cross validation partition)、样本量(NumObservations)、测试集个数(NumTestSets)、TrainSize(训练集样本量)、TestSize(测试集样本量)。
cvpartition类型的成员函数有如下两个最常用:
函数1:TrainIndex = training(c,TestIndex)
函数2:TestIndex = test(c,TestIndex)

函数training()获取第TestIndex个训练集样本的逻辑值,函数test()获取第TestIndex测试集样本的逻辑值,举例如下。后续就可以获取训练集数据训练模型,很方便。

>TrainIndex = training(c,2)
TrainIndex =
     1
     1
     1
     0
     0
     0
     1
     1
     1
     1

利用cvpartition()实现K折交叉验证的数据划分代码示例如下:

% Data定义为数据集
PointCount = size(Data,1);%获取样本量
c = cvpartition(PointCount,'KFold',4);%k-fold法
for ModelIndex = 1:c.NumTestSets
            TrainIndex = training(c,ModelIndex);
            TestIndex = test(c,ModelIndex);
            TrainData = Data(TrainIndex,:);%提取训练数据
            TestData = Data(TestIndex,:);%提取验证数据
            ...%训练模型代码
end

小结:cvpartition()函数将交叉验证的输出做成cvpartition类,十分有助于后续代码的一致性书写,总体十分友好。
注意:cvpartition()函数包含留一法交叉验证,但是没有留P法交叉验证

crossval()

crossval()函数是将交叉验证和模型训练聚合在一起,是对cvpartition()的又一次升级。用法如下:
语法1:vals = crossval(fun,X,Y);
说明:fun是个函数句柄,使用类似于‘fx = @(x) f(x)’的形式定义;X和Y分别是输入和输出变量;此时,要求fun至少有四个输入,分别是(XTRAIN,YTRAIN,…,XTEST,YTEST,…),输出为评价指标,默认使用10折交叉验证
语法2:mse = crossval(‘mse’,X,Y,‘Predfun’,predfun);
说明:该语法表明模型评价指标采用MSE(均方误差),除了可以用‘mse’,还可以用‘mcr’(错分类率);predfun的定义是三个输入,分别是(XTRAIN,ytrain,XTEST),输出为XTEST的模型计算结果。
语法3:vals = crossval(…,‘name’,value)
说明:'name’表示使用什么类型的交叉验证,crossval支持简单随机、k折、留一法、cvpartition类型(没有留P法),参数及定义见下表:
在这里插入图片描述
小结:crossval()可以将模型和交叉验证结合在一起来做,真的很不错。模型可以先按crosscal的输入输出要求写好函数,然后在函数句柄中调用该函数,就可以实现模型与交叉验证的完美结合了!

留P法交叉验证Matlab代码

根据对crossvalind()等函数的学习,留P交叉验证也可以使用逻辑数组的形式实现,本人利用Matlab中自带的**nchoosek(n,P)**函数实现获取从n个样本中选取P个样本作为测试集的所有情形,代码如下,仅供参考:

PointCount = size(Data,1);%数据集的样本数
p=3;%以p=3为例
TestAll = nchoosek(1:PointCount,p);%nchoosek函数返回选取样本的序号,构成C_n^p×p矩阵
TrainGroup = logical(ones(size(TestAll,1),PointCount));%逻辑矩阵
TestGroup = logical(zeros(size(TestAll,1),PointCount));%逻辑矩阵
for row=1:size(TestAll,1)
    TestGroup(row,TestAll(row,:)) = true;%训练集
    TrainGroup(row,:) = ~TestGroup(row,:);%验证集
end
for ModelIndex = 1:size(TestAll,1)
    TrainData = PData(TrainGroup(ModelIndex,:),:);
    TrainTime = PTime(TrainGroup(ModelIndex,:));%提取训练数据
    TestData = PData(TestGroup(ModelIndex,:),:);
    TestTime = PTime(TestGroup(ModelIndex,:));%提取验证数据
    ...%训练模型代码
end

总结

本文对Matlab的三种交叉验证函数做了详细介绍,并补充自己书写的留P法交叉验证代码,希望对读者有帮助。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐