SKN/Remind

sk네트웍스 family AI 캠프 11기 4월 5주차 회고록

claovy☘️ 2025. 5. 4. 17:50

회고기간 : 2025.04.28월~2025.05.02금

이번 주차는 fine tunning 기법에 대해 배웠다

 

Fine tunning

- 이미 학습된 LLM을 특정 작업이나 데이터셋에 맞춰 재학습하는 기능
- 프롬프트 엔지니어링만으로 해결하기 어려운 문제 해결
- 모델 추가학습 없이 입력을 조정하하는 프롬프트 엔지니어링과 달리 모델을 재학습하여 특정 작업에 최적화

 

sLLM

  • LLM의 축소판으로, 학습과 배포 비용을 줄이기 위해 설계
  • sLLM은 파인튜닝이 비교적 간단하고 리소스가 적게 듦 (양자화가 필요없음)
  • 주로 제한된 연산 리소스를 가진 환경에서 활용
sLLM 모델 (meta-llama/Llama-2-7b-hf) 사용해보기
from transformers import AutoTokenizer, AutoModelForCausalLM # AutoModelForCausalLM : 문장 생성에 특화된 모델 로드

promt = "Explain the history of AI in simple term."
model_name = "meta-llama/Llama-2-7b-hf"

# 1. 토크나이저 로드
tokenizer = AutoTokenizer.from_pretrained(model_name)

# 2. 모델 로드
model = AutoModelForCausalLM.pre_trained(model_name, device_map="auto")

# 3. 입력값
inputs = tokenizer(prompt, return_tensors='pt').to(model.device) 

# 4. 출력값(텍스트 생성)
outputs = model.generate(**inputs, max_length=500)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)

print(response)

 

⚠️ 입력값에서 tokenizer의 인자 return_tensors='pt'는 텍스트를 pytorch 텐서로 변환하여 모델이 처리할 수 있는 형태로 변환

만약, return_tensors='pt' 없이

tokenizer = AutoTokenizer.from_pretrained(model_name) 만 한다면 출력형태는 

딕셔너리, 리스트 기반이 되어 텐서로 변환이 필요하다. 

 

하지만 return_tensors를 적용하면 모델 입력이 가능한 형태가 된다 

 


LoRA

  • 모델 전체 파라미터를 건드리지 않고 일부 레이어만 미세조정
  • q_proj, v_proj, p_proj 등 일부 Linear 층에 LoRA를 인젝션
  • LoraConfig + get_peft_model()
LoRA 기반 gpt2 감성 분석 모델 튜닝 
순서 : 토크나이저 및 모델 pad_token -> LoRA 설정 및 적용 -> 데이터셋 로드 -> 데이터 분리(학습용 검증용) -> 데이터 변환(텐스트는 토큰 id, 라벨은 텐서로 변환) ->배치 자동화 -> 학습 설정 및 트레이너 설정 -> 훈련
!pip install peft datasets transformers

# 허깅페이스 로그인
from huggingface_hub import login
login(token="input your token")

import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer
from peft import LoraConfig, get_peft_model
from dataset import load dataset

# gpu 사용 설정을 위한 device 변수 생성
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)

# 병렬 토크나이저 경고 방지
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
# 모델 로드
model_name = 'gpt2'

tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token  # [pad] 문자열 토큰에 [EOS] 할당 

base_model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
base_model.config.pad_token_id = tokenizer.pad_token_id # 토큰의 id를 모델이 인식할 수 있도록 알려주는 설정값

 

💡여기서 pad_token을 맞춰주는 이유?

문장 길이가 다를 때, 모델이 어디까지 입력이고 어디부터 padding인지 인식할 수 있도록 해줘야함

sLLM처럼 텍스트 생성은 문장을 왼->오 로 생성하기 때문에 sequpadding을 비교하지 않아도 o

하지만 분류나 QA처럼 문장 구조를 비교하는 모델은 의미 있는 문장을 끊어 학습하기 위해 pad_token과 attention_mask가 필

구조 설명 pad_token 필요성
Sequential 한 토큰씩 순서대로 처리 X
Parallel 여러 문장을 한번에 처리 -> 길이 맞춰야함 O

 

# LoRA 설정
lora_config = LoraConfig(
    r=8,                                         # LoRA 저랭크 차원
    lora_alpha=32,                               # LoRA 가중치 스케일링 파라미터
    target_modules=["c_attn", "c_fc", "c_proj"], # LoRA 적용할 계층
    lora_dropout=0.1                             # 드롭아웃 설정
)

# 기반 모델에 LoRA 적용 (+ GPU로 이동)
model = get_peft_model(base_model, lora_config).to(device)
## 학습용 데이터 준비
긍정 리뷰 train에서 500개 샘플링
부정 리뷰 train에서 500개 샘플링
train_texts = > 긍정+부정 리뷰에서 text인 것
train_label => 긍정+부정 리뷰에서 label인 것

## 검증용 데이터 준비
긍정 리뷰 train에서 100개 샘플링
부정 리뷰 train에서 100개 샘플링
eval_texts = > 긍정+부정 리뷰에서 text인 것
eval_label => 긍정+부정 리뷰에서 label인 것
# 데이터 토큰화
def preprocess_data(texts, labels):
    encodings = tokenizer(
        texts,
        truncation=True,
        padding="max_length",
        max_length=512,
        return_tensors="pt"
    )
    encodings["labels"] = torch.tensor(labels, dtype=torch.long)
    return encodings

train_encodings = preprocess_data(train_texts, train_labels)
eval_encodings = preprocess_data(eval_texts, eval_labels)
# 데이터셋 변환을 위한 클래스
class IMDBDataset(torch.utils.data.Dataset):
    def __init__(self, encodings):
        self.encodings = encodings

    def __len__(self):
        return len(self.encodings["input_ids"])

    def __getitem__(self, idx):
        return {
            key: val[idx]
            for key, val in self.encodings.items()
        }

train_dataset = IMDBDataset(train_encodings)
eval_dataset = IMDBDataset(eval_encodings)
# 배치를 GPU로 자동 이동시키는 data_collator 함수
def collate_fn(batch):
    batch = {key: torch.stack([item[key] for item in batch]) for key in batch[0]}
    return batch
# 학습 설정
training_args = TrainingArguments(
    output_dir='./results',        # 모델 저장 경로
    per_device_train_batch_size=4, # 훈련 배치 크기
    per_device_eval_batch_size=4,  # 평가(검증) 배치 크기
    num_train_epochs=5,            # 학습 횟수 (에포크)
    save_steps=100,                # 저장 주기
    save_total_limit=2,            # 최대 저장 모델 개수
    eval_strategy='epoch',         # 에포크 단위 평가
    logging_dir='./logs',          # 로그 저장 경로 
    logging_steps=10,              # 로그 출력 주기
    fp16=True                      # FP16 연산 최적화
)
# 트레이너 설정
trainer = Trainer(
    model=model,  
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    data_collator=collate_fn
)

trainer.train()
def predict_sentiment(text):
    inputs = tokenizer(
        text,
        return_tensors="pt",
        padding=True,
        truncation=True,
        max_length=512
    ).to(device)

    with torch.no_grad():
        outputs = model(**inputs)
        logits = outputs.logits
        prediction = torch.argmax(logits, dim=-1).item()

    return "긍정" if prediction == 1 else "부정"
    

text_review = "It was very boring..."
result = predict_sentiment(text_review)
result

Q-LoRA

  • 4bit 양자화 모델 + LoRA 를 결합하여 GPU 효율을 극대화
  • BitsAndBytesConfig로 4bit 설정 + LoRA 모델
Q-LoRA 사용
순서 : 양자화 설정 -> 토크나이저 및 모델 로드 -> 데이터셋 로드 -> 데이터셋 전처리 -> LoRA 설정 및 적용 ->Train
# 4-bit 양자화된 모델 로드를 위한 설정
bnb_config = {
    "load_in_4bit": True,
    "bnb_4bit_compute_dtype": torch.float16,
    "bnb_4bit_quant_type": "nf4",
    "device_map": "auto"
}

# 토크나이저 및 모델 로드 (모델 로드 시 4-bit 양자화 설정)
tokenizer = AutoTokenizer.from_pretrained(model_name)
base_model = AutoModelForCausalLM.from_pretrained(model_name, **bnb_config)

 


Soft Prompt

  • 모델 파라미터는 고정하고, 임베딩만 학습해서 성능 개선
  • 기존 문장 앞에 학습 가능한 가상 프롬프트 토큰 삽입
  • PromptTuningConfig 사용
peft_config = PromptTuningConfig(
    task_type="CAUSAL_LM",
    num_virtual_tokens=10,
    token_dim=model.config.hidden_size
)
print(model.config.hidden_size)

model = get_peft_model(model, peft_config)

RLHF

  • 인간 피드백을 반영해 보상 모델을 만들고, 강화학습(PPO)로 보정 학습
  • 지도학습 -> 보상모델 학습 -> PPO 튜닝
  • ChatGPT처럼 인간 친화적 응답에 LLM을 적용함 
# 1. 시 로드 및 지도학습
# LoRA 설정
lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

# LoRA 적용
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

model.train()   # 모델 학습 모드 설정
# 2. 리워드 모델 생성 및 학습
reward_model = AutoModelForCasulML.from_pretrained(
    model_name,
    quantization_config=bnb_config
)

reward_model = prepare_model_for_kbit_training(reward_model)
# 3. orpo 설정 및 적용 
orpo_config = ORPOConfig(
    output_dir="./orpo_output",
    per_device_train_batch_size=8,
    gradient_accumulation_steps=16,
    learning_rate=2e-6,
    num_train_epochs=5,     # 3~5가 적합
    logging_steps=50,
    save_total_limit=2,
    remove_unused_column=False,
    fp16=True,
    gradient_checkpointing=True,
    max_grad_norm=1.0,
    warmup_steps=100,
    save_steps=500
)

DPO

  • 선호-비선호 응답 쌍으로 선호 응답을 잘 예측하도록 학습시키는 방식 
  • PPO와 달리 보상모델이 필요하지 않아 간단하게 구현할 수 있다.
class DPOTrainer(Trainer):
    def compute_loss(self, model, inputs, beta=0.1, *args, **kwargs):
    	# 1. 입력값 준비 
        input_ids=inputs['input_ids'].to(model.device),
        attention_mask=inputs['attention_mask'].to(model.device),
        preferred_ids=inputs['preferred_ids'].to(model.device),
        dispreferred_ids=inputs['dispreferred_ids'].to(model.device)
		
        # 2. 두 응답 각각의 모델에 통과시킴 
        preferred_output = model(input_ids=input_ids, attention_mask=attention_mask, labels=preferred_ids)
        attention_mask=attention_mask, lables=preferred_ids
        dispreferred_output = model(input_ids=input_ids, attention_mask=attention_mask, labels=dispreferred_ids)
        attention_mask=attention_mask, lables=dispreferred_ids
		
        # 3. 선호 비교 후 로스를 계산
        preferred_loss=preferred_output.loss
        dispreferred_loss=dispreferred_output.loss

        loss = -F.logsigmoid(beta * (dispreferred_loss - preferred_loss)).mean()
        return loss

 

⭐checkpoint 설정

checkpoint

: 학습 중간에 저장한 모델 상태(가중치, 옵티마이저, 스텝 정보)의 기록

: TrainingArguments에서 확인 가능 (save_steps, save_total_limit)


💡 Keep

이번주는 없습니다. (열심히 부지런히 살기)

 

⚠️ Problem

연휴라고 너무 펑펑펑 놀지 않기

 

🔥 Try

다음주에 시작될 프로젝트를 위해 제때 복습하고 방향성 잘 설계하기

파인튜닝 지도학습, 강화학습을 제대로 복습하면서 앞으로 있을 프로젝트에 파인튜닝을 어떻게 해야 적합할지에 대해 조금 더 고민해봐야겠다.