성능 개선을 위해 변경사항을 살펴보자.
1. 모델 생성 단계에서 B7보다 파라미터가 적은 EfficientNet-B1,B2,B3를 사용하자. 각 모델을 예측하고 결과를 앙상블 하자.
2. 모델 훈련 단계에서 스케줄러를 설정하자.
3. 예측 단계에서 앙상블을 하자.
13.4 성능 개선
시드값 고정 및 GPU 장비 설정
시드값 고정
import torch # 파이토치
import random
import numpy as np
import os
# 시드값 고정
seed = 50
os.environ['PYTHONHASHSEED'] = str(seed)
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.enabled = False
GPU 장비 설정
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
데이터 준비
# 데이터 경로
data_path = '/kaggle/input/chest-xray-pneumonia/chest_xray/'
# 훈련, 검증, 테스트 데이터 경로 설정
train_path = data_path + 'train/'
valid_path = data_path + 'val/'
test_path = data_path + 'test/'
데이터 증강을 위한 이미지 변환기 정의
from torchvision import transforms
# 훈련 데이터용 변환기
transform_train = transforms.Compose([
transforms.Resize((250, 250)), # 이미지 크기 조정
transforms.CenterCrop(180), # 중앙 이미지 확대
transforms.RandomHorizontalFlip(0.5), # 좌우 대칭
transforms.RandomVerticalFlip(0.2), # 상하 대칭
transforms.RandomRotation(20), # 이미지 회전
transforms.ToTensor(), # 텐서 객체로 변환
transforms.Normalize((0.485, 0.456, 0.406),
(0.229, 0.224, 0.225))]) # 정규화
# 테스트 데이터용 변환기
transform_test = transforms.Compose([
transforms.Resize((250, 250)),
transforms.CenterCrop(180),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406),
(0.229, 0.224, 0.225))])
데이터셋 및 데이터 로더 생성
from torchvision.datasets import ImageFolder
# 훈련 데이터셋
datasets_train = ImageFolder(root=train_path, transform=transform_train)
# 검증 데이터셋
datasets_valid = ImageFolder(root=valid_path, transform=transform_test)
def seed_worker(worker_id):
worker_seed = torch.initial_seed() % 2**32
np.random.seed(worker_seed)
random.seed(worker_seed)
g = torch.Generator()
g.manual_seed(0)
from torch.utils.data import DataLoader
batch_size = 8
loader_train = DataLoader(dataset=datasets_train, batch_size=batch_size,
shuffle=True, worker_init_fn=seed_worker,
generator=g, num_workers=2)
loader_valid = DataLoader(dataset=datasets_valid, batch_size=batch_size,
shuffle=False, worker_init_fn=seed_worker,
generator=g, num_workers=2)
13.4.1 모델 생성 및 훈련
모델 생성
models_list =[] # 모델 저장용 리스트
from efficientnet_pytorch import EfficientNet
# 모델 생성
efficientnet_b1 = EfficientNet.from_pretrained('efficientnet-b1', num_classes=2)
efficientnet_b2 = EfficientNet.from_pretrained('efficientnet-b2', num_classes=2)
efficientnet_b3 = EfficientNet.from_pretrained('efficientnet-b3', num_classes=2)
# 장비 할당
efficientnet_b1 = efficientnet_b1.to(device)
efficientnet_b2 = efficientnet_b2.to(device)
efficientnet_b3 = efficientnet_b3.to(device)
# 리스트에 모델 저장
models_list.append(efficientnet_b1)
models_list.append(efficientnet_b2)
models_list.append(efficientnet_b3)
다음으로 각 모델의 파라미터 개수를 출력해보자. 참고로 b7의 파라미터는 6천만개이다.
for idx, model in enumerate(models_list):
num_parmas = sum(param.numel() for param in model.parameters())
print(f'모델{idx+1} 파라미터 개수 : {num_parmas}')
손실함수, 옵티마이저, 스케줄러 설정
import torch.nn as nn
criterion = nn.CrossEntropyLoss()
optimizer1 = torch.optim.AdamW(models_list[0].parameters(), lr=0.0006, weight_decay=0.001)
optimizer2 = torch.optim.AdamW(models_list[1].parameters(), lr=0.0006, weight_decay=0.001)
optimizer3 = torch.optim.AdamW(models_list[2].parameters(), lr=0.0006, weight_decay=0.001)
모델이 세 개라서 옵티마이저를 각각 설정하자.
세 번째로는 스케줄러를 설정하자. 이번에도 get_cosine_schedule_with_warmup()을 사용하자.
from transformers import get_cosine_schedule_with_warmup
epochs = 20 # 총 에폭
# 스케줄러
scheduler1 = get_cosine_schedule_with_warmup(optimizer1,
num_warmup_steps=len(loader_train)*3,
num_training_steps=len(loader_train)*epochs)
scheduler2 = get_cosine_schedule_with_warmup(optimizer2,
num_warmup_steps=len(loader_train)*3,
num_training_steps=len(loader_train)*epochs)
scheduler3 = get_cosine_schedule_with_warmup(optimizer3,
num_warmup_steps=len(loader_train)*3,
num_training_steps=len(loader_train)*epochs)
모델 훈련 및 성능 검증
from sklearn.metrics import accuracy_score # 정확도 계산 함수
from sklearn.metrics import recall_score # 재현율 계산 함수
from sklearn.metrics import f1_score # F1 점수 계산 함수
from tqdm.notebook import tqdm # 진행률 표시 막대
def train(model, loader_train, loader_valid, criterion, optimizer,
scheduler=None, epochs=10, save_file='model_state_dict.pth'):
valid_loss_min = np.inf # 최소 손실값 초기화 (검증 데이터용)
# 총 에폭만큼 반복
for epoch in range(epochs):
print(f'에폭 [{epoch+1}/{epochs}] \n-----------------------------')
# == [ 훈련 ] ==============================================
model.train() # 모델을 훈련 상태로 설정
epoch_train_loss = 0 # 에폭별 손실값 초기화 (훈련 데이터용)
# '반복 횟수'만큼 반복
for images, labels in tqdm(loader_train):
# 이미지, 레이블(타깃값) 데이터 미니배치를 장비에 할당
images = images.to(device)
labels = labels.to(device)
# 옵티마이저 내 기울기 초기화
optimizer.zero_grad()
# 순전파 : 이미지 데이터를 신경망 모델의 입력값으로 사용해 출력값 계산
outputs = model(images)
# 손실 함수를 활용해 outputs와 labels의 손실값 계산
loss = criterion(outputs, labels)
# 현재 배치에서의 손실 추가 (훈련 데이터용)
epoch_train_loss += loss.item()
loss.backward() # 역전파 수행
optimizer.step() # 가중치 갱신
if scheduler != None: # 스케줄러 학습률 갱신
scheduler.step()
# 훈련 데이터 손실값 출력
print(f'\t훈련 데이터 손실값 : {epoch_train_loss/len(loader_train):.4f}')
# == [ 검증 ] ==============================================
model.eval() # 모델을 평가 상태로 설정
epoch_valid_loss = 0 # 에폭별 손실값 초기화 (검증 데이터용)
preds_list = [] # 예측값 저장용 리스트 초기화
true_list = [] # 실젯값 저장용 리스트 초기화
with torch.no_grad(): # 기울기 계산 비활성화
for images, labels in loader_valid:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
epoch_valid_loss += loss.item()
# 예측값 및 실제값
preds = torch.max(outputs.cpu(), dim=1)[1].numpy()
true = labels.cpu().numpy()
preds_list.extend(preds)
true_list.extend(true)
# 정확도, 재현율, F1 점수 계산
val_accuracy = accuracy_score(true_list, preds_list)
val_recall = recall_score(true_list, preds_list)
val_f1_score = f1_score(true_list, preds_list)
# 검증 데이터 손실값 및 정확도, 재현율, F1점수 출력
print(f'\t검증 데이터 손실값 : {epoch_valid_loss/len(loader_valid):.4f}')
print(f'\t정확도 : {val_accuracy:.4f} / 재현율 : {val_recall:.4f} / F1 점수 : {val_f1_score:.4f}')
# == [ 최적 모델 가중치 찾기 ] ==============================
# 현 에폭에서의 손실값이 최소 손실값 이하면 모델 가중치 저장
if epoch_valid_loss <= valid_loss_min:
print(f'\t### 검증 데이터 손실값 감소 ({valid_loss_min:.4f} --> {epoch_valid_loss:.4f}). 모델 저장')
# 모델 가중치를 파일로 저장
torch.save(model.state_dict(), save_file)
valid_loss_min = epoch_valid_loss # 최소 손실값 갱신
return torch.load(save_file) # 저장한 모델 가중치를 불러와 반환
train()함수를 baseline 때와 동일하게 정의한 다음, 이어서 train()을 이용해 세 모델을 순차적으로 학습시키자.
# 첫 번째 모델 훈련
model_state_dict = train(model=models_list[0],
loader_train=loader_train,
loader_valid=loader_valid,
criterion=criterion,
optimizer=optimizer1,
scheduler=scheduler1,
epochs=epochs)
# 첫 번째 모델에 최적 가중치 적용
models_list[0].load_state_dict(model_state_dict)
# 두 번째 모델 훈련
model_state_dict = train(model=models_list[1],
loader_train=loader_train,
loader_valid=loader_valid,
criterion=criterion,
optimizer=optimizer2,
scheduler=scheduler2,
epochs=epochs)
# 두 번째 모델에 최적 가중치 적용
models_list[1].load_state_dict(model_state_dict)
# 세 번째 모델 훈련
model_state_dict = train(model=models_list[2],
loader_train=loader_train,
loader_valid=loader_valid,
criterion=criterion,
optimizer=optimizer3,
scheduler=scheduler3,
epochs=epochs)
# 세 번째 모델에 최적 가중치 적용
models_list[2].load_state_dict(model_state_dict)
models_list에는 훈련이 완료된, 즉 최적 가중치로 갱신된 모델들이 저장돼 있다.
13.4.2 예측 및 평가 결과
datasets_test = ImageFolder(root=test_path, transform=transform_test)
loader_test = DataLoader(dataset=datasets_test, batch_size=batch_size,
shuffle=False, worker_init_fn=seed_worker,
generator=g, num_workers=2)
모델별 예측
def predict(model, loader_test, return_true=False):
model.eval() # 모델을 평가 상태로 설정
preds_list = [] # 예측값 저장용 리스트 초기화
true_list = [] # 실제값 저장용 리스트 초기화
with torch.no_grad(): # 기울기 계산 비활성
for images, labels in loader_test:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
preds = torch.max(outputs.cpu(), dim=1)[1].numpy() # 예측값
true = labels.cpu().numpy() # 실제값
preds_list.extend(preds)
true_list.extend(true)
if return_true:
return true_list, preds_list
else:
return preds_list
predict() 함수는 baseline과 동일하다.
true_list, preds_list1 = predict(model=models_list[0],
loader_test=loader_test,
return_true=True)
true_list는 실젯값이고 preds_list1은 첫 번째 모델( EfficientNet-B1 )로 예측한 값이다.
실젯값을 반환하려면 return_true = True를 전달하면 된다.
preds_list2 = predict(model=models_list[1],
loader_test=loader_test)
두 번째 모델(EfficientNet-B2)로 예측값을 구해보자. 실젯값은 이미 구했으니 return_true를 생략하자. (defalut가 false이므로)
preds_list3 = predict(model=models_list[2],
loader_test=loader_test)
세 번째 모델(EfficientNet-B3)
print('#'*5, 'efficientnet-b1 모델 예측 결과 평가 점수', '#'*5)
print(f'정확도 : {accuracy_score(true_list, preds_list1):.4f}')
print(f'재현율 : {recall_score(true_list, preds_list1):.4f}')
print(f'F1 점수 : {f1_score(true_list, preds_list1):.4f}')
print('#'*5, 'efficientnet-b2 모델 예측 결과 평가 점수', '#'*5)
print(f'정확도 : {accuracy_score(true_list, preds_list2):.4f}')
print(f'재현율 : {recall_score(true_list, preds_list2):.4f}')
print(f'F1 점수 : {f1_score(true_list, preds_list2):.4f}')
print('#'*5, 'efficientnet-b3 모델 예측 결과 평가 점수', '#'*5)
print(f'정확도 : {accuracy_score(true_list, preds_list3):.4f}')
print(f'재현율 : {recall_score(true_list, preds_list3):.4f}')
print(f'F1 점수 : {f1_score(true_list, preds_list3):.4f}')
이를 통해, 베이스 라인보다는 낫지만, 파라미터가 많다고 무조건 좋은 성능이 보장되는 건 아니라는 것을 알 수 있다.
앙상블 예측
ensemble_preds = []
for i in range(len(preds_list1)):
pred_element = np.round((preds_list1[i] + preds_list2[i] + preds_list3[i])/3)
ensemble_preds.append(pred_element)
앙상블 원리는 단순하다. 세 예측값을 합친 뒤 3으로 나누고 np.round() 함수로 반올림 하면 된다.
평가 결과
print('#'*5, '최종 앙상블 결과 평가 점수', '#'*5)
print(f'정확도 : {accuracy_score(true_list, ensemble_preds):.4f}')
print(f'재현율 : {recall_score(true_list, ensemble_preds):.4f}')
print(f'F1 점수 : {f1_score(true_list, ensemble_preds):.4f}')
모든 점수에서 개별 모델보다 높은 점수가 나왔다 !
'Ai > Keggle' 카테고리의 다른 글
Baseline - Chest X-Rat Images - [ 머신러닝-딥러닝 문제해결 전략 ] (1) | 2023.10.04 |
---|---|
EDA - Chest X-Rat Images - [ 머신러닝-딥러닝 문제해결 전략 ] (0) | 2023.10.04 |
Modeling2 - Plant Pathology 2020 - [ 머신러닝-딥러닝 문제해결 전략 ] (0) | 2023.10.02 |
Modeling - Plant Pathology 2020 - [ 머신러닝-딥러닝 문제해결 전략 ] (0) | 2023.10.02 |
Baseline - Plant Pathology 2020 - [ 머신러닝-딥러닝 문제해결 전략 ] (1) | 2023.10.02 |
댓글