인공지능/자연어 처리

자연어 처리 살펴보기 - Google colab 환경에서 Huggingface 기초 실습

이게될까 2024. 3. 2. 19:09
728x90
728x90

Huggingface Tutorial

Introduction

Chapter 1. 자연어처리 살펴보기 강의의 google colab 환경에서 Huggingface 기초 실습 강의입니다.

자연어처리 실습을 위한 google colab 환경을 소개하고, 자연어처리 분야의 주요 모듈인 Huggingface의 사용법을 익히기 위해, Tokenizer 및 모델 실습을 진행합니다.

Hugging Face, Inc.는 기계 학습을 사용하여 애플리케이션을 구축하기 위한 도구를 개발하는 미국 회사입니다. 자연어 처리 애플리케이션용으로 구축된 Transformers 라이브러리와 사용자가 기계 학습 모델 및 데이터 세트를 공유할 수 있는 플랫폼으로 가장 유명합니다.

우리는 이 라이브러리를 통해 오늘날 사용 가능한 대부분의 크고 최첨단 transformer 모델을 쉽게 사용하여 자연어처리 Task를 시작 수 있습니다.

BERT, RoBERTa, GPT, GPT-2, XLNet 및 HuggingFace의 자체 DistilBERT 및 DistilGPT-2를 포함하여 (이에 국한되지 않음) 모델을 다운로드하고 작업을 시작하기 위해 3 줄 이상이 필요하지 않습니다.

from transformers import TFAutoModel, AutoTokenizer
model = TFAutoModel.from_pretrained("<model-name>")
tokenizer = AutoTokenizer.from_pretrained("<model-name>")

모델의 검색 역시 매우 간단합니다.
검색 창에 사용하고 싶으신 모델만 입력하면 결과를 바로 확인할 수 있습니다.

먼저 transformers를 설치하겠습니다.    
transformers만 설치해줘도 해당 transformers의 버젼에 맞는 tokenizers가 자동으로 설치됩니다.   

!pip install transformers
## Huggingface에서 모델 다운로드

from transformers import AutoModel, AutoTokenizer, BertTokenizer

Transformer 기반의 대표 모델인 BERT model을 사용해보도록 하겠습니다.

**PretrainedModel**

Huggingface에 업로드된 모델들은 기본적으로 모델들은 `PretrainedModel` 클래스를 상속받고 있습니다.

`PretrainedModel` 클래스는 학습된 모델을 불러오고, 다운로드하고, 저장하는 등 모델 전반에 걸쳐 적용되는 메소드를 가지고 있으며,상속 구조를 가지고 있습니다.

따라서, 실제로 사용할 모델이 **BERT**인지 **GPT**인지에 상관없이 모델을 불러오고 다운로드/저장하는 등의 작업에 활용하는 메소드는 부모 클래스의 것을 도일하게 활용할 수 있습니다.

물론, Huggingface에 등록된 모델이어야합니다!

 

**AutoModel**

모든 클래스에 걸쳐 Auto 클래스가 존재합니다.
모델도 마찬가지로, AutoModel 클래스가 존재하며, AutoModel을 사용하면 모델에 관한 정보를 처음부터 명시하지 않아도 되므로 유용하게 사용할 수 있습니다.

예를 들어서, BERT 모델을 사용하는 경우에, 모델의 상세정보를 확인할 필요 없이 Model ID (ex:bert-base-cased) 만으로도 손쉽게 모델 구성이 가능합니다.

물론, 정확한 용도에 맞게 사용하려면 모델별 상세 안내 페이지를 참고해서 최적의 모델 선택해야 합니다.

# Store the model we want to use
BERT_MODEL_NAME = "bert-base-cased"  # 모델 ID이다.

# We need to create the model
bert_model = AutoModel.from_pretrained(BERT_MODEL_NAME)

 Tokenizer 실습

이후 강의에서 자세히 배우겠지만, 모델을 사용하려면, 해당 모델이 학습한 Tonenizer를 불러와야 합니다.

다행히, AutoModel 클래스를 사용하면, 우리가 불러온 모델에 대한 Tokenizer도 손쉽게 불러올 수 있습니다.

# We need to create the tokenizer
bert_tokenizer = AutoTokenizer.from_pretrained(BERT_MODEL_NAME)

print(bert_tokenizer.vocab_size) # 28996

for i, key in enumerate(bert_tokenizer.get_vocab()):
    print(key)
    if i > 10:
        break

##WD
Premiership
tramway
1874
##neck
ecology
##chanized
flames
Prof
Own
commemorating
##R

우리가 불러온 BERT("bert-base-cased") 모델은 [BertTokenizerFast](https://huggingface.co/transformers/model_doc/bert.html#berttokenizerfast) class로 되어있습니다.

print(type(bert_tokenizer))

<class 'transformers.models.bert.tokenization_bert_fast.BertTokenizerFast'>

 

대부분이 영어로 되어있기 때문에 일단 영어로 실습을 진행한다.

sample_1 = "welcome to the natural language class"
sample_2 = "welcometothenaturallanguageclass"

tokenized_input_text = bert_tokenizer(sample_1, return_tensors="pt") # 여기선 띄어쓰기 단위로 토큰화가 되어있다.
for key, value in tokenized_input_text.items():
    print("{}:\n\t{}".format(key, value))

input_ids:
tensor([[ 101, 7236, 1106, 1103, 2379, 1846, 1705,  102]])
token_type_ids:
tensor([[0, 0, 0, 0, 0, 0, 0, 0]])
attention_mask:
tensor([[1, 1, 1, 1, 1, 1, 1, 1]])

tokenized_input_text_merged = bert_tokenizer(sample_2, return_tensors="pt")# 단어 단위로 잘 되어있는 것을 볼 수 있다.
for key, value in tokenized_input_text.items():
    print("{}:\n\t{}".format(key, value))

input_ids:
tensor([[ 101, 7236, 1106, 1103, 2379, 1846, 1705,  102]])
token_type_ids:
tensor([[0, 0, 0, 0, 0, 0, 0, 0]])
attention_mask:
tensor([[1, 1, 1, 1, 1, 1, 1, 1]])

# input text를 tokenizing한 후 vocab의 id
print(tokenized_input_text['input_ids'])
print(tokenized_input_text.input_ids)

# segment id (sentA or sentB)
print(tokenized_input_text['token_type_ids'])
print(tokenized_input_text.token_type_ids)

# special token (pad, cls, sep) or not
print(tokenized_input_text['attention_mask'])
print(tokenized_input_text.attention_mask)

tensor([[ 101, 7236, 1106, 1103, 2379, 1846, 1705,  102]])
tensor([[ 101, 7236, 1106, 1103, 2379, 1846, 1705,  102]])
tensor([[0, 0, 0, 0, 0, 0, 0, 0]])
tensor([[0, 0, 0, 0, 0, 0, 0, 0]])
tensor([[1, 1, 1, 1, 1, 1, 1, 1]])
tensor([[1, 1, 1, 1, 1, 1, 1, 1]])

tokenized_text = bert_tokenizer.tokenize(sample_1) # 토큰을 단어화
print(tokenized_text)

input_ids = bert_tokenizer.encode(sample_1) # 엔코더를 거쳐 토큰화
print(input_ids)

decoded_ids = bert_tokenizer.decode(input_ids)
print(decoded_ids)

['welcome', 'to', 'the', 'natural', 'language', 'class']
[101, 7236, 1106, 1103, 2379, 1846, 1705, 102]
[CLS] welcome to the natural language class [SEP]

tokenized_text = bert_tokenizer.tokenize(sample_1, add_special_tokens=False) 
print(tokenized_text)

input_ids = bert_tokenizer.encode(sample_1, add_special_tokens=False)# 이렇게 하면 101,102가 없어졌다.
print(input_ids)

decoded_ids = bert_tokenizer.decode(input_ids)
print(decoded_ids)

['welcome', 'to', 'the', 'natural', 'language', 'class']
[7236, 1106, 1103, 2379, 1846, 1705]
welcome to the natural language class

tokenized_text = bert_tokenizer.tokenize(
    sample_1,
    add_special_tokens=False,
    max_length=5,
    truncation=True
    )
print(tokenized_text)

['welcome', 'to', 'the', 'natural', 'language']

input_ids = bert_tokenizer.encode(
    sample_1,
    add_special_tokens=False,
    max_length=5,
    truncation=True
    )
print(input_ids)

decoded_ids = bert_tokenizer.decode(input_ids)
print(decoded_ids)

[7236, 1106, 1103, 2379, 1846]
welcome to the natural language

print(bert_tokenizer.pad_token) # 짧은 문장에서 사용된다.
print(bert_tokenizer.pad_token_id)

[PAD]
0

tokenized_text = bert_tokenizer.tokenize(
    sample_1,
    add_special_tokens=False,
    max_length=20,
    padding="max_length"
    )
print(tokenized_text)

input_ids = bert_tokenizer.encode(
    sample_1,
    add_special_tokens=False,
    max_length=20,
    padding="max_length"
    )
print(input_ids)

decoded_ids = bert_tokenizer.decode(input_ids)
print(decoded_ids)

['welcome', 'to', 'the', 'natural', 'language', 'class', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']
[7236, 1106, 1103, 2379, 1846, 1705, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
welcome to the natural language class [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD]

내가 MAX를 설정한 내용 뒤에는 PAD로 채워지게 된다.

실습1. 한국어 Tokenizer 실습

아쉽게도, Huggingface에서 사용할 수 있는 BERT("bert-base-cased") 모델은 영어 데이터로만 학습을 한 모델입니다.

따라서, 한국어 문장을 토큰화하려고 하면, [UNK] 토큰으로 변환됩니다.

kor_text = "아버지 가방에 들어가신다"

tokenized_text = bert_tokenizer.tokenize(
    kor_text,
    add_special_tokens=False,
    max_length=20,
    padding="max_length"
    )
print(tokenized_text)

['[UNK]', '[UNK]', '[UNK]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']

tokenized_input_text = bert_tokenizer(kor_text, return_tensors="pt")
for key, value in tokenized_input_text.items():
    print("{}:\n\t{}".format(key, value))

input_ids:
tensor([[101, 100, 100, 100, 102]])
token_type_ids:
tensor([[0, 0, 0, 0, 0]])
attention_mask:
tensor([[1, 1, 1, 1, 1]])

의미를 분리할수도, 학습할 수도 없다.

tokenized_text = bert_tokenizer.tokenize(kor_text)
print(tokenized_text)

input_ids = bert_tokenizer.encode(kor_text)
print(input_ids)

decoded_ids = bert_tokenizer.decode(input_ids)
print(decoded_ids)

['[UNK]', '[UNK]', '[UNK]']
[101, 100, 100, 100, 102]
[CLS] [UNK] [UNK] [UNK] [SEP]

따라서, 한국어 텍스트로 Tokenizer를 실습하기 위해서는 한국어 데이터로 학습한 모델이 필요합니다.

다행히, Huggingface에는 다양한 언어로 구성된 학습 데이터셋으로 학습한 multi-lingual BERT 모델이 있어요.

한국어 텍스트로 실습하기 위해서 multi-lingual bert model도 불러오겠습니다.

# Store the model we want to use
MULTI_BERT_MODEL_NAME = "bert-base-multilingual-cased"

# We need to create the model and tokenizer
multi_bert_model = AutoModel.from_pretrained(MULTI_BERT_MODEL_NAME)
multi_bert_tokenizer = AutoTokenizer.from_pretrained(MULTI_BERT_MODEL_NAME)

print(multi_bert_tokenizer.vocab_size) # 119547

불러온 multi-lingual BERT를 사용하여, 여러분이 직접 다양한 한국어 텍스트를 Tokenizing 해보세요!

Tokenizer에 새로운 Token 추가

이번엔 새로운 token을 추가해보도록 하겠습니다.

한국어 실습을 위해서, 이전 실습에서 불러왔던 multi-lingual BERT 모델의 토크나이저를 사용할게요!

이후 전처리 강의에서 배우겠지만, 토큰화 과정에서 만들어진 단어 사전 내에 없는 단어를 Out of Vocabulary (OOV)라 합니다.

OOV에 대응하기 위해서 단어 사전에 새로운 단어를 추가해줘야 할 때가 있습니다.

unk_text = "한꾺인 뜰만 알아뽈 쑤 있꼐 짝썽하꼤씁니따"

tokenized_text = multi_bert_tokenizer.tokenize(unk_text, add_special_tokens=False)
print(tokenized_text)
input_ids = multi_bert_tokenizer.encode(unk_text, add_special_tokens=False)
print(input_ids)
decoded_ids = multi_bert_tokenizer.decode(input_ids)
print(decoded_ids)

['[UNK]', '[UNK]', '[UNK]', '쑤', '[UNK]', '[UNK]']
[100, 100, 100, 9510, 100, 100]
[UNK] [UNK] [UNK] 쑤 [UNK] [UNK]

이렇게 되면 추론 및 학습에 사용할 수 없다.

added_token_num = multi_bert_tokenizer.add_tokens(["한꾺인", "뜰만", "알아뽈", "있꼐", "짝썽하꼤씁니따"])
print(added_token_num)

tokenized_text = multi_bert_tokenizer.tokenize(unk_text, add_special_tokens=False)
print(tokenized_text)

input_ids = multi_bert_tokenizer.encode(unk_text, add_special_tokens=False)
print(input_ids)

decoded_ids = multi_bert_tokenizer.decode(input_ids)
print(decoded_ids)

0
['한꾺인', '뜰만', '알아뽈', '쑤', '있꼐', '짝썽하꼤씁니따']
[119547, 119548, 119549, 9510, 119550, 119551]
한꾺인 뜰만 알아뽈 쑤 있꼐 짝썽하꼤씁니따

특정 역할을 수행하기 위해서, special token들도 추가할 수 있습니다.

학습에 주로 사용되는 토큰들은 다음과 같아요

[BOS], [EOS], [UNK], [SEPT]

모델한테 좀 더 의미를 부여할 수 있다. 학습의 용이성 부여한다!

special_token_text = "[DAD]아빠[/DAD]가 방에 들어가신다"

tokenized_text = multi_bert_tokenizer.tokenize(special_token_text, add_special_tokens=False)
print(tokenized_text)

input_ids = multi_bert_tokenizer.encode(special_token_text, add_special_tokens=False)
print(input_ids)

decoded_ids = multi_bert_tokenizer.decode(input_ids)
print(decoded_ids)

['[', 'DA', '##D', ']', '아', '##빠', '[', '/', 'DA', '##D', ']', '가', '방', '##에', '들어', '##가', '##신', '##다']
[164, 47855, 11490, 166, 9519, 119008, 164, 120, 47855, 11490, 166, 8843, 9328, 10530, 71568, 11287, 25387, 11903]
[ DAD ] 아빠 [ / DAD ] 가 방에 들어가신다

토큰이 스페셜 토큰의 의미를 하지 못하게 잘렸다

special_token_text = "[DAD]아빠[/DAD]가 방에 들어가신다"

added_token_num = multi_bert_tokenizer.add_special_tokens({"additional_special_tokens":["[DAD]", "[/DAD]"]}) # 이렇게 하면 자르지 않게 된다.

tokenized_text = multi_bert_tokenizer.tokenize(special_token_text, add_special_tokens=False)
print(tokenized_text)

input_ids = multi_bert_tokenizer.encode(special_token_text, add_special_tokens=False)
print(input_ids)

decoded_ids = multi_bert_tokenizer.decode(input_ids)
print(decoded_ids)

['[DAD]', '아', '##빠', '[/DAD]', '가', '방', '##에', '들어', '##가', '##신', '##다']
[119552, 9519, 119008, 119553, 8843, 9328, 10530, 71568, 11287, 25387, 11903]
[DAD] 아빠 [/DAD] 가 방에 들어가신다

decoded_ids = multi_bert_tokenizer.decode(input_ids,skip_special_tokens=True)
print(decoded_ids)

아빠 가 방에 들어가신다

print(added_token_num) # 2

입력을 문장의 리스트로 구성하여 tokenizer의 입력으로 사용하면 출력 결과도 배열로 저장됩니다.

sample_list = ["아빠가 방에 들어가신다", "[DAD]아빠[/DAD]가방에들어가신다"]

# Padding highlight
tokens = multi_bert_tokenizer(
    sample_list, 
    padding=True  # First sentence will have some PADDED tokens to match second sequence length
)

for i in range(2):
    print("Tokens (int)      : {}".format(tokens['input_ids'][i]))
    print("Tokens (str)      : {}".format([multi_bert_tokenizer.convert_ids_to_tokens(s) for s in tokens['input_ids'][i]]))
    print("Tokens (attn_mask): {}".format(tokens['attention_mask'][i]))
    print()

Tokens (int)      : [101, 9519, 119008, 11287, 9328, 10530, 71568, 11287, 25387, 11903, 102, 0, 0]
Tokens (str)      : ['[CLS]', '아', '##빠', '##가', '방', '##에', '들어', '##가', '##신', '##다', '[SEP]', '[PAD]', '[PAD]']
Tokens (attn_mask): [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]

Tokens (int)      : [101, 119552, 9519, 119008, 119553, 8843, 42337, 10530, 93200, 11287, 25387, 11903, 102]
Tokens (str)      : ['[CLS]', '[DAD]', '아', '##빠', '[/DAD]', '가', '##방', '##에', '##들어', '##가', '##신', '##다', '[SEP]']
Tokens (attn_mask): [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

BERT 모델 실습 - [MASK] 토큰 예측

이번에는 아까 다운받은 multi-lingual BERT 모델을 활용해볼게요

BERT가 학습할 때 사용했듯 문장 내 [MASK] 토큰에 어떤 단어가 올지 예측해보겠습니다.

masked_text = "아빠가 [MASK] 들어가신다"
tokenized_text = multi_bert_tokenizer.tokenize(masked_text)

print(tokenized_text)

['아', '##빠', '##가', '[MASK]', '들어', '##가', '##신', '##다']

from transformers import pipeline

nlp_fill = pipeline('fill-mask', model=MULTI_BERT_MODEL_NAME)
nlp_fill(masked_text)

Some weights of the model checkpoint at bert-base-multilingual-cased were not used when initializing BertForMaskedLM: ['cls.seq_relationship.weight', 'cls.seq_relationship.bias']
- This IS expected if you are initializing BertForMaskedLM from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).
- This IS NOT expected if you are initializing BertForMaskedLM from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).

[{'score': 0.053005971014499664,
  'token': 9654,
  'token_str': '잘',
  'sequence': '아빠가 잘 들어가신다'},
 {'score': 0.050629131495952606,
  'token': 11287,
  'token_str': '##가',
  'sequence': '아빠가가 들어가신다'},
 {'score': 0.04738825932145119,
  'token': 8982,
  'token_str': '나',
  'sequence': '아빠가 나 들어가신다'},
 {'score': 0.033286966383457184,
  'token': 9056,
  'token_str': '다',
  'sequence': '아빠가 다 들어가신다'},
 {'score': 0.026803260669112206,
  'token': 14867,
  'token_str': '##면',
  'sequence': '아빠가면 들어가신다'}]

multi-lingual BERT가 한국어 전용 모델이 아니다보니, 결과가 아쉽네요.

한국어가 학습 된 KOBERT를 사용하면 된다!

실습2. 영어 문장의 마스크 토큰 예측

그렇다면, 처음 사용했던 BERT 모델을 다시 활용해서 영어 문장의 [MASK] 토큰을 예측해봅시다.

불러온 BERT를 사용하여, 여러분이 직접 다양한 영어 텍스트를 마스킹하고, [MASK] 토큰을 예측해보세요!

다음과 같이 모델의 출력 결과도 획득할 수 있습니다.

tokens_pt = multi_bert_tokenizer("아빠가 방에 들어가신다", return_tensors="pt")
for key, value in tokens_pt.items():
    print("{}:\n\t{}".format(key, value))

outputs = multi_bert_model(**tokens_pt)
last_hidden_state = outputs.last_hidden_state
pooler_output = outputs.pooler_output

print("\nToken wise output: {}, Pooled output: {}".format(last_hidden_state.shape, pooler_output.shape))

input_ids:
tensor([[   101,   9519, 119008,  11287,   9328,  10530,  71568,  11287,  25387,
          11903,    102]])
token_type_ids:
tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
attention_mask:
tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])

Token wise output: torch.Size([1, 11, 768]), Pooled output: torch.Size([1, 768])

print(pooler_output) # [CLS] token to 768 dimension

만약에 vocab을 새롭게 추가했다면, 반드시 model의 embedding layer 사이즈를 늘려주세요!

print(multi_bert_model.get_input_embeddings())
multi_bert_model.resize_token_embeddings(multi_bert_tokenizer.vocab_size + added_token_num)
print(multi_bert_model.get_input_embeddings())

Embedding(119547, 768, padding_idx=0)
Embedding(119549, 768)

728x90