Github 구현 코드 먼저 보기 : https://github.com/WontaeKim89/embedding_SimCSE_LoRA
오늘은 LoRA를 이용해서 Embedding Model을 학습하는 과정을 정리해보려 한다.
구현코드 링크를 위에 첨부해두었는데,
먼저 내용 이해에 도움이 되는 내용들을 아래에 정리해두었으니, 포스팅 내용을 쭉 읽어보신 후,
위 링크를 통해 구현 코드를 살펴보시는걸 권장드린다.
임베딩 모델은 추천시스템, 검색, 챗봇, 번역, 감정분석, Text Summarization등
거의 모든 NLP 분야에서 사용되는 매우 중요한 언어모델 분야중 하나이다.
최근 워낙 생성 모델쪽으로 관심이 집중되어있고, 관련 연구도 훨씬 활발하긴 하지만, 임베딩 모델도 꾸준히 발전하고 있다.
임베딩 모델은 아직까지는 BERT계열이 많이 사용되고있는듯 하나, 최근 LLM을 활용한 임베딩 모델인 LLM2Vec와 같이,
디코더 기반의 모델을 활용한 임베딩 모델이 SOTA 리더보드 상단을 차지하고 있다.
다만, 이러한 LLM기반의 임베딩 모델은 LLM의 특성 상, dimension이 4,096이상을 가지기때문에,
모델 운영상의 리소스나 레이턴시 관점에서 아직은 조금 부담이 될수도 있기때문에 768차원의 비교적 가벼운 임베딩으로
좋은 성능을 보이는 BERT, RoBERTa와 같은 모델들이 여전히 많이 사용된다.
학습 목표
우선 이번에 내가 Fine-Tuning을 통해 얻고자하는것을 간단히 정리하면 아래와 같다.
<현재 상태>
- 서비스중인 챗봇에서는 Function Call과 RAG를 통해 답변의 품질을 향상시키고 있음.
- 사용자의 질문에 대한 적절한 API를 찾는 Function Call Retrieval과정에서 임베딩 모델을 통해 유사도 기반 Top10 API를 LLM에게 추천함
- LLM은 추천된 모델의 Description을 통해 사용할 API를 선택하여, 호출하게 됨.
<해결 과제>
- Top10 API의 Description을 LLM에게 input하는 과정에서 많은 token비용이 소모됨
- Top5의 API만 높은 정확도로 추천하여, LLM input시 발생하는 Token비용을 절감하자!
- 등록된 API가 많아지게 되면서, 도메인 언어의 미묘한 차이를 임베딩 모델이 구분하지 못하고, 유사 API들을 혼동하는 경우가 많아짐
- 도메인 텍스트에 대한 Fine-Tuning을 통해, 모델의 도메인 텍스트에 대한 임베딩 성능을 높이자!
학습데이터
1) 유저들이 입력한 챗봇 질문 데이터(회계/세법 관련 질문) - Unlabeled data
2) API 설명데이터 (API의 기능을 설명한 Description) - Unlabeled data
> 1+2 도합 약 42만건의 문장 데이터
3) API 설명 + 사용자 질문 Pair set - Labeled data
> 3번 : 약 4,600건의 pair set 데이터
학습 모델
나는 이번에 한국어 Pre-trained Embedding Model을 특정 도메인(회계/세법)을 더 잘표현할 수 있는 임베딩 모델로
Fine-Tuning을 진행하려한다.
학습할 모델은 RoBERTa기반의 pre-trained model을 선택했다.
해당 모델은 아래를 참고하기 바란다.
https://huggingface.co/BM-K/KoSimCSE-roberta-multitask
위 모델을 선택한 이유는, 최근 좋은 한국어 pre-trained embedding model을 찾기위해 서칭하던 중,
허깅페이스에 공개된 몇가지 임베딩 모델과 OpenAI의 임베딩 모델(text-embedding-3-large API)들을
비교 테스트한적이 있었는데, 회계/세법 도메인에서는 비교적 위 모델이 가장 좋은 성능을 보였다.
모델의 baseline성능이 좋을 경우, Fine-Tuning결과도 좀 더 기대해봄직하기때문에 위 모델을 선택하게 되었다.
학습 방식
1) SimCSE
학습방식으로는 contrastive learning 방법중 하나인 SimCSE 방식으로 학습을 진행하기로 했다.
이번 학습에 사용될 데이터로 회계/세법데이터를 마련하긴했으나, pair간 관계정보가 labeling된 데이터가 소수(4,600건)있었고,
unlabeled data(단순 sentences)가 420,000건 정도 있었다.
우선 SimCSE 방식은 supervised learning방식과 unsupervised learning 방식을 모두 지원하고 있고,
간편한 Unsupervised 구현 방식에도 불구하고, 매우 높은 학습 성능을 보여주는 학습 방법이다.
SimCSE방식에서의 Unsupervise learning 방법을 간단하게 설명하자면,
먼저, 동일한 문장을 쌍으로 만들어 이를 각각 drop-out이 적용된 다른 layer에 input해서 약간의 차이를 가지는 vector를 구한다.
이렇게 만들어낸 pair를 positive pair로 간주하고, 해당 문장과 동일 batch내의 다른 문장과는 negative pair 관계로 간주하여
데이터를 구성한다. 그리고, Loss로는 contrastive learning 방식에 일반적으로 활용되는 InfoNCE Loss를 활용하여,
Positive pair간의 거리를 가깝게하고, Negative Pairs의 거리를 멀게하도록 학습하여
Vector 공간내에서 문장들의 위치를 최적화 하는 학습방식이다.
(SimCSE 방식을 아주 심플하게 설명했는데, SimCSE 방식의 특징에 대해서, 다음 기회에 포스팅 해보도록 하겠다.)
💡TIP (중요)
SimCSE 방식으로 학습할때, 데이터셋을 구성하는 팁을 한가지 설명하자면,
한개의 Batch내의 텍스트들은 서로 독립된 의미를 가지도록 최대한 shuffle을 신경써서 해야한다는 점이다!
batch내의 문장은 각각 본인문장 x 2를 positive pair,
본인문장과 다른문장을 묶어 negative pair로 구성하게 되는데,
만약 배치내에서 유사문장이 섞여있을 경우, (본인문장, 유사문장) 으로 묶인 데이터는 실제로는 의미적으로 유사한
context를 가지고 있지만, negative pair로 분류되어 vector공간에서 서로 멀어지도록 학습되기때문에,
이런 데이터가 많을 경우 학습 성능이 매우 떨어지게 된다.
따라서, batch내의 문장들은 가급적 서로 의미적으로 관계성이 낮은 문장들로 묶어줄 필요가 있다.
SimCSE는 이러한 방식으로 unsupervised learning만으로도 높은 학습 성능을 보이는 방법으로 최근에도 많이 활용되는
학습 방식이기때문에, Fine-Tuning 학습 방식으로 선택하게 되었다.
Ref. https://arxiv.org/abs/2104.08821
2) LoRA
우선, 예전에 임베딩 모델의 Full Fine-Tuning을 진행한적이 있었다.
마찬가지로 도메인 성향이 강한 텍스트를 통해 Supervised learning방식의 contrastive learning을 진행했는데,
도메인 텍스트를 제외한 일반적 텍스트의 임베딩 성능은 크게 떨어졌다.
Full Fine-Tuning을 제대로 할 경우, 모델의 모든 weight parameter를 업데이트 할 수 있기때문에 잘 학습하면,
최적의 임베딩 성능을 뽑아낼 수 있지만, 그것은 질 좋은 데이터를 균형있게 잘 구성했을때 최적화가 가능하다는 점을 명심해야한다.
Full Fine-tuning의 경우, 기존에 학습했던 데이터와 같은 일반적인 학습 데이터와 새로 학습할 데이터를 균형있게 잘 넣어주어야하고,
많은 데이터를 학습에 사용해야, 좋은 성능을 기대할 수 있다.
현재 내가 학습하고자하는 데이터셋은 강한 도메인 성향을 가진 데이터만으로 구성되어있고,
일반적 텍스트 데이터가 없기 떄문에, PEFT 방식의 학습을 진행하기로 결정했고,
그중에서도 높은 성능을 보이는 LoRA 방식으로 Fine-Tuning을 진행하려한다.
💡TIP
만약, 다양한 학습데이터를 통해 주기적 또는 다양한 데이터셋을 통해 여러번의 Fine-Tuning과정이 필요하다고 생각된다면,
LoRA의 사용을 조금 고민해 봐야한다.
LoRA는 adapter에서 설정한 rank에 따라, 차원을 compression - decompression 하는 과정을 거치게되는데,
논문에서 권장하는 rank는 4~8차원으로, 낮은 차원 공간을 가진 weight matrix에서 다양한 학습내용을 담아야한다.
이 때문에, 최초 fine-tuning에서는 높은 학습 성능을 보이지만,
반복된 fine-tuning에서는 앞전의 학습내용을 망각 하는 문제점이 발생한다.
이러한 문제 때문에, 만약 여러번의 fine-tuning을 진행해야하는 task에서는
유사한 아키텍쳐를 가지면서, LoRA의 망각 현상을 개선한 'MoRA' 학습방식을 고민해보시길 권장드린다.
만약, LoRA의 개념을 다시 한번 확인해보고자 한다면, 이전에 LoRA 포스팅한 내용을 담은, 아래 링크를 참고해보시기 바란다.
https://familia-89.tistory.com/102
학습 Process
1) LoRA Architecture
자, 이제 학습 방법론은 SimCSE와 LoRA방식을 활용하는것으로 정리했는데, 한가지 문제가 있다.
보통 LoRA를 통해 Fine-Tuning을 진행할때,
PEFT라이브러리에서 제공하는 LoraConfig라는 클래스를 이용해서 간단하게 LoRA를 설정한 후, 학습을 진행할 수 있다.
from transformers import AutoModelForSeq2SeqLM
from peft import get_peft_config, get_peft_model, LoraConfig, TaskType
model_name_or_path = "bigscience/mt0-large"
tokenizer_name_or_path = "bigscience/mt0-large"
peft_config = LoraConfig(
task_type=TaskType.SEQ_2_SEQ_LM,
inference_mode=False,
r=8,
lora_alpha=32,
lora_dropout=0.1
)
model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
"trainable params: 2359296 || all params: 1231940608 || trainable%: 0.19151053100118282"
#ref.https://github.com/huggingface/peft/tree/main
위 코드는 PEFT Github에서 제공하는 LoraConfig 를 통한 간단한 학습 예제코드이다.
학습하고자하는 모델의 성격에 따라, task_type 인자에 적절한 옵션을 선택할 수 있다.
다만, task_type인자에서 임베딩 모델에 대한 contrastive learning 옵션을 현재는 제공하고 있지 않은것으로 보인다.
따라서, 임베딩 모델 학습 시, 아직은 PEFT 라이브러리를 사용할 수 없기 때문에,
LoRA 학습 아키텍처를 직접 구성해서 사용해야한다.
아래는 LoRA 학습 아키텍처를 구현한 코드 내용이다.
import torch
from torch import nn
from transformers import RobertaModel, RobertaTokenizer
from transformers.models.roberta.modeling_roberta import RobertaSelfAttention
class LoRA(nn.Module):
def __init__(self, model, r=8, alpha=16, dropout_rate=0.1):
super(LoRA, self).__init__()
self.model = model
self.r = r
self.alpha = alpha
self.scaling = alpha / r
self.dropout_rate = dropout_rate
for param in self.model.parameters():
param.requires_grad = False
for name, module in self.model.named_modules():
if isinstance(module, RobertaSelfAttention):
self._inject_lora(module)
def _inject_lora(self, module):
for proj in ["query", "key", "value"]:
weight = getattr(module, proj).weight
bias = getattr(module, proj).bias
out_features, in_features = weight.shape
lora_A = nn.Linear(in_features, self.r, bias=False)
lora_B = nn.Linear(self.r, out_features, bias=False)
nn.init.normal_(lora_A.weight, std=0.02)
nn.init.zeros_(lora_B.weight)
lora_proj = LoRAProjection(weight, bias, lora_A, lora_B, self.scaling, dropout_rate=self.dropout_rate)
setattr(module, proj, lora_proj)
def forward(self, *args, **kwargs):
return self.model(*args, **kwargs)
class LoRAProjection(nn.Module):
def __init__(self, weight, bias, lora_A, lora_B, scaling, dropout_rate=0.0):
super(LoRAProjection, self).__init__()
self.weight = weight
self.bias = bias
self.lora_A = lora_A
self.lora_B = lora_B
self.scaling = scaling
if dropout_rate > 0.0:
self.dropout = nn.Dropout(p=dropout_rate)
else:
self.dropout = None
def forward(self, x):
lora_out = self.lora_A(x)
if self.dropout is not None:
lora_out = self.dropout(lora_out)
lora_out = self.lora_B(lora_out)
return nn.functional.linear(x, self.weight, self.bias) + self.scaling * lora_out
먼저, LoRA의 기본 원리를 간단히 되짚어보자면,
1) 기존의 pre-trained model weight parameter는 freezing한 후,
2) last_hidden_state를 가져와 별도로 구성된 lora adapter에 input한다.
3) lora adapter의 output을 기존 모델의 output값에 더해, 값을 보정한다.
정도로 요약할 수 있다.
LoRA의 동작원리를 염두에두고, 위 코드를 부분별로 확인해보자.
1) Parameter Define
def __init__(self, model, r=8, alpha=16, dropout_rate=0.1):
super(LoRA, self).__init__()
self.model = model
self.r = r
self.alpha = alpha
self.scaling = alpha / r
self.dropout_rate = dropout_rate
LoRA클래스에서는, lora학습에 사용할 몇가지 옵션들을 초기화하고있다.
먼저, loara adapter를 적용할 backbone model을 인자로 받게된다.
r인자는, adapter 크기를 결정할 rank옵션에 해당하는 값을 의미한다.
그리고, 반영정도(scale)를 조절할 alpha 옵션등을 인자로 받아 LoRA학습 프로세스로 반영할 수 있게 정의하고
마지막으로 dropout 비율을 정의하게된다.
2) weight freezing & LoRA projection 주입하기
for param in self.model.parameters():
param.requires_grad = False
for name, module in self.model.named_modules():
if isinstance(module, RobertaSelfAttention):
self._inject_lora(module)
fine-tuning의 타겟이 될 Backbone model의 weight parameter가
학습과정에서 업데이트되지 않도록 Freezing처리 한다.
그리고, 모델의 모듈을 순회하면서, self-attention 모듈에 해당할 경우, LoRA projection을 주입한다.
3) Adapter layer 정의
def _inject_lora(self, module):
for proj in ["query", "key", "value"]:
weight = getattr(module, proj).weight
bias = getattr(module, proj).bias
out_features, in_features = weight.shape
lora_A = nn.Linear(in_features, self.r, bias=False)
lora_B = nn.Linear(self.r, out_features, bias=False)
nn.init.normal_(lora_A.weight, std=0.02)
nn.init.zeros_(lora_B.weight)
lora_proj = LoRAProjection(weight, bias, lora_A, lora_B, self.scaling, dropout_rate=self.dropout_rate)
setattr(module, proj, lora_proj)
_inject_lora 매서드는 LoRAProjection을 주입하기 위한 매서드이다.
먼저, 각 프로젝션(key, query, value)의 weight를 가져온 후 정규분포로 초기화를 진행한다.
그리고, Adapter A, B에 해당하는 layer를 설정된 rank에 맞게 정의한다.
마지막으로 weight, bias, adapter 등 정의된 값을 통해 LoRAProjection 클래스를 instantiation한다.
4) LoRAProjection
class LoRAProjection(nn.Module):
def __init__(self, weight, bias, lora_A, lora_B, scaling, dropout_rate=0.0):
super(LoRAProjection, self).__init__()
self.weight = weight
self.bias = bias
self.lora_A = lora_A
self.lora_B = lora_B
self.scaling = scaling
if dropout_rate > 0.0:
self.dropout = nn.Dropout(p=dropout_rate)
else:
self.dropout = None
def forward(self, x):
lora_out = self.lora_A(x)
if self.dropout is not None:
lora_out = self.dropout(lora_out)
lora_out = self.lora_B(lora_out)
return nn.functional.linear(x, self.weight, self.bias) + self.scaling * lora_out
LoRAProjection에서는 인자로 받은 weigt, bias, adapter, dropout, scaling을 통해서, LoRA Adapter를
학습하는 부분을 담고있는데, LoRA A와 B adapter를 차례로 거친 output을
기존 weight, bias가 계산된 결과에 더하게 된다.
덧셈 방식으로 가중치를 반영하는 이유는, 기존 모델의 결과값에 '미세조정'하는 효과를 얻기 위해서 이다.
기본적으로 행렬곱이 발생할 경우, 벡터공간에서 결과값은 완전하게 다른 방향 또는 다른 위치의 정보를 갖게되는데,
덧셈방식을 적용할 경우, 약간의 위치 조정만 반영 되면서, 미세조정의 효과를 얻을 수 있게된다.
2) SimCSE Dataset
자, 다음은 SimCSE 학습을 위한 데이터셋 및 데이터로더를 구현하는 부분에 대해 정리해보도록 하자.
SimCSE 논문에 표현된 위 그림을 통해 SimCSE의 학습데이터 구성방식을 이해할 수 있다.
먼저, Unsupervised learning 쪽을 먼저 확인해보자.
SimCSE는 대표적인 contrastive learning 방식으로, 학습데이터로 구성된 pair간의 비교관계를 통해
의미관계를 학습하는 방식이다.
이때, pair로 구성된 쌍의 각 원소들은 비슷한 의미를 가지거나(positive pair), 반대의 의미를 가짐(negative pair)으로써,
문장간의 멀고 가까운 관계 정보를 통해 모델이 context를 학습하게 된다.
이때, SimCSE는 이러한 관계정보 없이, 단순 문장의 나열된 데이터셋만으로 supervised learning 방식
이상의 효과를 거두고 있는 연구이다.
만약 아래와 같은 텍스트 데이터가 있다고 가정해보자.
[
'오늘 날씨 어때?',
'지금 몇 시야?',
'근처 카페 추천해 줘.',
'오늘 일정 확인해 줘.'
...
]
위는 단순 문장의 나열일 뿐, pair 쌍 형태의 학습 정보가 존재하지 않는다.
SimCSE 방식은 여기에, 각 문장별로 동일한 문장을 이어 붙여 쌍을 생성한다.
예를들어, ('오늘 날씨 어때?', '오늘 날씨 어때?') 와 같이 같은 문장을 pair로 묶는다.
동일한 문장은 같은 Vector를 나타내는데, 어떻게 학습이되는지 궁금할 수 있는데,
drop-out을 통해 차이를 만들어 학습하게 된다.
[Positive Pair]
pair의 문장은 동일한 문장이지만, 서로 다른 layer내에서 drop-out을 적용받게되면서,
비슷하지만 약간의 차이를 가진 vector로 변환된다.
그리고, 이 pair쌍은 동일한 문장이기때문에, 학습과정에서 거리가 최소화 되도록 학습해야한다.
그래서,가까운것은 더 가깝게, 멀리있는것은 더 멀리 위치하도록 하는 특성을 가진 InfoNCE Loss를 통해,
두개 문장의 벡터상 거리를 좁히는 방식으로 학습을 진행하게 된다.
[Negative Pair]
이때, 마찬가지로 위 나열된 문장 데이터만으로 Negative pair 또한 생성해낼 수 있는데,
첫번째, 문장인 "오늘 날씨 어때?" 라는 문장과 나머지 문장들을 하나씩 돌아가며 쌍을 지어,
간단한게 negative pair를 생성할 수 있다.
다만, 데이터셋 생성과정에서 위 데이터들을 일일이 만들어줄 필요는 없다.
그 이유는 뒤의 training 과정에서 설명하도록 하겠다.
3) Trainer & & InfoNCE Loss
for cnt, batch in enumerate(iterator):
optimizer.zero_grad()
input_ids, attention_mask = batch['input_ids'].to(device), batch['attention_mask'].to(device)
outputs1 = model(input_ids, attention_mask=attention_mask, return_dict=True)
hidden_states1 = outputs1.last_hidden_state
embeddings_1 = hidden_states1[:, 0, :].to(device)
outputs2 = model(input_ids, attention_mask=attention_mask, return_dict=True)
hidden_states2 = outputs2.last_hidden_state
embeddings_2 = hidden_states2[:, 0, :].to(device)
loss = info_nce_loss(embeddings_1, embeddings_2, loss_temperature, device)
if loss<best_loss:
best_loss = loss
early_stop_num=0
early_stop_num+=1
if early_stop_num>=early_stop_limit:
break
if logging:
wandb.log({'Nce_loss[Train]': loss.item()})
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
위는 trainer.py 파일의 학습코드 일부를 발췌 했다.
위 코드를 보면 기존 pre_trained model에 batch데이터를 두번 input하여 drop-out을 각각 적용받은 embedding 추출한다.
그리고, 이 두개의 임베딩을 loss에 넣게된다.
그리고, 실제로 SimCSE 방식을 적용하는 내용은 Loss 함수 내부에 담기게 되는데, 아래 코드를 확인해보자.
def info_nce_loss(embeddings_1, embeddings_2, temperature, device):
sim_matrix = F.cosine_similarity(embeddings_1.unsqueeze(1), embeddings_2.unsqueeze(0), dim=-1) / temperature
batch_size = sim_matrix.size(0)
labels = torch.arange(batch_size).to(device)
loss_fn = torch.nn.CrossEntropyLoss()
loss = loss_fn(sim_matrix, labels)
return loss
위의 info_nce_loss의 인자로 받게되는 embedding_1, embedding_2의 경우 각각 batch_size의 길이를 가진 vector로 구성되어있다.
그리고, 두 임베딩 배열간의 유사도를 구하게 될 경우, batch_size x batch_size형태의 유사도 행렬이 산출된다.
이때, 유사도 행렬의 대각선에 해당하는 유사도들은 positive_pair간의 유사도를 나타내고 나머지 유사도들은 negative pair간의 유사도를 의미하게 된다.
구분 | A문장 | B문장 | C문장 |
A문장 | 유사도 AA (Positive Pair) | 유사도 BA (Negative Pair) | 유사도 CA (Negative Pair) |
B문장 | 유사도 AB (Negative Pair) | 유사도 BB (Positive Pair) | 유사도 CB (Negative Pair) |
C문장 | 유사도 AC (Negative Pair) | 유사도 BC (Negative Pair) | 유사도 CC (Positive Pair) |
즉, 유사도 행렬을 통해 positive pair간의 유사도와 negative pair유사도를 모두 생성할 수 있고,
이를 통해 info nce loss를 적용할 수 있게 된다.
자, 지금까지 학습코드의 주요 부분에 대해서 코드레벨로 프로세스를 확인해보았다.
다만, 위 코드대로 학습을 진행할때, 몇가지 이슈사항이 있었는데,
혹시 추후 해당 프로세스를 참고하여 Fine-Tuning을 진행할때, 꼭 참고해서 학습해주시길 바란다.
1) batch_size는 작게 설정해야한다.
처음에 적절한 batch_size를 고민했을때, SimCSE논문을 참고했었다.
논문에서는 RoBERTa모델의 경우, 512 batch_size로 테스트를 진행했고, 긍정적인 결과를 얻었다고 기재되어있다.
하지만, 위 학습 프로세스를 따를때 주의할 점은 batch_size를 매우 작게 설정해야한다는 것이다.
왜냐하면, 논문에서는 RoBERTa 아키텍쳐의 모든 weight parameter를 활용한 full fine-tuning을 진행했다.
따라서, batch_size가 크게 설정되더라도, 배치내 많은 수의 데이터에 대한 context를 학습할 충분한 공간이 있었다.
다만, 우리는 논문과는 다르게, 기존 학습 parameter들은 freezing하고, LoRA adapter 부분만을 통해 학습을 진행하기때문에
큰 batch_size내의 다양한 context를 반영할 공간이 상대적으로 부족하다.
이때문에 batch_size를 작게 가져가는것이 학습에 유리하다.(실제로 batch_size를 낮췄을때, 성능도 올라갔다.)
내가 테스트한 데이터셋에서는 batch_size=8 정도에서 가장 좋은 성능을 보였다.
2) Learning Rate는 너무 크지 않게 설정해야한다.
LoRA Adapter만이 학습 대상이 되기 때문에, Learning Rate를 우선 너무 크지 않게 잡는게 중요하다.
이번에 내가 학습한 데이터셋을 기준으로는 1e-4 ~ 3e-4에서 가장 좋은 성능을 보였다.
3) 반복적인 Fine-Tuning 학습이 예상된다면, LoRA를 사용하지않는것을 권장드린다.
위에서도 잠깐 언급했지만, LoRA는 Adapter만을 학습 공간으로 활용하기때문에,
다양하고 풍부한 데이터를 반복적으로 학습하기에는 공간이 부족하다는 단점이 있다.
때문에, 반복적 Fine-Tuning이 이뤄질 경우, 초기 학습한 내용에 대한 망각 현상이 발생한다.
이를 보완하기 위해, Adapter의 Rank를 늘리는 방법이 있기는 하지만,
이를 통해 해결할 경우, LoRA 고유의 장점을 잃어버릴 수 있기때문에
적절한 값을 찾는것이 중요해진다.
최근 LoRA의 이러한 단점을 보완하기 위해, Adapter의 input과 output이 멱등한 구조를 가지고,
adapter 앞뒤로 compression, decompression 과정을 추가하여,
반복학습에도 망각현상을 줄인 'MoRA'라는 아키텍처가 있으니,
이것을 참고하시길 권장드린다.
<Reference>
https://www.mokosmart.com/ko/lora-technology/
https://huggingface.co/BM-K/KoSimCSE-roberta-multitask
https://arxiv.org/abs/2104.08821
'DEVELOP_NOTE > LLM' 카테고리의 다른 글
[PEFT] LoRA(Low-Rank Adaptation of Large Language Models) Fine-Tuning 학습 방식 뜯어보기! (내부 학습 코드 해석) (0) | 2024.10.18 |
---|---|
Llama3-70B(Llama-cpp)로 Langchain Function Call 구현하기 (0) | 2024.05.28 |