-
[DL 경진대회] 항공 사진 내 선인장 식별(2)ML.DL 2023. 1. 30. 17:27
<Musthave 머신러닝딥러닝 문제해결 전략> 11장을 실습한 내용입니다.
4. 성능 개선
지난 글에서는 간단한 CNN 모델을 사용해 베이스라인을 만들었습니다. 이번엔 다음 네 가지를 개선해서 성능을 높여 보겠습니다.
- 다양한 이미지 변환을 수행하기
- 더 깊은 CNN 모델을 만들기
- 더 뛰어난 옵티마이저를 사용하기
- 훈련 시 에폭 수를 늘리기
① 데이터 준비
먼저, 시드값 고정부터 '데이터 준비'의 '2. 데이터셋 클래스 정의'까지는 베이스라인과 똑같이 진행합니다.
이미지 변환기 정의
이미지를 변환하여 데이터 수를 늘리는 방식을 데이터 증강이라고 하는데, 이를 해 줄 이미지 변환기를 직접 정의해 보겠습니다. 성능을 개선하기 위해 다양한 이미지 변환기를 활용할 텐데, 훈련 데이터용과 검증 및 테스트 데이터용을 따로 만듭니다.
from torchvision import transforms # 이미지 변환을 위한 모듈 # 훈련 데이터용 변환기 transform_train = transforms.Compose([transforms.ToTensor(), transforms.Pad(32, padding_mode='symmetric'), transforms.RandomHorizontalFlip(), transforms.RandomVerticalFlip(), transforms.RandomRotation(10), transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))]) # 검증 및 테스트 데이터용 변환기 transform_test= transforms.Compose([transforms.ToTensor(), transforms.Pad(32, padding_mode='symmetric'), transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))])
transforms.Compose()로 여러 변환기들을 하나로 묶었습니다. 사용된 변환기들을 간단히 살펴보겠습니다.
- transforms.ToTensor() : 이미지를 텐서 객체로 만듭니다.
- transforms.Pad() : 이미지 주변에 패딩을 추가합니다.
- transforms.RandomHorizontalFlip(), transforms.RandomVerticalFlip() : 각각 무작위로 이미지를 좌우, 상하 대칭 변환합니다.
- transforms.RandomRotation() : 이미지를 회전시킵니다.
- transforms.Normalize() : 데이터를 지정한 평균과 분산에 맞게 정규화해 줍니다.
데이터셋 및 데이터 로더 생성
ImageDataset 클래스로 훈련 및 검증 데이터셋을 만듭니다. 전달하는 변환기 빼고는 베이스라인 코드와 똑같습니다. 훈련 데이터셋을 만들 때는 훈련용 변환기를, 검증 데이터셋을 만들 때는 검증/테스트용 변환기를 전달합니다.
dataset_train = ImageDataset(df=train, img_dir='train/', transform=transform_train) dataset_valid = ImageDataset(df=valid, img_dir='train/', transform=transform_test)
데이터 로더도 만들겠습니다.
from torch.utils.data import DataLoader # 데이터 로더 클래스 loader_train = DataLoader(dataset=dataset_train, batch_size=32, shuffle=True) loader_valid = DataLoader(dataset=dataset_valid, batch_size=32, shuffle=False)
② 모델 생성
이번에는 더 깊은 CNN을 만들겠습니다. 신경망 계층이 깊어지면 대체로 예측력이 좋아집니다. 배치 정규화를 적용하고 활성화 함수를 Leacky ReLU로 바꿔서 성능을 높이겠습니다.
{합성곱, 배치 정규화, 최대 풀링} 계층이 총 5개에, 전결합 계층도 2개로 늘렸습니다.
import torch.nn as nn # 신경망 모듈 import torch.nn.functional as F # 신경망 모듈에서 자주 사용되는 함수 class Model(nn.Module): # 신경망 계층 정의 def __init__(self): super().__init__() # 상속받은 nn.Module의 __init__() 메서드 호출 # 1 ~ 5번째 {합성곱, 배치 정규화, 최대 풀링} 계층 self.layer1 = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=2), nn.BatchNorm2d(32), # 배치 정규화 nn.LeakyReLU(), # LeakyReLU 활성화 함수 nn.MaxPool2d(kernel_size=2)) self.layer2 = nn.Sequential(nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=2), nn.BatchNorm2d(64), nn.LeakyReLU(), nn.MaxPool2d(kernel_size=2)) self.layer3 = nn.Sequential(nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=2), nn.BatchNorm2d(128), nn.LeakyReLU(), nn.MaxPool2d(kernel_size=2)) self.layer4 = nn.Sequential(nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, padding=2), nn.BatchNorm2d(256), nn.LeakyReLU(), nn.MaxPool2d(kernel_size=2)) self.layer5 = nn.Sequential(nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, padding=2), nn.BatchNorm2d(512), nn.LeakyReLU(), nn.MaxPool2d(kernel_size=2)) # 평균 풀링 계층 self.avg_pool = nn.AvgPool2d(kernel_size=4) # 전결합 계층 self.fc1 = nn.Linear(in_features=512 * 1 * 1, out_features=64) self.fc2 = nn.Linear(in_features=64, out_features=2) # 순전파 출력 정의 def forward(self, x): x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.layer5(x) x = self.avg_pool(x) x = x.view(-1, 512 * 1 * 1) # 평탄화 x = self.fc1(x) x = self.fc2(x) return x
마지막으로 방금 정의한 Model 클래스를 활용해 CNN 모델을 만든 뒤, 장비에 할당하겠습니다.
model = Model().to(device)
③ 모델 훈련
손실 함수와 옵티마이저 설정
손실 함수는 베이스라인과 같은 걸로 하겠습니다.
# 손실 함수 criterion = nn.CrossEntropyLoss()
옵티마이저는 Adamax로 바꿔 보겠습니다.
# 옵티마이저 optimizer = torch.optim.Adamax(model.parameters(), lr=0.00006)
모델 훈련
훈련을 더 많이 하기 위해 에폭 수를 10에서 70으로 늘리겠습니다. 나머지 코드는 베이스라인과 같습니다.
epochs = 70 # 총 에폭 # 총 에폭만큼 반복 for epoch in range(epochs): epoch_loss = 0 # 에폭별 손실값 초기화 # '반복 횟수'만큼 반복 for images, labels in loader_train: # 이미지, 레이블 데이터 미니배치를 장비에 할당 images = images.to(device) labels = labels.to(device) # 옵티마이저 내 기울기 초기화 optimizer.zero_grad() # 순전파 : 이미지 데이터를 신경망 모델의 입력값으로 사용해 출력값 계산 outputs = model(images) # 손실 함수를 활용해 outputs와 labels의 손실값 계산 loss = criterion(outputs, labels) # 현재 배치에서의 손실 추가 epoch_loss += loss.item() # 역전파 수행 loss.backward() # 가중치 갱신 optimizer.step() print(f'에폭 [{epoch+1}/{epochs}] - 손실값: {epoch_loss/len(loader_train):.4f}')
총 70 에폭만큼 훈련하며 손실값을 출력했습니다.
(실행이 엄청 오래 걸리네요...)④ 성능 검증
검증 데이터로 모델 성능을 평가하겠습니다. 이번에도 코드는 베이스라인과 같습니다.
from sklearn.metrics import roc_auc_score # ROC AUC 점수 계산 함수 임포트 # 실제값과 예측 확률값을 담을 리스트 초기화 true_list = [] preds_list = [] model.eval() # 모델을 평가 상태로 설정 with torch.no_grad(): # 기울기 계산 비활성화 for images, labels in loader_valid: # 이미지, 레이블 데이터 미니배치를 장비에 할당 images = images.to(device) labels = labels.to(device) # 순전파 : 이미지 데이터를 신경망 모델의 입력값으로 사용해 출력값 계산 outputs = model(images) preds = torch.softmax(outputs.cpu(), dim=1)[:, 1] # 예측 확률값 true = labels.cpu() # 실제값 # 예측 확률값과 실제값을 리스트에 추가 preds_list.extend(preds) true_list.extend(true) # 검증 데이터 ROC AUC 점수 계산 print(f'검증 데이터 ROC AUC : {roc_auc_score(true_list, preds_list):.4f}')
베이스라인의 ROC AUC는 0.9902였는데, 성능 개선을 하니 0.9998이 되었네요! ROC AUC의 최댓값이 1이니 거의 완벽한 점수입니다.
⑤ 예측 및 결과 제출
이제 테스트 데이터로 예측해 볼 시간입니다! 이번에도 transform_test 변환기를 이용해 데이터셋을 만들었습니다.
dataset_test = ImageDataset(df=submission, img_dir='test/', transform=transform_test) loader_test = DataLoader(dataset=dataset_test, batch_size=32, shuffle=False) # 예측 수행 model.eval() # 모델을 평가 상태로 설정 preds = [] # 타깃 예측값 저장용 리스트 초기화 with torch.no_grad(): # 기울기 계산 비활성화 for images, _ in loader_test: # 이미지 데이터 미니배치를 장비에 할당 images = images.to(device) # 순전파 : 이미지 데이터를 신경망 모델의 입력값으로 사용해 출력값 계산 outputs = model(images) # 타깃값이 1일 확률(예측값) preds_part = torch.softmax(outputs.cpu(), dim=1)[:, 1].tolist() # preds에 preds_part 이어붙이기 preds.extend(preds_part)
제출 파일을 만듭니다.
submission['has_cactus'] = preds submission.to_csv('submission.csv', index=False)
이미지 파일은 삭제합니다.
import shutil shutil.rmtree('./train') shutil.rmtree('./test')
커밋 후 제출해 봅시다.
최종 점수가 0.9998로 점수가 많이 올랐습니다!
점수를 더 높이는 방법
이번 장에서는 제공된 훈련 데이터를 9:1로 나눠 9 만큼만 모델을 훈련했습니다. 아래 캐글러의 코드를 참고해서, '훈련 데이터 전체'로 모델을 훈련시키면 최종 점수가 0.9999가 된다고 합니다.
첫 딥러닝 경진대회에 참가해 보았습니다. 난이도가 낮아 따라가기는 쉬웠지만, 머신러닝 경진대회와는 다르게 새로운 절차와 개념이 등장해서 재미있었습니다. 다음에는 조금 더 어려운 경진대회에 참여해 보겠습니다!
'ML.DL' 카테고리의 다른 글
[리니지 이탈 예측 모형 개발] 데이터 탐색 및 시각화 (0) 2023.09.25 [리니지 이탈 예측 모형 개발] 데이터 탐색 (1) 2023.09.18 [DL 경진대회] 항공 사진 내 선인장 식별(1) (2) 2023.01.30 [ML 경진대회] 향후 판매량 예측-성능 개선 (2) 2022.11.28 [ML 경진대회] 향후 판매량 예측-베이스라인 모델 (0) 2022.11.27