파이토치

[파이토치] Cross-Validation

알 수 없는 사용자 2022. 2. 14. 12:22

In k-fold cross-validation, the original sample is randomly partitioned into k equal sized subsamples

  • Of the k subsamples, a single subsample is retained as the validation data
  • The remaining k-1 subsamples are used as training data
  • The cross-validation process is then repeated k times with each of the k samples used exactly once as the validation data
  • 학습을 위한 데이터가 적은 경우 유용한 방법론이다.

 

import pandas as pd # 데이터프레임 형태를 다룰 수 있는 라이브러리
import numpy as np
from sklearn.model_selection import train_test_split # 전체 데이터를 학습 데이터와 평가 데이터로 나눈다.

# ANN
import torch
from torch import nn, optim # torch 내의 세부적인 기능을 불러온다. (신경망 기술, 손실함수, 최적화 방법 등)
from torch.utils.data import DataLoader, Dataset # 데이터를 모델에 사용할 수 있도록 정리해 주는 라이브러리
import torch.nn.functional as F # torch 내의 세부적인 기능을 불러온다. (신경망 기술 등)
# Cross Validation
from sklearn.model_selection import KFold
# Loss
from sklearn.metrics import mean_squared_error # Regression 문제의 평가를 위해 MSE(Mean Squared Error)를 불러온다.
# Plot
import matplotlib.pyplot as plt # 시각화 도구
df = pd.read_csv('./data/reg.csv', index_col=[0])
# 데이터를 넘파이 배열로 만들기
X = df.drop('Price', axis=1).to_numpy() # 데이터프레임에서 타겟값(Price)을 제외하고 넘파이 배열로 만들기
Y = df['Price'].to_numpy().reshape((-1,1)) # 데이터프레임 형태의 타겟값을 넘파이 배열로 만들기
# 텐서 데이터로 변환하는 클래스(3강 참고)
class TensorData(Dataset):

    def __init__(selfx_datay_data):
        self.x_data = torch.FloatTensor(x_data)
        self.y_data = torch.FloatTensor(y_data)
        self.len = self.y_data.shape[0]

    def __getitem__(selfindex):

        return self.x_data[index], self.y_data[index] 

    def __len__(self):
        return self.len
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.7)
trainset = TensorData(X_train, Y_train) # trainDataLoader는 for문 내에서 선언한다.
testset = TensorData(X_test, Y_test)
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)
class Regressor(nn.Module):
    def __init__(self):
        super().__init__() # 모델 연산 정의
        self.fc1 = nn.Linear(1350, bias=True# 입력층(13) -> 은닉층1(50)으로 가는 연산
        self.fc2 = nn.Linear(5030, bias=True# 은닉층1(50) -> 은닉층2(30)으로 가는 연산
        self.fc3 = nn.Linear(301, bias=True# 은닉층2(30) -> 출력층(1)으로 가는 연산
    
    def forward(selfx):
        x = self.fc1(x) 
        x = self.fc2(x) 
        x = self.fc3(x) 
      
        return x
kfold = KFold(n_splits=3, shuffle=True) # k는 3으로 설정
criterion = nn.MSELoss()
def evaluation(dataloader):
    
    predictions = torch.tensor([], dtype=torch.float# 예측값을 저장하는 텐서
    actual = torch.tensor([], dtype=torch.float# 실제값을 저장하는 텐서
        
    with torch.no_grad():
        model.eval() # 평가를 할 때에는 .eval() 반드시 사용해야 한다.
        for data in dataloader:
            inputs, values = data
            outputs = model(inputs)

            predictions = torch.cat((predictions, outputs), 0# cat을 통해 예측값을 누적
            actual = torch.cat((actual, values), 0# cat을 통해 실제값을 누적
    
    predictions = predictions.numpy() # 넘파이 배열로 변경
    actual = actual.numpy() # 넘파이 배열로 변경
    rmse = np.sqrt(mean_squared_error(predictions, actual)) # sklearn을 이용하여 RMSE 계산
    model.train() # 학습 중간에 평가를 했지때문에 모델을 eval()로 변환했다면 train()으로 재선언해야한다.
    return rmse  
 
# 이번 예시에서는 상관없으나 평가 시에는 정규화 기술을 배제하여 온전한 모델로 평가를 해야한다. 따라서 .eval()을 사용한다.
# 즉, 드랍아웃이나 배치 정규화 등과 같이 학습 시에만 사용하는 기술들이 적용 된 모델은 평가 시에는 비활성화 해야하며 학습 시 .train()을 사용한다.
validation_loss = []

for fold, (train_idx, val_idx) in enumerate(kfold.split(trainset)):
    # fold는 1,2,3의 값을 가질것이고, train_idx와 val_idx는 각각 인덱스 값을 가진다.
    train_subsampler = torch.utils.data.SubsetRandomSampler(train_idx) # index 생성
    val_subsampler = torch.utils.data.SubsetRandomSampler(val_idx) # index 생성
    
    # sampler를 이용한 DataLoader 정의
    trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, sampler=train_subsampler) 
    valloader = torch.utils.data.DataLoader(trainset, batch_size=32, sampler=val_subsampler)
    
    # 모델 선언
    model = Regressor()
    optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-7)
    
    for epoch in range(400): # 400번 학습을 진행한다.

        for data in trainloader: # 무작위로 섞인 32개 데이터가 있는 배치가 하나 씩 들어온다.

            inputs, values = data # data에는 X, Y가 들어있다.

            optimizer.zero_grad() # 최적화 초기화

            outputs = model(inputs) # 모델에 입력값 대입 후 예측값 산출
            loss = criterion(outputs, values) # 손실 함수 계산
            loss.backward() # 손실 함수 기준으로 역전파 설정 
            optimizer.step() # 역전파를 진행하고 가중치 업데이트

    train_rmse = evaluation(trainloader) # 학습 데이터의 RMSE
    val_rmse = evaluation(valloader)
    print("k-fold", fold," Train Loss: %.4f, Validation Loss: %.4f" %(train_rmse, val_rmse)) 
    validation_loss.append(val_rmse)

validation_loss = np.array(validation_loss)
mean = np.mean(validation_loss)
std = np.std(validation_loss)
print("Validation Score: %.4f, ± %.4f" %(mean, std))    
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=False)
train_rmse = evaluation(trainloader) # train 데이터의 RMSE
test_rmse = evaluation(testloader) # test 데이터의 RMSE

print("Train RMSE: %.4f" %train_rmse)
print("Test RMSE: %.4f" %test_rmse)