다시 이음

BERT 모델 임베딩 이해하기 본문

Pre_Onboarding by Wanted(자연어 처리)

BERT 모델 임베딩 이해하기

Taeho(Damon) 2022. 3. 1. 18:26

안녕하세요.

 

오늘은 BERT모델의 구조, 임베딩 개념에 대해서 이해하기 위해서 여러방법으로 임베딩을 설정하는 방법을 설명하려고 합니다.

 

BERT를 활용하는 데 집중할 것이기 때문에 먼저 간단한 이론에서 임베딩 활용 순서대로 보려고합니다.(BERT에 대한 특징 NSP, MLM 같은 내용은 논문을 정리한 내용을 통해 공부를 하면 좋습니다.)

 

BERT

 

기본적으로 1개의 input_embedding과 12개의 히든레이어(인코더)를 가지고 있는 구조입니다.

BERT 구조

 

위에서 보이듯이 BERT의 임베딩은 단어 임베딩, 문장 임베딩이 가능합니다.

 

 

여기서 BERT의 활용을 보기 위해서 빠르게 사전학습된 데이터를 불러옵니다.

 

BERT는 pre_trained 모델이기 때문에 저희는 BertTokenizerBertModel을 불러와서 사용합니다.

tokenizer_bert = BertTokenizer.from_pretrained("bert-base-cased")
model_bert = BertModel.from_pretrained("bert-base-cased")

 

input내용을 설정해주고 tokenizer을 사용해서 토큰화 해줍니다.

inputs = tokenizer_bert(
    text = 대화상대1,
    text_pair = 대화상대2,
    truncation = True,
    padding = "longest", #긴 문장에 맞춰서 padding해줍니다.[pad] 토큰 추가
    return_tensors='pt' # tensor형태로 리턴해줍니다.
    )

 

토큰화 과정을 거치면 토큰 id(input_ids)가 생성됩니다. 

( attention_mask= 패딩이 아닌 토큰이 1, 패딩인 토큰이 0으로 실제 토큰이 자리하는지 아닌지를 나타내는 정보, token_type_id = 세그먼트(segment) 정보로 파인튜닝을 실시할 때는 모두 0)

 

입력데이터와 모델을 GPU에 올려줍니다.

#GPU 활용 가능한지 확인하는 코드
if torch.cuda.is_available():
    device = torch.device("cuda")
else:
    device = torch.device("cpu")
print(device)

# 입력 데이터와 BERT 모델을 "GPU" 장치로 로드함
inputs = inputs.to(device)
model_bert.to(device)

 

encode, decode

 

토크나이저를 사용하여 특정 단어에 대해 encode했을 때 id를 확인할 수 있고, 반대로 특정 토큰 id에 대해 decode를 통해 단어를 가져올 수 있습니다.

# decoding
for i in range(len(inputs['input_ids'])):
    print(f"Coversation {i} -> '{tokenizer_bert.decode(inputs['input_ids'][i])}'")
    
# "code" 단어의 token id(각 단어에게 고유하게 주어진 id)를 출력(encode)
tokenizer_bert.encode('code', add_special_tokens=False)

 

이제 BERT 모델을 활용하여 output을 생성할수 있습니다.

# 입력 데이터를 BERT 모델에 넣어 출력값을 가져옴
outputs = model_bert(
    **inputs, 
    output_hidden_states=True
    )

 

여기서 output으로 ['last_hidden_state', 'pooler_output', 'hidden_states'] 가 생성됩니다.

 

pooler_output = 마지막 히든 레이어의 CLS 토큰에 해당하는 임베딩값으로 문장 벡터라고 하며 전체 문장에 대한 문장유사도를 구할 때 사용됩니다.

last_hidden_state : 마지막 히든 레이어의 임베딩 값입니다.

hidden_states : 히든레이어 층 모두의 임베딩 값입니다.

 

여기서 저희는 hidden_states 를 통해 여러가지의 임베딩을 추출하는 걸 배울겁니다.

 

 

hidden_states는 아래와 같이 구성되어 있습니다.

  1. The layer number (13 layers)
  2. The batch number (2 sentence) = 입력된 값에 따라 다릅니다. 문장의 개수
  3. The word / token number (36 tokens in our sentence) = 입력된 문장중 단어에 따라 token수가 다릅니다.
  4. The hidden unit / feature number (768 features) = 기본 768로 설정
[# layers, # batches, # tokens, # features]

위와 같은 텐서로 구성되어 있는 내용을 통해서 원하는 문장의 단어를 임베딩 할수 있습니다.

 

예를 들어

총 2개의 문장, 36개 토큰을 가지고 있을때,

마지막에서 두번째 레이어에 가중치를 사용할 것인데 첫번째 문장에서 13번째 위치한 단어에 대한 임베딩을 구할 것이라면

 

[-2, 0, 13,768] 텐서를 구하면 됩니다.

 

그렇게 인덱싱을 하면 구해지는 임베딩은 [768]size입니다.

 

 

 

최적의 임베딩은?

 

최근에는 마지막 4개의 레이어를 합하여 사용하는 것이 가장 성능이 좋다라고 받아들여지고 있는 것 같습니다.

 

위처럼 임베딩 인덱싱을 통해 첫번째 레이어 값이든, 마지막 레이어 값이든, 마지막 4개의 레이어 값이든 구할 수 있습니다.

# 마지막 4개의 레이어 값을 총합하여 구할 때 예시
token_vecs_sum= torch.sum(token_embeddings[-4:], dim=0)

sum_last4_layer_emb = token_vecs_sum
print(sum_last4_layer_emb.shape)

 

 

이런 방식을 통하면 한 문장에 있는 똑같이 생긴 단어가 과연 같은 뜻의 단어인지도 확인이 가능합니다.

ex) 요즘 배가 제철인데 배먹고 싶다. 육지가는 배부터 타야 먹을 수 있지.

 

1. 문장의 단어에 대한 임베딩을 각각 구합니다.

2. 코사인 유사도를 통해 단어 유사도를 확인합니다.