Hugging Face Transformers 모범 사례
허깅페이스 트랜스포머(Transformers) 라이브러리 사용에 대한 종합 가이드로, 모델 로딩, 토크나이제이션, 파인튜닝 워크플로우, 파이프라인 사용법, 사용자 정의 데이터셋 및 배포 최적화를 다룹니다.
빠른 참조
이 스킬을 사용해야 할 때:
- 사전 훈련된 트랜스포머 로딩 및 사용 (BERT, GPT, T5, LLaMA 등)
- 사용자 정의 데이터로 모델 파인튜닝
- NLP 태스크 구현 (분류, QA, 생성 등)
- 추론 최적화 (양자화, ONNX 등)
- 토크나이제이션 문제 디버깅
- Hugging Face 파이프라인 사용
- 트랜스포머를 프로덕션에 배포
다루는 모델:
- 인코더: BERT, RoBERTa, DeBERTa, ALBERT
- 디코더: GPT-2, GPT-Neo, LLaMA, Mistral
- 인코더-디코더: T5, BART, Flan-T5
- 비전: ViT, CLIP, Stable Diffusion
파트 1: 모델 로딩 패턴
패턴 1: 기본 모델 로딩
from transformers import AutoModel, AutoTokenizer
# 모델과 토크나이저 로딩
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
# 특정 태스크용
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(
model_name,
num_labels=3 # 3-클래스 분류용
)
패턴 2: 특정 설정으로 로딩
from transformers import AutoConfig, AutoModel
# 설정 수정
config = AutoConfig.from_pretrained("bert-base-uncased")
config.hidden_dropout_prob = 0.2 # 사용자 정의 드롭아웃
config.attention_probs_dropout_prob = 0.2
# 사용자 정의 설정으로 모델 로딩
model = AutoModel.from_pretrained("bert-base-uncased", config=config)
# 또는 설정으로 처음부터 모델 생성
model = AutoModel.from_config(config)
패턴 3: 양자화된 모델 로딩 (메모리 효율적)
from transformers import AutoModel, BitsAndBytesConfig
import torch
# 8비트 양자화 (메모리 50% 감소)
quantization_config = BitsAndBytesConfig(load_in_8bit=True)
model = AutoModel.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=quantization_config,
device_map="auto" # 자동 디바이스 배치
)
# 4비트 양자화 (메모리 75% 감소)
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True
)
model = AutoModel.from_pretrained(
"meta-llama/Llama-2-13b-hf",
quantization_config=quantization_config,
device_map="auto"
)
패턴 4: 로컬 경로에서 로딩
# 로컬에 모델 저장
model.save_pretrained("./my-model")
tokenizer.save_pretrained("./my-model")
# 로컬 경로에서 로딩
model = AutoModel.from_pretrained("./my-model")
tokenizer = AutoTokenizer.from_pretrained("./my-model")
파트 2: 토크나이제이션 모범 사례
중요한 토크나이제이션 패턴
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
# ✅ 올바름: 모든 필수 인자 포함
tokens = tokenizer(
text,
padding=True, # 배치 내 가장 긴 길이에 맞춰 패딩
truncation=True, # max_length로 자르기
max_length=512, # 최대 시퀀스 길이
return_tensors="pt" # PyTorch 텐서 반환
)
# 구성 요소 접근
input_ids = tokens['input_ids'] # 토큰 ID
attention_mask = tokens['attention_mask'] # 패딩 마스크
token_type_ids = tokens.get('token_type_ids') # 세그먼트 ID (BERT)
# ❌ 잘못됨: 중요한 인자 누락
tokens = tokenizer(text) # 패딩, 자르기, 텐서 형식 없음!
배치 토크나이제이션
# 여러 텍스트를 효율적으로 토크나이즈
texts = ["첫 번째 텍스트", "두 번째 텍스트", "세 번째 텍스트"]
tokens = tokenizer(
texts,
padding=True, # 배치 내 가장 긴 길이에 맞춰 패딩
truncation=True,
max_length=128,
return_tensors="pt"
)
# 결과 형태: [batch_size, max_length]
print(tokens['input_ids'].shape) # torch.Size([3, max_len_in_batch])
특수 토큰 처리
# 특수 토큰 추가
tokenizer.add_special_tokens({
'additional_special_tokens': ['[CUSTOM]', '[MARKER]']
})
# 모델 임베딩 크기 조정
model.resize_token_embeddings(len(tokenizer))
# 특수 토큰을 유지하며 인코딩
text = "Hello [CUSTOM] world"
tokens = tokenizer(text, add_special_tokens=True)
# 디코딩
decoded = tokenizer.decode(tokens['input_ids'][0], skip_special_tokens=False)
다양한 태스크를 위한 토크나이제이션
# 텍스트 분류 (단일 시퀀스)
tokens = tokenizer(
"이 영화는 훌륭했습니다!",
padding="max_length",
truncation=True,
max_length=128,
return_tensors="pt"
)
# 질문 답변 (시퀀스 쌍)
question = "프랑스의 수도는 무엇입니까?"
context = "프랑스는 유럽의 한 국가입니다. 파리가 그 수도입니다."
tokens = tokenizer(
question,
context,
padding="max_length",
truncation="only_second", # 컨텍스트만 자르기
max_length=384,
return_tensors="pt"
)
# 텍스트 생성 (디코더 전용 모델)
prompt = "옛날옛적에"
tokens = tokenizer(prompt, return_tensors="pt")
# 생성 입력에는 패딩 불필요
파트 3: 파인튜닝 워크플로우
패턴 1: Trainer를 사용한 간단한 파인튜닝
from transformers import (
AutoModelForSequenceClassification,
AutoTokenizer,
Trainer,
TrainingArguments
)
from datasets import load_dataset
# 1. 데이터셋 로딩
dataset = load_dataset("glue", "mrpc")
# 2. 모델 로딩
model = AutoModelForSequenceClassification.from_pretrained(
"bert-base-uncased",
num_labels=2
)
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
# 3. 데이터셋 토크나이즈
def tokenize_function(examples):
return tokenizer(
examples["sentence1"],
examples["sentence2"],
padding="max_length",
truncation=True,
max_length=128
)
tokenized_datasets = dataset.map(tokenize_function, batched=True)
# 4. 훈련 인자 정의
training_args = TrainingArguments(
output_dir="./results",
evaluation_strategy="epoch",
learning_rate=2e-5,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
num_train_epochs=3,
weight_decay=0.01,
logging_dir="./logs",
logging_steps=100,
save_strategy="epoch",
load_best_model_at_end=True,
metric_for_best_model="accuracy",
)
# 5. 메트릭 정의
from datasets import load_metric
import numpy as np
metric = load_metric("accuracy")
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels)
# 6. Trainer 생성
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
compute_metrics=compute_metrics,
)
# 7. 훈련
trainer.train()
# 8. 저장
trainer.save_model("./fine-tuned-model")
패턴 2: LoRA 파인튜닝 (파라미터 효율적)
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model, TaskType
# 기본 모델 로딩
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
load_in_8bit=True, # 메모리 효율을 위한 8비트
device_map="auto"
)
# LoRA 설정
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=8, # LoRA 랭크
lora_alpha=32, # LoRA 알파
lora_dropout=0.1,
target_modules=["q_proj", "v_proj"], # 적응시킬 레이어
)
# LoRA 적용
model = get_peft_model(model, lora_config)
# 훈련 가능한 파라미터 확인
model.print_trainable_parameters()
# 출력: trainable params: 4.2M || all params: 6.7B || trainable%: 0.062%
# Trainer로 훈련 (이전과 동일)
# LoRA 파라미터만 업데이트됩니다!
패턴 3: 사용자 정의 훈련 루프
import torch
from torch.utils.data import DataLoader
from transformers import AdamW, get_scheduler
# 데이터로더 준비
train_dataloader = DataLoader(tokenized_datasets["train"], batch_size=16, shuffle=True)
eval_dataloader = DataLoader(tokenized_datasets["validation"], batch_size=16)
# 옵티마이저
optimizer = AdamW(model.parameters(), lr=2e-5)
# 학습률 스케줄러
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=500,
num_training_steps=num_training_steps
)
# 훈련 루프
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
for epoch in range(num_epochs):
model.train()
for batch in train_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
# 평가
model.eval()
for batch in eval_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
with torch.no_grad():
outputs = model(**batch)
# 메트릭 계산
파트 4: Pipeline 사용법 (고수준 API)
텍스트 분류 Pipeline
from transformers import pipeline
# Pipeline 로딩
classifier = pipeline(
"text-classification",
model="distilbert-base-uncased-finetuned-sst-2-english"
)
# 단일 예측
result = classifier("나는 이 제품이 정말 마음에 듭니다!")
# [{'label': 'POSITIVE', 'score': 0.9998}]
# 배치 예측
results = classifier([
"훌륭한 서비스!",
"끔찍한 경험이었습니다",
"평균적인 품질"
])
질문 답변 Pipeline
qa_pipeline = pipeline("question-answering", model="distilbert-base-uncased-distilled-squad")
result = qa_pipeline(
question="프랑스의 수도는 무엇입니까?",
context="프랑스는 유럽의 한 국가입니다. 수도는 파리이며, 아름다운 도시입니다."
)
# {'score': 0.98, 'start': 49, 'end': 54, 'answer': 'Paris'}
텍스트 생성 Pipeline
generator = pipeline("text-generation", model="gpt2")
outputs = generator(
"옛날옛적에",
max_length=50,
num_return_sequences=3,
temperature=0.7,
top_k=50,
top_p=0.95,
do_sample=True
)
for output in outputs:
print(output['generated_text'])
제로샷 분류 Pipeline
classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
result = classifier(
"이것은 파이썬 프로그래밍에 관한 강좌입니다.",
candidate_labels=["교육", "기술", "비즈니스", "스포츠"]
)
# {'sequence': '...', 'labels': ['교육', '기술', ...], 'scores': [0.85, 0.12, ...]}
파트 5: 추론 최적화
최적화 1: 배치 처리
# ❌ 느림: 하나씩 처리
for text in texts:
output = model(**tokenizer(text, return_tensors="pt"))
# ✅ 빠름: 배치로 처리
batch_size = 32
for i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
inputs = tokenizer(batch, padding=True, truncation=True, return_tensors="pt")
outputs = model(**inputs)
최적화 2: 혼합 정밀도 (AMP)
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
for batch in dataloader:
optimizer.zero_grad()
# 혼합 정밀도 순전파
with autocast():
outputs = model(**batch)
loss = outputs.loss
# 스케일된 그래디언트로 역전파
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
최적화 3: ONNX 내보내기
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from optimum.onnxruntime import ORTModelForSequenceClassification
# ONNX로 내보내기
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")
model.save_pretrained("./onnx-model", export=True)
# ONNX 모델 로딩 (더 빠른 추론)
ort_model = ORTModelForSequenceClassification.from_pretrained("./onnx-model")
# 추론 (2-3배 빠름)
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
inputs = tokenizer("Hello world", return_tensors="pt")
outputs = ort_model(**inputs)
최적화 4: 동적 양자화
import torch
# 모델을 int8로 양자화
quantized_model = torch.quantization.quantize_dynamic(
model,
{torch.nn.Linear}, # 선형 레이어만 양자화
dtype=torch.qint8
)
# 4배 작은 모델, CPU에서 2-3배 빠른 추론
파트 6: 일반적인 문제 및 해결 방법
문제 1: CUDA 메모리 부족
문제: RuntimeError: CUDA out of memory
해결 방법:
# 해결 방법 1: 배치 크기 줄이기
training_args = TrainingArguments(
per_device_train_batch_size=8, # 기존 32
gradient_accumulation_steps=4, # 유효 배치 = 8*4 = 32
)
# 해결 방법 2: 그래디언트 체크포인팅 사용
model.gradient_checkpointing_enable()
# 해결 방법 3: 8비트 모델 사용
from transformers import BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(load_in_8bit=True)
model = AutoModel.from_pretrained("model-name", quantization_config=quantization_config)
# 해결 방법 4: 캐시 지우기
import torch
torch.cuda.empty_cache()
문제 2: 느린 토크나이제이션
문제: 토크나이제이션이 병목
해결 방법:
# 해결 방법 1: 빠른 토크나이저 사용
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased", use_fast=True)
# 해결 방법 2: 데이터셋을 한 번 토크나이즈하고 캐싱
tokenized_dataset = dataset.map(
tokenize_function,
batched=True,
num_proc=4, # 병렬 처리
remove_columns=dataset.column_names,
load_from_cache_file=True # 결과 캐싱
)
# 해결 방법 3: 토크나이제이션에 더 큰 배치 사용
tokenizer(
texts,
padding=True,
truncation=True,
max_length=512,
return_tensors="pt",
batched=True, # 여러 텍스트를 한 번에 처리
batch_size=1000
)
문제 3: 일관되지 않은 결과
문제: 동일한 입력에 대해 모델 출력이 다름
해결 방법:
# 재현성을 위한 시드 설정
import random
import numpy as np
import torch
def set_seed(seed: int):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
set_seed(42)