1. Hugging Face와 Transformers
Hugging Face란?
- NLP 모델을 누구나 쉽게 사용할 수 있게 하는 플랫폼이다.
- 최신 논문이 나오면 빠르게 프레임워크에 반영하고, Pretrained model, Dataset, Tokenizer 등을 표준화해서 제공한다.
Transformers란?
- Hugging Face에서 만든 파이썬 라이브러리로, BERT, GPT 등 최신 NLP 모델들을 쉽게 call해서 사용할 수 있게 만든 표준 프레임워크이다.

Hugging Face가 다른 플랫폼에 비해 더 많이 사용되는 이유
- 최신 NLP 논문과 모델이 가장 빠르게 업데이트되며, 가장 많은 종류의 모델을 지원한다.
- PyTorch, TensorFlow 등 딥러닝 프레임워크에 상관없이 사용 가능하다.
- 모델이나 task가 바뀌어도 코드는 거의 동일하게 유지할 수 있도록 API가 모듈화 되어있다.
2. Transformers의 설계 구조 (Class Structure)
모델링의 흐름 (Task 정의 → Dataset 가공 → Model 선택 → 학습 → 저장배포)을 처리하기 위해 Model, Tokenizer, Config, Trainer 등의 Class들로 구성되어 있다.
2.1 Model
모델을 정의하고 Weight를 관리하는 클래스이다.
- PretrainedModel: 모든 모델의 부모 클래스로, 모델 다운로드, 저장(save_pretrained), 로드(from_pretrained) 등의 메서드를 제공
- AutoModel vs Specific Model:
- AutoModel은 모델 ID(ex: bert-base-cased)만 입력하면 그에 맞는 모델 구조를 자동으로 불러오며 범용적으로 쓰기 좋다.
- Specific Model은 특정 모델(ex: BertModel)의 클래스를 직접 명시하며, 모델의 Input/Output shape이나 구조를 명확히 파악하고 사용할 때 많이 쓰인다.
2.2 Tokenizer
토크나이저란 이름 그대로 input 데이터를 숫자 토큰 형태로 변환하는 클래스이다.
- input_ids, token_type_ids, attention_mask 등을 생성하며, Padding, Truncation 등의 전처리도 수행한다.
- AutoTokenizer로 호출하는데, AutoModel과 마찬가지로 ID만 알면 자동으로 해당 모델에 맞는 토크나이저를 로드해준다.
- 이때, Model ID와 동일한 Tokenizer ID를 사용해야 한다.
2.3 Condig
모델 파라미터 등의 설정을 포함하고 있는 JSON 파일 기반의 클래스이다.
- Layer 수, Attention head 수, 학습 파라미터들, Special Token 정보 등을 포함한다.
- Pretrained Model을 로드할 때 자동으로 같이 로드되지만, 하이퍼파라미터를 바꾸거나 처음부터 모델을 만들 경우엔 직접 수정해야한다.
2.4 Trainer
Training, Fine-tuning, Evaluation 과정을 모두 담고 있는 클래스이다.
- TrainingArgumets: Trainer에게 전달하는 학습 설정값들의 집합.
- 이때 group_by_length=True로 설정하면, 학습 시 길이가 비슷한 데이터끼리 묶어서 배치를 구성한다.
- 이 Bucketing 기법은 쓸모없는 padding을 최소화하므로 학습 효율을 높인다(시간 절약 & Acc 소폭 향상 가능).
- 학습 루프를 직접 짤 필요 없이 알아서 해준다는 장점이 있다(데이터, 모델, 설정값 던져주면 알아서 다 해줌. 너무 편함).
3. NSMC(네이버 영화 리뷰 코퍼스) 데이터 감성분석 실습
Hugging Face에서 pretrained Model을 가져와서 간단한 파인튜닝을 해보자.
3.1 라이브러리 설치 & 로드
!pip install transformers datasets evaluate accelerate scikit-learn
import pandas as pd
import numpy as np
import torch
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from transformers import TrainingArguments, Trainer, DataCollatorWithPadding
import evaluate
3.2 데이터셋 로드
# 데이터셋 로드
# NSMC 데이터셋의 원본 URL
train_url = "https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt"
test_url = "https://raw.githubusercontent.com/e9t/nsmc/master/ratings_test.txt"
# 구분자를 탭(\t)으로
dataset = load_dataset(
"csv",
data_files={"train": train_url, "test": test_url},
delimiter="\t",
keep_default_na=False # 빈 문자열이 NaN으로 변환되는 것을 방지
)
# 데이터 구조 확인
print(f"Train dataset size: {len(dataset['train'])}")
print(f"Test dataset size: {len(dataset['test'])}")
# 데이터 예시 및 라벨 분포 확인
# 0: 부정, 1:긍정
df_train = pd.DataFrame(dataset['train'])
print("\n--- 데이터 예시 ---")
print(df_train.head())
print("\n--- 라벨 분포 ---")
print(df_train['label'].value_counts())
Train dataset size: 150000
Test dataset size: 50000
--- 데이터 예시 ---
id document label
0 9976970 아 더빙.. 진짜 짜증나네요 목소리 0
1 3819312 흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나 1
2 10265843 너무재밓었다그래서보는것을추천한다 0
3 9045019 교도소 이야기구먼 ..솔직히 재미는 없다..평점 조정 0
4 6483659 사이몬페그의 익살스런 연기가 돋보였던 영화!스파이더맨에서 늙어보이기만 했던 커스틴 ... 1
--- 라벨 분포 ---
label
0 75173
1 74827
Name: count, dtype: int64
라벨 분포는 거의 50:50으로 아주 균형적이다.
3.3 Model & Tokenizer 로드
위에서 정리한 내용을 바탕으로 pretrained 모델과 토크나이저를 로드해보자.
로드할 모델 id는 "klue/bert-base"이다.
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Device: {device}")
model_id = "klue/bert-base"
# Tokenizer 로드
tokenizer = AutoTokenizer.from_pretrained(model_id)
# model 로드
model = AutoModelForSequenceClassification.from_pretrained(
model_id,
num_labels=2
).to(device)
따로 모델과 토크나이저를 정의할 필요 없이 그냥 딸깍 하면 끝이다
3.4 데이터 전처리
# 전처리 함수
def preprocessing(examples):
return tokenizer(
examples["document"],
truncation=True,
max_length=128,
padding=True
)
# 전체 데이터셋에 적용
tokenized_datasets = dataset.map(preprocessing, batched=True)
print("Columns (Before):", tokenized_datasets['train'].column_names)
# 학습에 불필요한 칼럼 제거
tokenized_datasets = tokenized_datasets.remove_columns(['id', 'document'])
print("Columns (After):", tokenized_datasets['train'].column_names)
# list -> tensor 변환
tokenized_datasets.set_format("torch")
print("Train dataset example:", tokenized_datasets['train'][0])
Map: 0%| | 0/50000 [00:00<?, ? examples/s]
Columns (Before): ['id', 'document', 'label', 'input_ids', 'token_type_ids', 'attention_mask']
Columns (After): ['label', 'input_ids', 'token_type_ids', 'attention_mask']
Train dataset example: {'label': tensor(0), 'input_ids': tensor([ 2, 1376, 831, 2604, 18, 18, 4229, 9801, 2075, 2203, 2182, 4243,
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0]), 'token_type_ids': tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0]), 'attention_mask': tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0])}
max_length를 128로 잡고 패딩까지 해주었다.
3.5 모델 학습
# Metric 로드
accuracy = evaluate.load("accuracy")
def compute_metrics(eval_pred):
predictions, labels = eval_pred
predictions = np.argmax(predictions, axis=1)
return accuracy.compute(predictions=predictions, references=labels)
# 학습 인자(args) 설정
training_args = TrainingArguments(
output_dir = "models",
eval_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
per_device_train_batch_size=32,
per_device_eval_batch_size=32,
fp16=True, # GPU 메모리 절약 + 속도 향상
num_train_epochs=3,
weight_decay=0.01,
load_best_model_at_end=True,
metric_for_best_model="accuracy",
group_by_length=False # Bucketing 비교를 위해 여기선 False
)
# Trainer 정의
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["test"],
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
# 학습 진행
train_result_baseline = trainer.train()
metrics_baseline = train_result_baseline.metrics
print(f"Training Time: {metrics_baseline['train_runtime']:.2f} sec")
print(f"Final Accuracy: {trainer.evaluate()['eval_accuracy']:.4f}")
[14064/14064 51:11, Epoch 3/3]
Epoch Training Loss Validation Loss Accuracy
1 0.247000 0.241034 0.902000
2 0.184800 0.243800 0.905480
3 0.116600 0.298962 0.905940
Training Time: 3072.16 sec
[1563/1563 01:13]
Final Accuracy: 0.9059
Trainer 클래스를 사용하니 학습, 검증 함수를 이것저것 세팅할 필요 없이 알아서 다 해주는 모습이다.
3 에폭만 돌렸음에도 Acc가 90 가량 나오는 것을 알 수 있는데, pretrained 모델의 힘이다.
하지만 학습 시간이 약 50분으로 꽤 오래 걸렸다.
근데 위의 2.4절에서 Trainer의 인자로 group_by_length=True를 줘서 Bucketing을 활성화 시키면 학습 효율을 높일 수 있다고 했었으니, 한 번 적용해보고 시간과 성능 차이를 baseline 모델과 비교해보자.
+ dynamic padding (동적 패딩: 고정 길이 패딩이 아닌, 배치(batch) 내의 모든 데이터 시퀀스 길이를 그 배치에서 가장 긴 시퀀스의 길이에 맞춰서 동적으로 결정하고 패딩하는 방식)까지 적용해보자.
4. Bucketing & dynamic padding 적용 모델과의 비교
def preprocessing_no_padding(examples):
return tokenizer(
examples["document"],
truncation=True,
max_length=128,
padding=False # dynamic padding 할 것이기 때문에 False
)
# 전체 데이터셋에 적용
tokenized_datasets = dataset.map(preprocessing_no_padding, batched=True)
print("Columns (Before):", tokenized_datasets['train'].column_names)
# 학습에 불필요한 칼럼 제거
tokenized_datasets = tokenized_datasets.remove_columns(['id', 'document'])
print("Columns (After):", tokenized_datasets['train'].column_names)
# list -> tensor 변환
tokenized_datasets.set_format("torch")
print("Train dataset example:", tokenized_datasets['train'][0])
Map: 0%| | 0/150000 [00:00<?, ? examples/s]
Map: 0%| | 0/50000 [00:00<?, ? examples/s]
Columns (Before): ['id', 'document', 'label', 'input_ids', 'token_type_ids', 'attention_mask']
Columns (After): ['label', 'input_ids', 'token_type_ids', 'attention_mask']
Train dataset example: {'label': tensor(0), 'input_ids': tensor([ 2, 1376, 831, 2604, 18, 18, 4229, 9801, 2075, 2203, 2182, 4243,
3]), 'token_type_ids': tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 'attention_mask': tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])}
아까와 달리 전처리 시에 패딩을 하지 않는다.
# Metric 로드
accuracy = evaluate.load("accuracy")
def compute_metrics(eval_pred):
predictions, labels = eval_pred
predictions = np.argmax(predictions, axis=1)
return accuracy.compute(predictions=predictions, references=labels)
training_args_bucketing = TrainingArguments(
output_dir = "models",
eval_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
per_device_train_batch_size=32,
per_device_eval_batch_size=32,
fp16=True, # GPU 메모리 절약 + 속도 향상
num_train_epochs=3,
weight_decay=0.01,
load_best_model_at_end=True,
metric_for_best_model="accuracy",
group_by_length=True # Bucketing
)
# Data Collator (dynamic padding 적용)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
# 모델 초기화
model_bucketing = AutoModelForSequenceClassification.from_pretrained(
model_id,
num_labels=2
).to(device)
trainer_bucketing = Trainer(
model=model_bucketing,
args=training_args_bucketing,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["test"],
tokenizer=tokenizer,
data_collator=data_collator,
compute_metrics=compute_metrics,
)
# 학습 진행
print("\n--- Bucketing 적용 학습 시작 ---")
train_result_d_b = trainer_bucketing.train()
metrics_d_b = train_result_d_b.metrics
print("\n=== Bucketing 적용 학습 결과 ===")
print(f"Training Time: {metrics_d_b['train_runtime']:.2f} sec")
print(f"Final Accuracy: {trainer_bucketing.evaluate()['eval_accuracy']:.4f}")
--- Bucketing 적용 학습 시작 ---
[14064/14064 24:51, Epoch 3/3]
Epoch Training Loss Validation Loss Accuracy
1 0.250500 0.242254 0.901980
2 0.175100 0.258474 0.904960
3 0.122400 0.295870 0.907060
=== Bucketing 적용 학습 결과 ===
Training Time: 1492.27 sec
[1563/1563 00:29]
Final Accuracy: 0.9071
4.1 실험 결과
Baseline 모델과 dynamic padding과 Bucketing을 적용한 Advanced 모델의 학습 소요 시간과 정확도를 비교해 보았다.
단, dynamic padding과 Bucketing 적용 여부 외의 모든 변인은 동일하게 했다.
model training Time Validation Acc
Baseline 51 min 0.9059
Advanced 25 min 0.9071
학습 결과, dynamic padding과 Bucketing 기법을 적용한 Advanced 모델이 baseline 모델에 비해 학습 소요 시간이 절반 가량 크게 감소한 것을 확인할 수 있었으며, Validation Accuracy 또한 미세하게 향상된 것을 확인할 수 있었다.
회고
느낀 점:
- 지금까지 토크나이저, 모델 등을 모두 직접 구현하고 바닥부터 학습시키다가 huggingface로 pretrined 토크나이저와 모델을 가져오니 신세계를 보았다. 학습 시간은 물론 성능이 이전과는 비교할 수 없을 정도로 좋아지니 참 마음이 편안했다. (실험 과정 자체도 코드가 훨씬 짧아지니 소요 시간도 줄어서 다른 공부를 할 여유가 늘어서 좋다.)
개선할 점:
- 시각화와 정성평가가 부족했던 것 같다.
- 팀원중 한 분은 wandb를 사용해 실험 관리를 하셨는데, 굉장히 유용한 tool로 보인다. 다음에 적용해보자.
'AI > NLP' 카테고리의 다른 글
| [12/09] 아이펠 리서치 15기 TIL | 미니 논문 작성 (1) | 2025.12.10 |
|---|---|
| [12/03] 아이펠 리서치 15기 TIL | InstructGPT | RLHF(Reinforcement Learning from Human Feedback) (0) | 2025.12.06 |
| [11/28] 아이펠 리서치 15기 TIL | Transformer 기반 NLP # 1. Transfer Learning, Language Modeling, ELMo (1) | 2025.12.01 |
| [11/24] 아이펠 리서치 15기 TIL | Seq2Seq 영-한 번역기 만들기 (0) | 2025.11.27 |
| [11/19] 아이펠 리서치 15기 TIL | WEAT score (1) | 2025.11.27 |