[BoostCamp]Day10 파이토치 실전 연습 (파인튜닝 + ray을 이용한 하이퍼파라미터 탐색)
부스트캠프 10일차 공부 정리.
지금까지 배웠던 파이토치를 사용한 딥러닝 모델 실습을 한번 쭉 따라가려 합니다.
토치 비전에 있는 FashionMNIST 데이터를 resnet18 모델에 사용합니다. 이후 ray를 이용한 하이퍼파라미터 탐색을 진행합니다.
BoostCamp 심화과제 코드를 많이 참고해 진행합니다.
!pip uninstall -y -q pyarrow
!pip install -q -U ray[tune]
!pip install -q ray[debug]
!pip install torchsummary
import torchvision
import torch
from torchsummary import summary
import numpy as np
from ray import tune
from ray.tune.suggest.hyperopt import HyperOptSearch
from ray.tune import CLIReporter
import ray
from tqdm.notebook import tqdm
import math
common_transform = torchvision.transforms.Compose([torchvision.transforms.ToTensor()])
fashion_train_transformed = torchvision.datasets.FashionMNIST(root='./fashion', train=True, download=True, transform=common_transform)
fashion_test_transformed = torchvision.datasets.FashionMNIST(root='./fashion', train=False, download=True, transform=common_transform)
fashion_train_transformed[0][0].size()
이미지 데이터는 1차원 형태 입니다.
fashion_train_transformed.classes
타겟 값의 종류는 총 10개 입니다.
from torchsummary import summary
imagenet_resnet18 = torchvision.models.resnet18(pretrained=True)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# torchsummary의 summary함수를 이용하면 깔끔한 출력물을 볼 수 있다.
summary(imagenet_resnet18.to(device), (3,224,224))
출력물을 보고 이 모델의 입력값과 출력값을 조정해줘야한다고 느껴집니다.
입력값은 사용 데이터가 1차원이기 때문에 3차원이 아닌 1차원을 받는 Conc2d 층을 만들어야합니다.
출력값은 데이터 라벨 종류가 총 10개이기 때문에 출력층도 1000개에서 10개로 축소해야합니다.
imagenet_resnet18.fc.parameters()
FASHION_INPUT_NUM = 1
FASHION_CLASS_NUM = 10
# 앞서 말한대로 모델 레이어 일부를 갈아낌
imagenet_resnet18.conv1 = torch.nn.Conv2d(FASHION_INPUT_NUM, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
imagenet_resnet18.fc = torch.nn.Linear(in_features=512, out_features=FASHION_CLASS_NUM, bias=True)
# 가중치 초기값 넣어주는 모습. 근거는 생략함.
torch.nn.init.xavier_uniform_(imagenet_resnet18.fc.weight)
stdv = 1. / math.sqrt(imagenet_resnet18.fc.weight.size(1))
imagenet_resnet18.fc.bias.data.uniform_(-stdv, stdv)
# 역전파로 인한 가중치 업데이트는 맨 앞과 맨 뒤만 처리해줌.
# 이를 frozen 시켰다고 말함.
for param in imagenet_resnet18.parameters():
param.requires_grad = False
for param in imagenet_resnet18.conv1.parameters():
param.requires_grad = True
for param in imagenet_resnet18.fc.parameters():
param.requires_grad = True
imagenet_resnet18
출력물을 확인하면 정상적으로 처리된 것을 알 수 있습니다.
def get_adam_by_learningrate(model, learning_rate:float):
return torch.optim.Adam(model.parameters(), lr=learning_rate)
# epoch 출력 함수
def get_epoch_by_epoch(epoch:int):
return epoch
# batchsize에 따른 데이터로더 출력 함수, train/test가 dict형태로 출력
def get_dataloaders_by_batchsize(batch_size:int):
BATCH_SIZE = batch_size
fashion_train_dataloader = torch.utils.data.DataLoader(fashion_train_transformed, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)
fashion_test_dataloader = torch.utils.data.DataLoader(fashion_test_transformed, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)
dataloaders = {
"train" : fashion_train_dataloader,
"test" : fashion_test_dataloader
}
return dataloaders
# 하이퍼파라미터 랜덤서칭, tune 이용함.
config_space = {
"NUM_EPOCH" : tune.choice([4,5,6,7,8,9]),
"LearningRate" : tune.uniform(0.0001, 0.001),
"BatchSize" : tune.choice([32,64,128]),
}
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
loss_fn = torch.nn.CrossEntropyLoss()
def training(config):
imagenet_resnet18.to(device)
# 함수 입력값 config를 통해 하이퍼파라미터 입력하기.
# 에포크수, 배치사이즈, 러닝레이트 3개 요소
NUM_EPOCH = get_epoch_by_epoch(config["NUM_EPOCH"])
dataloaders = get_dataloaders_by_batchsize(config["BatchSize"])
optimizer = get_adam_by_learningrate(imagenet_resnet18, config["LearningRate"])
# 학습시작
best_test_accuracy = 0.
best_test_loss = 9999.
for epoch in range(NUM_EPOCH):
for phase in ["train", "test"]:
running_loss = 0.
running_acc = 0.
if phase == "train":
imagenet_resnet18.train() # 모델을 train mode로.
elif phase == "test":
imagenet_resnet18.eval() # 모델을 test mode로.
pbar = tqdm(dataloaders[phase]) # tqdm 출력물을 보다 다양하게
for ind, (images, labels) in enumerate(pbar):
# 데이터를 GPU에 실고
images = images.to(device)
labels = labels.to(device)
# 1) 옵티마이저 가중치 초기화
optimizer.zero_grad()
## train 모드일 시에는 gradient를 계산하고, 아닐 때는 gradient를 계산하지 않아 연산량 최소화
with torch.set_grad_enabled(phase == "train"):
# 2) 모델에 입력값을 넣어 아웃풋 배출
logits = imagenet_resnet18(images)
_, preds = torch.max(logits, 1)
# 3) 아웃풋 값을 loss 함수에 입력해 loss 값 출력
loss = loss_fn(logits, labels)
if phase == "train":
# 4) loss 값을 이용해 역전파 gradient 값을 구함
loss.backward()
# 5) 계산된 gradient를 가지고 옵티마이저 이용해 모델 업데이트
optimizer.step()
# 한 Batch에서의 loss 값 저장
running_loss += loss.item() * images.size(0)
# 한 Batch에서의 Accuracy 값 저장
running_acc += torch.sum(preds == labels.data)
accs = torch.sum(preds == labels.data) / config["BatchSize"]
pbar.set_description(f"epoch : {epoch}, running_loss : {loss.item() * images.size(0)}, running_acc : {accs}")
# 한 epoch이 모두 종료되었을 때
epoch_loss = running_loss / len(dataloaders[phase].dataset)
epoch_acc = running_acc / len(dataloaders[phase].dataset)
# phase가 test일 때, best accuracy/loss 계산
if phase == "test" and best_test_accuracy < epoch_acc:
best_test_accuracy = epoch_acc
if phase == "test" and best_test_loss > epoch_loss:
best_test_loss = epoch_loss
# tune을 사용해 accuracy, loss 값 기록
tune.report(accuracy=best_test_accuracy.item(), loss=best_test_loss)
from ray.tune.suggest.hyperopt import HyperOptSearch
optim = HyperOptSearch( # HyperOptSearch 통해 Search를 진행합니다. 더 다양한 Optimizer들은 https://docs.ray.io/en/master/tune/api_docs/suggestion.html#bayesopt 문서를 참고해주세요
metric='accuracy', # hyper parameter tuning 시 최적화할 metric을 결정합니다. 본 실험은 test accuracy를 target으로 합니다
mode="max", # target objective를 maximize 하는 것을 목표로 설정합니다
)
from ray.tune import CLIReporter
import ray
# 실험을 최대 수행할 횟수
NUM_TRIAL = 10
# 중간 수행 결과 출력
reporter = CLIReporter(
parameter_columns=["NUM_EPOCH", "LearningRate", "BatchSize"],
metric_columns=["accuracy", "loss"])
# ray 초기화 후 실행
ray.shutdown()
analysis = tune.run(
training, # 전체과정 수행 함수
config=config_space, # 함수에 입력하는 값. 딕셔너리 형태
search_alg=optim, # HyperOptSearch 형태로 목푯값 정의
#verbose=1,
progress_reporter=reporter, # 중간 수행 결과 출력
num_samples=NUM_TRIAL,
resources_per_trial={'gpu': 1}
best_trial = analysis.get_best_trial('accuracy', 'max')
print(f"최고 성능 config : {best_trial.config}")
print(f"최고 test accuracy : {best_trial.last_result['accuracy']}")
이번주 배운 내용을 전반적으로 정리할 수 있어서 좋았습니다.
구성도 좋고 설명도 친절한 심화과제 덕분에 더 쉽게 배울 수 있었던 것 같습니다.