📌 Llama 3.1 8B, 한국어 대화에 약하다.
최근 Llama 3.1 8B 모델을 테스트해본 결과, 한국어 대화에서 문맥이 맞지 않는 답변을 하는 경우가 많았다. 🤔
특히, 감성적인 대화에서 어색한 표현이 종종 등장하여 자연스러운 대화를 이어가기 어려웠음.
그래서 "감성대화 말뭉치" 데이터를 학습시켜, 보다 자연스럽고 문맥에 맞는 대화를 생성할 수 있도록 개선해보았다! 🚀
📂 사용한 데이터셋
📌 데이터셋 이름: 감성대화 말뭉치
📌 출처: AI Hub 감성대화 말뭉치
📌 설명:
- 약 5만 건의 대화 데이터 포함 💬
- 사람과 시스템의 응답이 기록되어 있음 📝
🛠️ 전처리 과정
1. 먼저 Data를 csv 파일로 변환하고 파일구조를 변경시킴.
db/emotional_data/train/test.CSV
db/emotional_data/test/train.CSV
2. CSV 파일을 Jsonl 형태로 변환
# data_preprocess.py
import pandas as pd
import json
# CSV 파일 불러오기
csv_file = "db/emotional_data/train/train.CSV"
df = pd.read_csv(csv_file)
# JSONL 변환을 위한 리스트
jsonl_data = []
for _, row in df.iterrows():
messages = [{"role": "system", "content": "당신은 감정을 공감해주는 챗봇입니다."}]
# 대화 쌍을 순서대로 추가
for i in range(1, 4): # 사람문장1~3, 시스템문장1~3
# NaN을 안전하게 문자열로 처리
user_text = str(row.get(f"사람문장{i}", "")).strip()
assistant_text = str(row.get(f"시스템문장{i}", "")).strip()
# 유효한 텍스트만 추가
if user_text and user_text != "nan": # 빈 문자열, 'nan' 제외
messages.append({"role": "user", "content": user_text})
if assistant_text and assistant_text != "nan": # 빈 문자열, 'nan' 제외
messages.append({"role": "assistant", "content": assistant_text})
# 메시지가 시스템 메시지만 있는 경우 제외
if len(messages) > 1:
jsonl_data.append({"messages": messages})
# JSONL 파일 저장
jsonl_file = "db/emotional_data/train/train.jsonl"
with open(jsonl_file, "w", encoding="utf-8") as f:
for entry in jsonl_data:
json.dump(entry, f, ensure_ascii=False)
f.write("\n")
print(f"✅ 변환 완료! {jsonl_file} 파일을 확인하세요. (총 {len(jsonl_data)}개 대화)")
# Output:
# ✅ 변환 완료! db/emotional_data/train/train.jsonl 파일을 확인하세요. (총 51630 개 대화)
🔬 Finetuning 과정
첫번째 시도 (로컬에서 돌리기)
로컬 GPU: RTX 3060 Ti (VRAM 8GB)
=> 학습시간이 약 21시간이 걸리고, GPU가 버티질 못해 Colab에서 자원을 할당받기로 결정했다.
🚫 실패
더보기
# finetune_model.py
# Description: LoRA를 적용한 Meta-Llama 모델을 감정 대화 데이터로 파인튜닝하는 코드
import torch
import pandas as pd
from transformers import AutoTokenizer, LlamaForCausalLM, BitsAndBytesConfig, TrainingArguments, DataCollatorForLanguageModeling
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
from trl import SFTTrainer
# GPU 메모리 캐시 정리
torch.cuda.empty_cache()
import gc
gc.collect()
# 모델과 토크나이저 로드
model = "meta-llama/Meta-Llama-3.1-8B"
tokenizer = AutoTokenizer.from_pretrained(model)
tokenizer.pad_token = tokenizer.eos_token # 패딩 토큰 설정
# 🛠 chat_template 수동 설정
tokenizer.chat_template = "{% for message in messages %}\n{{ message['role'] }}: {{ message['content'] }}{% endfor %}"
# QLoRA 설정 적용
quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16, # VRAM 절약을 위해 float16 사용
bnb_4bit_quant_type="nf4", # QLoRA에서 일반적으로 사용하는 양자화 방식
bnb_4bit_use_double_quant=True, # 2단계 양자화 적용하여 VRAM 절약
)
# 모델 로드
model = LlamaForCausalLM.from_pretrained(
model, # 모델 이름
quantization_config=quant_config, # 양자화 설정
device_map="auto", # 모델을 어떤 장치에 할당할지 지정 (GPU 또는 CPU 자동 선택)
)
# 입력 텐서에 대한 gradient 계산 활성화
model.enable_input_require_grads()
model.train() # 학습 모드 활성화
# LoRA 설정
lora_config = LoraConfig(
r=32,
lora_alpha=32,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
# JSONL 데이터 로드 (ChatML 포맷 사용)
data_path = "db/emotional_data/train/train.jsonl"
dataset = load_dataset("json", data_files={"train": data_path}, split="train")
# 데이터 전처리 함수 정의
def preprocess_function(examples):
# 여러 개의 샘플을 처리하기 위해 리스트 생성
formatted_texts = []
for messages in examples["messages"]:
# 개별 메시지를 하나의 텍스트로 변환
text = "\n".join([f"{m['role'].capitalize()}: {m['content']}" for m in messages])
formatted_texts.append(text)
# 토크나이징
encodings = tokenizer(
formatted_texts,
truncation=True,
padding="max_length",
max_length=256,
return_tensors="pt"
)
encodings["labels"] = encodings["input_ids"].clone() # Causal LM 라벨 설정
return encodings
tokenized_dataset = dataset.map(preprocess_function, batched=True)
# 데이터 컬레이터 설정 (MLM 비활성화)
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False # Causal LM에서는 MLM 사용하지 않음
)
# 학습 설정
training_args = TrainingArguments(
output_dir="./lora_finetuned_model", # 출력 디렉토리
per_device_train_batch_size=1, # 장치 당 배치 크기
gradient_accumulation_steps=16, # 그래디언트 누적 스텝
num_train_epochs=1, # 학습 에폭 수
learning_rate=1e-4, # 학습률
fp16=True, # 혼합 정밀도 학습
save_steps=250, # 모델 저장 주기
logging_steps=50, # 로그 출력 주기
optim="adamw_torch", # 옵티마이저
lr_scheduler_type="cosine", # 학습률 스케줄러
)
# SFTTrainer로 파인튜닝
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
processing_class=tokenizer,
)
# 학습 시작
trainer.train()
# 학습된 모델 저장
model.save_pretrained("./lora_finetuned_model")
tokenizer.save_pretrained("./lora_finetuned_model")
두번째 시도 (Colab에서 무료 T4 GPU 할당)
=> GPU 할당량이 모자라서, 9.99 달러를 지불하기로 결정했다.
🚫 실패
세번째 시도 (Colab에서 유로 A100 GPU 할당, 배치사이즈 8)
성공 ✅


더보기

# 학습 설정
training_args = TrainingArguments(
output_dir="/content/drive/MyDrive/Colab Notebooks/Llama3.1 8B/lora_finetuned_model", # 출력 디렉토리
per_device_train_batch_size=8, # 장치 당 배치 크기
gradient_accumulation_steps=2, # 그래디언트 누적 스텝
num_train_epochs=1, # 학습 에폭 수
learning_rate=2e-4, # 학습률
fp16=True, # 혼합 정밀도 학습
save_steps=250, # 모델 저장 주기
logging_steps=50, # 로그 출력 주기
optim="adamw_torch_fused", # 옵티마이저
lr_scheduler_type="cosine", # 학습률 스케줄러
dataloader_num_workers=4, # 데이터 로딩 병렬화
report_to="tensorboard", # 학습 모니터링
)

📊 파인튜닝 평가 - TensorBoard를 이용해 정확도, 손실, 학습률 시각화
종합평가
- 성공 여부: 3200스텝(1 에포크)을 약 54분 만에 완료했으며, 손실 감소와 정확도 향상은 학습이 잘 진행되었음을 보여줍니다. 평균 손실 1.4135과 토큰 정확도 0.6552는 초기 미세 조정 결과로 만족할 만합니다.
더보기




1. 손실 (Training Loss)
- 그래프 분석: 손실 곡선(train/loss)은 0 스텝에서 약 1.6 이상으로 시작해 3227스텝(1epoch 완료)에서 약 1.41 으로 수렴했습니다. 초기에는 급격히 감소하다가 중반 이후에는 완만한 감소 추세를 보이며 안정화되었습니다.
- 의미: 손실이 감소했다는 것은 모델이 훈련 데이터를 점점 더 잘 학습하고 있음을 나타냅니다. 약 1.41 수준의 손실은 감정 대화 태스크에서 모델이 적절히 적응했음을 시사하지만, 실제 성능(예: 감정 공감 품질)은 검증 데이터로 평가해야 합니다.
- 해석: 손실 감소가 부드럽게 이루어졌으므로 과적합 없이 학습이 잘 진행된 것으로 보입니다. 그러나 더 낮은 손실(예: 1.3 이하)을 목표로 한다면 에포크를 추가하거나 학습률을 조정할 수 있습니다.

2. 토큰 정확도 (Mean Token Accuracy)
- 그래프 분석: 토큰 정확도(train/mean_token_accuracy)는 0 스텝에서 약 0.62로 시작해 3227 스텝에서 약 0.655로 증가했습니다. 초기에는 변동이 컸으나, 중반 이후에는 안정적인 상승세를 보이며 약 0.655 수준에서 수렴했습니다.
- 의미: 토큰 정확도는 모델이 예측한 토큰이 실제 레이블과 얼마나 일치하는지를 나타냅니다. 0.655는 모든 토큰의 65%를 정확히 예측했음을 의미하며, 감정 대화 데이터의 복잡성을 고려하면 괜찮은 수준입니다. 하지만 대화 생성 모델에서는 정확도만으로 성능을 판단하기 어렵습니다.
- 해석: 정확도가 0.65로 비교적 낮게 수렴한 것은 모델이 아직 학습 데이터에 완전히 적응하지 못했을 수 있음을 시사합니다.

3. 학습률 (Learning Rate)
- 그래프 분석: 학습률(train/learning_rate)은 0 스텝에서 약 2e-4(0.0002)로 시작해 3227 스텝에서 0으로 수렴했습니다. cosine 스케줄러에 따라 부드럽게 감소하는 곡선을 그리며, 중반 이후에는 급격히 줄어드는 경향을 보였습니다.
- 의미: 학습률이 점진적으로 감소하며 모델이 초기에는 크게 업데이트되고, 후반에는 미세 조정을 한다는 것을 나타냅니다. 이는 과적합을 방지하고 수렴을 돕는 일반적인 전략입니다.
- 해석: 학습률이 적절히 조정되어 손실 감소와 정확도 증가에 기여한 것으로 보입니다. 그러나 마지막 단계에서 너무 빠르게 0에 도달했다면, 더 긴 학습이나 다른 스케줄러(예: linear)를 고려할 수 있습니다.
글이 길어져서 테스트는 다음 글에 적겠습니다!
'LLM > LLM 개발' 카테고리의 다른 글
Llama 3.1 8B 파인튜닝 하기(2) - 테스트(BLEU, 코사인 유사도) (0) | 2025.03.18 |
---|---|
Llama 3.1 8B 로컬에서 실행하기 (0) | 2025.03.13 |