TOP
class="layout-aside-left paging-number">
본문 바로가기
[파이썬 Projects]/<파이썬 Gen AI, LLM>

[LLM] 텍스트 분류 모델 학습시키기

by 기록자_Recordian 2024. 12. 27.
728x90
반응형
이전 내용
 

[LLM] 허깅페이스 라이브러리 사용법 익히기

허깅페이스 [AI 플랫폼] 허깅페이스: AI와 머신러닝의 새로운 지평허깅페이스란? 허깅페이스(Hugging Face)는 자연어 처리(NLP)와 머신러닝(ML) 커뮤니티에서 가장 혁신적이고 중요한 플랫폼 중 하나

puppy-foot-it.tistory.com


모델 학습시키기

 

한국어 기사 제목을 바탕으로 기사의 카테고리를 분류하는 텍스트 분류 모델을 학습하는 실습을 진행해 본다.

 

먼저 실습에 사용할 데이터셋을 준비하고 모델과 토크나이저를 불러와 모델을 학습시킨다.

허깅페이스 트랜스포머에서는 간편하게 모델 학습을 수행할 수 있도록 학습 과정을 추상화한 트레이너(Trainer) API를 제공하는데, 이를 사용하면 학습을 간편하게 할 수 있다는 장점이 있지만 내부에서 어떤 과정을 거치는지 알기 어렵다(블랙박스)는 단점도 있다.


데이터 준비

 

실습 데이터는 KLUE 데이터셋의 YNAT 서브셋을 활용한다. YNAT에는 연합 뉴스 기사의 제목과 기사가 속한 카테고리 정보가 있는데, 이를 바탕으로 카테고리를 예측하는 모델을 만들어 본다.

먼저 데이터셋을 불러온 뒤, KLUE의 YNAT 학습 및 검증 데이터셋을 다운로드해 각각 klue_tc_train, klue_tc_eval 변수에 저장한다.

# 모델 학습에 사용할 연합 뉴스 데이터셋 다운로드
from datasets import load_dataset
klue_tc_train = load_dataset('klue', 'ynat', split='train')
klue_tc_eval = load_dataset('klue', 'ynat', split='validation')
klue_tc_train

▶ klue_tc_train에 저장된 데이터를 확인하면 뉴스 제목(title), 뉴스가 속한 카테고리(label) 등의 칼럼으로 이뤄진 총 45678개의 데이터가 있다.

 

개별 데이터의 형태를 보기 위해 첫 번째 데이터를 예시로 살펴본다.

klue_tc_train[0]

각 칼럼의 의미는 다음과 같다.

  • guid: 데이터의 고유 ID
  • title: 뉴스 제목
  • label: 속한 카테고리 ID
  • url: 뉴스 링크
  • date: 뉴스 입력 시간

데이터에는 레이블 값이 숫자로 되어 있는데, 어떤 카테고리인지 확인하기 위해 featurs 속성에서 label 칼럼의 항목별 이름을 확인한다.

klue_tc_train.features['label'].names

 

분류 모델을 학습시킬 때 guid, url, date 컬럼을 필요하지 않으므로 데이터에서 제거한다.

klue_tc_train = klue_tc_train.remove_columns(['guid', 'url', 'date'])
klue_tc_eval = klue_tc_eval.remove_columns(['guid', 'url', 'date'])
klue_tc_train

 

카테고리를 확인하기 쉽도록 label_str 컬럼을 추가한다.

int2str 메서드에 아이디1을 입력하면 '경제' 카테고리를 반환하는 것을 확인할 수 있다.

klue_tc_train.features['label'].int2str(1)

이를 활용해 label 컬럼의 숫자형 아이디를 카테고리 이름으로 변환하는 make_str_label 함수를 정의하고 데이터셋의 map 메서드를 사용해 label_str 컬럼을 추가한다.

klue_tc_label = klue_tc_train.features['label']

def make_str_label(batch):
    batch['label_str'] = klue_tc_label.int2str(batch['label'])
    return batch

klue_tc_train = klue_tc_train.map(make_str_label, batched=True, batch_size=1000)
klue_tc_train[0]

 

학습 데이터 중 10,000개만 추출하여 학습 데이터셋과 테스트 데이터셋으로 분리한다. 데이터셋에서 train_test_split 메서드를 사용해 test_size를 10,000으로 지정해 랜덤으로 10,000개의 데이터를 추출한다. 학습이 잘되고 있는지 확인할 검증 데이터와 성능 확인에 사용할 테스트 데이터는 검증 데이터셋에서 각각 1,000개씩 뽑아 사용한다.

train_dataset = klue_tc_train.train_test_split(test_size=10_000, shuffle=True, seed=42)['test']
dataset = klue_tc_eval.train_test_split(test_size=1000, shuffle=True, seed=42)
test_dataset = dataset['test']
valid_dataset = dataset['train'].train_test_split(test_size=1000, shuffle=True, seed=42)['test']

print('train_dataset:',len(train_dataset))
print('test_dataset:', len(test_dataset))
print('valid_dataset:', len(valid_dataset))

▶ 해당 코드를 실행하면 학습 데이터 중 10,000개만 남기고 검증 데이터 중 1,000개는 검증용, 다른 1,000개는 테스트용으로 분리됐음을 확인할 수 있다.


트레이너 API를 사용해 학습하기

 

허깅페이스는 학습에 필요한 다양한 기능을 학습 인자만으로 쉽게 활용할 수 있는 트레이너 API를 제공한다.

아래 코드는 필요한 라이브러리를 불러오고 모델과 토크나이저를 불러와 데이터셋에 토큰화를 수행한다.

# Trainer를 사용한 학습 준비
import torch
import numpy as np
from transformers import (
    Trainer,
    TrainingArguments,
    AutoModelForSequenceClassification,
    AutoTokenizer
)

def tokenize_function(examples):
    return tokenizer(examples["title"], padding="max_length", truncation=True)

model_id = "klue/roberta-base"
model = AutoModelForSequenceClassification.from_pretrained(model_id, num_labels=len(train_dataset.features['label'].names))
tokenizer = AutoTokenizer.from_pretrained(model_id)

train_dataset = train_dataset.map(tokenize_function, batched=True)
test_dataset = test_dataset.map(tokenize_function, batched=True)
valid_dataset = valid_dataset.map(tokenize_function, batched=True)
  • tokenize_function 은 데이터의 title 컬럼에 토큰화를 수행한다.
  • 학습에 사용할 분류 모델을 불러오기 위해 AutoModelForSequenceClassification 클래스로 klue/roberta-base 모델을 불러온다.
  • 모델 바디의 파라미터만 있는 klue/roberta-base 모델을 불러오면 분류 헤드 부분은 랜덤으로 초기화된다. 여기서는 분류 헤드의 분류 클래스 수를 지정하기 위해 num_labels 인자에 데이터셋의 레이블 수인 len(train_dataset.features['label'].names)를 전달했다.

다음으로 학습에 사용할 인자를 설정하는 TrainingArguments에 학습 인자를 입력한다.

# Trainer를 사용한 학습: 학습 인자와 평가 함수 정의
training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=1,
    per_device_train_batch_size=8,
    per_device_eval_batch_size=8,
    evaluation_strategy="epoch",
    learning_rate=5e-5,
    push_to_hub=False
)

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=1)
    return {"accuracy": (predictions == labels).mean()}

 

만약 아래와 같은 에러가 발생할 경우,

ImportError: Using the `Trainer` with `PyTorch` requires `accelerate>=0.21.0`: 
Please run `pip install transformers[torch]` or `pip install accelerate -U`

 

하단의 코드를 실행한 후, 커널을 종료한 뒤 재시작한다.

pip install transformers[torch] -U

 

그리고 Trainer에 앞서 준비한 데이터셋과 설정을 인자로 전달하고 train() 메서드로 학습을 진행한다.

학습이 끝나면 evaluate() 메서드로 테스트 데이터셋에 대한 성능 평가를 수행한다.

# Trainer를 사용한 학습: 학습 진행
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=valid_dataset,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)

trainer.train()

trainer.evaluate(test_dataset)

GPU 관련 문제로 Colab으로 바꿔서 진행했다. 85.2%의 정확도가 나왔다.


트레이너 API를 사용하지 않고 학습하기

 

앞서 살펴본 트레이너 API는 추상화를 통해 간편하게 사용할 수 있다는 장점이 있지만 그만큼 내부 동작을 파악하기 어렵다는 단점이 있다. 따라서 트레이너 API를 사용하지 않고 허깅페이스 모델을 학습시켜 본다.

# Trainer를 사용하지 않는 학습: 학습을 위한 모델과 토크나이저 준비
import torch
from tqdm.auto import tqdm
from torch.utils.data import DataLoader
from transformers import AdamW

def tokenize_functuion(examples): #제목 컬럼에 대한 토큰화
  return teokenizer(examples["title"], padding="max_length", truncation=True)

# 모델과 토크나이저 불러오기
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model_id = "klue/roberta-base"
model = AutoModelForSequenceClassification.from_pretrained(model_id, num_labels=len(train_dataset.features['label'].names))
tokenizer = AutoTokenizer.from_pretrained(model_id)
model.to(device)

 

다음으로 데이터 전처리를 수행한다.

# Trainer를 사용하지 않는 학습: 학습을 위한 데이터 준비
def make_dataloader(dataset, batch_size, shuffle=True):
  dataset = dataset.map(tokenize_function, batched=True).with_format("torch")

#  데이터셋에 토큰화 수행
  dataset = dataset.rename_column("label", "labels") # 컬럼 이름 변경
  dataset = dataset.remove_columns(column_names=["title"]) # 불필요한 컬럼 제거
  return DataLoader(dataset, batch_size=batch_size, shuffle=shuffle)

# 데이터로더 만들기
train_dataloader = make_dataloader(train_dataset, batch_size=8, shuffle=True)
valid_dataloader = make_dataloader(valid_dataset, batch_size=8, shuffle=False)
test_dataloader = make_dataloader(test_dataset, batch_size=8, shuffle=False)
  • 전처리를 위해 make_dataloaer 함수 정의.
  • 해당 함수 내부에서 tokenize_function 함수를 사용해 토큰화를 수행하고, rename_column 메서드를 통해 기존에 "label" 이었던 컬럼 이름을 "labels"로 변경한다.
  • title 컬럼의 토큰화를 수행했기 때문에 이제는 불필요해진 title 컬럼을 remove_columns 메서드를 사용해 제거한다.
  • 파이토치에서 제공하는 DataLoader 클래스를 사용해 데이터셋을 배치 데이터로 만든다.

학습과 평가에 사용할 함수를 만든다.

def train_epoch(model, dataloader, optimizer):
  model.train()
  total_loss = 0
  for batch in tqdm(dataloader):
    optimizer.zero_grad()
    input_ids = batch["input_ids"].to(device) #모델에 입력한 토큰 아이디
    attention_mask = batch["attention_mask"].to(device) # 모델에 입력한 어텐션 마스크
    labels = batch["labels"].to(device) # 모델에 입력한 레이블
    outputs = model(input_ids, attention_mask=attention_mask, labels=labels) # 모델 계산
    loss = outputs.loss # 손실
    loss.backward() # 역전파
    optimizer.step() # 모델 업데이트
    total_loss += loss.item()
  avg_loss = total_loss / len(dataloader)
  return avg_loss
  • 학습을 수행하는 train_epoch 함수에서는 먼저, train() 메서드를 사용해 모델을 학습 모드로 변경하고, 앞서 생성한 데이터로더에서 배치 데이터를 가져와 모델에 입력으로 전달한다.
  • 배치 데이터 안에는 토큰 아이디를 담고 있는 input_ids, 어텐션 마스크를 갖고 있는 attention_mask, 정답 레이블을 가진 labels 가 있는데, 각각 model 에 인자로 전달해 모델 계산을 수행한다.
  • 모델의 계산을 거친 결과에는 레이블과의 차이를 통해 계산된 손실이 있는데, 이 손실 값을 통해 역전파를 수행한다.
  • 옵티마이저의 step() 메서드를 호출하면 역전파 결과를 바탕으로 모델을 업데이트한다.
  • total_loss의 경우 학습이 잘되고 있는지 확인하기 위해 집계한다.

그리고 평가에 사용할 evaluate 함수를 정의한다. evaluate 함수는 모델 계산을 수행하고, 손실을 집계하는 등 많은 부분에서 train_epoch 함수와 비슷하나

# Trainer를 사용하지 않는 학습: 평가를 위한 함수 정의
def evaluate(model, dataloader):
  model.eval()
  total_loss = 0
  predictions = []
  true_labels = []
  with torch.no_grad():
    for batch in tqdm(dataloader):
      input_ids = batch["input_ids"].to(device)
      attention_mask = batch["attention_mask"].to(device)
      labels = batch["labels"].to(device)
      outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
      logits = outputs.logits
      loss = outputs.loss
      total_loss += loss.item()
      preds = torch.argmax(logits, dim=-1)
      predictions.extend(torch.argmax(preds.cpu().numpy()))
      true_labels.extend(labels.cpu().numpy())
  avg_loss = total_loss / len(dataloader)
  accuracy = np.mean(np.array(predictions) ==  np.array(true_labels))
  return avg_loss, accuracy
  • 모델을 학습 모드가 아닌 추론 모드로 설정하고
  • 모델 계산 결과의 logits 속성을 가져와 torch.armax 함수를 사용해 가장 큰 값으로 예측한 카테고리 정보를 찾고
  • 실제 정답과 비교해 정확도를 계산하는 부분이 추가돼 있다.
  • 이를 통해 손실뿐만 아니라 정확도를 직접 확인할 수 있다.

이제 앞서 정의한 함수를 사용해 학습을 진행해 본다.

학습 에포크 수는 1로 설정하고 학습에 AdamW 옵티마이저를 사용하며, 학습률(lr)도 이전 실습과 동일하게 설정했다.

for 문을 통해 한 에포크 학습을 수행하며, 이때 train_epoch 함수로 학습을, evaluate 함수로 성능을 평가한다.

# Trainer를 사용하지 않는 학습: 학습 수행
num_epochs = 1
optimizer = AdamW(model.parameters(), lr=5e-5)

# 학습 루프
for epoch in range(num_epochs):
  print(f"Epoch {epoch+1}/{num_epochs}")
  train_loss = train_epoch(model, train_dataloader, optimizer)
  print(f"Training Loss: {train_loss}")
  valid_loss, valid_accuracy = evaluate(model, valid_dataloader)
  print(f"Valid Loss: {valid_loss}")
  print(f"Valid Accuracy: {valid_accuracy}")

# 테스트
_, test_accuracy = evaluate(model, test_dataloader)
print(f"Test Accuracy: {test_accuracy}")

 

 

학습한 모델은 나중에 다시 사용할 수 있도록 저장하거나 협업을 위해 공유하는 경우가 많은데, 이를 위해 허깅페이스에서는 모델 허브에 모델을 업로드하고 불러올 수 있는 기능을 지원한다.


학습한 모델 업로드하기

 

huggingface_hub 라이브러리는 허깅페이스에 프로그래밍 방식으로 접근할 수 있는 기능을 지원하는데, 허깅페이스의 계정 토큰을 통해 로그인할 수 있다.

업로드 방법은 크게

  • Trainer 사용: trainer 인스턴스에서 push_to_hub() 메서드 사용 시, 학습한 모델과 토크나이저를 함께 모델 허브에 업로드
  • Trainer 미사용(직접 학습): 모델과 토크나이저를 각각 push_to_hub() 메서드로 업로드 

★ 허깅페이스 토큰 만들기

먼저 허깅페이스에 로그인하고, setting - Access Tokens 클릭

[+Create new token] 클릭

 

Token name을 입력하고 (본인에 맞게) 스크롤을 내려 [Create token] 을 클릭하면

참고로, 해당 항목을 모두 체크했다.

 

하단과 같은 화면이 나오고 Copy 버튼을 눌러 토큰을 잘 저장해 둔다.

 

그리고, 코드에서 노출되지 않기 위해 환경변수를 설정한 뒤, 다음부터는 코드로 불러온다.

 

- 환경변수 설정하기
Google Colab에서는 os 모듈을 사용하여 환경변수를 설정할 수 있다. 아래와 같은 코드를 사용하여 토큰을 환경변수에 저장할 수 있다.

import os

# 허깅페이스 토큰 입력.
os.environ["HUGGINGFACE_TOKEN"] = "your_huggingface_token_here"

 

허깅페이스 허브에 모델 업로드

# 허깅페이스 허브에 모델 업로드
from huggingface_hub import login
from google.colab import userdata

token = userdata.get('HUGGINGFACE_TOKEN')

login(token=token)
reop_id = f"Recordian/roberta-base-klue-ynat-classification"

# Trainer 사용 시
trainer.push_to_hub(reop_id)

# Trainer 미사용 시
model.push_to_hub(reop_id)
tokenizer.push_to_hub(reop_id)


모델 추론하기

 

모델을 추론할 때는 모델을 활용하기 쉽도록 추상화한 파이프라인을 활용하는 방법과 직접 모델과 토크나이저를 불러와 활용하는 방법이 있다.

 

◆ 파이프라인을 활용한 추론

허깅페이스는 토크나이저와 모델을 결합해 데이터의 전추처리와 모델 추론을 간단하게 수행하는 pipeline을 제공한다.

파이프라인은 아래의 사항을 입력으로 받는다.

  • 작업 종류: 텍스트 분류, 토큰 분류 등 작업에 맞춰 설정
  • 모델: 저장소 아이디 설정
  • 설정

아래는 텍스트 분류 작업을 위한 모델을 불러오기 위해 pipeline에 인자로 text-classification 과 모델 아이디를 전달한다.

앞서 코드를 실행해 모델을 허깅페이스 허브에 업로드했으면 모델 아이디의 계정 부분을 자신의 아이디로 입력하면 된다.

from transformers import pipeline

# model_id 는 허깅페이스 허브에 업로드한 자신의 아이디
model_id = "Recordian/roberta-base-klue-ynat-classification"

model_pipeline = pipeline("text-classification", model=model_id)

model_pipeline(test_dataset["title"][:5])

 

◆ 직접 추론하기

직접 모델과 토크나이저를 불러와 pipeline과 유사하게 추론을 구현한다면 아래와 같이 구현할 수 있다.

# 커스텀 파이프라인 구현
import torch
from torch.nn.functional import softmax
from transformers import AutoModelForSequenceClassification, AutoTokenizer

class CustomPipeline:
  def __init__(self, model_id):
    self.model = AutoModelForSequenceClassification.from_pretrained(model_id)
    self.tokenizer = AutoTokenizer.from_pretrained(model_id)
    self.model.eval()

  def __call__(self, text):
    tokenized = self.tokenizer(text, return_tensors="pt", truncation=True, padding=True)
    
    with torch.no_grad():
      outputs = self.model(**tokenized)
      logits = outputs.logits

    probabilities = softmax(logits, dim=-1)
    scores, labels = torch.max(probabilities, dim=-1)
    labels_str = [self.model.config.id2label[label_idx] for label_idx in labels.tolist()]

    return [{"label": label, "score": score.item()} for label, score in zip(labels_str, scores)]
    
custom_pipeline = CustomPipeline(model_id)
custom_pipeline(test_dataset["title"][:5])

  • __init__ 메서드에서 입력받은 모델 아이디(mode_id)에 맞는 모델과 토크나이저를 불러온다.
  • CustomPipeline으 인스턴스를 호출할 때 내부적으로 __call__ 메서드를 사용하는데, tokenizer를 통해 토큰화 수행
  • 모델 추론 수행
  • 가장 큰 예측 확률을 갖는 클래스를 추출해 결과로 반환

다음 내용

 

[LLM] GPU 효율적인 학습

이전 내용 [LLM] 텍스트 분류 모델 학습시키기이전 내용 [LLM] 허깅페이스 라이브러리 사용법 익히기허깅페이스 [AI 플랫폼] 허깅페이스: AI와 머신러닝의 새로운 지평허깅페이스란? 허깅페이스(

puppy-foot-it.tistory.com


[출처]

LLM을 활용한 실전 AI 어플리케이션 개발

https://hunseop2772.tistory.com/372

728x90
반응형