자연어에서 문맥의 정보를 처리하기위해 도입한 RNN은 그 구조 자체가 하나의 Layer를 계속해서 순환하여 사용하는 구조적인 문제로 데이터의 병렬 처리가 불가능하며 시간에 따른 장기 기억 손실 문제를 해결할 수 없었다.
LSTM이나 그 구조를 개선한 GRU의 경우에도 위 문제를 해결할 수 없었고.... 그래서 나온 것이 각 경우에 따라 별도의 중요도를 매긴다는 Attention 기법 이다.
이 기법만 똑 떼어온 것이 transformer, 오늘은 이 transformer를 다루는 Hugging Face에 대해 알아보자.
Hugging Face에서는 transformers 모델에 관련된 라이브러리를 제공해준다. 사전 학습된 모델, 그 모델의 구조나 토크나이저, 혹은 직접 모델을 Fine Tuning 할 trainer 객체까지 이 라이브러리에 담겨있다.
from transformers import pipeline
pipe = pipeline(
task='text-classification',
# model=''
>>> No model was supplied, defaulted to distilbert/distilbert-base-uncased-finetuned-sst-2-english ~~
pipe.model
>>> DistilBertForSequenceClassification(~)
pipi.tokenizer
>>> DistilBertTokenizerFast(~)
모델의 특정 task 만을 지정해주었더니 알아서 기본 모델과 토크나이저를 모두 불러오는 모습이다... 너무 편한데?
실제로 사용도 해보자.
result = pipe('I am bored... getting tired')
result
>>> [{'label': 'NEGATIVE', 'score': 0.9998125433921814}]
Hugging Face에서는 이와 같이 텍스트 생성, 텍스트 요약, 번역 등 약 50개 가량의 task에 대해 백 만개가 넘는 모델을 제공해준다.
조금 더 하위레벨에서 이를 구경해보자.
자연어, 음성, 사진이나 비디오 등 비정형 데이터는 전처리기를 거쳐 수치화된 후 Embedding Vector로 변환, 이 값을 통해 각 모델은 Feature 를 추출해내며 이 Feature는 최종 Classifier를 거쳐 우리가 원하는 출력값으로 변환되게 된다.
Hugging Face에서는 이 일련의 과정들에 대해 AutoTokenizer, AutoProcessor, AutoImageProcessor, AutoFeatureExtractor 등 비정형 데이터 타입에 맞는 전처리기를 따로 불러올 수 있도록 해주며 AutoModel을 통해 전처리된 데이터의 Feature 를 추출하는 모델도 불러올 수 있도록 해준다.
위 메소드는 Hugging Face에 존재하는 이미 사전학습이 완료된 전처리기와 모델을 가져오는 것이다. pipeline 과의 차이점이라고 한다면 이들을 따로 가져온다는 점....?
from transformers AutoTokenizer AutoModel
model_id = 'bert-base-uncased'
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(model_id)
type(tokenizer)
>>> transformers.models.bert.tokenization_bert_fast.BertTokenizerFast
type(model)
>>> transformers.models.bert.modeling_bert.BertModel
참고로 Tokenizer 의 ~Fast 는 Rust 언어로 작성되어 더 빠른 전처리를 해주는 아이라고 한다.
그럼 이제 위 pipeline 에서 했던 작업을 step by step 으로 진행해보자.
# 토큰화
token_list = tokenizer(
'I am bored... getting tired',
return_tensors='pt'
)
token_list
>>> {'input_ids': [101, 1045, 2572, 11471, 1012, 1012, 1012, 2893, 5458, 102], 'token_type_ids': ~, 'attention_mask': ~}
# 임베딩 벡터
model.embeddings
>>> BertEmbeddings(
(word_embeddings): Embedding(30522, 768, padding_idx=0)
(position_embeddings): Embedding(512, 768)
(token_type_embeddings): Embedding(2, 768)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
model.config.vocab_size
>>> 30522
model.config.hidden_size
>>> 768
model.config.max_position_embeddings
>>> 512
# Feature Extraction
context_vector = model(**token_list)
help(context_vector)
>>> ~~ Base class for model's outputs that also contains a pooling of the last hidden states. ~~
help(model) 을 쳐보니 model은 torch.tensor 를 입력으로 받는다기에 return_tensors 변수를 설정해주었다.
tokenizer의 출력으로 나온 input_ids 는 각 토큰의 id, token_type_ids 는 각 토큰이 몇 번 째 문장인지, attention_mask 는 해당 토큰이 [PAD] 토큰인지를 알려주는 역할이다. 모두 함께 model 에 전달해주어야 올바른 출력이 나오므로 **<dict> 를 통해 전달해주었다.
AutoModel 로 생성된 객체는 내부에 model.embeddings 로 임베딩 벡터를 가지고 있다. 전체 어휘 사전 30522 개를 768 개의 차원을 가진 임베딩 벡터로 바꿈을 확인할 수 있었다. Attention 기법을 활용하기 위해서는 각 토큰이 문장에서 어디에 위치하는지에 대한 정보 역시 필요하므로 이를 position_embeddings 를 통해 별도로 계산함도 알 수 있었다.
context_vector.last_hidden_state.shape
>>> torch.Size([1, 10, 768])
context_vector.pooler_output.shape
torch.Size([1, 768])
이렇게 출력된 값은 Classifier 에 들어가는 last_hidden_state , 문장 전체의 의미를 압축한 벡터 표현인 pooler_output 값을 기본적으로 가지며 추가 설정에 따라 각 hidden_state, attention value 등의 정보를 담을 수 있다.
위에서 사전학습이 완료된 모델을 가져다 썼다면 이 메소드는 학습이 이루어지지 않은 랜덤하게 초기화된 가중치를 가진 모델을 불러오는 메소드이다. 단! 해당 모델의 구조나 하이퍼 파라미터만을 반영.
# 설정 불러오기
from transformers import AutoConfig
model_id = 'bert-base-uncased'
model_class = 'bert'
config = AutoConfig.from_pretrained(model_id)
config
>>> 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.52.4",
"type_vocab_size": 2,
"use_cache": true,
"vocab_size": 30522
}
config2 = AutoConfig.for_model(model_class)
config2
>>> BertConfig ~~~ 동일한 값
AutoConfig 를 통해 가져온 값은 hidden_size, num_hidden_layers, vocab_size 등과 같이 모델의 전체적인 구조를 알려주는 설정들이었다. 이를 통해 모델을 만들어보자
from transformers import AutoModel
model = AutoModel.from_config(config)
model
>>> BertModel(
(embeddings): BertEmbeddings( ~ )
(encoder): BertEncoder( ~ )
(pooler): BertPooler( ~ )
)
이렇게 만들어진 모델은 학습이 하나도 되어있지 않은 상태이므로 이 친구에게 무언가 일을 맡겼다가는 엉뚱한 결과만을 받게 될 것이다... !
추출된 Feature 를 해석하는 Classifier 까지 붙은 버전에 대한 내용과 AutoClass.from_pretrained( ) 를 통해 가져온 모델을 다른 datasets 를 이용해 파인 튜닝하는 과정에 대한 내용은 후에 추가하겠다.
실제 task 를 해결하는 단계까지 먼 걸음이었다.
거기에서 오는 호기심과 즐거움은 이전에 비해 훨씬 커졌지만 동시에 GPU 의 필요성을 느끼게 된 한 주이기도 하다. 텍스트 생성 너무 오래 걸려....
최종 프로젝트까지 얼마 남지 않았다. 화이팅 !