본문 바로가기

NLP

[Day 9] 한권으로 끝내는 실전 LLM 파인튜닝 - 단일 GPU Gemma-2B-it 파인튜닝 학습 코드

다음은 <한권으로 끝내는 실전 LLM 파인튜닝> 도서의 스터디 Day9 요약입니다.

3.4.8 학습 파라미터 설정

wandb.init(project="gemma-2B-it-Full-Fine-Tuning", entity="wandb 사용자 계정")

training_args = TrainingArguments(
    output_dir='./keywords_gemma_results',
    max_steps=800,
    per_device_train_batch_size=4,
    per_device_eval_batch_size=8,
    gradient_accumulation_steps=8,
    learning_rate=2e-4,
    weight_decay=0.01,
    warmup_steps=0,
    logging_dir="./logs",
    logging_steps=100,
    report_to='wandb'
)
  • max_steps : 몇 스텝을 학습할지 설정하는 옵션. (현재 1epoch에 250step 정도 진행)
  • per_device_train_batch_size, per_device_eval_batch_size로 각 GPU에서 학습/평가 시에 한번에 처리할 데이터 샘플 수를 결정
  • weight_decay : 과적합 방지를 위해 모델 가중치를 조절하는 강도. 학습 중에 가중치가 너무 커지지 않도록 규제하는 역할
  • wandb에 연결함으로써 Wandb에서 시각적으로 학습 과정을 모니터링할 수 있음.

3.4.9 평가 메트릭 정의

  • BLEU : 생성된 텍스트와 참조 텍스트 사이의 연속된 n-gram 단어의 일치도를 바탕으로 평가를 진행. 정밀도와 텍스트 길이의 간결 성 패널티를 고려한다. 0-1 사이의 값을 가지고 있으며 1에 가까울수록 생성된 텍스트가 참조 텍스트와 더 비슷하다는 것을 의미함. 
import evaluate

bleu = evaluate.load("bleu")
acc = evaluate.load('accuracy')

def preprocess_logits_for_metrics(logits, labels):
  if isinstance(logits, tuple):
    logits = logits[0]
  # token ID를 얻기 위해 argmax를 수행
  return logits.argmax(dim=-1)

def compute_metrics(eval_preds):
  preds, labels = eval_preds
  labels = labels[:, 1:]
  preds = preds[:, :-1]

  # -100 : DataCollatorForCompletionOnlyLM에서 사용되는 ignore_index의 기본값
  mask = labels == -100
  # -100을 토크나이저가 디코드 할 수 있는 값으로 대체
  labels[mask] = tokenizer.pad_token_id
  preds[mask] = tokenizer.pad_token_id

  # BLEU 계산은 텍스트 input을 받기 때문에 token_id -> Text로 변환 필요
  decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
  decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)
  bleu_score = bleu.compute(predictions=decoded_preds, references=decoded_labels)

  # acc : 정수 리스트를 입력으로 받음
  # 여기서 -100이 아닌 부분만 평가하고 싶으므로 마스크의 부정(~)을 사용함 
  accuracy = acc.compute(predictions=preds[~mask], references=labels[~mask])

  return {**bleu_score, **accuracy}
  • preprocess_logits_for_metrics : 평가를 위해 모델 출력을 가장 높은 확률의 token id로 바꿈
  • compute_metrics : 실제 평가를 수행. 예측값과 실제값을 비교함. 이 과정에서 padding이나 무시해야 할 토큰을 나타내는 특별한 값(-100)을 제외하고 유효한 토큰만을 평가함. 
  • accuracy와 bleu 지표를 통해 생성한 텍스트의 품질과 정확성을 종합적으로 평가 가능. (하지만 자연어 생성 task에서 bleu는 단어 등장 순서가 바뀌어도 동시에 등장만 한다면 점수가 높아지는 단점 존재, 정확한 평가를 위해서는 평가 dataset으로 성능평가해야함. )

3.4.10 모델 학습 및 평가

trainer = Trainer(
    args=training_args,
    model=model,
    tokenizer=tokenizer,
    data_collator=collator,
    train_dataset=tokenized_sample_dataset['train'],
    eval_dataset=tokenized_sample_dataset['test'],
    preprocess_logits_for_metrics=preprocess_logits_for_metrics,
    compute_metrics=compute_metrics,
    callbacks=[WandbCallback()]
)
trainer.train()
trainer.evaluate()
  • Trainer() 클래스를 이용해서 학습 진행. 사전에 정의한 collator를 활용해 여러 데이터 샘플을 하나의 배치로 묶고 라벨을 제공한다. 
  • trainer.train()으로 Trainer 객체를 활용해 학습을 시작한다. 
  • trainer.evaluate()으로 Trainer 객체를 사용해 모델의 성능을 평가한다. compute_metrics 함수를 통해 정한 평가 지표를 계산하며 가중치를 바꾸지 않고 현 모델의 성능만을 평가함. 

3.4.11 파인튜닝한 모델 테스트 

def get_chat_format(input_text):
  return [
      {"role": "user", "content": f"다음 텍스트를 한국어로 간단히 요약 및 관련 키워드를 추출해 주세요:\n{input_text}"},
      {"role": "assistant", "content": "한국어 요약:\n키워드:\n"},
  ]

def change_inference_chat_format(input_text):
  return[
      {"role": "user", "content": f"{input_text}"},
      {"role": "assistant", "content": ""},
  ]

prompt = change_inference_chat_format(element['original'])
# tokenizer 초기화 및 적용
inputs = tokenizer.apply_chat_template(prompt, tokenize=True, add_generation_prompt=True, return_tensors='pt').to(model.device)
outputs = model.generate(inputs, max_new_tokens=512, use_cache=True)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
  • 학습이 충분히 진행되지 못할 경우 키워드 추출 및 요약 성능이 이상할 수 있음. 
  • 파인튜닝을 통해 기사를 요약하고 키워드를 추출하는 작업을 동시에 수행하는 모델을 만들어 낼 수 있다.