baseline을 이용하여 모델을 학습시켜 보니, train set에서는 f1 score가 79~ 80점대로 형성되는 반면, validation set에서는 f1 score가 50~51점대로 형성되는 것을 확인할 수 있었다. 이는 상당히 과대적합되어있다고 판단되어, 딥러닝 학습에서 over fitting을 해결하는 방법에 대해 탐색해 보았다. 또한 전처리 과정을 하나하나 넣어서 모델을 학습시켜보니 isolation forest기법을 이용한 이상치 처리는 데이터의 유실이 많아서 그런지 과소적합 현상이 발생하여 이상치는 따로 처리해 주지 않는 방향을 선택하였다.
3) Under/Over-fitting 해결방법
Overfitting
Overfitting(과대적합)이란 모델이 Train set 에서는 좋은 성능을 내지만 Validation set 에서는 낮은 성능을 내는 경우를 말합니다. 즉 Train 데이터에 모델이 너무 적합하게 학습 되어 있기 때문에 Train 데이터가 아닌 다른 데이터가 들어오게 되면 정확도가 떨어지는 현상입니다.
Overfitting 해결 방법
Overfitting은 모델의 성능을 떨어트리는 주요 문제입니다. 따라서 이러한 문제를 해결하기 위한 다양한 방법이 있습니다.
- 데이터 양 늘리기
모델은 데이터의 양이 적을수록 해당 데이터의 특징 패턴이나 노이즈까지 암기해버려서 Overfitting이 될 확률이 높습니다. 그래서 데이터의 양을 늘릴수록 모델은 일반적인 패턴을 할습하여 Overfitting을 방지할 수 있습니다.
데이터의 양을 늘리는 방법으로는 Data Augmentation 이곳에 설명이 되어있으니 참고하시면 됩니다.
- 모델의 복잡도 줄이기
인공지능 신경망의 복잡도는 Hidden layer의 수나 매개변수의 수 등으로 결정이 됩니다. Overfitting 현상이 포착되었을 때, 인공 신경망 모델에 대해서 할 수 있는 한 가지 조치는 인공 신경망의 복잡도를 줄이는 방법입니다.
- Dropout 사용하기
Dropout은 학습 과정에서 신경망의 일부를 사용하지 않는 방법입니다. 예를 들어서 Dropout의 비율을 0.5로 한다면 아래 그림처럼 학습 과정마다 랜덤으로 절반의 뉴런을 사용하지 않고 절반만 사용하게 됩니다.
Dropout은 신경망 학습 시에만 사용하고, 예측 시에는 사용하지 않는 것이 일반적입니다. 학습 시에 인공 신경망이 특정 뉴런 또는 특정 조합에 너무 의존적이게 되는 것을 방지해주고, 매번 랜덤 선택으로 뉴런들을 사용하지 않으므로 서로 다른 신경망들을 앙상블하여 사용하는 것 같은 효과를 내어 과적합을 방지합니다.
- 4. 출력층 직전 은닉층의 노드 수 줄이기
통계학에서 Overfitting을 해결하는 방법은 쓸데없는 변수를 제거해서 입력 변수의 수를 줄이는 것인데, 통계학 관점에서 출력층 직전 은닉층 노드 수는 설명 변수의 수가 됩니다. 따라서 의미있는 설명 변수들을 남기기 위해 출력 직전 노드를 줄이는 것입니다.
- 5. Batch Normalization
뉴럴 네트워크에서 각 활성함수의 미분값은 역전파 과정속에서 계속 곱해지기 때문에 중요한 부분 중 하나입니다. 시그 모이드 함수의 경우 일정 수준 이상 혹은 이하의 값이 입력되었을때 미분값이 0에 가까워 지게 되는데 이때 Gradient Vanishing 문제가 발생합니다. 이렇게 되면 파라미터의 업데이트가 거의 일어나지 않고 수렴 속도도 아주 느리게 되기 때문에 최적화에 실패하게 됩니다. 따라서 이를 해결하기 위해서는 Batch Normalization이 이용됩니다. mini batch 별로 분산과 표준편차를 구해 분포를 조정합니다.
Under Fitting
Underfitting은 이미 있는 Train set도 학습을 하지 못한 상태를 의미합니다. 즉 아직 학습이 덜 된 모델이라고 생각하면 편합니다. 이 문제가 발생하는 이유는 학습 반복 횟수가 너무 적고, 데이터의 특성에 비해 모델이 너무 간단하기 때문입니다. 또한 데이터 양이 너무 적은 문제도 있을 수 있습니다.
Reference
머신러닝 - 과대적합(overfitting)과 과소적합(underfitting), 정규화모델 튜닝 하는 방법 - 과대적합과 과소적합과적합과 과소적합 (Overfitting & Underfitting)
나는 lgbm모델을 이용해 feature importance를 뽑아낸 후, 중요도가 낮은 feature을 drop하는 방식으로 train set을 조절하는 방식을 선택했고, test set을 훈련시킬 때 epoch를 100으로 변경해주었다.(기존 epoch 30)
위 그림을 기준으로 feature importance 지수가 50미만인 feature들을 drop해주었다.
최종 코드
from google.colab import drive
drive.mount('/content/drive')
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
from sklearn.metrics import f1_score
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import SMOTE
import os
import pandas as pd
import numpy as np
from tqdm.auto import tqdm
import random
import warnings
warnings.filterwarnings(action='ignore')
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
CFG = {
'EPOCHS': 30,
'LEARNING_RATE':1e-2,
'BATCH_SIZE':256,
'SEED':41
}
CFG1 = {
'EPOCHS': 100,
'LEARNING_RATE':1e-2,
'BATCH_SIZE':256,
'SEED':41
}
def seed_everything(seed):
random.seed(seed)
os.environ['PYTHONHASHSEED'] = str(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = True
seed_everything(CFG['SEED'])
train = pd.read_csv('/content/drive/MyDrive/DACON 오일기계/train_dropmissing1.csv')
test = pd.read_csv('/content/drive/MyDrive/DACON 오일기계/test.csv')
categorical_features = ['YEAR', 'COMPONENT_ARBITRARY']
# Inference(실제 진단 환경)에 사용하는 컬럼
test_stage_features = ['COMPONENT_ARBITRARY', 'ANONYMOUS_1', 'YEAR' , 'ANONYMOUS_2', 'AG', 'CO', 'CR', 'CU', 'FE', 'H2O', 'MN', 'MO', 'NI', 'PQINDEX', 'TI', 'V', 'V40', 'ZN']
all_X = train.drop(['ID', 'Y_LABEL', 'Unnamed: 0', 'BE', 'U100', 'U50', 'FUEL', 'U75', 'U14', 'U25', 'FOPTIMETHGLY', 'U4', 'CD', 'FOXID', 'FSO4', 'U6', 'SOOTPERCENTAGE', 'FH2O', 'FNOX', 'U20', 'FTBN', 'V100', 'LI'], axis = 1)
all_y = train['Y_LABEL']
test = test.drop(['ID'], axis = 1)
train_X, val_X, train_y, val_y = train_test_split(all_X, all_y, test_size=0.2, random_state=CFG['SEED'], stratify=all_y)
def get_values(value):
return value.values.reshape(-1, 1)
for col in train_X.columns:
if col not in categorical_features:
scaler = StandardScaler()
train_X[col] = scaler.fit_transform(get_values(train_X[col]))
val_X[col] = scaler.transform(get_values(val_X[col]))
if col in test.columns:
test[col] = scaler.transform(get_values(test[col]))
le = LabelEncoder()
for col in categorical_features:
train_X[col] = le.fit_transform(train_X[col])
val_X[col] = le.transform(val_X[col])
if col in test.columns:
test[col] = le.transform(test[col])
smote = SMOTE(random_state=42)
train_X_over, train_y_over = smote.fit_resample(train_X, train_y)
print("SMOTE 적용 전 학습용 피처/레이블 데이터 세트 : ", train_X.shape, train_y.shape)
print('SMOTE 적용 후 학습용 피처/레이블 데이터 세트 :', train_X_over.shape, train_y_over.shape)
print('SMOTE 적용 후 값의 분포 :\n',pd.Series(train_y_over).value_counts() )
class CustomDataset(Dataset):
def __init__(self, data_X, data_y, distillation=False):
super(CustomDataset, self).__init__()
self.data_X = data_X
self.data_y = data_y
self.distillation = distillation
def __len__(self):
return len(self.data_X)
def __getitem__(self, index):
if self.distillation:
# 지식 증류 학습 시
teacher_X = torch.Tensor(self.data_X.iloc[index])
student_X = torch.Tensor(self.data_X[test_stage_features].iloc[index])
y = self.data_y.values[index]
return teacher_X, student_X, y
else:
if self.data_y is None:
test_X = torch.Tensor(self.data_X.iloc[index])
return test_X
else:
teacher_X = torch.Tensor(self.data_X.iloc[index])
y = self.data_y.values[index]
return teacher_X, y
train_dataset = CustomDataset(train_X, train_y, False)
val_dataset = CustomDataset(val_X, val_y, False)
train_dataset = CustomDataset(train_X, train_y, False)
val_dataset = CustomDataset(val_X, val_y, False)
class Teacher(nn.Module):
def __init__(self):
super(Teacher, self).__init__()
self.classifier = nn.Sequential(
nn.Linear(in_features=32, out_features=256),
nn.BatchNorm1d(256),
nn.LeakyReLU(),
nn.Linear(in_features=256, out_features=1024),
nn.BatchNorm1d(1024),
nn.LeakyReLU(),
nn.Linear(in_features=1024, out_features=256),
nn.BatchNorm1d(256),
nn.LeakyReLU(),
nn.Linear(in_features=256, out_features=1),
nn.Sigmoid()
)
def forward(self, x):
output = self.classifier(x)
return output
def train(model, optimizer, train_loader, val_loader, scheduler, device):
model.to(device)
best_score = 0
best_model = None
criterion = nn.BCELoss().to(device)
for epoch in range(CFG["EPOCHS"]):
train_loss = []
model.train()
for X, y in tqdm(train_loader):
X = X.float().to(device)
y = y.float().to(device)
optimizer.zero_grad()
y_pred = model(X)
loss = criterion(y_pred, y.reshape(-1, 1))
loss.backward()
optimizer.step()
train_loss.append(loss.item())
val_loss, val_score = validation_teacher(model, val_loader, criterion, device)
print(f'Epoch [{epoch}], Train Loss : [{np.mean(train_loss) :.5f}] Val Loss : [{np.mean(val_loss) :.5f}] Val F1 Score : [{val_score:.5f}]')
if scheduler is not None:
scheduler.step(val_score)
if best_score < val_score:
best_model = model
best_score = val_score
return best_model
def competition_metric(true, pred):
return f1_score(true, pred, average="macro")
def validation_teacher(model, val_loader, criterion, device):
model.eval()
val_loss = []
pred_labels = []
true_labels = []
threshold = 0.35
with torch.no_grad():
for X, y in tqdm(val_loader):
X = X.float().to(device)
y = y.float().to(device)
model_pred = model(X.to(device))
loss = criterion(model_pred, y.reshape(-1, 1))
val_loss.append(loss.item())
model_pred = model_pred.squeeze(1).to('cpu')
pred_labels += model_pred.tolist()
true_labels += y.tolist()
pred_labels = np.where(np.array(pred_labels) > threshold, 1, 0)
val_f1 = competition_metric(true_labels, pred_labels)
return val_loss, val_f1
model = Teacher()
model.eval()
optimizer = torch.optim.Adam(model.parameters(), lr=CFG['LEARNING_RATE'])
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=1, threshold_mode='abs',min_lr=1e-8, verbose=True)
teacher_model = train(model, optimizer, train_loader, val_loader, scheduler, device)
class Student(nn.Module):
def __init__(self):
super(Student, self).__init__()
self.classifier = nn.Sequential(
nn.Linear(in_features=18, out_features=128),
nn.BatchNorm1d(128),
nn.LeakyReLU(),
nn.Linear(in_features=128, out_features=512),
nn.BatchNorm1d(512),
nn.LeakyReLU(),
nn.Linear(in_features=512, out_features=128),
nn.BatchNorm1d(128),
nn.LeakyReLU(),
nn.Linear(in_features=128, out_features=1),
nn.Sigmoid()
)
def forward(self, x):
output = self.classifier(x)
return output
def distillation(student_logits, labels, teacher_logits, alpha):
distillation_loss = nn.BCELoss()(student_logits, teacher_logits)
student_loss = nn.BCELoss()(student_logits, labels.reshape(-1, 1))
return alpha * student_loss + (1-alpha) * distillation_loss
def distill_loss(output, target, teacher_output, loss_fn=distillation, opt=optimizer):
loss_b = loss_fn(output, target, teacher_output, alpha=0.1)
if opt is not None:
opt.zero_grad()
loss_b.backward()
opt.step()
return loss_b.item()
def student_train(s_model, t_model, optimizer, train_loader, val_loader, scheduler, device):
s_model.to(device)
t_model.to(device)
best_score = 0
best_model = None
for epoch in range(CFG1["EPOCHS"]):
train_loss = []
s_model.train()
t_model.eval()
for X_t, X_s, y in tqdm(train_loader):
X_t = X_t.float().to(device)
X_s = X_s.float().to(device)
y = y.float().to(device)
optimizer.zero_grad()
output = s_model(X_s)
with torch.no_grad():
teacher_output = t_model(X_t)
loss_b = distill_loss(output, y, teacher_output, loss_fn=distillation, opt=optimizer)
train_loss.append(loss_b)
val_loss, val_score = validation_student(s_model, t_model, val_loader, distill_loss, device)
print(f'Epoch [{epoch}], Train Loss : [{np.mean(train_loss) :.5f}] Val Loss : [{np.mean(val_loss) :.5f}] Val F1 Score : [{val_score:.5f}]')
if scheduler is not None:
scheduler.step(val_score)
if best_score < val_score:
best_model = s_model
best_score = val_score
return best_model
def validation_student(s_model, t_model, val_loader, criterion, device):
s_model.eval()
t_model.eval()
val_loss = []
pred_labels = []
true_labels = []
threshold = 0.35
with torch.no_grad():
for X_t, X_s, y in tqdm(val_loader):
X_t = X_t.float().to(device)
X_s = X_s.float().to(device)
y = y.float().to(device)
model_pred = s_model(X_s)
teacher_output = t_model(X_t)
loss_b = distill_loss(model_pred, y, teacher_output, loss_fn=distillation, opt=None)
val_loss.append(loss_b)
model_pred = model_pred.squeeze(1).to('cpu')
pred_labels += model_pred.tolist()
true_labels += y.tolist()
pred_labels = np.where(np.array(pred_labels) > threshold, 1, 0)
val_f1 = competition_metric(true_labels, pred_labels)
return val_loss, val_f1
train_dataset = CustomDataset(train_X, train_y, True)
val_dataset = CustomDataset(val_X, val_y, True)
train_loader = DataLoader(train_dataset, batch_size = CFG1['BATCH_SIZE'], shuffle=True)
val_loader = DataLoader(val_dataset, batch_size = CFG1['BATCH_SIZE'], shuffle=False)
train_dataset = CustomDataset(train_X, train_y, True)
val_dataset = CustomDataset(val_X, val_y, True)
train_loader = DataLoader(train_dataset, batch_size = CFG1['BATCH_SIZE'], shuffle=True)
val_loader = DataLoader(val_dataset, batch_size = CFG1['BATCH_SIZE'], shuffle=False)
def choose_threshold(model, val_loader, device):
model.to(device)
model.eval()
thresholds = [0.1, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5]
pred_labels = []
true_labels = []
best_score = 0
best_thr = None
with torch.no_grad():
for _, x_s, y in tqdm(iter(val_loader)):
x_s = x_s.float().to(device)
y = y.float().to(device)
model_pred = model(x_s)
model_pred = model_pred.squeeze(1).to('cpu')
pred_labels += model_pred.tolist()
true_labels += y.tolist()
for threshold in thresholds:
pred_labels_thr = np.where(np.array(pred_labels) > threshold, 1, 0)
score_thr = competition_metric(true_labels, pred_labels_thr)
if best_score < score_thr:
best_score = score_thr
best_thr = threshold
return best_thr, best_score
best_threshold, best_score = choose_threshold(best_student_model, val_loader, device)
print(f'Best Threshold : [{best_threshold}], Score : [{best_score:.5f}]')
test_datasets = CustomDataset(test, None, False)
test_loaders = DataLoader(test_datasets, batch_size = CFG1['BATCH_SIZE'], shuffle=False)
def inference(model, test_loader, threshold, device):
model.to(device)
model.eval()
test_predict = []
with torch.no_grad():
for x in tqdm(test_loader):
x = x.float().to(device)
model_pred = model(x)
model_pred = model_pred.squeeze(1).to('cpu')
test_predict += model_pred
test_predict = np.where(np.array(test_predict) > threshold, 1, 0)
print('Done.')
return test_predict
preds = inference(best_student_model, test_loaders, best_threshold, device)
submit3 = pd.read_csv('/content/drive/MyDrive/DACON 오일기계/sample_submission.csv')
submit3['Y_LABEL'] = preds
submit3.head()
submit3.to_csv("oil_submit_final.csv", index = False)
'Project > DACON: 건설기계 오일 상태 분류 AI 경진대회' 카테고리의 다른 글
느낀 점, 보완점 (0) | 2023.01.12 |
---|---|
Modeling (1) Baseline 분석 (0) | 2023.01.12 |
Data Processing (3) 불균형 데이터 처리 (0) | 2023.01.12 |
Data Processing (2) Feature Scaling (0) | 2023.01.11 |
Data Processing (1) 결측치 처리 (0) | 2023.01.10 |