티스토리 뷰

LLM/Fine Tuning

LoRA

4567은 소수 2024. 4. 14. 18:28

LoRA (Low Rank Adaption for Large Language Model) 는 아래 논문에서 처음 발표되었습니다.

https://arxiv.org/abs/2106.09685

 

LoRA: Low-Rank Adaptation of Large Language Models

An important paradigm of natural language processing consists of large-scale pre-training on general domain data and adaptation to particular tasks or domains. As we pre-train larger models, full fine-tuning, which retrains all model parameters, becomes le

arxiv.org

 

LoRA는 LLM fine tuning 시, 전체 파라미터를 조정하는 것이 아닌, 일부 파라미터 조정 후 기존 LLM 모델과 합치는 방식을 제안합니다.

 

논문 그림에도 나와 있듯, "We only train A and B" 가 해당 논문의 가장 핵심 문구입니다. 기존 LLM 모델 (파란 부분) 은 그대로 두고, fine-tuning을 위한 새로운 부분 A,B만 따로 학습 후 기존 LLM과 합치겠다. 라는 의미입니다.

(구체적으로 왜 되는 지는 잘 모릅니다...)

 

huggingface의 LoRA 튜토리얼을 따라 bionlp2004 라는 데이터셋을 이용해 text 내에서 단백질, dna 등을 의미하는 것을 분류하도록 해보겠습니다.

https://huggingface.co/docs/peft/main/en/task_guides/token-classification-lora

 

LoRA for token classification

Low-Rank Adaptation (LoRA) is a reparametrization method that aims to reduce the number of trainable parameters with low-rank representations. The weight matrix is broken down into low-rank matrices that are trained and updated. All the pretrained model pa

huggingface.co

 

bionlp 2004 데이터셋 라벨은 다음과 같습니다.

"O", "B-DNA", "I-DNA", "B-protein", "I-protein", "B-cell_type", "I-cell_type", "B-cell_line", "I-cell_line", "B-RNA", "I-RNA"

 

생물학 전공이 아니다 보니 무엇인지는 잘 모르겠지만, gpt에 따르면 다음과 같습니다.

(즉 "O"는 분류 대상이 아닌 것을 의미하고, 나머지 라벨은 특별한 의미를 갖는 것 같습니다.)

 

Env : Google Colab

 

1. 필요 패키지 설치

!pip install -q peft transformers datasets evaluate seqeval

 

2. 필요 패키지 import

from datasets import load_dataset
from transformers import AutoModelForTokenClassification, AutoTokenizer, DataCollatorForTokenClassification, TrainingArguments, Trainer
from peft import get_peft_config, PeftModel, PeftConfig, get_peft_model, LoraConfig, TaskType
import evaluate
import torch
import numpy as np

 

- datasets : bionlp2004 데이터셋을 불러오기 용

 

 - transformers : 대부분 LLM에서 따르는 트랜스포머 모델

--- AutoModelForTokenClassification : classification (분류) 모델 

--- AutoTokenizer : 데이터셋을 토큰화하기 위한 용도

--- DataCollatorForTokenClassification : tokenizer에 대해 padding, batch 작업 

--- TrainingArguments : training 을 위한 파라미터 설정

--- Trainer : training 도구

 

- peft : PEFT 작업 위한 패키지

--- get_peft_config : peft 작업 위한 설정값 세팅 (PeftConfig)

--- PeftModel : peft 모델 생성용

--- PeftConfig : peft 모델에 필요한 설정값 

--- get_peft_model : 학습 완료된 모델 불러오기

--- LoraConfig : LoRA 모델에 대한 설정값 세팅

--- TaskType : 어떤 작업할 것인지 (LoraConfig)

 

- evaluate : precision, recall, f1 score, accuracy 평가용

 

- torch : 학습용 딥러닝 프레임워크 (huggingface에서 만든 transformers, peft 등 라이브러리가 pytorch 기반으로 동작)

 

3. 모델 및 파라미터 세팅

model = "roberta-base"
lr = 1e-3
batchSize = 16
epoches = 3

 

huggingface 튜토리얼에서는 roberta-large 모델을 사용하였고, epoch을 10으로 잡았지만, 학습 속도를 비교적 빠르게 테스트하기 위해 base 모델을 사용하였고, epoch을 3으로 잡았습니다.

더 좋은 성능을 사용하고 싶으면 large 등 다른 모델을 사용하거나 epoch을 키우면 됩니다.

learning rate (lr) 과 batch size는 튜토리얼과 동일한 값으로 사용하였습니다.

 

 4. bionlp 2004 데이터셋 불러오기

bioNlp = load_dataset("tner/bionlp2004")

 

bioNlp["train"][0] 로 학습용 데이터 중 첫 번째 값 확인 시 다음과 같습니다.

HUVECs 는 label 7, VCAM-1 은 label 3, 나머지는 모두 label 0 입니다. 

구성된 token으로 보아, 데이터셋은 text의 띄어쓰기, '.', ',' 를 기준으로 토큰화가 이루어진 것을 알 수 있습니다.

 

5. sequence 라벨링 평가에 사용될 모듈 생성 ( precision, recall, f1 score, accuracy 평가)

seqEval = evaluate.load("seqeval")

 

6. 데이터셋의 라벨값 분류

labelList = [
    "O",
    "B-DNA",
    "I-DNA",
    "B-protein",
    "I-protein",
    "B-cell_type",
    "I-cell_type",
    "B-cell_line",
    "I-cell_line",
    "B-RNA",
    "I-RNA",
]

 

7. precision, recall, f1 score, accuracy 측정을 위한 metrics

def compute_metrics(p) :
  predictions, labels = p
  predictions = np.argmax(predictions, axis=2)

  true_predictions = [
      [ labelList[p] for (p, l) in zip(prediction, label) if l != -100 ]
      for prediction, label in zip(predictions, labels)
  ]

  true_labels = [
      [ labelList[l] for (p, l) in zip(prediction, label) if l != -100 ]
      for prediction, label in zip(predictions, labels)
  ]

  results = seqEval.compute(predictions = true_predictions, references = true_labels)

  return {
      "precision" : results["overall_precision"],
      "recall" : results["overall_recall"],
      "f1" : results["overall_f1"],
      "accuracy" : results["overall_accuracy"]
  }

 

8. tokenizer 생성

tokenizer = AutoTokenizer.from_pretrained(model, add_prefix_space=True)

 

9. 데이터셋을 tokenizer에 토큰화로 적용하기 위한 함수

def tokenize_and_align_labels(examples) :
  tokenized_inputs = tokenizer(examples["tokens"], truncation=True, is_split_into_words=True)
  labels = []
  for i, label in enumerate(examples[f"tags"]) :
    word_ids = tokenized_inputs.word_ids(batch_index = i)
    previous_word_idx = None
    label_ids = []

    for word_idx in word_ids :
      if word_idx is not None and word_idx != previous_word_idx :
        label_ids.append(label[word_idx])
      else :
        label_ids.append(-100)
      previous_word_idx = word_idx
    labels.append(label_ids)

  tokenized_inputs["labels"] = labels

  return tokenized_inputs

 

10. bionlp 2004 데이터셋 tokenization

tokenizedBioNlp = bioNlp.map(tokenize_and_align_labels, batched=True)

 

11. tokenization 된 데이터셋을 학습에 맞게 padding 등 진행

data_collator = DataCollatorForTokenClassification(tokenizer = tokenizer)

 

12. 기존 데이터셋 라벨링에 index 부여 

id2label = {
    0: "O",
    1: "B-DNA",
    2: "I-DNA",
    3: "B-protein",
    4: "I-protein",
    5: "B-cell_type",
    6: "I-cell_type",
    7: "B-cell_line",
    8: "I-cell_line",
    9: "B-RNA",
    10: "I-RNA",
}
label2id = {
    "O": 0,
    "B-DNA": 1,
    "I-DNA": 2,
    "B-protein": 3,
    "I-protein": 4,
    "B-cell_type": 5,
    "I-cell_type": 6,
    "B-cell_line": 7,
    "I-cell_line": 8,
    "B-RNA": 9,
    "I-RNA": 10,
}

 

13. classification 모델 생성

model = AutoModelForTokenClassification.from_pretrained(model, num_labels = 11, id2label = id2label, label2id = label2id)

 

14. peft 모델 설정

peft_config = LoraConfig(task_type = TaskType.TOKEN_CLS, inference_mode=False, r=16, lora_alpha=16, lora_dropout=0.1, bias="all")
model = get_peft_model(model, peft_config)

 

 

단순 classification 이므로 답변 시 필요한 inference 는 false

r, alpha 는 LoRA 모델에서 사용할 파라미터

 

model.print_trainable_parameters() 로 기존 roberta-base 모델에 비해 fine-tuning 해야 할 파리미터 비교 시 다음과 같습니다.

기존 roberta-base 모델의 경우, 124,661,782 개의 파라미터를 갖지만, LoRA를 이용한 fine-tuning에 사용될 parameter는 700, 427 개로, 전체 파라미터의 0.5 % 만 fine-tuning 시킬 수 있습니다.

 

15. train config 설정

training_args = TrainingArguments(
    output_dir = "roberta-base-lora-token-classification",
    learning_rate = lr,
    per_device_train_batch_size = batchSize,
    per_device_eval_batch_size = batchSize,
    num_train_epochs = epoches,
    weight_decay = 0.01,
    evaluation_strategy = "epoch",
    save_strategy = "epoch",
    load_best_model_at_end = True,
)

 

16. train 시작

trainer = Trainer(
    model = model,
    args = training_args,
    train_dataset = tokenizedBioNlp["train"],
    eval_dataset = tokenizedBioNlp["validation"],
    tokenizer = tokenizer,
    data_collator = data_collator,
    compute_metrics = compute_metrics,
)

trainer.train()

 

학습이 완료된 후, colab 왼쪽 파일 아이콘 클릭 시, training_args 에서 output_dir 에 설정한 디렉토리 아래에 학습된 모델이 기록되어 있습니다.

해당 checkpoint 중 가장 마지막 checkpoint 를 기준으로 이후 학습된 모델을 불러와 테스트를 진행합니다.

 

17. 학습된 모델 불러오기

peft_model_id = "./roberta-base-lora-token-classification/checkpoint-3117"
config = PeftConfig.from_pretrained(peft_model_id)
inference_model = AutoModelForTokenClassification.from_pretrained(
    config.base_model_name_or_path, num_labels=11, id2label=id2label, label2id=label2id
)
tokenizer = AutoTokenizer.from_pretrained(config.base_model_name_or_path)
model = PeftModel.from_pretrained(inference_model, peft_model_id)

 

여기서 학습 진행 시점에 따른 오픈 소스 모델 업데이트, 파라미터 수에 따라 checkpoint 값이 다르므로 확인 후 해당 값을 입력하면 됩니다. 

 

18. 테스트용 text

test_text = "The activation of IL-2 gene expression and NF-kappa B through CD28 requires reactive oxygen production by 5-lipoxygenase."
inputs = tokenizer(test_text, return_tensors="pt")

 

19. 테스트

with torch.no_grad():
    logits = model(**inputs).logits

tokens = inputs.tokens()
predictions = torch.argmax(logits, dim=2)

for token, prediction in zip(tokens, predictions[0].numpy()):
    print((token, model.config.id2label[prediction]))

 

20. 테스트 결과

위 그림은 제가 테스트한 결과, 아래는 huggingface 튜토리얼 페이지 결과입니다.

 

<s>, </s> 는 tokenization을 진행하면서 생긴 구분자이므로 크게 신경을 안 써도 됩니다. 

위 test_text 를 대상으로 띄어쓰기 부분 대신 G' 과 같은 문자가 추가되었습니다. (인코딩 문제인 듯)

해당 값을 무시하고 판단하면, 분류 시 "O" 로 분류된 값 외에는 huggingface 튜토리얼과 거의 유사한 값을 가지게 되었습니다. 

 

21. 결론

저렇게 분류한 것이 옳게 분류가 된 것인지는 생물학 전공이 아니다 보니 잘 모르겠지만, huggingface 튜토리얼과 거의 흡사한 값을 갖게 되었으므로 roberta-large 대신 roberta-base 모델, 에폭 수 작게 등 빠르게 학습시켜도 어느 정도 효과를 갖는 것으로 판단됩니다.

 

따라서 이후 본인이 원하는 데이터셋으로 fine-tuning을 할 때,

해당 데이터셋을 peft에 적용 가능 하도록 전처리만 진행한다면 (이것도 매우 힘든 작업일 듯...) 

peft에서 제공하는 라이브러리를 바탕으로 LoRA 등 여러 fine-tuning 기법을 몇 가지 config 설정만으로 진행할 수 있습니다. 

'LLM > Fine Tuning' 카테고리의 다른 글

Prompt Tuning  (0) 2024.04.14
P-Tuning  (0) 2024.04.14
Prefix Tuning  (0) 2024.04.14
PEFT  (0) 2024.04.14
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함