
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)을 초과하는 샘플은 학습 데이터에서 제외했습니다.
이번 프로젝트에서는 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함수는 프롬프트를 입력받아 모델의 응답을 생성하고 출력합니다.
2 epoch 학습 후, /kaggle/working/korean_2_epoch2.lora.h5 파일에 LoRA 가중치가 저장되었습니다.

다음은 검증 데이터셋의 샘플 프롬프트에 대한 모델의 응답 예시입니다.
(여기에 text_gen 함수를 사용한 추론 결과 예시를 추가)
아직 학습 epoch 수가 적어서 모델의 응답이 완벽하지는 않지만, 한국어 감정 대화의 맥락을 어느 정도 이해하고 응답을 생성하는 것을 확인할 수 있었습니다.
이번 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/))을 사용하여 글을 작성하면 가독성을 높일 수 있습니다.
이 정도면 블로그 글의 초안으로 충분할 것 같습니다. 여기에 살을 붙이고, 스크린샷, 추론 결과 예시 등을 추가하면 훌륭한 블로그 글이 될 것입니다.