【人工知能入門】K分割交差検証
1. 簡易検証とその欠点
1.1 簡易検証の概要
- 単純な検証: 元のデータ セットをトレーニング セットと検証セットにランダムに分割します。たとえば、データを 7:3 の比率に従って 2 つの部分に分割し、サンプルの 70% がモデルのトレーニングに使用され、30% がモデルのトレーニングに使用されます。以下に示すように、サンプルの一部はモデルの検証に使用されます。
1.2 簡易検証のデメリット
- データは 1 回だけ使用されます。
- 検証セットで計算された評価指標は、元のグループ化と強い関係があります。
- 時系列シーケンスの場合、時系列情報を保存するために、データの順序を崩してランダムにデータを横取りすることができないことが多く、たとえば、学習には常に春、夏、秋のデータが使用されます。テストには冬のデータが使用されていますが、これは明らかに問題があり、容認できません。
2. K 分割相互検証
- 単純な相互検証の欠点を解決するために、K 分割相互検証が導入されます。これにより、データセット内のデータ量が不十分であるという問題を解決できるだけでなく、パラメーター調整の問題も解決できます。。
2.1 K 分割相互検証の考え方
- まず、すべてのサンプルを同じサイズの k 個のサンプル サブセットに分割します。
- k 個のサブセットは順番に走査され、そのたびに現在のサブセットが検証セットとして使用され、残りのすべてのサンプルがモデルをトレーニングおよび評価するためのトレーニング セットとして使用されます。
- 最後に、k個の評価指標の平均が最終的な評価指標として使用される。実際の実験では、下図に示すように、k は通常 10 になります。
2.2 細かい詳細
- K フォールド相互検証には、次のフォールドのトレーニングが前のフォールドに基づいていないという詳細があります。つまり、新しいフォールドごとにモデル パラメーターを再初期化する必要があります。
- K 分割相互検証は検証のみに使用できるため、その結果をモデル パラメーターの保存や判断の基礎として使用することはできませんが、スーパー パラメーターの組み合わせを決定したり、モデル構造を調整したりするために使用できます。トレーニング用にモデルを再初期化し、モデル パラメーターを改善します。
- 連続した情報を含むデータの場合、異なるフォールド間でパフォーマンスに大きな差があるかどうかを確認する必要があります。
2.3 K 分割相互検証の欠点
- トレーニング セッションを実行するために K フォールド相互検証に必要なエポックの総数は、各フォールドのトレーニング エポック数とフォールドの総数 (K) の積であるため、トレーニングのコストは 2 倍になります。
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)