llm lora fine tuning

Hyunwoo·2025년 2월 12일


https://www.kaggle.com/competitions/gemma-language-tuning/overview

https://www.kaggle.com/code/bebechien/how-to-finetuning-gemma2-for-spoken-language-tasks


좋습니다! 제공해주신 Kaggle 노트북 코드와 이전 대화 내용을 바탕으로, 블로그 글 예시를 좀 더 구체적으로 완성해 보겠습니다.

## Kaggle "Google - Unlock Global Communication with Gemma" 대회 참가 후기: 한국어 감정 대화 모델 파인튜닝 도전기

안녕하세요, AI/ML에 관심이 많은 개발자/학생/연구원 Hyun-woo Kim입니다. 최근 Kaggle에서 [Google - Unlock Global Communication with Gemma](https://www.kaggle.com/competitions/gemma-language-tuning/overview) 대회가 열린 것을 보고, 평소 한국어 자연어 처리(NLP)에 관심이 많았던 터라 참가하게 되었습니다.

이 대회는 Google의 오픈 모델인 Gemma 2를 특정 언어 또는 문화권에 맞게 파인튜닝하는 것을 목표로 합니다. 저는 한국어 감정 대화 모델을 만들어, 정신 건강 지원 챗봇 등에 활용할 수 있는 가능성을 탐색해 보고자 했습니다.

대회 참가를 위해, Kaggle에서 한국인 참가자가 작성한 [How to Finetuning Gemma2 for spoken-language tasks](https://www.kaggle.com/code/bebechien/how-to-finetuning-gemma2-for-spoken-language-tasks) 노트북을 참고하여 코드를 작성했습니다. 특히, 이 노트북에서 Keras를 사용하여 Gemma 2 모델을 로드하고 파인튜닝하는 방법을 참고했습니다. 제 코드는 [여기](https://www.kaggle.com/code/hwkims/gemma2-2b-lora-ed-ai-hub-fine-tuning)에서 확인하실 수 있습니다.

### 1. 데이터셋: AI Hub "감정 대화 말뭉치"

이번 프로젝트에서는 AI Hub의 ["감정 대화 말뭉치"](https://www.aihub.or.kr/aihubdata/data/view.do?currMenu=115&topMenu=100&dataSetSn=86)를 사용했습니다. 이 데이터셋은 다양한 감정 상황에서의 한국어 대화로 구성되어 있어, 감정 대화 모델을 학습하는 데 적합하다고 판단했습니다.

데이터는 JSON 파일 형태로 제공되며, 각 대화는 여러 개의 발화(utterance)로 구성되어 있습니다.  다음은 데이터 전처리에 사용한 코드의 일부입니다.

```python
def preprocess_dialogue(item):
    dialogue = item['talk']['content']
    prompt = ""
    response = ""
    history = ""
    for i in range(1, 10):  # 최대 9턴까지의 대화 기록을 고려
        human_key = f"HS0{i}" if i < 10 else f"HS{i}"
        system_key = f"SS0{i}" if i < 10 else f"SS{i}"

        if human_key in dialogue and dialogue[human_key]:
            history += f"<start_of_turn>user\\n{dialogue[human_key]}<end_of_turn>\\n"
            if system_key in dialogue and dialogue[system_key]:
                response = dialogue[system_key]
            else:
                # 시스템 응답이 없으면, 마지막 사용자 발화가 프롬프트가 됨
                prompt = dialogue[human_key]
                return prompt, None  # 타겟 응답 없음

        elif system_key in dialogue and dialogue[system_key]:
            history += f"<start_of_turn>model\\n{dialogue[system_key]}<end_of_turn>\\n"

    if history and response:
        return history.strip(), response.strip()
    return None, None

train_texts = []
for item in train_data_raw:
    prompt, response = preprocess_dialogue(item)
    if prompt and response:
        full_text = f"{prompt}<start_of_turn>model\\n{response}<end_of_turn>"
        if len(tokenizer(full_text)) < token_limit: # 토큰 수가 제한을 넘지 않는 경우만 추가
            train_texts.append(full_text)

preprocess_dialogue 함수는 JSON 파일에서 대화 내용을 추출하고, <start_of_turn><end_of_turn> 토큰을 사용하여 대화 기록과 모델 응답을 구성합니다. 최대 9턴까지의 대화 기록을 고려하도록 설정했습니다. 또한, tokenizer를 사용하여 텍스트를 토큰화하고, 토큰 수가 token_limit(256)을 초과하는 샘플은 학습 데이터에서 제외했습니다.

2. 모델 및 파인튜닝: Gemma 2와 LoRA

이번 프로젝트에서는 Google의 Gemma 2 모델(gemma2_instruct_2b_en)을 사용했습니다. Gemma 2는 다양한 언어에 대한 이해 능력이 뛰어나고, 오픈 소스로 제공되어 접근성이 좋다는 장점이 있습니다.

# 모델 로드
model_id = "gemma2_instruct_2b_en"
gemma_lm = keras_nlp.models.GemmaCausalLM.from_preset(model_id)
gemma_lm.summary()

파인튜닝에는 LoRA (Low-Rank Adaptation) 기법을 적용했습니다. LoRA는 전체 모델 가중치를 업데이트하는 대신, 작은 "어댑터" 행렬만 학습하여 파라미터 효율성을 높이는 방법입니다. 이를 통해 학습 시간과 메모리 사용량을 줄일 수 있습니다.

# LoRA 활성화
lora_rank = 2
gemma_lm.backbone.enable_lora(rank=lora_rank)

LoRA rank는 2로 설정했습니다. (더 높은 rank도 시도해 보았지만, 시간 관계상 2로 진행했습니다.)

# 학습 설정
lr_value = 1e-4
train_epoch = 2 # 에폭 수
optimizer = keras.optimizers.AdamW(
    learning_rate=lr_value,
    weight_decay=0.01,
)
optimizer.exclude_from_weight_decay(var_names=["bias", "scale"])

gemma_lm.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=optimizer,
    weighted_metrics=[keras.metrics.SparseCategoricalAccuracy()],
)

학습에는 AdamW 옵티마이저를 사용했고, learning rate는 1e-4, epoch 수는 2로 설정했습니다.

# LoRA 가중치 저장을 위한 CustomCallback
class CustomCallback(keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs=None):
        model_name = f"/kaggle/working/korean_{lora_rank}_epoch{epoch+1}.lora.h5"
        gemma_lm.backbone.save_lora_weights(model_name)
        print(f"Saved LoRA weights to {model_name}")

        # 검증 데이터셋의 첫 번째 샘플로 추론 테스트
        if validation_data_raw:
            sample_item = validation_data_raw[0]
            eval_prompt, _ = preprocess_dialogue(sample_item)
            if eval_prompt:
                text_gen(eval_prompt)  # text_gen 함수는 아래에 정의
            else:
                print("Could not create evaluation prompt from validation sample.")


history = gemma_lm.fit(train_texts, epochs=train_epoch, batch_size=1, callbacks=[CustomCallback()])

매 epoch마다 LoRA 가중치를 저장하고, 검증 데이터셋의 첫 번째 샘플로 추론을 수행하여 모델의 학습 경과를 확인했습니다.

def text_gen(prompt):
    tick() # 시간 측정 시작
    input = f"<start_of_turn>user\\n{prompt}<end_of_turn>\\n<start_of_turn>model\\n"
    output = gemma_lm.generate(input, max_length=token_limit)
    print("\\nGemma output:")
    print(output)
    tock() # 시간 측정 종료

text_gen함수는 프롬프트를 입력받아 모델의 응답을 생성하고 출력합니다.

3. 결과 및 평가

2 epoch 학습 후, /kaggle/working/korean_2_epoch2.lora.h5 파일에 LoRA 가중치가 저장되었습니다.

다음은 검증 데이터셋의 샘플 프롬프트에 대한 모델의 응답 예시입니다.

(여기에 text_gen 함수를 사용한 추론 결과 예시를 추가)

아직 학습 epoch 수가 적어서 모델의 응답이 완벽하지는 않지만, 한국어 감정 대화의 맥락을 어느 정도 이해하고 응답을 생성하는 것을 확인할 수 있었습니다.

4. 결론 및 향후 계획

이번 Kaggle 대회 참가를 통해 Gemma 2 모델을 한국어 감정 대화 데이터에 파인튜닝하는 경험을 쌓을 수 있었습니다. 비록 짧은 시간 동안 제한된 리소스로 진행했지만, LoRA를 활용하여 효율적으로 모델을 학습하고, 한국어 대화 생성 가능성을 확인했다는 점에서 의미 있는 시도였다고 생각합니다.

향후에는 더 많은 epoch 동안 학습을 진행하고, LoRA rank, learning rate 등 하이퍼파라미터를 튜닝하여 모델의 성능을 개선할 계획입니다. 또한, 더 다양한 검증 데이터셋을 사용하여 모델의 일반화 성능을 평가하고, Gradio 등을 활용하여 챗봇 UI를 만들어 사용자들이 직접 모델과 상호작용할 수 있도록 할 예정입니다.


**참고:**

*   스크린샷은 예시입니다.  실제 노트북 실행 결과에 맞는 스크린샷으로 교체해주세요.
*   `text_gen` 함수를 사용한 추론 결과 예시를 추가해주세요.  다양한 프롬프트에 대한 응답을 보여주면 좋습니다.
*   정량적인 평가 지표(예: perplexity)를 추가할 수 있다면 더 좋습니다.
*   글의 흐름과 내용, 코드 설명 등은 자유롭게 수정하고 보완하셔도 됩니다.
*   마크다운 문법([https://www.markdownguide.org/basic-syntax/](https://www.markdownguide.org/basic-syntax/))을 사용하여 글을 작성하면 가독성을 높일 수 있습니다.

이 정도면 블로그 글의 초안으로 충분할 것 같습니다. 여기에 살을 붙이고, 스크린샷, 추론 결과 예시 등을 추가하면 훌륭한 블로그 글이 될 것입니다.
profile
현우

0개의 댓글