텍스트 데이터를 모델이 알아들을 수 있는 형태로 변환하기 위해서는 tokenization 과정을 거쳐 encoding을 진행해야합니다.
이 때 text data를 token화 하고 특정 숫자로 encoding 하는 과정을 모두 수행하는것이 transformers tokenizer 역할입니다.
Q: PLM이 없던 시기에는 tokenizer을 어떻게 사용했을까? A : 내가 가지고 있는 데이터를 기반으로 parsing을 진행(성능 좋은 Parser를 사용했음)한 후 dictionary를 데이터별로 만들어 직접 숫자를 부여했습니다 참고
example = "첫눈 오는 이런 오후에"
model_name = 'klue/bert-base'
tokenizer = AutoTokenizer.from_pretrained(model_name) ⛄
# klue/bert-base는 한글에 대한 tokenizer이므로 한글을 인식할 수 있음
print('tokenization 결과 : ', tokenizer.tokenize(example))
print('tokenization + encoding 결과 : ', tokenizer.encode(example))
'''
tokenization 결과 : ['첫눈', '오', '##는', '이런', '오후', '##에']
tokenization + encoding 결과 : [2, 24122, 1443, 2259, 3667, 4082, 2170, 3]
'''
Subword 토크나이징 자주 쓰이는 글자 조합은 한 단위로 취급하고, 자주 쓰이지 않는 조합은 subword로 쪼갠다. "##"는 디코딩을 할 때 해당 토큰을 앞 토큰에 띄어쓰기 없이 붙인다는 것을 뜻한다.
⛔️ tokenizer 사용시 주의사항
train data의 언어를 이해 할 수 있는 tokenizer인지 확인
from transformers import AutoTokenizer
example = "첫눈 오는 이런 오후에"
model_name = 'bert-base-cased'
tokenizer = AutoTokenizer.from_pretrained(model_name)
# bert-base-cased는 영어에 대한 tokenizer이므로 한글을 전혀 이해하지 못함
# 따라서 tokenizer output은 [unk]으로 나옴
print('tokenization 결과 : ', tokenizer.tokenize(example))
print('tokenization + encoding 결과 : ', tokenizer.encode(example))
'''
tokenization 결과 : ['[UNK]', '[UNK]', '[UNK]', '[UNK]']
tokenization + encoding 결과 : [101, 100, 100, 100, 100, 102]
'''
사용하고자 하는 pretrained model과 동일한 tokenizer인지 확인
적절한 tokenizer를 사용하지 않을 경우 vocab size mismatch에러가 발생하거나 special token이 [unk]으로 처리되는 🤦🏻♀️대참사🤦🏻♂️가 벌어질 수 있음
단어의 개수와 special token이 완전히 일치하는 모델은 (예를들어 klue의 roberta, bert) tokenizer를 cross로 사용 '할 수도' 있지만 옳은 방법은 아님
첨언하자면, 공개된 영어 bert와 roberta는 tokenizer가 호환되지 않습니다. (bert vocab 28996개, roberta vocab 50265개)
klue bert는 동일한 기관에서 생성된 모델이므로 32000개로 총 vocab 사이즈가 동일하지만 이는 우연의 일치입니다.
허깅페이스 Config
사전 학습 모델을 사용하기 위해서는 사전학습 모델이 가진 setting을 그대로 가져와야합니다.
모델마다 vocab size, hidden dimension등 각각의 파라미터 세팅이 상이하므로
transformers는 이 정보를 Config로 쉽게 불러올 수 있는 기능을 제공합니다.
모델명+Config.from_pretrained가 가장 기본적인 형태였지만, 요즘은 Auto class로 더욱 편리하게 configuration을 가져올 수 있습니다.
from transformers import AutoConfig
model_name = 'klue/bert-base'
# pretrained 모델과 동일한 configuration을 가져옵니다.
model_config = AutoConfig.from_pretrained(model_name)
model_config
⛔️ config 사용시 주의사항
어떤 경우에는 config를 수정하여 사용하기도 하는데, 바꾸어도 되는 config와 바꾸지 말아야 하는 config가 정해져 있습니다.
바꾸면 안되는 config
Pretrained model 사용시 hidden dim등 이미 정해져 있는 모델의 아키텍쳐 세팅은 수정하면 안됩니다.
이를 수정해버릴 경우 에러가 발생하거나, 잘못된 방향으로 학습 될 수 있습니다.
바꾸어도 되는 config
vocab의 경우 special token을 추가한다면 config를 추가한 vocab의 개수만큼 추가하여 학습해야합니다.
downstream task를 위해 몇가지 config를 추가할 수도 있습니다. (아래에서 예시를 살펴봅시다)
sequence classification 모델을 config 세팅의 예시로 살펴봅시다. transformers document를 보면 사용하는 모델별로 미리 정의되어야 하는 config들에 대해 알려주고 있습니다
class BertForSequenceClassification(BertPreTrainedModel):
def __init__(self, config):
super().__init__(config)
self.num_labels = config.num_labels
self.config = config
self.bert = BertModel(config)
classifier_dropout = (
config.classifier_dropout if config.classifier_dropout is not None else config.hidden_dropout_prob
)
self.dropout = nn.Dropout(classifier_dropout)
self.classifier = nn.Linear(config.hidden_size, config.num_labels)
num_labels라는 정보를 꼭 기입해주어야 하는 상황입니다.
'''
현재 상황은 아래와 같습니다.
1) 사용 모델 : Sequence classification 모델을
2) special token 2개 추가함
3) label은 총 10개
case 1. 원하는 config의 값을 수정하는 케이스🍑
case 2. downstream task를 위해 추가해야하는 config 케이스🥑
'''
model_name = 'klue/bert-base'
# case 1 🍑
# config를 추가하는 방법은 두가지로 가능함
# [1] 호출 후 직접 config의 값을 수정하는 방법 🍑🍑
model_config = AutoConfig.from_pretrained(model_name)
model_config.vocab_size = model_config.vocab_size + 2
print('case 1 - [1] : ', model_config)
# [2] 호출과 동시에 수정하는 방법 🍑🤢🍑
# 하지만, vocab 수정은 해당 코드로 진행하는것을 권장하지 않음
# Advanced tutorial의 token 추가하기에서 자세히 다룸
model_config = AutoConfig.from_pretrained(model_name , vocab_size=32002)
print('case 1 - [2] : ', model_config)
# case 2 (sequence classification을 위해 num_labels를 설정하기) 🥑
# config를 추가하는 방법은 두가지로 가능함
# [1] 호출 후 직접 config의 값을 수정하는 방법
model_config = AutoConfig.from_pretrained(model_name)
model_config.num_labels = 10
# [2] 호출과 동시에 수정하는 방법
model_config = AutoConfig.from_pretrained(model_name , num_labels=10)
📣 아무런 값이나 config에 추가할 수 있을까요 ?
임의로 만들어진 config key는 config에 추가 될수는 있지만 모델 학습에 사용되지 않습니다.
# 해당 방법으로 생성하면 config에 추가 됨
model_config.hey = 'Love~'
model_config 프린트시 위와 같음 (뒷부분 생략)
하지만 아래와 같이 model_config를 불러오면 config에 hey가 추가되어 있지 않다.
간혹 모델의 성능을 높이기 위해 special token을 추가하거나, domain에 특화된 단어를 추가해주는 방법이 있습니다.
special token을 추가하는 경우 해당 token이 special token임을 tokenizer에게 알려주어야 합니다. 따라서 이 경우에는 add_special_tokens()메서드를 사용해야합니다.
일반 token을 추가하는 경우엔add_tokens()메서드를 사용하여 vocab을 늘려줄 수 있습니다.
tokenizer에 vocab을 추가했다면 pretrained model의 token embedding 사이즈를 변경해주어야합니다.: tokenizer는 len() 사용하면 vocab의 총 개수가 나오므로 이를 이용하면 됩니다. 추가한 개수 만큼 vocab을 늘려주고, embedding 사이즈도 늘려주는 과정을 통해 직관적으로 vocab을 추가합니다. model.resize_token_embedding을 이용하면 됩니다.
model_name = 'klue/bert-base'
config = AutoConfig.from_pretrained(
model_name,
)
tokenizer = AutoTokenizer.from_pretrained(
model_name,
)
# special token 추가하기
special_tokens_dict = {'additional_special_tokens': ['[special1]','[special2]','[special3]','[special4]']}
num_added_toks = tokenizer.add_special_tokens(special_tokens_dict)
# token 추가하기
new_tokens = ['COVID', 'hospitalization']
num_added_toks = tokenizer.add_tokens(new_tokens)
# 기존 config로 모델을 불러오기
# 모델을 불러오기전에 vocab을 수정하면 pretrained config와 충돌이 일어나 에러가 발생하니 주의
model = AutoModelForQuestionAnswering.from_pretrained(
model_name,
config=config,
)
# tokenizer config 수정해주기 (추후에 발생할 에러를 줄이기 위해)
config.vocab_size = len(tokenizer)
# model의 token embedding 사이즈 수정하기
model.resize_token_embeddings(len(tokenizer))
📣 special token을 추가할 때 항상 resize를 해주어야 하나요 ?
꼭 그렇지 않습니다. 잘 만들어진 모델은 resize를 하지않고도 모델에 새로운 vocab을 추가할 수 있도록 여분의 vocab 자리를 만들어 두었습니다. 여분의 vocab 개수는 모델에 따라 다르니 확인이 필요합니다.
tokenizer.vocab에 특정 단어가 포함되어있을지 확인해보려면 단어를 string 타입으로 넣어서 확인해보면 됩니다.
[unused0]이라는 인풋이 보시이나요 ? 사용하지 않는 dummy vocab을 추가했다는 의미입니다.
이러한 dummy vocab을 추가하는 이유는 무엇일까요 ? 사용자의 니즈에 따라서 단어를 추가할 수 있는 여유 공간을 제공한 것입니다. 즉, 유저가 pretrained model을 최대한 수정하지 않고도 다양한 vocab을 사용할 수 있도록 의도한 것입니다. 최근에 공개된 모델들은 대부분 'unused' vocab을 가지고 있습니다.
모든 모델이 dummy vocab을 고려하는것은 아닙니다.
예를들어, SKT의 KoBERT는 dummy vocab을 가지고 있지 않습니다.
따라서 추가 vocab을 넣을 경우에는 manual 하게 수정을 해줘야 하며, gluonnlp를 사용하는 부분을 수정해야합니다.
모델별로 dummy vocab을 위한 자리가 미리 마련된걸 알았으니, tokenizer loading시 cache가 저장되는 디렉토리로 이동해서 vocab.txt 파일을 manual하게 변경해주면 resize 없이 사용할 수 있습니다. (귀찮다면 add_token 후 resize를 합시다). vocab을 매우 많이 추가했다면 pretraining을 다시 수행하는것이 좋습니다 (TAPT: Task Adaptive PreTraining)
[CLS] output 추출하기
model에서 [CLS] 자리의 embedding만 가지고 오고 싶은 경우가 있습니다.
이때 전체 output representation에서 indexing으로 [CLS]embedding을 가지고 올 수 도 있지만
.pooler_output 을 이용하면 보다 쉽게 값을 가져올 수 있습니다
from transformers import AutoTokenizer, AutoModel
import torch
model_name = 'klue/bert-base'
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
inputs = tokenizer("내가 먼저 엿보고 온 시간들", return_tensors="pt")
outputs = model(**inputs)
cls_output = outputs.pooler_output
cls_output