【人工智能概论】 K折交叉验证
一. 简单验证及其缺点
1.1 简单验证简介
- 简单验证: 将原始数据集随机划分成训练集和验证集两部分,例,将数据按照7:3的比例分成两部分,70%的样本用于训练模型;30%的样本用于模型验证,如下图。
1.2 简单验证的缺点
- 数据都只被用了一次;
- 验证集上计算出来的评估指标与原始分组有很大关系;
- 对于时序序列,要保存时序信息,往往不能打乱数据的顺序对数据进行随机截取,这就带来了问题,比如总用春、夏、秋的数据做训练,用冬的数据做测试,这显然是有问题的,是不能容忍的。
二. K折交叉验证
- 为了解决简单交叉验证的不足,引出K折交叉验证,其既可以解决数据集的数据量不够大的问题,也可以解决参数调优的问题。。
2.1 K折交叉验证的思路
- 首先,将全部样本划分成k个大小相等的样本子集;
- 依次遍历这k个子集,每次把当前子集作为验证集,其余所有样本作为训练集,进行模型的训练和评估;
- 最后把k次评估指标的平均值作为最终的评估指标。在实际实验中,k通常取10,如下图。
2.2 小细节
- K折交叉验证中有这样一个细节,下一折的训练不是在上一折的基础上进行的,即每训练新的一折都要重新初始化模型参数。
- K折交叉验证只能做验证使用,因此不能根据它的结果做为模型参数的保存判断依据,但可以基于它做超参组合的确定与模型结构的调整,然后再重新初始化模型,进行训练得到较好的模型参数。
- 对于有时序信息的数据,要看看不同折之间性能表现会不会有明显差距。
2.3 K折交叉验证的缺点
- 因为K折交叉验证执行一次训练的总轮数是每一折的训练轮数(epochs)与总折数(K)的乘积,因此训练的成本会翻倍。
2.4 K折交叉验证的代码
import torch
import random
from torch.utils.data import DataLoader, TensorDataset
from Model.ReconsModel.Recoder import ReconsModel, Loss_function
from Model.ModelConfig import ModelConfig
def get_Kfold_data(k, i, x):
fold_size = x.size(0) // k
val_start = i * fold_size
if i != k - 1:
val_end = (i + 1) * fold_size
valid_data = x[val_start: val_end]
train_data = torch.cat((x[0: val_start], x[val_end:]), dim=0)
else:
valid_data = x[val_start:]
train_data = x[0: val_start]
return train_data, valid_data
def train(model, train_data, valid_data, batch_size, lr,epochs):
train_loader = DataLoader(TensorDataset(train_data), batch_size, shuffle=True)
valid_loader = DataLoader(TensorDataset(valid_data), batch_size, shuffle=True)
criterion = Loss_function()
optimizer = torch.optim.Adam(params=model.parameters(), lr=lr)
train_loss = []
valid_loss = []
for epoch in range(epochs):
tra_loss = 0
val_loss = 0
for i , data in enumerate(train_loader):
data = torch.stack(data)
data = data.squeeze(0)
optimizer.zero_grad()
recon, mu, log_std = model(data, if_train=True)
loss = criterion.loss_function(recon, data, mu, log_std)
loss.backward()
optimizer.step()
tra_loss = tra_loss + loss.item()
tra_loss = tra_loss / len(train_data)
train_loss.append(tra_loss)
with torch.no_grad():
for i, data in enumerate(valid_loader):
data = torch.stack(data)
data = data.squeeze(0)
optimizer.zero_grad()
recon, mu, log_std = model(data, if_train=False)
test_loss = criterion.loss_function(recon, data, mu, log_std).item()
val_loss = val_loss + test_loss
val_loss = val_loss / len(valid_data)
valid_loss.append(val_loss)
print('第 %d 轮, 训练的平均误差为%.3f, 测试的平均误差为%.3f 。'%(epoch+1, tra_loss, val_loss))
return train_loss, valid_loss
def k_test(config, datas):
valid_loss_sum = 0
for i in range(config.k):
model = ReconsModel(config)
print('-'*25,'第',i+1,'折','-'*25)
train_data , valid_data = get_Kfold_data(config.k, i, datas)
train_loss, valid_loss = train(model, train_data, valid_data, config.batch_size, config.lr, config.epochs)
train_loss_ave = sum(train_loss)/len(train_loss)
valid_loss_ave = sum(valid_loss)/len(valid_loss)
print('-*-*-*- 第 %d 折, 平均训练损失%.3f,平均检验损失%.3f -*-*-*-'%(i+1, train_loss_ave,valid_loss_ave))
valid_loss_sum = valid_loss_sum + valid_loss_ave
valid_loss_k_ave = valid_loss_sum / config.k
print('*' * 60, )
print('基于K折交叉验证的验证损失为%.4f'%valid_loss_k_ave)
if __name__ == "__main__":
X = torch.rand(5000, 16, 38)
index = [i for i in range(len(X))]
random.shuffle(index)
X = X[index]
config = ModelConfig()
config.load('./Model/config.json')
k_test(config, X)