Patrick's 데이터 세상

Huggingface 허깅페이스 - NLP 본문

Deep Learning/NLP 개발

Huggingface 허깅페이스 - NLP

patrick610 2022. 12. 2. 11:41
반응형
SMALL

 

 

 

 

2022.11.21 - [Deep Learning/NLP 개발] - Huggingface 허깅페이스 파헤치기

 

Huggingface 허깅페이스 파헤치기

자연어 처리 포함 모든 딥러닝 학습 시에 pretrained 된 모델을 사용하다 보면 가장 많이 접하는 것이 바로 Huggingface이다. 이 포스팅에서는 Huggingface를 좀 더 자세히 알아보고 주요 기능에 대한 docume

hipster4020.tistory.com

이전 포스팅에서 Huggingface의 전체적인 기능에 대해 알아보았다.

 

 

이번에는 Huggingface의 많은 기능 중에서 내가 중점적으로 다루는 NLP(Natural Language Processing) 기능에 대해 알아보려고 한다.

 

 


👉🏻 Use tokenizers from Huggingface Tokenizers

허깅페이스 토크나이저를 활용하여 토크나이저 학습을 하여 사용하는 방법을 알아보자.

bpe, unigram, wordlevel, wordpiece 방식으로 방식이 가능하다.

 

from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import Whitespace
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
trainer = BpeTrainer(special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])

tokenizer.pre_tokenizer = Whitespace()
files = [...]
tokenizer.train(files, trainer)
tokenizer.save_pretrained(tokenizer_path)
tokenizer.save("tokenizer.json")

 

from transformers import PreTrainedTokenizerFast

# tokenizer load
fast_tokenizer = PreTrainedTokenizerFast(tokenizer_object=tokenizer_path)
# json load
fast_tokenizer = PreTrainedTokenizerFast(tokenizer_file="tokenizer.json")

unknown token, special_tokens를 추가하여 직접 학습한 토크나이저를 학습하고 저장하고 object나 json으로 다시 load 하여 사용할 수 있다.

 

 

 

👉🏻 Multilingual models for inference

Transfomers에는 여러 다국어 모델을 사용할 수 있다.

 

 

XLM(Cross-lingual Language Model)

10개의 서로 다른 체크포인트가 있고 그중 하나만 단일 언어. 나머지 9개 모델 체크포인트는 언어 임베딩을 사용하는 체크포인트, 사용하지 않는 체크포인트 두 범주로 나눈다.

 

아래 XLM 모델은 언어 임베딩을 사용하여 추론에 사용되는 언어를 지정.

  • xlm-mlm-ende-1024 (Masked language modeling, English-German)
  • xlm-mlm-enfr-1024 (Masked language modeling, English-French)
  • xlm-mlm-enro-1024 (Masked language modeling, English-Romanian)
  • xlm-mlm-xnli15-1024 (Masked language modeling, XNLI languages)
  • xlm-mlm-tlm-xnli15-1024 (Masked language modeling + translation, XNLI languages)
  • xlm-clm-enfr-1024 (Causal language modeling, English-French)
  • xlm-clm-ende-1024 (Causal language modeling, English-German)

 

ex) xlm-clm-enfr-1024 checkpoint (Causal language modeling, English-French)

import torch
from transformers import XLMTokenizer, XLMWithLMHeadModel

tokenizer = XLMTokenizer.from_pretrained("xlm-clm-enfr-1024")
model = XLMWithLMHeadModel.from_pretrained("xlm-clm-enfr-1024")

 

토크나이저의 lang2id는 모델의 언어와 해당 id를 표시.

print(tokenizer.lang2id)

 

input_ids = torch.tensor([tokenizer.encode("Wikipedia was used to")])  # batch size of 1
language_id = tokenizer.lang2id["en"]  # 0
langs = torch.tensor([language_id] * input_ids.shape[1])  # torch.tensor([0, 0, 0, ..., 0])

# We reshape it to be of size (batch_size, sequence_length)
langs = langs.view(1, -1)  # is now of shape [1, sequence_length] (we have a batch size of 1)
outputs = model(input_ids, langs=langs)

예제 input 값을 만들어 확인해보자.

언어를 "en"으로 설정하고 영어에 해당하는 0으로 채워진 언어 임베딩을 정의한다.

이 텐서는 input_ids와 같은 값이어야 한다.

 

 

XLM without language embeddings

추론 중에 언어 임베딩이 필요하지 않은 모델.

이전 XLM 체크포인트와 달리 일반 문장 표현에 사용된다.

  • xlm-mlm-17-1280 (Masked language modeling, 17 languages)
  • xlm-mlm-100-1280 (Masked language modeling, 100 languages)

 

BERT

아래 버트 모델들은 다국어 번역을 사용할 수 있다.

추론 중에 언어 임베딩이 필요하지 않다.

  • bert-base-multilingual-uncased (Masked language modeling + Next sentence prediction, 102 languages)
  • bert-base-multilingual-cased (Masked language modeling + Next sentence prediction, 104 languages)

 

XLM-RoBERTa

아래 XLM-RoBERTa 모델들은 다국어 번역을 사용할 수 있다.

CommonCrawl 데이터 2.5TB로 100개 언어를 가지고 학습하였다.

분류, 시퀀스 레이블 지정 및 질문 답변과 같은 다운스트림 작업에서 mBERT, XLM 등 모델보다 성능이 좋다.

  • xlm-roberta-base (Masked language modeling, 100 languages)
  • xlm-roberta-large (Masked language modeling, 100 languages)

 

M2M100

아래 M2M100 모델들은 다국어 번역을 사용할 수 있다.

  • facebook/m2m100_418M (Translation)
  • facebook/m2m100_1.2B (Translation)

 

중국어 → 영어 번역

from transformers import M2M100ForConditionalGeneration, M2M100Tokenizer

en_text = "Do not meddle in the affairs of wizards, for they are subtle and quick to anger."
chinese_text = "不要插手巫師的事務, 因為他們是微妙的, 很快就會發怒."

tokenizer = M2M100Tokenizer.from_pretrained("facebook/m2m100_418M", src_lang="zh")
model = M2M100ForConditionalGeneration.from_pretrained("facebook/m2m100_418M")
encoded_zh = tokenizer(chinese_text, return_tensors="pt")
generated_tokens = model.generate(**encoded_zh, forced_bos_token_id=tokenizer.get_lang_id("en"))
tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)
'Do not interfere with the matters of the witches, because they are delicate and will soon be angry.'

 

MBart

아래 MBart 모델들은 다국어 번역을 사용할 수 있다.

  • facebook/mbart-large-50-one-to-many-mmt (One-to-many multilingual machine translation, 50 languages)
  • facebook/mbart-large-50-many-to-many-mmt (Many-to-many multilingual machine translation, 50 languages)
  • facebook/mbart-large-50-many-to-one-mmt (Many-to-one multilingual machine translation, 50 languages)
  • facebook/mbart-large-50 (Multilingual translation, 50 languages)
  • facebook/mbart-large-cc25

 

핀란드어 → 영어 번역
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM

en_text = "Do not meddle in the affairs of wizards, for they are subtle and quick to anger."
fi_text = "Älä sekaannu velhojen asioihin, sillä ne ovat hienovaraisia ja nopeasti vihaisia."

tokenizer = AutoTokenizer.from_pretrained("facebook/mbart-large-50-many-to-many-mmt", src_lang="fi_FI")
model = AutoModelForSeq2SeqLM.from_pretrained("facebook/mbart-large-50-many-to-many-mmt")
encoded_en = tokenizer(en_text, return_tensors="pt")
generated_tokens = model.generate(**encoded_en, forced_bos_token_id=tokenizer.lang_code_to_id("en_XX"))
tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)
"Don't interfere with the wizard's affairs, because they are subtle, will soon get angry."

"facebook/mbart-large-50-many-to-one-mmt" 체크포인트를 사용하는 경우 대상 언어 ID를 처음 생성된 토큰으로 강제할 필요가 없다.

 

 

👉🏻 NLP Task

 

◉ Text Classification

텍스트 분류는 레이블이나 클래스를 텍스트에 할당하여 작업하는 일반적인 NLP 방법이다.

가장 많이 사용되는 텍스트 분류는 긍정/부정/중립과 같은 레이블을 할당하는 감정 분석이다.

 

아래 가이드는 DistilBERT를 미세 조정하여 학습하는 방법.

 

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")

tokenizer load.

 

def preprocess_function(examples):
    return tokenizer(examples["text"], truncation=True)

DistilBERT 최대 입력 길이보다 길지 않도록 텍스트를 토큰화하고 시퀀스를 자르는 전처리 함수.

 

from datasets import load_dataset

# 예시 데이터 imdb 긍부정 데이터셋
imdb = load_dataset("imdb")
tokenized_imdb = imdb.map(preprocess_function, batched=True)

Datasets map으로 전체 데이터셋에 전처리를 적용한다.

batched=True로 데이터셋의 여러 요소를 한 번에 처리 가능하다.

 

from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

 

DataCollatorWithPadding을 사용하면 배치 생성 시 일괄 처리에서 가장 긴 요소의 길이에 맞게 텍스트를 균등하게 padding을 씌워 길이를 맞춘다. (tokenizer에서 padding=True로 패딩 하는 것보다 동적 패딩이 더 효율적 일 수 있다.)

 

from transformers import AutoModelForSequenceClassification, TrainingArguments, Trainer

model = AutoModelForSequenceClassification.from_pretrained("distilbert-base-uncased", num_labels=2)

training_args = TrainingArguments(
    output_dir="./results",
    learning_rate=2e-5,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    num_train_epochs=5,
    weight_decay=0.01,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_imdb["train"],
    eval_dataset=tokenized_imdb["test"],
    tokenizer=tokenizer,
    data_collator=data_collator,
)

trainer.train()

AutoModelForSequenceClassification를 사용하여 DistilBERT를 로드한다.

TrainingArguments에서 training hyperparameters를 정의.

model, dataset, tokenizer, data_collator를 trainer에 전달하여 학습한다.

 

 

 

◉ Token Classification

토큰 분류는 문장 개별 토큰에 레이블을 할당.

일반적인 토큰 분류는 NER(Named Entity Recognition)이다.

NER은 사람, 장소, 기관 등을 문장의 각 엔터티에서 찾는 작업이다.

 

가이드에서는 WNUT 17 데이터셋에서 DistilBERT를 미세 조정하여 사용한다.

 

from datasets import load_dataset

wnut = load_dataset("wnut_17")
wnut["train"][0]

Load WNUT 17 dataset.

 

label_list = wnut["train"].features[f"ner_tags"].feature.names
label_list

ner_tags의 각 숫자는 엔터티를 표현한다.

ner_tag에는 회사, 위치, 사람 등을 표현하고 있다.

B는 엔터티 시작, I는 토큰이 동일한 엔터티 내에 포함되어 있는 것, O은 어떤 엔터티에도 해당하지 않는 것을 의미한다.

 

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")

Load DistilBERT tokenizer.

 

tokenized_input = tokenizer(example["tokens"], is_split_into_words=True)
tokens = tokenizer.convert_ids_to_tokens(tokenized_input["input_ids"])
tokens

is_split_into_words=True로 단어를 하위 단어로 토큰화.

 

def tokenize_and_align_labels(examples):
    tokenized_inputs = tokenizer(examples["tokens"], truncation=True, is_split_into_words=True)

    labels = []
    for i, label in enumerate(examples[f"ner_tags"]):
        word_ids = tokenized_inputs.word_ids(batch_index=i)  # Map tokens to their respective word.
        previous_word_idx = None
        label_ids = []
        for word_idx in word_ids:  # Set the special tokens to -100.
            if word_idx is None:
                label_ids.append(-100)
            elif word_idx != previous_word_idx:  # Only label the first token of a given word.
                label_ids.append(label[word_idx])
            else:
                label_ids.append(-100)
            previous_word_idx = word_idx
        labels.append(label_ids)

    tokenized_inputs["labels"] = labels
    return tokenized_inputs
tokenized_wnut = wnut.map(tokenize_and_align_labels, batched=True)

특수 토큰 [CLS], [SEP]와 하위 단어 토큰화를 추가하면 입력, 레이블 간에 불일치가 발생한다.

단일 레이블에 해당하는 단일 단어가 두 개의 하위 단어로 분절될 수 있기 때문에 위와 같이 토큰과 레이블을 재정렬해야 한다.

 

from transformers import DataCollatorForTokenClassification

data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)

DataCollatorForTokenClassification를 사용하여 배치를 만든다.

가장 긴 요소의 길이로 동적으로 패딩을 균일하게 채운다.

 

from transformers import AutoModelForTokenClassification, TrainingArguments, Trainer

model = AutoModelForTokenClassification.from_pretrained("distilbert-base-uncased", num_labels=14)
training_args = TrainingArguments(
    output_dir="./results",
    evaluation_strategy="epoch",
    learning_rate=2e-5,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    num_train_epochs=3,
    weight_decay=0.01,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_wnut["train"],
    eval_dataset=tokenized_wnut["test"],
    tokenizer=tokenizer,
    data_collator=data_collator,
)

trainer.train()

레이블 개수를 넣어서 Load AutoModelForTokenClassification.

TrainingArguments로 하이퍼 파라미터를 정의.

model, dataset, tokenizer, datacollator를 갖는 Trainer로 어규먼트를 전달한다.

train()으로 fine-tune model.

 

 

 

◉ Question answering

Q&A는 질문에 대한 답변을 반환하는 작업이다.

질문 답변 형식에는 일반적으로 2가지가 있다.

 

  ∙ 추출 : 주어진 컨텍스트에서 답을 추출.

  ∙ 추상화 : 질문에 올바르게 대답하는 컨텍스트에서 답변을 생성.

 

아래 가이드는 추출 질문 답변을 위해 SQuAD 데이터셋에서 DistilBERT를 미세 조정한다.

from datasets import load_dataset

squad = load_dataset("squad")
squad["train"][0]

Load SQuAD dataset.

답변 필드는 답변의 시작 위치와 답변 텍스트를 포함하는 사전.

 

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")

DistilBERT tokenizer를 로드해서 질문 및 컨텍스트 필드를 처리한다.

 

def preprocess_function(examples):
    questions = [q.strip() for q in examples["question"]]
    inputs = tokenizer(
        questions,
        examples["context"],
        max_length=384,
        truncation="only_second",
        return_offsets_mapping=True,
        padding="max_length",
    )

    offset_mapping = inputs.pop("offset_mapping")
    answers = examples["answers"]
    start_positions = []
    end_positions = []

    for i, offset in enumerate(offset_mapping):
        answer = answers[i]
        start_char = answer["answer_start"][0]
        end_char = answer["answer_start"][0] + len(answer["text"][0])
        sequence_ids = inputs.sequence_ids(i)

        # Find the start and end of the context
        idx = 0
        while sequence_ids[idx] != 1:
            idx += 1
        context_start = idx
        while sequence_ids[idx] == 1:
            idx += 1
        context_end = idx - 1

        # If the answer is not fully inside the context, label it (0, 0)
        if offset[context_start][0] > end_char or offset[context_end][1] < start_char:
            start_positions.append(0)
            end_positions.append(0)
        else:
            # Otherwise it's the start and end token positions
            idx = context_start
            while idx <= context_end and offset[idx][0] <= start_char:
                idx += 1
            start_positions.append(idx - 1)

            idx = context_end
            while idx >= context_start and offset[idx][1] >= end_char:
                idx -= 1
            end_positions.append(idx + 1)

    inputs["start_positions"] = start_positions
    inputs["end_positions"] = end_positions
    return inputs
tokenized_squad = squad.map(preprocess_function, batched=True, remove_columns=squad["train"].column_names)

위 함수는 컨텍스트에 대해 응답 시작 및 종료 토큰을 자르고 매핑하는 함수이다.

데이터셋의 일부에서 모델의 최대 입력 길이를 초과하는 긴 컨텍스트가 있을 수 있으므로 truncation="only_second"로 컨텍스트를 자른다.

다음으로 return_offset_mapping=True로 답변 시작 및 끝 위치를 원래 컨텍스트에 매핑.

 

from transformers import DefaultDataCollator

data_collator = DefaultDataCollator()

DefaultDataCollator로 배치를 생성.

 

from transformers import AutoModelForQuestionAnswering, TrainingArguments, Trainer

model = AutoModelForQuestionAnswering.from_pretrained("distilbert-base-uncased")
training_args = TrainingArguments(
    output_dir="./results",
    evaluation_strategy="epoch",
    learning_rate=2e-5,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    num_train_epochs=3,
    weight_decay=0.01,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_squad["train"],
    eval_dataset=tokenized_squad["validation"],
    tokenizer=tokenizer,
    data_collator=data_collator,
)

trainer.train()

Load DistilBERT with AutoModelForQuestionAnswering.

 

TrainingArguments로 하이퍼 파라미터를 정의.

model, dataset, tokenizer, datacollator를 갖는 Trainer로 어규먼트를 전달한다.

train()으로 fine-tune model.

 

 

 

반응형
LIST

'Deep Learning > NLP 개발' 카테고리의 다른 글

VSCode Formatter  (0) 2023.02.04
pyenv + pyenv-virtualenv 가상환경 구축  (0) 2023.02.04
Okt 사용자 사전 추가  (0) 2022.12.01
Okt jvm path 못찾는 문제  (0) 2022.12.01
맞춤법 검사기 - hanspell  (0) 2022.11.22
Comments