HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
장지원 페이지/
📝
AI Advanced
/
[Adapter]

[Adapter]

파일과 미디어
간단한 설명
PEFT fine-tuning
태그
Transfer L
📌
Reference
[Article]
arxiv.org
arxiv.org

arxiv.org

전생했더니 인공지능이었던 건에 대하여전생했더니 인공지능이었던 건에 대하여[논문리뷰] Vision Transformer Adapter for Dense Predictions (ViT-Adapter)
[논문리뷰] Vision Transformer Adapter for Dense Predictions (ViT-Adapter)

[논문리뷰] Vision Transformer Adapter for Dense Predictions (ViT-Adapter)

ViT-Adapter 논문 리뷰 (ICLR 2023)

전생했더니 인공지능이었던 건에 대하여전생했더니 인공지능이었던 건에 대하여
[Blog]
TISTORYTISTORYLoRA (Low-Rank Adaptation of Large Language Models)
LoRA  (Low-Rank Adaptation of Large Language Models)

LoRA (Low-Rank Adaptation of Large Language Models)

Previous 연구 정리 unipelt : a unified framework for parameter-efficient language model tuning 소개영상에서 가져옴. (UNIPELT 는 일단정리안함) 출처 : https://www.youtube.com/watch?v=Cmtvh_2MtGg&t=1612s pelt : adapter, LoRA, prefix learning = language modeling 이 어떻게 효율적으로 fine-tuning 하는가에 대해 해결하기 위해 trainable parameter 수를 (많이) 줄이는 방법론. PELT 는 위 3가지 방법 (Adapter, LoRA, Prefix Learning) 을 모두 사용하는 방법이다. 이 방법들은 항상 pre-train..

TISTORYTISTORY
[Adapter] Parameter-Efficient Transfer Learning for NLP
[Adapter] Parameter-Efficient Transfer Learning for NLP

[Adapter] Parameter-Efficient Transfer Learning for NLP

Parameter-Efficient Transfer Learning for NLP, ICML 2019

[Paper Review] Parameter-Efficient Transfer Learning for NLP
[Paper Review] Parameter-Efficient Transfer Learning for NLP

[Paper Review] Parameter-Efficient Transfer Learning for NLP

논문 링크 : https://arxiv.org/pdf/1902.00751.pdf ICML 2019 첫번째 논문 리뷰이다. Abstract 현재 Downstream tasks들을 풀기 위해서 Fine-tuning 을 사용하면, parameter inefficient

[Code]

[Adapter]

What
FineTuning의 비효율성 문제를 해결하기 위해 나온 메서드
(파인튜닝은 기존에 task를 위한 layer를 추가하고, 전체를 모두 학습 시켜야 한다 → 물론 다수의 파라미터를 frozen 시키고, 일부 파라미터만 업데이트 할 수 있음!!)
작은 모델을 원래 모델에 삽입하고 학습시킨다. → 기존 layer 위에 adapter layer를 삽입하고, 이만 학습 시킨다.
밑 그림의 왼쪽처럼 Adapter layer를 삽입한다.
⇒ 즉 기존의 파라미터는 아예 건드리지 말고 학습시킨다.
notion image
위 그림에서 처럼 각 sub layer skip-connection 전의(skip-connection은 중간 과정을 생략해서 연결하는 것을 의미한다, 보라색 선) 윗 단에 adapter를 삽입한다.
notion image
details
각 layer 당 추가되는 파라미터는 2md+d+m이다. (d: 원래의 차원, m: adapter의 차원)
첫 번째 adapter: d→m ⇒ dm + m만큼의 편향(bias) 추가 (down projection)
두 번째 adapter: m→d ⇒ dm + d만큼의 편향(bias) 추가 (up projection)
전체의 3.6%라고 한다..
Main feature
  • 적은 파라미터
  • near identity 초기화: 입력을 그대로 출력한다 → 원래 모델을 손상하지 않으려면 이 전략을 활용할 수 있다 (skip-connection을 이용하는 방법이지 않을까?)
++
  1. 훈련중에, 어댑터들은 전체 네트워크의 활성화 분포를 변경하려 할 수 있다.
  1. 어댑터 모듈은 필요하지 않으면 무시될 수 있다.
  1. 몇가지 어댑터는 다른 어댑터보다 더 네트워크에 영향을 줄 수 있다.
  1. 초기화가 Identity function와 멀리 떨어진다면 학습이 실패할 수 있다.
++
  1. 데이터 셋 마다 최적 어댑터의 크기가 달랐다.
  1. MNLI에는 256, RTE에서는 8이 선택되었다.
  1. 항상 크기를 64로 선택하면 정확도가 79.6으로 줄어들었다.
LoRA와 Adapter의 차이점?? → LoRA Adapter 인 듯!?
LoRA는 Adapter의 일종 같다.
LoRA는 row-rank param이 Adapter를 구성하는 Adapter의 한 형태!?
LoRA와 Adapter의 차이는 arch(아키텍쳐)의 위치 차이이다.
notion image
Adapter in ViT
ViT를 위한 Adapter를 찾기 위한 논문
notion image
 

추가로 안 것들
Transfer L은 두 가지로 나눌 수 있다.
머 아닐수도 있고…
Feature-Based
모델 그 자체를 활용한다. 학습이나 inference 시에 추가 파라미터 학습이 없다.
Fine-Tuning
사전 모델을 기반으로 다른 task에서 성능을 내고자 할 때 사용한다.

Code
import torch import torch.nn as nn import torch.optim as optim import torchvision.transforms as transforms import torchvision.datasets as datasets from torch.utils.data import Subset, DataLoader from torch.utils.data.dataset import Dataset from PIL import Image import timm # ViT 모델 로드 def load_vit_model(): model = timm.create_model('vit_base_patch16_224', pretrained=True) preprocess = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5)), ]) return model, preprocess # Adapter 모듈 정의 class Adapter(nn.Module): def __init__(self, in_features, bottleneck_size=64): super(Adapter, self).__init__() self.down_project = nn.Linear(in_features, bottleneck_size) self.relu = nn.ReLU() self.up_project = nn.Linear(bottleneck_size, in_features) def forward(self, x): x = self.down_project(x) x = self.relu(x) x = self.up_project(x) return x # Filtered CIFAR-10 데이터셋 정의 class FilteredCIFAR10(Dataset): def __init__(self, root, train=True, transform=None, download=False): self.cifar10 = datasets.CIFAR10(root=root, train=train, transform=transform, download=download) self.data = [] self.targets = [] for img, target in zip(self.cifar10.data, self.cifar10.targets): if target < 8: # Only keep classes 0-7 self.data.append(img) self.targets.append(target) def __len__(self): return len(self.data) def __getitem__(self, idx): img, target = self.data[idx], self.targets[idx] img = Image.fromarray(img) if self.cifar10.transform: img = self.cifar10.transform(img) return img, target # ViT 미세 조정 모델 정의 (Adapter 포함) class ViTFineTuningWithAdapter(nn.Module): def __init__(self, base_model, num_classes): super(ViTFineTuningWithAdapter, self).__init__() self.base_model = base_model self.adapters = nn.ModuleList([Adapter(base_model.blocks[i].mlp.fc1.in_features) for i in range(len(base_model.blocks))]) self.fc = nn.Linear(self.base_model.head.in_features, num_classes) self.base_model.head = self.fc # 모델의 헤드 부분을 새로운 FC 레이어로 교체 def forward(self, x): x = self.base_model.patch_embed(x) cls_tokens = self.base_model.cls_token.expand(x.shape[0], -1, -1) x = torch.cat((cls_tokens, x), dim=1) x = self.base_model.pos_drop(x + self.base_model.pos_embed) for i, blk in enumerate(self.base_model.blocks): x = blk(x) + self.adapters[i](x) x = self.base_model.norm(x) x = x[:, 0] # 첫 번째 클래스 토큰만 사용 x = self.fc(x) return x # GPU 사용 설정 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # ViT 모델과 전처리 함수 로드 base_model, preprocess = load_vit_model() model = ViTFineTuningWithAdapter(base_model, num_classes=8).to(device) # num_classes 변경 # 기본 모델의 모든 파라미터 고정 for name, param in model.base_model.named_parameters(): param.requires_grad = False # Adapter와 FC 레이어만 학습 가능하게 설정 for name, param in model.named_parameters(): if 'adapters' in name or 'fc' in name: param.requires_grad = True criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(filter(lambda p: p.requires_grad, model.parameters()), lr=0.01, momentum=0.9) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1) # 학습률 스케줄러 추가 # 전처리 함수로 데이터 로딩 train_dataset = FilteredCIFAR10(root='./data', train=True, download=True, transform=preprocess) test_dataset = FilteredCIFAR10(root='./data', train=False, download=True, transform=preprocess) # 훈련 및 테스트 데이터셋에서 100개의 샘플 선택 train_subset = Subset(train_dataset, range(100)) test_subset = Subset(test_dataset, range(100)) # 데이터 로더 train_loader = DataLoader(train_subset, batch_size=64, shuffle=True, num_workers=2) test_loader = DataLoader(test_subset, batch_size=64, shuffle=False, num_workers=2) # 훈련 루프 (간단히) num_epochs = 20 for epoch in range(num_epochs): model.train() for images, labels in train_loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() scheduler.step() # 학습률 스케줄러 스텝 print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}') # 에포크마다 정확도 계산 model.eval() with torch.no_grad(): correct = 0 total = 0 for images, labels in test_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() accuracy = 100 * correct / total print(f'Accuracy after epoch {epoch+1}: {accuracy:.2f} %') print('Finished Training') # 최종 정확도 model.eval() with torch.no_grad(): correct = 0 total = 0 for images, labels in test_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print(f'Final Test Accuracy: {100 * correct / total:.2f} %')
수정한 코드
import torch import torch.nn as nn import timm import torchvision.transforms as transforms # ViT 모델 로드 def load_vit_model(): model = timm.create_model('vit_base_patch16_224', pretrained=True) preprocess = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5)), ]) return model, preprocess # Adapter 모듈 정의 (논문 기반) class Adapter(nn.Module): def __init__(self, in_features, bottleneck_size=64): super(Adapter, self).__init__() self.down_project = nn.Linear(in_features, bottleneck_size) self.relu = nn.ReLU() self.up_project = nn.Linear(bottleneck_size, in_features) self.layernorm = nn.LayerNorm(in_features) # 논문에서는 LayerNorm을 사용하기도 함 def forward(self, x): # 원본 특징을 보존하기 위해 잔차 연결 (Residual Connection) 추가 residual = x x = self.layernorm(x) x = self.down_project(x) # 차원 축소 x = self.relu(x) # 비선형성 추가 x = self.up_project(x) # 차원 복원 return x + residual # 잔차 연결 # ViT 미세 조정 모델 정의 (Adapter 포함) class ViTFineTuningWithAdapter(nn.Module): def __init__(self, base_model, num_classes): super(ViTFineTuningWithAdapter, self).__init__() self.base_model = base_model # 각 Transformer 블록에 Adapter 삽입 self.adapters = nn.ModuleList([ Adapter(base_model.blocks[i].mlp.fc1.in_features) for i in range(len(base_model.blocks)) ]) self.fc = nn.Linear(self.base_model.head.in_features, num_classes) self.base_model.head = self.fc # 모델의 헤드 부분을 새로운 FC 레이어로 교체 def forward(self, x): x = self.base_model.patch_embed(x) cls_tokens = self.base_model.cls_token.expand(x.shape[0], -1, -1) x = torch.cat((cls_tokens, x), dim=1) x = self.base_model.pos_drop(x + self.base_model.pos_embed) for i, blk in enumerate(self.base_model.blocks): x = blk(x) x = self.adapters[i](x) # 각 Transformer 블록 뒤에 Adapter 적용 x = self.base_model.norm(x) x = x[:, 0] # 첫 번째 클래스 토큰만 사용 x = self.fc(x) return x # 나머지 코드 (데이터 로딩, 훈련 루프 등)는 동일하게 유지
LoRA, Adapter 모두 중간에 코드가 터지는데, 일반 FL에서 block 2개 train 시키는 거 보다, LoRA, Adapter가 더 메모리를 많이 쓰는 것일까?