Skip to main content

PyTorch Lightning을 활용한 전이 학습

이 글에서는 이전 글의 이미지 분류 예제를 바탕으로 PyTorch Lightning을 활용한 전이 학습을 간략히 소개합니다. 이 글은 AI 번역본입니다. 오역이 있을 수 있으니 댓글로 알려 주세요.
Created on September 15|Last edited on September 15



에서 이전 글에서는 이미지 분류 파이프라인을 구축했고, 이는 PyTorch Lightning. 이 글에서는 PyTorch Lightning으로 전이 학습을 수행하도록 파이프라인을 확장하겠습니다.

목차



소개

전이 학습은 모델을 ‘작업’ A로 학습하면서 얻은 지식을 ‘작업’ B에 활용하는 기법입니다. 여기서 A와 B는 동일한 딥러닝 작업이더라도 서로 다른 데이터셋에서 수행될 수 있습니다.

왜?

  • 딥 뉴럴 네트워크의 초기 몇 개 은닉층은 데이터셋에 대한 일반적이고 추상적인 특징을 학습합니다. 이후의 층들은 작업 특화 지식을 담습니다. 이렇게 학습된 특징들은 가중치 초기화로 매우 유용합니다.
  • 신경망을 처음부터 학습시키는 것은 비용이 많이 듭니다. 전이 학습은 학습 시간을 줄이고 모델의 정확도를 높여, 지금까지 매우 효과적으로 작동해 왔습니다.
  • 보통은 학습에 사용할 표본이 많지 않습니다. 데이터 증강을 적용해도 정확도 향상에는 한계가 있습니다. 이럴 때 전이 학습이 해결책이 될 수 있습니다.

방법?

딥러닝에서 전이 학습을 활용할 때 가장 일반적인 워크플로는 다음과 같습니다:
  • 이전에 학습된 모델에서 몇 개의 층을 가져옵니다. 보통 이런 모델은 대규모 데이터셋으로 학습되어 있습니다.
  • 향후 학습 과정에서 포함된 정보를 훼손하지 않도록 해당 층들을 고정하세요.
  • 고정된 층 위에 학습 가능한 새 층을 몇 개 추가하세요. 이 새 층들은 작업에 특화된 특징을 학습합니다. 당신의 데이터셋으로 이 새 층들을 학습시키세요.
  • 선택 사항 단계는 파인튜닝이는 위에서 얻은 전체 모델의 동결을 해제하고, 매우 낮은 학습률로 새로운 데이터에 다시 학습시키는 과정을 의미합니다. 전체 모델은 부분적으로, 또는 일부만 순차적으로 동결 해제할 수 있습니다(몇 개 층의 동결을 해제해 학습한 뒤 계속 진행하는 방식).
이 보고서를 이해하려면 이미지 분류 작업을 위한 PyTorch Lightning에 어느 정도 익숙해야 합니다. 이전 글에서 자세히 다뤘으니 참고하세요 PyTorch Lightning을 활용한 이미지 분류 시작해 봅시다. 먼저 전이 학습을 적용한 경우와 적용하지 않은 경우로 나누어 모델을 학습해 보겠습니다. 스탠퍼드 카즈 데이터셋에서 결과를 비교하며 가중치와 편향.

데이터셋

우리의 이미지 분류기를 학습시키기 위해 Stanford Cars 데이터셋을 사용하겠습니다. Cars 데이터셋에는 196개 자동차 클래스에 걸친 이미지 16,185장이 포함되어 있습니다. 데이터는 학습용 8,144장과 테스트용 8,041장으로 나뉘며, 각 클래스는 대략 50 대 50 비율로 분할되어 있습니다. 클래스는 보통 제조사, 모델, 연식 수준으로 구성되며, 예를 들어 2012 Tesla Model S 또는 2012 BMW M3 쿠페와 같습니다.
PyTorch Lightning의 DataModule을 사용하면 다운로드 로직, 전처리 단계, 증강 정책 등을 하나의 클래스에서 정의할 수 있습니다. 이렇게 하면 데이터 파이프라인을 공유 가능하고 재사용 가능한 하나의 클래스로 구성할 수 있습니다. DataModule에 대해 더 알아보기 여기.
class StanfordCarsDataModule(pl.LightningDataModule):
def __init__(self, batch_size, data_dir: str = './'):
super().__init__()
self.data_dir = data_dir
self.batch_size = batch_size

# Augmentation policy for training set
self.augmentation = transforms.Compose([
transforms.RandomResizedCrop(size=256, scale=(0.8, 1.0)),
transforms.RandomRotation(degrees=15),
transforms.RandomHorizontalFlip(),
transforms.CenterCrop(size=224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])
])
# Preprocessing steps applied to validation and test set.
self.transform = transforms.Compose([
transforms.Resize(size=256),
transforms.CenterCrop(size=224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])
])
self.num_classes = 196

def prepare_data(self):
pass

def setup(self, stage=None):
# build dataset
dataset = StanfordCars(root=self.data_dir, download=True, split="train")
# split dataset
self.train, self.val = random_split(dataset, [6500, 1644])

self.test = StanfordCars(root=self.data_dir, download=True, split="test")
self.test = random_split(self.test, [len(self.test)])[0]

self.train.dataset.transform = self.augmentation
self.val.dataset.transform = self.transform
self.test.dataset.transform = self.transform
def train_dataloader(self):
return DataLoader(self.train, batch_size=self.batch_size, shuffle=True, num_workers=8)

def val_dataloader(self):
return DataLoader(self.val, batch_size=self.batch_size, num_workers=8)

def test_dataloader(self):
return DataLoader(self.test, batch_size=self.batch_size, num_workers=8)


LightningModule - 시스템 정의

LightningModule은 모델 정의, 학습, 검증, 테스트 코드를 한곳에 구성하는 또 다른 클래스입니다. 이에 대해 더 알아보기 여기.
PyTorch Lightning에서 전이 학습을 어떻게 활용하는지 확인하기 위해 모델 정의를 살펴보겠습니다.
에서 LitModel 클래스에서 Torchvision이 제공하는 사전 학습 모델을 특성 추출기로 사용해 분류 모델을 구성할 수 있습니다. 여기서는 ResNet-18을 사용합니다. PyTorch Lightning에서 제공하는 사전 학습 모델 목록은 다음에서 확인할 수 있습니다 여기.
  • ~할 때 pretrained=True의 경우 사전 학습된 가중치를 사용하고, 그렇지 않으면 가중치는 무작위로 초기화됩니다.
  • 만약 .eval() 를 사용하면 레이어가 고정됩니다.
  • 단일 Linear 레이어가 출력 레이어로 사용됩니다. 여러 개의 레이어를 그 위에 쌓을 수도 있습니다. feature_extractor.
class LitModel(pl.LightningModule):
def __init__(self, input_shape, num_classes, learning_rate=2e-4, transfer=False):
super().__init__()
# log hyperparameters
self.save_hyperparameters()
self.learning_rate = learning_rate
self.dim = input_shape
self.num_classes = num_classes
# transfer learning if pretrained=True
self.feature_extractor = models.resnet18(pretrained=transfer)

if transfer:
# layers are frozen by using eval()
self.feature_extractor.eval()
# freeze params
for param in self.feature_extractor.parameters():
param.requires_grad = False
n_sizes = self._get_conv_output(input_shape)

self.classifier = nn.Linear(n_sizes, num_classes)

self.criterion = nn.CrossEntropyLoss()
self.accuracy = Accuracy()
# returns the size of the output tensor going into the Linear layer from the conv block.
def _get_conv_output(self, shape):
batch_size = 1
tmp_input = torch.autograd.Variable(torch.rand(batch_size, *shape))

output_feat = self._forward_features(tmp_input)
n_size = output_feat.data.view(batch_size, -1).size(1)
return n_size
# returns the feature tensor from the conv block
def _forward_features(self, x):
x = self.feature_extractor(x)
return x
# will be used during inference
def forward(self, x):
x = self._forward_features(x)
x = x.view(x.size(0), -1)
x = self.classifier(x)
return x
def training_step(self, batch):
batch, gt = batch[0], batch[1]
out = self.forward(batch)
loss = self.criterion(out, gt)

acc = self.accuracy(out, gt)

self.log("train/loss", loss)
self.log("train/acc", acc)

return loss
def validation_step(self, batch, batch_idx):
batch, gt = batch[0], batch[1]
out = self.forward(batch)
loss = self.criterion(out, gt)

self.log("val/loss", loss)

acc = self.accuracy(out, gt)
self.log("val/acc", acc)

return loss
def test_step(self, batch, batch_idx):
batch, gt = batch[0], batch[1]
out = self.forward(batch)
loss = self.criterion(out, gt)
return {"loss": loss, "outputs": out, "gt": gt}
def test_epoch_end(self, outputs):
loss = torch.stack([x['loss'] for x in outputs]).mean()
output = torch.cat([x['outputs'] for x in outputs], dim=0)
gts = torch.cat([x['gt'] for x in outputs], dim=0)
self.log("test/loss", loss)
acc = self.accuracy(output, gts)
self.log("test/acc", acc)
self.test_gts = gts
self.test_output = output
def configure_optimizers(self):
return torch.optim.Adam(self.parameters(), lr=self.learning_rate) .
.
초기화하는 동안 LitModule 통과 transfer=True ResNet-18 백본을 고정하고, 전이 학습을 위해 사전 학습된 가중치를 사용합니다.

결과




정의한 모델을 처음부터 학습한 경우(ResNet-18 백본을 처음부터 학습)와 전이 학습을 적용한 경우(ImageNet으로 사전 학습된 ResNet-18) 모두 실험했습니다. 그 외 하이퍼파라미터는 모두 동일하게 유지했습니다. 결과 재현을 위해 Colab 노트북을 확인하세요.
처음부터 학습하려면 다음을 전달해야 합니다 pretrained 인수로서 False 그리고 해당 줄을 주석 처리하세요 self.feature_extractor.eval().
지표를 비교해 보며 전이 학습의 마법.
  • 전이 학습으로 학습한 모델이 TransferLearning) 처음부터 학습한 모델보다 훨씬 더 좋은 성능을 보입니다( FromScratch ).
  • 테스트 정확도는 TransferLearning 약 32%이며, …의 경우에는 FromScratch 약 16%입니다.

결론 및 참고 자료

이 보고서가 도움이 되었길 바랍니다. 여러분이 코드를 직접 실험해 보고, 원하는 데이터셋으로 처음부터 학습하는 방법과 전이 학습을 활용하는 방법 모두로 이미지 분류기를 학습해 보시길 권합니다.
전이 학습에 대해 더 알아보려면 다음 자료를 참고하세요:

이 글은 AI로 번역된 기사입니다. 오역이 의심되는 부분이 있으면 댓글로 알려주세요. 원문 보고서는 다음 링크에서 확인하실 수 있습니다: 원문 보고서 보기
Sumeet Singh
Sumeet Singh •  
Hi Ayush, Regarding the following snippet: # layers are frozen by using eval() self.feature_extractor.eval() In addition to calling .eval(), don't you also need to set requires_grad=False on all self.feature_extractor.parameters()?
1 reply
Sam Jackson
Sam Jackson •  
Thanks for this easy to follow through report Ayush. Transfer Learning is a crucial step in many machine learning workflow. PyTorch Lightning in my limited experience is the new Keras.
1 reply