LLM Training: Post-Training vs Fine-Tuning
- LLM Training: Post-Training vs Fine-Tuning
- Pre-training vs Post-training vs Fine-tuning
- Post-Training: 모델 정렬 (Model Alignment)
- Fine-Tuning: 특화 성능 향상
- Post-Training vs Fine-Tuning 비교
- 실제 적용 예시
- 결론
LLM Training: Post-Training vs Fine-Tuning
LLM (Large Language Model) 훈련은 크게 Pre-training, Post-training, 그리고 Fine-tuning으로 나눌 수 있습니다. 이 글에서는 Post-training과 Fine-tuning의 차이점과 각각의 목적을 명확히 설명합니다.
Pre-training vs Post-training vs Fine-tuning
| Stage | Purpose | Data Type | Scale | Techniques |
|---|---|---|---|---|
| Pre-training | 언어 이해 및 생성 기초 능력 학습 | Raw text (web, books) | Massive (TB scale) | Next token prediction |
| Post-training | 인간의 선호도에 맞는 유용하고 안전한 모델로 정렬 | Human feedback, prompts | Medium (GB scale) | SFT, RLHF, Constitutional AI |
| Fine-tuning | 특정 task나 domain에 특화된 성능 향상 | Task-specific labeled | Small (MB-GB) | Supervised learning, LoRA |
Post-Training: 모델 정렬 (Model Alignment)
Post-training은 pre-trained 기본 모델을 인간의 가치와 선호도에 맞게 정렬(alignment)하는 과정입니다.
목적
- 유용성(Helpfulness): 사용자의 질문에 도움이 되는 답변 생성
- 무해성(Harmlessness): 유해하거나 편향된 내용 생성 방지
- 정직성(Honesty): 정확하고 사실에 기반한 정보 제공
주요 기법들
1. Supervised Fine-Tuning (SFT)
고품질의 instruction-response 쌍으로 모델을 훈련
# SFT 데이터 구조 예시
sft_data = [
{
"instruction": "파이썬에서 리스트를 정렬하는 방법을 설명해주세요.",
"response": "파이썬에서 리스트를 정렬하는 주요 방법은 다음과 같습니다:\n1. sort() 메서드: 원본 리스트를 변경\n2. sorted() 함수: 새로운 정렬된 리스트 반환..."
},
{
"instruction": "머신러닝에서 과적합을 방지하는 방법은?",
"response": "과적합 방지 방법들:\n1. 정규화(Regularization): L1, L2 정규화\n2. 조기 종료(Early Stopping)\n3. 드롭아웃(Dropout)\n4. 데이터 증강(Data Augmentation)"
}
]
# SFT 훈련 코드
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer
)
from datasets import Dataset
def prepare_sft_dataset(data, tokenizer, max_length=512):
def tokenize_function(examples):
# instruction과 response를 합쳐서 하나의 텍스트로 만듬
texts = []
for instruction, response in zip(examples['instruction'], examples['response']):
text = f"### Instruction:\n{instruction}\n\n### Response:\n{response}"
texts.append(text)
# 토크나이징
model_inputs = tokenizer(
texts,
max_length=max_length,
truncation=True,
padding="max_length",
return_tensors="pt"
)
# labels는 input_ids와 동일 (causal LM)
model_inputs["labels"] = model_inputs["input_ids"].clone()
return model_inputs
# 데이터셋 준비
dataset = Dataset.from_list(data)
tokenized_dataset = dataset.map(tokenize_function, batched=True)
return tokenized_dataset
# 모델과 토크나이저 로드
model_name = "microsoft/DialoGPT-medium"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
# pad token 설정
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
# 데이터셋 준비
train_dataset = prepare_sft_dataset(sft_data, tokenizer)
# 훈련 인자 설정
training_args = TrainingArguments(
output_dir="./sft_model",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=2,
warmup_steps=100,
weight_decay=0.01,
logging_dir="./logs",
logging_steps=10,
save_steps=500,
evaluation_strategy="no",
learning_rate=5e-5,
fp16=True,
)
# 트레이너 생성 및 훈련
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
tokenizer=tokenizer,
)
# SFT 훈련 실행
trainer.train()
trainer.save_model("./sft_final_model")
2. Reinforcement Learning from Human Feedback (RLHF)
인간의 선호도 피드백을 바탕으로 보상 모델을 학습하고, 이를 통해 모델을 최적화
# RLHF 단계 1: 보상 모델(Reward Model) 훈련
import torch
import torch.nn as nn
from transformers import AutoModel, AutoTokenizer
class RewardModel(nn.Module):
def __init__(self, model_name):
super().__init__()
self.base_model = AutoModel.from_pretrained(model_name)
self.reward_head = nn.Linear(self.base_model.config.hidden_size, 1)
def forward(self, input_ids, attention_mask):
outputs = self.base_model(input_ids=input_ids, attention_mask=attention_mask)
# 마지막 토큰의 hidden state 사용
last_hidden_state = outputs.last_hidden_state[:, -1, :]
reward = self.reward_head(last_hidden_state)
return reward
# 선호도 데이터 예시
preference_data = [
{
"prompt": "인공지능의 장점을 설명해주세요",
"chosen": "AI는 반복 작업 자동화, 데이터 분석 능력 향상, 24시간 서비스 제공 등의 장점이 있습니다.",
"rejected": "AI는 좋습니다. 끝."
},
{
"prompt": "파이썬 코딩 팁을 알려주세요",
"chosen": "1. PEP 8 스타일 가이드 준수\n2. 리스트 컴프리헨션 활용\n3. f-string 사용\n4. 적절한 변수명 사용",
"rejected": "코딩하면 됩니다."
}
]
def train_reward_model(model, data, tokenizer):
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-5)
for epoch in range(3):
for batch in data:
# chosen과 rejected 응답을 각각 인코딩
chosen_inputs = tokenizer(
batch["prompt"] + " " + batch["chosen"],
return_tensors="pt",
truncation=True,
padding=True
)
rejected_inputs = tokenizer(
batch["prompt"] + " " + batch["rejected"],
return_tensors="pt",
truncation=True,
padding=True
)
# 보상 점수 계산
chosen_reward = model(chosen_inputs["input_ids"], chosen_inputs["attention_mask"])
rejected_reward = model(rejected_inputs["input_ids"], rejected_inputs["attention_mask"])
# Bradley-Terry 모델 손실 함수
# P(chosen > rejected) = sigmoid(r_chosen - r_rejected)
loss = -torch.log(torch.sigmoid(chosen_reward - rejected_reward))
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Loss: {loss.item():.4f}")
# 보상 모델 훈련
tokenizer = AutoTokenizer.from_pretrained("microsoft/DialoGPT-medium")
reward_model = RewardModel("microsoft/DialoGPT-medium")
train_reward_model(reward_model, preference_data, tokenizer)
# RLHF 단계 2: PPO를 사용한 정책 최적화
from transformers import AutoModelForCausalLM
import torch.nn.functional as F
class PPOTrainer:
def __init__(self, policy_model, reward_model, tokenizer):
self.policy_model = policy_model
self.reward_model = reward_model
self.tokenizer = tokenizer
self.optimizer = torch.optim.AdamW(policy_model.parameters(), lr=1e-5)
def generate_responses(self, prompts, max_length=100):
"""정책 모델로 응답 생성"""
responses = []
log_probs = []
for prompt in prompts:
inputs = self.tokenizer(prompt, return_tensors="pt")
with torch.no_grad():
outputs = self.policy_model.generate(
inputs["input_ids"],
max_length=max_length,
do_sample=True,
temperature=0.7,
pad_token_id=self.tokenizer.eos_token_id,
return_dict_in_generate=True,
output_scores=True
)
response = self.tokenizer.decode(outputs.sequences[0], skip_special_tokens=True)
responses.append(response)
# log probability 계산 (간소화된 버전)
logits = torch.stack(outputs.scores, dim=1)
log_prob = F.log_softmax(logits, dim=-1).mean()
log_probs.append(log_prob)
return responses, log_probs
def compute_rewards(self, prompts, responses):
"""보상 모델로 보상 계산"""
rewards = []
for prompt, response in zip(prompts, responses):
full_text = prompt + " " + response
inputs = self.tokenizer(full_text, return_tensors="pt", truncation=True)
with torch.no_grad():
reward = self.reward_model(inputs["input_ids"], inputs["attention_mask"])
rewards.append(reward.item())
return rewards
def ppo_update(self, log_probs, rewards, old_log_probs):
"""PPO 알고리즘으로 정책 업데이트"""
epsilon = 0.2 # PPO clipping parameter
# Advantage 계산 (간소화)
advantages = torch.tensor(rewards) - torch.tensor(rewards).mean()
for log_prob, old_log_prob, advantage in zip(log_probs, old_log_probs, advantages):
# Importance sampling ratio
ratio = torch.exp(log_prob - old_log_prob)
# PPO clipped objective
clipped_ratio = torch.clamp(ratio, 1 - epsilon, 1 + epsilon)
policy_loss = -torch.min(ratio * advantage, clipped_ratio * advantage)
self.optimizer.zero_grad()
policy_loss.backward()
self.optimizer.step()
# PPO 훈련 실행
def train_with_ppo(policy_model, reward_model, tokenizer, prompts, epochs=5):
trainer = PPOTrainer(policy_model, reward_model, tokenizer)
for epoch in range(epochs):
# 1. 응답 생성
responses, log_probs = trainer.generate_responses(prompts)
# 2. 보상 계산
rewards = trainer.compute_rewards(prompts, responses)
# 3. PPO 업데이트
old_log_probs = [lp.detach() for lp in log_probs]
trainer.ppo_update(log_probs, rewards, old_log_probs)
print(f"Epoch {epoch}: Average Reward = {sum(rewards)/len(rewards):.3f}")
# 실행 예시
prompts = ["파이썬 코딩 팁을 알려주세요", "인공지능의 장점을 설명해주세요"]
policy_model = AutoModelForCausalLM.from_pretrained("microsoft/DialoGPT-medium")
train_with_ppo(policy_model, reward_model, tokenizer, prompts)
3. Constitutional AI
모델이 스스로 자신의 출력을 비판하고 개선하도록 훈련
# Constitutional AI 구현 예시
class ConstitutionalAI:
def __init__(self, model, tokenizer):
self.model = model
self.tokenizer = tokenizer
# Constitution (헌법) - 모델이 따라야 할 원칙들
self.constitution = [
"응답은 도움이 되고 정확해야 합니다.",
"유해하거나 차별적인 내용을 포함하면 안 됩니다.",
"사실에 기반한 정보를 제공해야 합니다.",
"예의 바르고 존중하는 톤을 유지해야 합니다."
]
def generate_initial_response(self, prompt):
"""초기 응답 생성"""
inputs = self.tokenizer(prompt, return_tensors="pt")
outputs = self.model.generate(
inputs["input_ids"],
max_length=200,
do_sample=True,
temperature=0.7,
pad_token_id=self.tokenizer.eos_token_id
)
response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
return response.replace(prompt, "").strip()
def critique_response(self, prompt, response):
"""응답 비판하기"""
critique_prompt = f"""
다음 질문과 답변을 평가해주세요:
질문: {prompt}
답변: {response}
다음 기준에 따라 이 답변의 문제점을 지적해주세요:
1. 도움이 되고 정확한가?
2. 유해하거나 차별적인 내용이 있는가?
3. 사실에 기반한 정보인가?
4. 예의 바르고 존중하는 톤인가?
문제점 (있다면):
"""
inputs = self.tokenizer(critique_prompt, return_tensors="pt")
outputs = self.model.generate(
inputs["input_ids"],
max_length=300,
do_sample=True,
temperature=0.3,
pad_token_id=self.tokenizer.eos_token_id
)
critique = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
return critique.replace(critique_prompt, "").strip()
def revise_response(self, prompt, original_response, critique):
"""응답 수정하기"""
revision_prompt = f"""
원본 질문: {prompt}
원본 답변: {original_response}
문제점: {critique}
위 문제점들을 고려하여 더 도움이 되고, 안전하며, 정확한 답변으로 수정해주세요:
수정된 답변:
"""
inputs = self.tokenizer(revision_prompt, return_tensors="pt")
outputs = self.model.generate(
inputs["input_ids"],
max_length=250,
do_sample=True,
temperature=0.5,
pad_token_id=self.tokenizer.eos_token_id
)
revised = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
return revised.replace(revision_prompt, "").strip()
def constitutional_process(self, prompt, max_iterations=3):
"""Constitutional AI 전체 프로세스"""
print(f"질문: {prompt}\n")
# 1. 초기 응답 생성
response = self.generate_initial_response(prompt)
print(f"초기 응답: {response}\n")
for iteration in range(max_iterations):
# 2. 응답 비판
critique = self.critique_response(prompt, response)
print(f"비판 {iteration+1}: {critique}\n")
# 비판에서 문제점이 없다고 판단되면 종료
if "문제없음" in critique or "적절함" in critique:
print("Constitutional AI 프로세스 완료: 문제점 없음")
break
# 3. 응답 수정
revised_response = self.revise_response(prompt, response, critique)
print(f"수정된 응답 {iteration+1}: {revised_response}\n")
response = revised_response
return response
# Constitutional AI 실행 예시
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("microsoft/DialoGPT-medium")
tokenizer = AutoTokenizer.from_pretrained("microsoft/DialoGPT-medium")
# pad token 설정
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
constitutional_ai = ConstitutionalAI(model, tokenizer)
# 테스트 실행
prompt = "인공지능의 위험성에 대해 설명해주세요"
final_response = constitutional_ai.constitutional_process(prompt)
print(f"최종 응답: {final_response}")
# Constitutional AI를 위한 Self-Supervised Training
def train_constitutional_ai(model, tokenizer, training_prompts, epochs=3):
"""
Constitutional AI 방식으로 모델을 훈련
"""
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-5)
constitutional_ai = ConstitutionalAI(model, tokenizer)
for epoch in range(epochs):
total_loss = 0
for prompt in training_prompts:
# 1. 초기 응답 생성
initial_response = constitutional_ai.generate_initial_response(prompt)
# 2. 자기 비판 및 수정
final_response = constitutional_ai.constitutional_process(prompt, max_iterations=2)
# 3. 개선된 응답으로 모델 훈련
# 초기 응답보다 수정된 응답을 더 높은 확률로 생성하도록 학습
improved_text = f"{prompt} {final_response}"
# 토크나이징
inputs = tokenizer(
improved_text,
return_tensors="pt",
truncation=True,
padding=True,
max_length=512
)
# Forward pass
outputs = model(**inputs, labels=inputs["input_ids"])
loss = outputs.loss
# Backward pass
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(training_prompts)
print(f"Epoch {epoch+1}, Average Loss: {avg_loss:.4f}")
return model
# Constitutional AI 훈련 데이터 예시
training_prompts = [
"인공지능의 위험성에 대해 설명해주세요",
"프로그래밍을 배우는 가장 좋은 방법은?",
"기후 변화의 주요 원인은 무엇인가요?",
"건강한 식습관에 대한 조언을 해주세요"
]
# Constitutional AI 훈련 실행
trained_model = train_constitutional_ai(model, tokenizer, training_prompts)
Fine-Tuning: 특화 성능 향상
Fine-tuning은 특정 작업이나 도메인에 특화된 성능을 향상시키는 과정입니다.
목적
- Task-specific 성능: 번역, 요약, 분류 등 특정 작업 성능 향상
- Domain adaptation: 의료, 법률, 금융 등 전문 분야 적응
- Style/Format: 특정 출력 형식이나 스타일 학습
주요 기법들
1. Full Fine-tuning
모델의 모든 파라미터를 업데이트
# Full Fine-tuning 완전한 구현 예시
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling
)
from datasets import Dataset
import torch
# 1. Task-specific 데이터 준비 (요약 작업 예시)
summarization_data = [
{
"text": "최근 인공지능 기술의 발전이 빠르게 진행되고 있다. 특히 대규모 언어모델들이 다양한 작업에서 인간 수준의 성능을 보여주고 있으며, 이는 자연어 처리 분야에 혁신을 가져오고 있다.",
"summary": "인공지능, 특히 대규모 언어모델이 자연어 처리 분야에 혁신을 가져오고 있다."
},
{
"text": "기후변화는 전 세계적인 문제로, 탄소배출 감소와 재생에너지 활용이 필요하다. 각국 정부와 기업들이 친환경 정책을 도입하고 있으며, 개인들도 일상생활에서 환경을 고려한 선택을 하고 있다.",
"summary": "기후변화 대응을 위해 정부, 기업, 개인이 친환경 정책과 행동을 실천하고 있다."
},
{
"text": "원격근무가 일반화되면서 업무 효율성과 워라밸에 대한 관심이 높아졌다. 기업들은 새로운 협업 도구를 도입하고, 직원들은 홈오피스 환경을 개선하고 있다.",
"summary": "원격근무 확산으로 업무 효율성과 워라밸에 대한 관심이 증가했다."
}
]
def prepare_full_finetuning_dataset(data, tokenizer, max_length=512):
def tokenize_function(examples):
# 입력: "요약해주세요: [텍스트]" / 출력: "[요약문]"
inputs = [f"다음 텍스트를 요약해주세요: {text}" for text in examples['text']]
targets = examples['summary']
# 입력-출력을 하나의 텍스트로 결합
full_texts = [f"{inp}\n\n요약: {target}" for inp, target in zip(inputs, targets)]
# 토크나이징
model_inputs = tokenizer(
full_texts,
max_length=max_length,
truncation=True,
padding="max_length",
return_tensors="pt"
)
# labels 설정 (causal LM이므로 input_ids와 동일)
model_inputs["labels"] = model_inputs["input_ids"].clone()
# 입력 부분은 loss 계산에서 제외 (선택사항)
# 실제 구현에서는 instruction 부분을 마스킹할 수 있음
return model_inputs
dataset = Dataset.from_list(data)
tokenized_dataset = dataset.map(tokenize_function, batched=True)
return tokenized_dataset
# 2. 모델과 토크나이저 준비
model_name = "microsoft/DialoGPT-medium" # 실제로는 더 큰 모델 사용
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16, # 메모리 효율성을 위해
device_map="auto"
)
# pad token 설정
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
# 3. 데이터셋 준비
train_dataset = prepare_full_finetuning_dataset(summarization_data, tokenizer)
# 4. 훈련 설정
training_args = TrainingArguments(
output_dir="./full_finetuned_model",
num_train_epochs=5,
per_device_train_batch_size=2, # GPU 메모리에 따라 조정
gradient_accumulation_steps=4, # 효과적인 배치 크기 = 2 * 4 = 8
warmup_steps=50,
weight_decay=0.01,
learning_rate=2e-5, # Full fine-tuning에서는 더 작은 학습률 사용
logging_dir="./logs",
logging_steps=10,
save_steps=100,
save_total_limit=2,
evaluation_strategy="no",
dataloader_drop_last=True,
fp16=True, # 혼합 정밀도 훈련
gradient_checkpointing=True, # 메모리 절약
report_to=None, # wandb 등 로깅 도구 비활성화
)
# 5. Data Collator 설정
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False, # Causal LM이므로 MLM 사용 안 함
)
# 6. Trainer 설정 및 훈련
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
tokenizer=tokenizer,
data_collator=data_collator,
)
print("Full Fine-tuning 시작...")
trainer.train()
# 7. 모델 저장
trainer.save_model("./full_finetuned_summary_model")
tokenizer.save_pretrained("./full_finetuned_summary_model")
print("Full Fine-tuning 완료!")
# 8. 훈련된 모델 테스트
def test_finetuned_model(model, tokenizer, test_text):
input_text = f"다음 텍스트를 요약해주세요: {test_text}\n\n요약:"
inputs = tokenizer(input_text, return_tensors="pt")
with torch.no_grad():
outputs = model.generate(
inputs["input_ids"],
max_length=inputs["input_ids"].shape[1] + 50,
do_sample=True,
temperature=0.7,
pad_token_id=tokenizer.eos_token_id,
eos_token_id=tokenizer.eos_token_id
)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
summary = response.split("요약:")[-1].strip()
return summary
# 테스트
test_text = "머신러닝은 데이터로부터 학습하는 인공지능의 한 분야이다. 지도학습, 비지도학습, 강화학습으로 나뉘며, 각각 다른 방식으로 모델을 훈련한다."
summary = test_finetuned_model(model, tokenizer, test_text)
print(f"생성된 요약: {summary}")
2. Parameter-Efficient Fine-tuning (PEFT)
일부 파라미터만 업데이트하여 효율성 향상
LoRA (Low-Rank Adaptation)
# LoRA 완전한 구현 예시
from peft import LoraConfig, get_peft_model, TaskType, prepare_model_for_kbit_training
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer,
BitsAndBytesConfig
)
import torch
# 1. QLoRA 설정 (4bit 양자화 + LoRA)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
# 2. 기본 모델 로드 (4bit 양자화로)
model_name = "microsoft/DialoGPT-medium"
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# pad token 설정
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
# 3. LoRA 설정
lora_config = LoraConfig(
r=16, # rank - 낮을수록 파라미터 적음, 높을수록 표현력 높음
lora_alpha=32, # scaling factor (일반적으로 r의 2배)
target_modules=[
"c_attn", # DialoGPT의 attention projection layers
"c_proj", # DialoGPT의 output projection
"c_fc", # DialoGPT의 feed-forward layers
],
lora_dropout=0.1,
bias="none",
task_type=TaskType.CAUSAL_LM,
)
# 4. LoRA 모델 생성
model = prepare_model_for_kbit_training(model)
lora_model = get_peft_model(model, lora_config)
# 훈련 가능한 파라미터 확인
lora_model.print_trainable_parameters()
# 출력 예시: "trainable params: 1,048,576 || all params: 355,804,160 || trainable%: 0.29%"
# 5. 훈련 데이터 준비 (의료 도메인 예시)
medical_data = [
{
"instruction": "고혈압의 증상은 무엇인가요?",
"response": "고혈압의 주요 증상으로는 두통, 어지러움, 목 뒤쪽 통증, 시야 흐림, 코피 등이 있습니다. 하지만 많은 경우 증상이 없어 '조용한 살인자'라고 불립니다."
},
{
"instruction": "당뇨병 환자의 식이요법은?",
"response": "당뇨병 환자는 규칙적인 식사, 탄수화물 조절, 섬유질 섭취 증가, 단순당 제한이 필요합니다. 혈당 지수가 낮은 음식을 선택하고 적절한 칼로리를 유지해야 합니다."
},
{
"instruction": "심장마비의 응급처치 방법은?",
"response": "심장마비 응급처치: 1) 즉시 119 신고 2) 환자를 평평한 곳에 눕히기 3) 기도 확보 4) 심폐소생술(CPR) 실시 5) AED 사용 가능시 사용"
}
]
def prepare_lora_dataset(data, tokenizer, max_length=512):
def format_instruction(example):
return f"### 질문: {example['instruction']}\n### 답변: {example['response']}"
def tokenize_function(examples):
texts = [format_instruction(ex) for ex in examples]
model_inputs = tokenizer(
texts,
max_length=max_length,
truncation=True,
padding="max_length",
return_tensors="pt"
)
model_inputs["labels"] = model_inputs["input_ids"].clone()
return model_inputs
# 리스트를 Dataset으로 변환
from datasets import Dataset
dataset = Dataset.from_list(data)
tokenized_dataset = dataset.map(
lambda x: tokenize_function([x]),
remove_columns=dataset.column_names
)
return tokenized_dataset
# 6. 데이터셋 준비
train_dataset = prepare_lora_dataset(medical_data, tokenizer)
# 7. 훈련 설정
training_args = TrainingArguments(
output_dir="./lora_medical_model",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=2,
warmup_steps=20,
weight_decay=0.01,
learning_rate=2e-4, # LoRA는 더 높은 학습률 사용 가능
logging_dir="./logs",
logging_steps=5,
save_steps=50,
save_total_limit=2,
evaluation_strategy="no",
fp16=False, # 4bit 양자화 사용시 fp16 비활성화
bf16=True,
gradient_checkpointing=True,
dataloader_drop_last=True,
report_to=None,
)
# 8. Trainer 설정 및 훈련
trainer = Trainer(
model=lora_model,
args=training_args,
train_dataset=train_dataset,
tokenizer=tokenizer,
)
print("LoRA Fine-tuning 시작...")
trainer.train()
# 9. LoRA 어댑터 저장
lora_model.save_pretrained("./lora_medical_adapter")
print("LoRA 어댑터 저장 완료!")
# 10. 훈련된 LoRA 모델 테스트
def test_lora_model(model, tokenizer, question):
input_text = f"### 질문: {question}\n### 답변:"
inputs = tokenizer(input_text, return_tensors="pt")
with torch.no_grad():
outputs = model.generate(
inputs["input_ids"],
max_length=inputs["input_ids"].shape[1] + 100,
do_sample=True,
temperature=0.7,
pad_token_id=tokenizer.eos_token_id,
eos_token_id=tokenizer.eos_token_id
)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
answer = response.split("### 답변:")[-1].strip()
return answer
# 테스트
test_question = "폐렴의 증상과 치료법을 알려주세요"
answer = test_lora_model(lora_model, tokenizer, test_question)
print(f"질문: {test_question}")
print(f"답변: {answer}")
# 11. LoRA 어댑터 로드하는 방법
# from peft import PeftModel
# base_model = AutoModelForCausalLM.from_pretrained(model_name)
# lora_model = PeftModel.from_pretrained(base_model, "./lora_medical_adapter")
Adapter Layers
# Adapter 구현 예시
import torch
import torch.nn as nn
class AdapterLayer(nn.Module):
def __init__(self, input_dim, adapter_dim=64, dropout=0.1):
super().__init__()
self.input_dim = input_dim
self.adapter_dim = adapter_dim
# Down projection: input_dim -> adapter_dim
self.down_proj = nn.Linear(input_dim, adapter_dim)
# Activation function
self.activation = nn.ReLU()
# Up projection: adapter_dim -> input_dim
self.up_proj = nn.Linear(adapter_dim, input_dim)
# Dropout
self.dropout = nn.Dropout(dropout)
# Initialize weights
nn.init.xavier_uniform_(self.down_proj.weight)
nn.init.zeros_(self.down_proj.bias)
nn.init.xavier_uniform_(self.up_proj.weight)
nn.init.zeros_(self.up_proj.bias)
def forward(self, x):
# 원본 입력 저장 (잔차 연결용)
residual = x
# Adapter 연산: down -> activation -> up
x = self.down_proj(x)
x = self.activation(x)
x = self.dropout(x)
x = self.up_proj(x)
# 잔차 연결: 원본 + adapter 출력
return residual + x
class TransformerWithAdapter(nn.Module):
"""Adapter가 포함된 Transformer 예시"""
def __init__(self, base_model, adapter_dim=64):
super().__init__()
self.base_model = base_model
# 기존 모델의 파라미터 고정
for param in self.base_model.parameters():
param.requires_grad = False
# Adapter layers 추가
self.adapters = nn.ModuleDict()
# 각 transformer layer에 adapter 추가
for name, module in self.base_model.named_modules():
if "attention" in name.lower() or "mlp" in name.lower():
# attention과 MLP 뒤에 adapter 추가
hidden_size = getattr(module, 'hidden_size', 768) # 기본값
self.adapters[name] = AdapterLayer(hidden_size, adapter_dim)
def forward(self, *args, **kwargs):
# 이 부분은 실제 모델 구조에 맞게 수정 필요
outputs = self.base_model(*args, **kwargs)
# Adapter 적용 (실제 구현에서는 hook이나 다른 방법 사용)
# 여기서는 개념적 예시만 제공
return outputs
# Adapter 훈련 예시
def train_adapter_model(base_model, train_data, tokenizer):
# Adapter 모델 생성
adapter_model = TransformerWithAdapter(base_model, adapter_dim=64)
# 훈련 가능한 파라미터 확인
trainable_params = sum(p.numel() for p in adapter_model.parameters() if p.requires_grad)
total_params = sum(p.numel() for p in adapter_model.parameters())
print(f"훈련 가능한 파라미터: {trainable_params:,}")
print(f"전체 파라미터: {total_params:,}")
print(f"훈련 비율: {100 * trainable_params / total_params:.2f}%")
# 훈련 설정
optimizer = torch.optim.AdamW(adapter_model.parameters(), lr=1e-4)
# 실제 훈련 루프는 데이터와 모델 구조에 따라 구현
# 여기서는 개념적 예시만 제공
return adapter_model
# 사용 예시
# base_model = AutoModelForCausalLM.from_pretrained("microsoft/DialoGPT-medium")
# adapter_model = train_adapter_model(base_model, train_data, tokenizer)
Post-Training vs Fine-Tuning 비교
언제 사용할까?
Post-Training이 필요한 경우
- 모델이 유해한 내용을 생성하는 경우
- 사용자 지시를 잘 따르지 않는 경우
- 일반적인 대화 능력이 부족한 경우
- 편향이나 부정확한 정보를 자주 생성하는 경우
Fine-tuning이 필요한 경우
- 특정 작업 (번역, 요약, 분류)의 성능을 높이고 싶은 경우
- 특정 도메인 (의료, 법률)에 특화시키고 싶은 경우
- 특정 출력 형식이나 스타일이 필요한 경우
- 제한된 데이터로 빠른 적응이 필요한 경우
데이터 요구사항
Post-Training
# RLHF 선호도 데이터 예시
{
"prompt": "인공지능의 위험성에 대해 설명해주세요",
"chosen": "AI는 잘못 사용될 경우 개인정보 침해, 편향 증폭 등의 위험이...",
"rejected": "AI는 위험하니까 사용하지 마세요"
}
Fine-tuning
# Task-specific 데이터 예시
{
"input": "다음 문서를 요약해주세요: [긴 텍스트]",
"output": "[3-4줄 요약문]"
}
실제 적용 예시
GPT-4 개발 과정
1. Pre-training: 웹 텍스트로 언어 모델 학습
2. Post-training:
- SFT로 instruction following 학습
- RLHF로 인간 선호도 정렬
3. Fine-tuning:
- Code generation (GitHub Copilot)
- 특정 API 호출 형식 학습
Domain-specific 모델 개발
1. Base Model (Llama-2)
2. Post-training: 일반적인 안전성/유용성 정렬
3. Fine-tuning: 의료 텍스트 데이터로 MedLlama 개발
결론
- Post-training은 모델을 인간의 가치와 정렬하는 과정
- Fine-tuning은 특정 작업이나 도메인에 특화시키는 과정
- Post-training은 모델의 전반적인 행동을 개선하고, Fine-tuning은 특정 성능을 향상
- 실제 상용 LLM은 두 과정을 모두 거쳐 개발됨
각 단계는 서로 다른 목적과 기법을 가지며, 최종적으로 안전하고 유용하며 특화된 성능을 갖춘 LLM을 만들기 위해 모두 필요한 과정입니다.