본문 바로가기

practical AI/Hugging Face

NLP Frameworks: Hugging Face 기본 사용법

NLP Frameworks: Hugging Face

세상에는 하루가 갈수록 수많은 NLP 모델들이 쏟아져 나오고 있다.

 

이러한 모델들을 직접 짜보는 것은 실력 향상에 도움이 되지만, 시간적/자원적으로 매우 힘든 일이다.

그리고 논문과 함께 공개된 리서치 코드를 이용해 모델을 사용할 수는 있지만, 프로젝트마다 코드 스타일이 다르고 서로 다른 딥러닝 프레임워크(텐서플로우 or 파이토치)를 사용하기 때문에 이 점에 있어서도 모델 사용에 어려움이 있다.

 

이러한 문제점들을 해결해주는 것이 바로 NLP Framework이다.

 framework란 프로젝트의 뼈대를 이루는 클래스와 인터페이스의 집합으로 이를 이용해 손쉽게 다양한 응용 프로그램을 제작할 수 있다.

대부분의 NLP framework들은 태스크나 데이터셋, 모델에 무관하게 통일적인 인터페이스를 기반으로 설계된 클래스 구조를 가지고 있어서, 최소한의 코드 구현만으로도 다양한 변화에 대응할 수 있게 해주는 장점이 있다.

 

 NLP Framework 종류

  • General Framework for NLP
    • 일반적으로 해결할 수 있는 톡합적 프레임워크를 목표로 설계된 것
    • AllenNLP, Fairseq, Fast.ai 등
  • Preprocessing Libraries 
    • 전통적으로 사용되었던 NLP 분야의 전처리 관련 framework들
    • 통합적으로 설계하여 NLP 태스크를 제너럴하게 수행하게 설계한 것이 아니라 tokenization, tagging, parsing 등 특정 전처리 작업을 위해 설계된 라이브러리
    • Spacy, NLTK, TorchText, KoNLPy 등
  • Transformer based Framework
    • HuggingFace transformers
    •  general한 NLP framework의 모습
    • 전통적인 모델까지 포괄하려고 했던 이전의 general NLP Framework 들에 비해, Huggingface의 transformers는 pretrained model 활용을 주로 지원하며 tokenizer 등 전처리 부분도 pretrained model들이 주로 사용하는 Subword tokenizer 기법에 집중되어 있다.

 

가장 대표적인 NLP Framework로는 Huggingface(🤗)의 transformers 를 알아보자

 

Huggingface transformers를 사용하는 이유

  • 광범위하고 신속한 NLP 모델 지원
    • 새로운 논문들이 발표될 때마다, 본인들의 framework에 흡수시킴
    • pretrained model을 제공
    • dataset과 tokenizer를 더욱 쉽게 이용할 수 있도록 framework화
    • 지원 범위가 가장 광범위하고, 최신 논문을 지원하는 속도도 가장 빠름
  • Pytorch, Tensorflow 모두에서 사용 가능
    • 기본적으로 PyTorch를 기반으로 작성되어 있으나 최근에는 Tensorflow로도 학습하고 사용할 수 있게끔 계속해서 framework를 확장하고 있는 중임
  • 잘 설계된 framework 구조
    •  쉽고 빠르게 어떠한 환경에서도 NLP모델을 사용할 수 있도록 끊임없이 변화함
    • 사용하기 쉽고 직관적일뿐더러 모델이나 태스크, 데이터셋이 달라지더라도 동일한 형태로 사용 가능하도록 잘 추상화되고 모듈화된 API 설계

Huggingface transformers 설계구조

  1.  <Processor, Tokenizer> Task를 정의하고 그에 맞게 dataset을 가공한다
  2.  <Model> 적당한 model을 선택하고 이를 만든다
  3.  <Trainer> model에 데이터를 넣어 돌린다
  4.  <Optimizer, Config> 나온 weight와 설정(config)를 저장한다
  5. 저장한 model checkpoint는 배포하거나 evaluation할 때 사용한다.

 

Huggingface transformers  - Model

  • 기본적인 모델들은 PretrainedModel 클래스를 상속받고 있음
  • PretrainedModel 클래스는 학습된 모델을 불러오고, 다운로드하고, 저장하는 등 모델 전반에 걸쳐 적용되는 메소드들을 갖고 있다.
  • 이러한 상속구조로 인해 모델 종류에 상관없이  모델을 불러오고 다운로드/저장하는 등의 작업에 활용하는 메소드는 부모 클래스의 것을 동일하게 사용할 수 있다.
  • 모델을 불러오는 방식은 총 2가지가 있다.

1. task에 적합한 모델을 직접 선택하여 import하고, 불러오는 방식.

from_pretrained라는 메소드를 사용한다.

from transformers import TFBertForPreTraining
model = TFBertForPreTraining.from_pretrained('bert-base-cased')

print(model.__class__)

# >> <class 'transformers.models.bert.modeling_tf_bert.TFBertForPreTraining'>

 

2.  AutoModel을 이용하는 방식

모델에 관한 정보를 처음부터 명시하지 않아도 된다.

 Huggingface가 지원하는 다양한 pretrained model을 Model ID를 사용해 선택할 수 있다.

from transformers import TFAutoModel
model = TFAutoModel.from_pretrained("bert-base-cased") # Model ID

print(model.__class__)

# >> <class 'transformers.models.bert.modeling_tf_bert.TFBertModel'>

 

1, 2 방법의 경우 Model ID는 동일하지만 model.__classs__를 확인해봤을 때 차이점이 있다.

둘 다 동일한 모델 파라미터를 사용하지만 Pretrain, Downstream Task 등 용도에 따라 모델의 Input이나 Output shape가 다를 수 있다.

따라서 모델을 정확한 용도에 맞게 사용하려면 모델별 상세 안내 페이지를 참고해서 최적의 모델을 선택하는 것이 좋다.

 

Huggingface transformers - Tokenizer

  •  다양한 tokenizer를 각 모델에 맞추어 이미 구현되어 있으므로 불러와서 사용하기만 하면 됨
  • 파라미터 구조가 동일한 Model이라 하더라도 Tokenizer가 다르거나 Tokenizer 내의 Dictionary가 달라지면 사실상 완전히 다른 모델이 되므로 어떤 모델이 어떤 토크나이저를 사용하는지 미리 알고 있어야 함
  • 토크나이저를 지정하는 방식도 2가지가 있음

1. 직접 명시하여 내가 사용할 토크나이저를 지정할 수 있음

from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-cased')

 

2. AutoTokenizer를 사용하여 이미 구비된 model에 알맞은 토크나이저를 자동으로 불러올 수 있음

  • 이때 model 사용시와 동일한 ID로 토크나이저를 생성해야 함
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('bert-base-cased')
# 토크나이저 사용해보기
encoded = tokenizer("This is Test for aiffel")
print(encoded)

# BERT의 tokenizer이기 때문에 인코딩이 된 input_ids 뿐만 아니라, token_type_ids와 attention_mask까지 모두 생성
# >> {'input_ids': [101, 1188, 1110, 5960, 1111, 170, 11093, 1883, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}

# batch 단위로도 input을 받을 수 있음

batch_sentences = ["Hello I'm a single sentence",
                    "And another sentence",
                    "And the very very last one"]

encoded_batch = tokenizer(batch_sentences)
print(encoded_batch)

## >> {'input_ids': [[101, 8667, 146, 112, 182, 170, 1423, 5650, 102], [101, 1262, 1330, 5650, 102], [101, 1262, 1103, 1304, 1304, 1314, 1141, 102]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]]}

# padding, truncation 등 다양한 옵션을 선택할  수 있다.
batch = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="tf")
print(batch)

"""
{'input_ids': <tf.Tensor: shape=(3, 9), dtype=int32, numpy=
array([[ 101, 8667,  146,  112,  182,  170, 1423, 5650,  102],
       [ 101, 1262, 1330, 5650,  102,    0,    0,    0,    0],
       [ 101, 1262, 1103, 1304, 1304, 1314, 1141,  102,    0]],
      dtype=int32)>, 'token_type_ids': <tf.Tensor: shape=(3, 9), dtype=int32, numpy=
array([[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]], dtype=int32)>, 'attention_mask': <tf.Tensor: shape=(3, 9), dtype=int32, numpy=
array([[1, 1, 1, 1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 0, 0, 0, 0],
       [1, 1, 1, 1, 1, 1, 1, 1, 0]], dtype=int32)>}

"""

 

Huggingface transformers - Config

  • 모델을 학습시키기 위한 요소들을 명시한 json 파일 형태
  • json파일 내에는 batch size, learning rate, weight_decay등 train에 필요한 요소들부터 tokenizer에 특수 토큰(special token eg.[MASK])들을 미리 설정하는 등 설정에 관한 전반적인 것들이 명시되어 있음
  • PretrainedModel에서 save_pretrained 메소드를 이용하면 모델의 체크포인트와 함께 저장됨
  • 원래는 자동으로 로드되지만 설정을 변경하고 싶거나 나만의 모델을 학습시킬 때에는 config 파일을 직접 불러와야 함
# 방식1
from transformers import BertConfig

config = BertConfig.from_pretrained("bert-base-cased")
print(config.__class__)
print(config)

# 방식2
from transformers import AutoConfig

config = AutoConfig.from_pretrained("bert-base-cased")
print(config.__class__)
print(config)

# 방식3: 모델을 이미 생성한 경우
model = TFBertForPreTraining.from_pretrained('bert-base-cased')

model.config


"""
<class 'transformers.models.bert.configuration_bert.BertConfig'>
BertConfig {
  "architectures": [
    "BertForMaskedLM"
  ],
  "attention_probs_dropout_prob": 0.1,
  "classifier_dropout": null,
  "gradient_checkpointing": false,
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "layer_norm_eps": 1e-12,
  "max_position_embeddings": 512,
  "model_type": "bert",
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pad_token_id": 0,
  "position_embedding_type": "absolute",
  "transformers_version": "4.11.3",
  "type_vocab_size": 2,
  "use_cache": true,
  "vocab_size": 28996
}

"""

 

Huggingface transformers - Trainer

  • 모델 학습을 위한 클래스
  • . training, fine-tuning, evaluation 모두 trainer class를 이용해야 가능
  • trainer을 사용할 경우, TrainingArguments 를 통해 Huggingface 프레임워크에서 제공하는 기능들을 통합적으로 커스터마이징하여 모델을 손쉽게 학습시킬 수 있음
  • trainer API를 사용하기 위해선 TrainingArguments인스턴스를 생성해야 하며, 이 때 학습에 필요한 여러 arguments들이 정의됨

 

HuggingFace transformer를 활용한 프로세스

1. 데이터셋 불러오기

from datasets import load_dataset
from transformers import AutoTokenizer, TrainingArguments, Trainer, AutoModelForSequenceClassification

raw_datasets = load_dataset("glue", "cola")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

raw_datasets

"""
DatasetDict({
    train: Dataset({
        features: ['sentence', 'label', 'idx'],
        num_rows: 8551
    })
    validation: Dataset({
        features: ['sentence', 'label', 'idx'],
        num_rows: 1043
    })
    test: Dataset({
        features: ['sentence', 'label', 'idx'],
        num_rows: 1063
    })
})
"""

 

2. 데이터를 학습시킬 모델과 토크나이저 불러오고 토큰화 함수 만들기

model_name_or_path = "bert-base-uncased"
model = AutoModelForSequenceClassification.from_pretrained(model_name_or_path, num_labels=2)    # COLA dataset의 라벨은 0(unacceptable)과 1(accpetable) 두 가지로 구분됨
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)

def tokenize_function(example):
    return tokenizer(example["sentence"], truncation=True)

tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)

 

3. trainer에 필요한  TrainingArguments를 선언하기

training_args = TrainingArguments(
    output_dir='./results',              # output이 저장될 경로
    num_train_epochs=1,              # train 시킬 총 epochs
    per_device_train_batch_size=16,  # 각 device 당 batch size
    per_device_eval_batch_size=64,   # evaluation 시에 batch size
    warmup_steps=500,                # learning rate scheduler에 따른 warmup_step 설정
    weight_decay=0.01,                 # weight decay
    logging_dir='./logs',                 # log가 저장될 경로
    do_train=True,                        # train 수행여부
    do_eval=True,                        # eval 수행여부
    eval_steps=1000,
    group_by_length=False, #같은 길이의 텍스트끼리 그룹화하여 학습함으로서 학습속도가 향상되고 메모리사용량이 감소함
)

 

4. 모델 훈련 및 추론

trainer = Trainer(
    model,                                                                    # 학습시킬 model
    args=training_args,                                                # TrainingArguments을 통해 설정한 arguments
    train_dataset=tokenized_datasets["train"],         # training dataset
    eval_dataset=tokenized_datasets["validation"], # validation dataset
    tokenizer=tokenizer,
)

# 모델 학습
trainer.train()

#TrainOutput(global_step=535, training_loss=0.5316225069705571, metrics={'train_runtime': 59.8752, 'train_samples_per_second': 142.814, 'train_steps_per_second': 8.935, 'total_flos': 91292341767000.0, 'train_loss': 0.5316225069705571, 'epoch': 1.0})