交叉验证(Cross Validation,CV)是一种常见的模型评估方法。简言之,就是将样本分为训练集(tranning set)和测试集(test set),训练集用来估计模型参数,测试集用来评价模型精度。交叉验证有多种类型,本篇主要介绍以下三种:
- 留一法交叉验证
- k折交叉验证
- 重复k折交叉验证
1 留一法交叉验证
假设样本数为,留一法交叉验证(Least-One-Out Cross-Validation,LOOCV),就是每次只留下一个样本作为测试集,剩余个样本作为训练集,重复次。
步骤如下:
Step 1:从个样本中拿出个样本来估计模型参数,这些样本称为训练集,剩余一个样本为测试集;
Step 2:使用上个步骤估计出的模型参数预测测试集样本的因变量取值;
Step 3:重复上述两个步骤,直至所有样本都有且仅有一次为测试集样本;
Step 4:记测试集的因变量预测值构成的向量为pred
,对应的因变量真实值为obs
;
Step 5:使用pred
和obs
两个向量计算R2、均方根误差(RMSE)等模型评价指标。
以线性模型为例。
以下模型的R2 = 0.8264:
model <- lm(mpg ~ wt + qsec, data = mtcars)
## R2 = 0.8264
使用留一法交叉验证计算模型的R2。
- 生成向量
pred
n = dim(mtcars)[1]
pred = 1:n
for(i in c(1:n)) {
mod <- lm(mpg ~ wt + qsec, data = mtcars[-i,])
pred[i] = predict(mod, mtcars[i,])
}
- 计算R2
前面的推文已经介绍了R2(线性回归(一)——模型表达式和输出结果
R2_CV <- function(pred, obs) {
err <- sum((pred - obs)^2)
ctr <- sum((mean(obs) - obs)^2)
R2_CV = 1 - err/ctr
return(R2_CV)
}
R2_CV(pred, mtcars$mpg)
## [1] 0.7785627
得到的R2 = 0.7785627。下面使用caret
工具包中的R2()
函数进行验证。
R2()
函数提供了两种计算R2的方法,一种是form = "corr"
(默认值),即将R2视为预测值和真实值相关系数的平方,另一种是form = "traditional"
,即上面列出的计算公式。
library(caret)
R2(pred, mtcars$mpg)
## [1] 0.7800446
cor(pred, mtcars$mpg)^2
## [1] 0.7800446
R2(pred, mtcars$mpg, form = "traditional")
## [1] 0.7785627
- 可以看出,使用
form = "traditional"
与自定义的R2_CV()
函数计算的结果相同。
2 k折交叉验证
k折交叉验证(k-Folder Cross-Validation),是将个样本随机分为等份,将其中份作为训练集,剩余一份作为测试集,并不断重复,直至每份都有且仅有一次作为过测试集。当时,k折交叉验证等价于留一法交叉验证。最常用的是10折交叉验证。
自定义将样本随机分为k等份的函数group_fun()
:
group_fun <- function(data, seed, k = 10) {
set.seed(seed)
n = dim(data)[1]
order_rand <- sample(1:n, n, replace = F)
group_rand <- order_rand %% k + 1
return(group_rand)
}
- data:包含样本变量的数据框;
- seed:随机数种子;
- k:等分的份数,默认值为10。
检测自定义函数的效果:
group_fun(mtcars, 123)
## [1] 2 6 10 5 4 1 9 3 2 6 1 10 4 1 10 9 9 8 8 3 7 8 5 2 5
## [26] 2 6 4 7 3 3 7
group_fun(mtcars, 1024, k = 5)
## [1] 2 3 3 4 1 5 5 2 3 4 1 2 5 2 1 3 3 1 5 3 4 3 4 5 2 4 5 2 1 2 1 4
- 返回的数字表示对应分组的编号。
10折交叉验证:
k = 10
x <- group_fun(mtcars, 1024, k = k)
pred <- NULL
obs <- NULL
for(i in 1:k) {
mod <- lm(mpg ~ wt + qsec, data = mtcars[x != i,])
pred = c(pred, predict(mod, mtcars[x == i,]))
obs <- c(obs, mtcars[x == i,]$mpg)
}
R2_CV(pred, obs)
## [1] 0.7871856
留一法交叉验证得到的R2是唯一的,而k折交叉验证由于分组过程存在随机性,因此计算的R2是不固定的。
以下列举了上述代码不同随机数种子对应的计算结果:
seed | R2 |
4 | 0.7837388 |
16 | 0.7888558 |
64 | 0.7782393 |
128 | 0.778737 |
256 | 0.7904926 |
1024 | 0.7871856 |
- 可以看出,虽然不同随机数种子对应的计算结果不同,但总体还是比较稳定的。
3 重复k折交叉验证
重复k折交叉验证(Repeated k-Fold Cross-Validation )是对样本进行多次随机分组,每次分组都进行一次k折交叉验证,再将多次计算的结果取平均值,以消除分组的偶然性对计算结果可能造成的偏差。
set.seed(1024)
rp = 10
seeds = runif(rp, 0, 10000)
r2 <- 1:rp
- rp:重复次数;
- seeds:储存随机数种子的向量,长度为rp;
- r2:储存每次R2的计算结果,长度为rp。
for(j in 1:rp) {
k = 10
x <- group_fun(mtcars, seeds[j], k = k)
pred <- NULL
obs <- NULL
for(i in 1:k) {
mod <- lm(mpg ~ wt + qsec, data = mtcars[x != i,])
pred = c(pred, predict(mod, mtcars[x == i,]))
obs <- c(obs, mtcars[x == i,]$mpg)
}
r2[j] = R2_CV(pred, obs)
}
mean(r2)
# [1] 0.7802496
seed | R2 |
4 | 0.7806959 |
16 | 0.7791301 |
64 | 0.7643035 |
128 | 0.7826845 |
256 | 0.7762842 |
1024 | 0.7802496 |
4 总结
- 留一法只适用于样本数较小的情况;
- k折交叉验证可用于样本量较大的情况。