PyTorch Lightning을 활용한 전이 학습
이 글에서는 이전 기사에서 다룬 이미지 분류 예제를 바탕으로, PyTorch Lightning을 활용한 전이 학습을 간단히 소개합니다. 이 글은 AI 번역본입니다. 오역이 있을 수 있으니 댓글로 알려주세요.
Created on September 15|Last edited on September 15
Comment
에서 이전 기사, 우리는 이미지 분류 파이프라인을 구축했으며 PyTorch Lightning이 글에서는 파이프라인을 확장하여 PyTorch Lightning으로 전이 학습을 수행해 보겠습니다.
목차
소개
전이 학습은 모델이 “작업” A를 학습하면서 획득한 지식을 “작업” B에 활용하는 기법입니다. 여기서 A와 B는 동일한 딥러닝 과제이지만 서로 다른 데이터셋에서 수행될 수 있습니다.
왜?
- 딥 뉴럴 네트워크의 초기 몇 개 은닉층은 데이터셋에 대한 일반적이고 추상적인 특징을 학습합니다. 반면에 이후의 층들은 작업에 특화된 지식을 담습니다. 이렇게 학습된 특징들은 가중치 초기화에 매우 유리합니다.
- 신경망을 처음부터 학습시키는 것은 비용이 많이 들 수 있습니다. 전이 학습은 학습 시간은 줄이고 모델 정확도는 높여 지금까지 매우 효과적으로 작동해 왔습니다.
- 대부분의 경우 학습에 사용할 수 있는 샘플 수가 많지 않습니다. 데이터 증강을 해도 정확도를 끌어올릴 수 있는 데에는 한계가 있습니다. 이럴 때 전이 학습이 큰 도움이 됩니다.
어떻게?
딥러닝에서 전이 학습을 활용하는 가장 일반적인 워크플로는 다음과 같습니다:
- 이전에 학습된 모델의 층을 가져와 사용합니다. 일반적으로 이러한 모델은 대규모 데이터셋으로 학습되어 있습니다.
- 향후 학습 과정에서 기존에 담긴 정보를 훼손하지 않도록 해당 층들을 동결하세요.
- 동결된 층 위에 새로운 학습 가능한 층을 몇 개 추가하세요. 이 새로운 층들은 작업에 특화된 특징을 학습하게 됩니다. 새로 추가한 층을 당신의 데이터셋으로 학습하세요.
- 하나의 선택 사항 단계는 파인튜닝즉, 앞에서 얻은 전체 모델의 동결을 해제하고 매우 낮은 학습률로 새로운 데이터에 다시 학습시키는 것을 말합니다. 전체 모델을 한꺼번에 해제하지 않고, 일부만 단계적으로(몇 개 층을 먼저 해제해 학습한 뒤 계속 확장하는 방식으로) 해제할 수도 있습니다.
이 보고서를 이해하려면 이미지 분류 작업을 위한 PyTorch Lightning에 대한 어느 정도의 사전 지식이 필요합니다. 제 이전 게시물을 참고하실 수 있습니다 PyTorch Lightning을 활용한 이미지 분류 시작해 봅시다. 전이 학습을 적용한 경우와 적용하지 않은 경우로 모델을 학습해 보겠습니다. Stanford Cars 데이터셋에서 학습한 뒤 결과를 비교해 보겠습니다 가중치와 편향.
데이터셋
이미지 분류기를 학습하기 위해 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_dirself.batch_size = batch_size# Augmentation policy for training setself.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 = 196def prepare_data(self):passdef setup(self, stage=None):# build datasetdataset = StanfordCars(root=self.data_dir, download=True, split="train")# split datasetself.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.augmentationself.val.dataset.transform = self.transformself.test.dataset.transform = self.transformdef 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 - 시스템 정의
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 hyperparametersself.save_hyperparameters()self.learning_rate = learning_rateself.dim = input_shapeself.num_classes = num_classes# transfer learning if pretrained=Trueself.feature_extractor = models.resnet18(pretrained=transfer)if transfer:# layers are frozen by using eval()self.feature_extractor.eval()# freeze paramsfor param in self.feature_extractor.parameters():param.requires_grad = Falsen_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 = 1tmp_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 blockdef _forward_features(self, x):x = self.feature_extractor(x)return x# will be used during inferencedef forward(self, x):x = self._forward_features(x)x = x.view(x.size(0), -1)x = self.classifier(x)return xdef 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 lossdef 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 lossdef 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 = gtsself.test_output = outputdef 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%입니다.
결론 및 참고 자료
이 보고서가 도움이 되었길 바랍니다. 직접 코드를 실행해 보면서, 원하는 데이터셋으로 처음부터 학습하는 방식과 transfer learning을 각각 활용해 이미지 분류기를 학습해 보시길 권합니다.
전이 학습에 대해 더 알아보려면 다음 자료를 참고하세요:
Add a comment
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
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

