본문 바로가기
개발/AI

[Day9] LLM 스터디 1기 - 단일 GPU Gemma 파인튜닝 #3

by 가리봉맨 2025. 1. 19.
반응형
한 권으로 끝내는 실전 LLM 파인튜닝 - 10점
강다솔 지음/위키북스

03. 전체 파인튜닝

학습 파라미터 설정

Weights & Biases(wandb)를 사용해서 모델 학습에 사용되는 파라미터 설정부터 시작한다. 참고로 wandb는 머신러닝 실험을 추적하고 시각화하는 데 널리 쓰이는 플랫폼이라고 한다. 가입 후에 사용자 계정과 로그인 API Key 등을 확인한다.

https://wandb.ai/

 

Weights & Biases: The AI Developer Platform

Weights & Biases is the leading AI developer platform to train and fine-tune models, manage models from experimentation to production, and track and evaluate GenAI applications powered by LLMs.

wandb.ai

wandb 로그인 및 학습 파라미터를 설정하는 코드는 아래와 같다.

wandb.init(project="gemma-2B-it-Full-Fine-Tuning", entity="Your_ID")

training_args = TrainingArguments(
    output_dir="./keywords_gemma_results",
    # num_train_epochs=1, # 1epoch에 250step정도 진행함 
    max_steps=800,
    per_device_train_batch_size=4,
    per_device_eval_batch_size=8,
    warmup_steps=0,
    weight_decay=0.01,
    learning_rate=2e-4,
    logging_dir="./logs",
    logging_steps=100,
    report_to="wandb",
    )

'Your_ID'에는 wandb 가입 시 설정한 사용자 계정을 넣는다. 나는 'hamjoon'이다.

매개변수 중 output_dir은 모델 체크포인트를 저장할 폴더인데 학습이 중간에 종료될 때 나중에 이어서 할 수 있게 해 준다. max_steps은 모델이 학습하는 총 스텝이다. per_device_train_batch_size, per_device_eval_batch_size는 각각 학습/평가 시 한 번에 처리할 데이터 샘플의 수를 의미한다. 메모리 사용량을 고려해서 적절한 값을 넣어야 한다.

평가 메트릭 정의

평가 메트릭은 모델이 얼마나 잘 학습됐는지 측정하는 지표다. 여기서는 BLEU(Bilingual Evaluation Understudy) 점수와 정확도(ACC)를 평가 지표로 사용한다. BLEU는 생성된 텍스트와 참조 텍스트 사이의 연속된 n개 단어의 일치도를 바탕으로 평가를 진행한다. 따라서 기계 번역 품질을 측정하는 데에 주로 사용되지만 요약과 키워드 추출에도 응용해서 사용할 수 있다고 한다. BLEU 점수는 0~1 값을 갖는데 1에 가까울수록 생성된 텍스트가 참조 텍스트와 더 비슷하다는 것을 의미한다. 아래는 코드는 실제 평가를 수행하는 compute_metrics 함수다.

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

    mask = labels == -100
    labels[mask] = tokenizer.pad_token_id
    preds[mask] = tokenizer.pad_token_id

    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)
    
    accuracy = acc.compute(predictions=preds[~mask], references=labels[~mask])

    return {**bleu_score, **accuracy}

패딩이나 무시해야 할 토큰을 나타내는 값(-100)을 제외하고 유용한 토큰만 평가한다. 토큰 ID를 텍스트로 변경한 뒤, BLEU 점수를 계산한다. 이어서 정확도도 유효한 토큰에 대해서만 평가한다.

모델 학습 및 평가

앞서 준비한 학습 설정, 모델, 토크나이저, 콜레이터 등을 불러와서 이를 바탕으로 학습을 도와주는 Trainer 클래스를 작성한다. 

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()]
)

마지막 라인, callbacks=[WandbCallback()] 는 학습 중간 결과를 wandb에 자동으로 기록하도록 설정한다. 아래 이미지는 드디어 모델 학습을 진행하고 성능을 평가하는 과정과 그 결과다. 훈련 시 미리 정해진 스텝 수만큼 훈련하고, 설정된 배치 크기에 맞춰 데이터를 처리한다. 

성능 평가 결과, eval_loss는 낮을 수록 좋다고 하는데 책 예제의 결과 값은 0.93인데 실제 돌려보니 무려 3.97이 나왔다. 문제가 있어 보인다. eval_bleu 점수는 위에서 언급한 대로 1에 가까울수록 좋은데 0.073 밖에 나오지 않았다. eval_accuracy는 0.352로 정확도가 약 35% 정도밖에 되지 않는다. 뭔가 잘못된 것 같지만 나중에 다시 보기로 하고 일단 넘어간다.

input_text = "부산의 한 왕복 2차선 도로에서 역주행 사고로 배달 오토바이 운전자인 고등학생이 숨지는 사고가 발생했다. 유족은 '가해자가 사고 후 곧바로 신고하지 않고 늑장 대응해 피해를 키웠다'고 주장하고 있다.\n11일 부산진경찰서는 교통사고처리특례법(교통사고처리법)상 업무상 과실치사 혐의로 지난 3일 A(59)씨를 검찰에 불구속 송치했다고 밝혔다. A씨는 교통사고처리법상 12대 중과실에 해당되는 '중앙선 침범'으로 역주행 교통사고를 일으킨 혐의를 받는다.\n경찰에 따르면 스포츠유틸리티차량(SUV) 운전자 A씨는 5월 19일 밤 11시 50분쯤 부산진구 가야고가교 밑 도로에서 중앙선을 넘어 역주행으로 140m를 달려 반대편 차선의 오토바이 운전자 조모(16)군을 들이받았다. 조군은 원동기장치자전거 면허를 취득한 상태였고 헬멧도 쓰고 있었지만 크게 다쳤다. 사고 당일 수술을 받았으나 얼마 후 2차 뇌출혈로 뇌사 판정이 내려졌고, 사고 발생 약 한 달 만인 지난달 16일 끝내 사망했다. 사고를 낸 A씨는 술을 마시거나 약물을 복용한 상태에서 운전하지는 않은 것으로 조사됐다. 경찰 관계자는 'A씨가 자신이 정주행을 하고 오토바이가 역주행을 한 것으로 착각했다고 진술했다'고 설명했다."

def get_chat_format(input_text):

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


def change_inference_chat_format(input_text):
    return [
    {"role": "user", "content": f"{input_text}"},
    {"role": "assistant", "content": ""}
    ]
prompt = change_inference_chat_format(input_text)
# tokenizer 초기화 및 적용t\
inputs = tokenizer.apply_chat_template(prompt, tokenize=True, add_generation_prompt=True, return_tensors="pt").to("cuda")
outputs = model.generate(inputs, max_new_tokens=512, use_cache=True)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

위와 같이 실제로 모델을 사용해서 텍스트를 생성하고 결과물을 확인해 봤는데, 역시나 아래와 같이 요약 및 키워드가 전혀 생성되지 않았다.

user
부산의 한 왕복 2차선 도로에서 역주행 사고로 배달 오토바이 운전자인 고등학생이 숨지는 사고가 발생했다. 유족은 '가해자가 사고 후 곧바로 신고하지 않고 늑장 대응해 피해를 키웠다'고 주장하고 있다.
11일 부산진경찰서는 교통사고처리특례법(교통사고처리법)상 업무상 과실치사 혐의로 지난 3일 A(59)씨를 검찰에 불구속 송치했다고 밝혔다. A씨는 교통사고처리법상 12대 중과실에 해당되는 '중앙선 침범'으로 역주행 교통사고를 일으킨 혐의를 받는다.
경찰에 따르면 스포츠유틸리티차량(SUV) 운전자 A씨는 5월 19일 밤 11시 50분쯤 부산진구 가야고가교 밑 도로에서 중앙선을 넘어 역주행으로 140m를 달려 반대편 차선의 오토바이 운전자 조모(16)군을 들이받았다. 조군은 원동기장치자전거 면허를 취득한 상태였고 헬멧도 쓰고 있었지만 크게 다쳤다. 사고 당일 수술을 받았으나 얼마 후 2차 뇌출혈로 뇌사 판정이 내려졌고, 사고 발생 약 한 달 만인 지난달 16일 끝내 사망했다. 사고를 낸 A씨는 술을 마시거나 약물을 복용한 상태에서 운전하지는 않은 것으로 조사됐다. 경찰 관계자는 'A씨가 자신이 정주행을 하고 오토바이가 역주행을 한 것으로 착각했다고 진술했다'고 설명했다.
model

model

저자가 제공한 아래 데이터를 사용해서 다시 훈련을 진행하고 평가까지 진행했다.

# 데이터를 직접 만드는데, 1시간 이상 소요되므로 제가 미리 만들어놓았습니다. 
# 필요하신 분들은 주석을 풀고 사용해주세요 

sample_dataset = datasets.load_dataset("daje/keyword_summary")
print(sample_dataset["train"][0])
tokenized_sample_dataset = sample_dataset.map(tokenize)
tokenized_sample_dataset = tokenized_sample_dataset['train'].train_test_split(test_size=0.1, seed=42)
tokenized_sample_dataset

낮을수록 좋은 eval_loss 값은 오히려 증가(3.97 -> 5.16)해서 안 좋아졌고, 1에 가까울수록 좋은 eval_bleu 값은 살짝 증가(0.07 -> 0.11)해서 조금 나아졌다. eval_accuracy 값도 다행히 살짝 올라갔다. 요약과 키워드 기능 확인을 위한 모델 테스트 결과는 아래와 같다.

이번엔 요약과 키워드가 추출되긴 했지만 만족스러운 결과는 아니다. 뭔가 문제가 있는 듯하지만 전체적인 파인튜닝을 학습하는 것이 주목적이기에 9일 차 스터디는 일단 여기서 마무리한다. 결과가 제대로 나오지 않는 문제는 따로 시간을 내서 확인할 예정이다. 끝.

반응형