# !git clone https://github.com/rickiepark/nlp-with-transformers.git
# %cd nlp-with-transformers
# from install import *
# install_requirements(chapter=2)
Text Classification Fine Tuning
-
colab에서 실습하길 바랍니다.
1. Data loading & Emotion encoding
# 허깅페이스 데이터셋을 사용하기
from huggingface_hub import list_datasets
from datasets import load_dataset
from datasets import ClassLabel
= load_dataset("emotion")
emotions
from transformers import AutoTokenizer
'train'].features['label'] = ClassLabel(num_classes=6, names=['sadness', 'joy', 'love', 'anger', 'fear', 'surprise'])
emotions[= "distilbert-base-uncased"
model_ckpt = AutoTokenizer.from_pretrained(model_ckpt)
tokenizer
def tokenize(batch):
return tokenizer(batch["text"], padding=True, truncation=True)
= emotions.map(tokenize, batched=True, batch_size=None) emotions_encoded
-
코드 설명 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
= "this is a test"
text = tokenizer(text, return_tensors="pt")
inputs 'input_ids'].size() inputs[
-
코드 설명 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
= torch.device("cuda" if torch.cuda.is_available() else "cpu")
device = 6
num_labels = "distilbert-base-uncased"
model_ckpt
# distilbert-base-uncased가 바디이고 AutoModelForSequenceClassification가 헤드이다.
# num_label이 6이므로 6개의 감정 클래스를 분류하는 헤드 하나가 추가된 것이다.
= (AutoModelForSequenceClassification
model =num_labels)
.from_pretrained(model_ckpt, num_labels .to(device))
-
코드 설명 1. GPU를 사용하기 위해 device 설정. 2. label의 개수는 위에서 할당한 대로 6개이고 model도 선언해준다. 3. 여기서 distilbert-base-uncased은 바디이고 AutoModelForSequenceClassification은 헤드이다. 사전학습된 bert모델에 감정 클래스 분류를 위해서 헤드를 추가했다.
5. Learning
from transformers import Trainer, TrainingArguments
= 64
batch_size = len(emotions_encoded["train"]) // batch_size
logging_steps = f"{model_ckpt}-finetuned-emotion"
model_name = TrainingArguments(output_dir=model_name,
training_args =2,
num_train_epochs=2e-5,
learning_rate=batch_size,
per_device_train_batch_size=batch_size,
per_device_eval_batch_size=0.01,
weight_decay="epoch",
evaluation_strategy=False,
disable_tqdm=logging_steps,
logging_steps=True,
push_to_hub="epoch",
save_strategy=True,
load_best_model_at_end="error",
log_level="none") report_to
def compute_metrics(pred):
= pred.label_ids
labels = pred.predictions.argmax(-1)
preds = f1_score(labels, preds, average="weighted")
f1 = accuracy_score(labels, preds)
acc return {"accuracy": acc, "f1": f1}
= Trainer(model=model, args=training_args,
trainer =compute_metrics,
compute_metrics=emotions_encoded["train"],
train_dataset=emotions_encoded["validation"],
eval_dataset=tokenizer)
tokenizer trainer.train()
-
코드 설명 1. training argument를 설정해준다. 2. 학습을 하고 결과를 보니 Loss, Accuracy, F1 들이 전부 향상된 것을 볼 수 있다. 즉 Fine tuning이 잘 이루어 졌다고 볼 수 있다.
6. Prediction
= trainer.predict(emotions_encoded["validation"])
output output.metrics
import numpy as np
= np.argmax(output.predictions,axis=1)
yy yy
7. Error analyze
from torch.nn.functional import cross_entropy
def forward_pass_with_label(batch):
# 모든 입력 텐서를 모델과 같은 장치로 이동합니다.
= {k:v.to(device) for k,v in batch.items()
inputs if k in tokenizer.model_input_names}
with torch.no_grad(): # 역전파를 사용하지 않음 (평가 단계이므로)
= model(**inputs) # 입력 데이터를 모델에 전달
output = torch.argmax(output.logits, axis=-1) # 가장 높은 점수를 가진 클래스 선택
pred_label = cross_entropy(output.logits, batch["label"].to(device), # loss 계산
loss ="none") # 평균을 내지 않고 개별 샘플의 손실을 반환
reduction
return {"loss": loss.cpu().numpy(), # 결과를 CPU로 이동 및 numpy 배열로 변환 # PyTorch 텐서는 dataset에서 다루기 어렵다.
"predicted_label": pred_label.cpu().numpy()}
# 데이터셋을 다시 파이토치 텐서로 변환
"torch",
emotions_encoded.set_format(=["input_ids", "attention_mask", "label"])
columns# 손실 값을 계산
"validation"] = emotions_encoded["validation"].map(
emotions_encoded[=True, batch_size=16) forward_pass_with_label, batched
-
코드 설명 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)
"pandas")
emotions_encoded.set_format(= ["text", "label", "predicted_label", "loss"]
cols = emotions_encoded["validation"][:][cols]
df_test "label"] = df_test["label"].apply(label_int2str)
df_test["predicted_label"] = (df_test["predicted_label"]
df_test[apply(label_int2str)) .
"loss", ascending=False).head(10) df_test.sort_values(
"loss", ascending=True).head(10) df_test.sort_values(
-
코드 설명 1. label에 있는 int형 값들을 사람이 알아보기 쉽게 str형태로 바꿔준다. 2. 결과를 살펴보면 sadness 레이블들은 loss도 적고 잘 맞추는 것을 알 수 있다.
9. Save model & Publish
="Training completed!") trainer.push_to_hub(commit_message
from transformers import pipeline
= "SangJinCha/distilbert-base-uncased-finetuned-emotion"
model_id = pipeline("text-classification", model=model_id) classifier
이제 모델에 hugging face 사용자 이름을 붙혀서 push 해주면 된다.