Text Classification Fine Tuning

Author

차상진

Published

March 17, 2025

- colab에서 실습하길 바랍니다.

# !git clone https://github.com/rickiepark/nlp-with-transformers.git
# %cd nlp-with-transformers
# from install import *
# install_requirements(chapter=2)

1. Data loading & Emotion encoding

# 허깅페이스 데이터셋을 사용하기
from huggingface_hub import list_datasets
from datasets import load_dataset
from datasets import ClassLabel

emotions = load_dataset("emotion")

from transformers import AutoTokenizer
emotions['train'].features['label'] = ClassLabel(num_classes=6, names=['sadness', 'joy', 'love', 'anger', 'fear', 'surprise'])
model_ckpt = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)

def tokenize(batch):
    return tokenizer(batch["text"], padding=True, truncation=True)

emotions_encoded = emotions.map(tokenize, batched=True, batch_size=None)

- 코드 설명 1. emotion 데이터를 불러온다. 2. emotion 데이터에서 train에 있는 레이블을 6개의 감정으로 할당해준다. 3. model을 설정하고 tokenizer도 모델에 맞게 불러온다. 4. tokenize 함수를 선언하고 문장 길이를 맞추기 위해 padding과 truncation을 True로 설정한다. 5. emotion을 토크나이징 한다.

Text tokenizing

from transformers import AutoModel
import torch
from sklearn.metrics import accuracy_score, f1_score

text = "this is a test"
inputs = tokenizer(text, return_tensors="pt")
inputs['input_ids'].size()

- 코드 설명 1. 임의의 테스트 text를 생성 후 토크나이징을 해준다. 2. tokenizer가 반환하는 데이터를 PyTorch 텐서(torch.Tensor) 형식으로 변환하기 위해서 return_tensors=“pt”를 설정한다.

3. HF login

from huggingface_hub import notebook_login

notebook_login()

4. model

from transformers import AutoModelForSequenceClassification

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
num_labels = 6
model_ckpt = "distilbert-base-uncased"

# distilbert-base-uncased가 바디이고 AutoModelForSequenceClassification가 헤드이다.
# num_label이 6이므로 6개의 감정 클래스를 분류하는 헤드 하나가 추가된 것이다.
model = (AutoModelForSequenceClassification
         .from_pretrained(model_ckpt, num_labels=num_labels)
         .to(device))

- 코드 설명 1. GPU를 사용하기 위해 device 설정. 2. label의 개수는 위에서 할당한 대로 6개이고 model도 선언해준다. 3. 여기서 distilbert-base-uncased은 바디이고 AutoModelForSequenceClassification은 헤드이다. 사전학습된 bert모델에 감정 클래스 분류를 위해서 헤드를 추가했다.

5. Learning

from transformers import Trainer, TrainingArguments

batch_size = 64
logging_steps = len(emotions_encoded["train"]) // batch_size
model_name = f"{model_ckpt}-finetuned-emotion"
training_args = TrainingArguments(output_dir=model_name,
                                  num_train_epochs=2,
                                  learning_rate=2e-5,
                                  per_device_train_batch_size=batch_size,
                                  per_device_eval_batch_size=batch_size,
                                  weight_decay=0.01,
                                  evaluation_strategy="epoch",
                                  disable_tqdm=False,
                                  logging_steps=logging_steps,
                                  push_to_hub=True,
                                  save_strategy="epoch",
                                  load_best_model_at_end=True,
                                  log_level="error",
                                  report_to="none")
def compute_metrics(pred):
    labels = pred.label_ids
    preds = pred.predictions.argmax(-1)
    f1 = f1_score(labels, preds, average="weighted")
    acc = accuracy_score(labels, preds)
    return {"accuracy": acc, "f1": f1}

trainer = Trainer(model=model, args=training_args,
                  compute_metrics=compute_metrics,
                  train_dataset=emotions_encoded["train"],
                  eval_dataset=emotions_encoded["validation"],
                  tokenizer=tokenizer)
trainer.train()

- 코드 설명 1. training argument를 설정해준다. 2. 학습을 하고 결과를 보니 Loss, Accuracy, F1 들이 전부 향상된 것을 볼 수 있다. 즉 Fine tuning이 잘 이루어 졌다고 볼 수 있다.

6. Prediction

output = trainer.predict(emotions_encoded["validation"])
output.metrics
import numpy as np
yy = np.argmax(output.predictions,axis=1)
yy

7. Error analyze

from torch.nn.functional import cross_entropy

def forward_pass_with_label(batch):
    # 모든 입력 텐서를 모델과 같은 장치로 이동합니다.
    inputs = {k:v.to(device) for k,v in batch.items()
              if k in tokenizer.model_input_names}

    with torch.no_grad(): # 역전파를 사용하지 않음 (평가 단계이므로)
        output = model(**inputs) # 입력 데이터를 모델에 전달
        pred_label = torch.argmax(output.logits, axis=-1) # 가장 높은 점수를 가진 클래스 선택
        loss = cross_entropy(output.logits, batch["label"].to(device), # loss 계산
                             reduction="none") # 평균을 내지 않고 개별 샘플의 손실을 반환

    return {"loss": loss.cpu().numpy(), # 결과를 CPU로 이동 및 numpy 배열로 변환 # PyTorch 텐서는 dataset에서 다루기 어렵다.
            "predicted_label": pred_label.cpu().numpy()}
# 데이터셋을 다시 파이토치 텐서로 변환
emotions_encoded.set_format("torch",
                            columns=["input_ids", "attention_mask", "label"])
# 손실 값을 계산
emotions_encoded["validation"] = emotions_encoded["validation"].map(
    forward_pass_with_label, batched=True, batch_size=16)

- 코드 설명 1. 모든 입력 텐서가 모델과 같아야 계산이 가능하기에 같은 장치로 이동 2. 입력 데이터를 **inputs으로 모델에 전달 후 가장 높은 logits값을 가진 클래스를 선택한다. 3. 이제 loss를 계산하고 평균을 내지 않는 이유는 label마다 loss값의 편차가 있는 것을 확인하기 위해 평균을 내지 않는다. 4. 결과를 numpy로 변환. (datasets.map() 함수는 PyTorch 텐서 대신 리스트나 NumPy 배열을 반환해야 함.) 5. 손실값을 계산하기 위해 PyTorch 텐서로 전환한다. (batch형태로 계산하기 위해서)

8. int -> str 변환

def label_int2str(row):
    return emotions["train"].features["label"].int2str(row)

emotions_encoded.set_format("pandas")
cols = ["text", "label", "predicted_label", "loss"]
df_test = emotions_encoded["validation"][:][cols]
df_test["label"] = df_test["label"].apply(label_int2str)
df_test["predicted_label"] = (df_test["predicted_label"]
                              .apply(label_int2str))
df_test.sort_values("loss", ascending=False).head(10)
df_test.sort_values("loss", ascending=True).head(10)

- 코드 설명 1. label에 있는 int형 값들을 사람이 알아보기 쉽게 str형태로 바꿔준다. 2. 결과를 살펴보면 sadness 레이블들은 loss도 적고 잘 맞추는 것을 알 수 있다.

9. Save model & Publish

trainer.push_to_hub(commit_message="Training completed!")
from transformers import pipeline

model_id = "SangJinCha/distilbert-base-uncased-finetuned-emotion"
classifier = pipeline("text-classification", model=model_id)

이제 모델에 hugging face 사용자 이름을 붙혀서 push 해주면 된다.