Transformer, BERT

장우솔·2022년 1월 9일
0

NLP

목록 보기
1/17

transformer와 bert 잊지 않기 위해 포스팅 합니다~!

논문 정리는 github에 자세히 올려놓았습니다.

BERT에 대해 알기 위해서는 Transformer의 구조부터 알아야합니다!

Transformer

인코더, 디코더 구조를 지닌 딥러닝 모델.
전통적인 RNN based인 encoder, decoder는 순차적으로 계산한다. 문맥벡터가 고정된 크기여서 책과 같은 긴 입력값은 처리가 어렵다. 하지만 transformer는 병렬화, 즉 RNN을 사용하지 않고 일을 한번에 처리한다. 또한, 고정된 크기의 문맥벡터를 사용하지 않고 인코더의 모든 상태값을 활용한다.
인코더 : 입력값을 양방향으로 처리
디코더 : 왼->오른쪽으로 단방향으로 처리

RNN을 적용하지 않는데, 단어의 위치 및 순서를 어떻게 알까?
positional encoding을 통해 상대적 위치 정보 알려준다. 함수로는 sin,cos 사용한다.

왜 sin, cos 함수를 사용할까?
1. 함수의 출력값은 입력값에 따라 달라지기에 출력값으로 입력값의 상대적인 위치를 알 수 있다.
2. 함수는 규칙적으로 증가 감소하기에 딥러닝모델이 이 규칙을 사용해서 입력값의 상대적위치를 쉽게 이용가능하다.
3. 출력값은 –1에서 1사이이기 때문에 무한대 길이의 입력값도 출력 가능하다.







Transformer 작동 순서

인코더

  1. 입력값이 인코더에 입력되면 토큰들은 포지셔닝 인코더와 더한다.
    단어들을 병렬로 만든 후, 어떠한 행렬과 곱하여 query, key, value값이 생성된다. 이때 행렬은 weight metrics로 딥러닝 모델학습과정에서 최적화된다.
    query, key, value값은 벡터의 형태이다.





2. 셀프 어텐션 연산을 해준다.

  • self attention : 인코더에서 이루어지는 어텐션 연산

self attention 과정
현재의 단어는 Query, 어떤 단어와의 상관관계를 구할 때 어떤 단어의 key값을 구한다. query와 key를 곱한 경우 attention score 가 된다. 이는 숫자가 높을수록 단어의 연관성이 높다. attention score를 0~1사이로 만들기 위해 softmax함수를 사용한다. softmax의 결과 값은 key값에 해당하는 단어가 현재 단어의 어느정도 연관성이 있는지 나타낸다.

예를들어,
I는 자기자신 I와 92%의 연관성을 갖는다. 각 key에 맞는 value값과 연관성을 곱해주면 연관성이 별로 없는 value값은 희미해진다. 이렇게 되면 최종 벡터 I는 단어 하나의 I가 아닌, 문장 속에서 전체적인 의미를 지닌 벡터 I가 된다.
모든 단어에 대해 어텐션 연산은 행렬곱으로 한번에 처리할 수 있다. --> 병렬처리의 장점!

  • multi head attention (병렬처리된 attention layer)
    기계번역에 큰 도움을 준다. 한 개의 어텐션으로 모호한 정보를 충분히 인코딩하기 어렵기에 멀티 헤드 어텐션을 사용해서 연관된 정보를 다른 관점에서 수집해서 이 점을 보완할 수 있다.

기억할 특징!
인코더는 출력벡터의 차원이 입력벡터와 동일하다. 따라서 여러 개 붙일 수도 있다. 각각의 인코더는 가중치를 공유하지 않고 따로 학습한다. 실제 transformer는 encoder 6개 붙임!




3. 딥러닝을 수행할 때, 역전파로 인해 position encoding이 손실될 수 있다. 이를 보완하기 위해 residual connection으로 다시 한번 더하고 layer normalization으로 학습 효율을 증진시킨다.

  • ResNet(residual connection)
    모델의 layer가 너무 깊어질수록 gradient vanishing(미분값이 작아져 weight정도가 작아지는 것) 문제 때문에 오히려 성능이 떨어지는 현상이 발생한다.
    Input은 그대로 가져오고, 나머지 잔여 정보인 F(x)만 추가적으로 더해주는 단순한 형태로 만들어 학습한다. 즉 output에 이전 레이어에서 학습했던 정보를 연결함으로써 해당 층에서 추가적으로 학습해야 할 정보만을 학습하게 된다. 이는 구현이 간단하며, 학습 난이도가 매우 낮아진다. 복잡도와 성능을 더 개선시키고 깊이가 깊어질수록 높은 정확도 향상을 보인다.

  • layer normalization
    각 샘플에 대해서, 모든 피처에 대해 평균과 분산을 구해 정규화하는 것이다.

인코더 순서

워드임베딩 -> position embedding 추가 -> multi head attention->feed forward(순방향 신경망)->residual connection






디코더

디코더 순서

masked multi head attention->multi head attention->feed forward layer(순방향 신경망)

  • 디코더 입력
    디코더 입력은 ① 인코더 마지막 블록에서 나온 소스 단어 벡터 시퀀스 ② 이전 디코더 블록의 수행 결과로 도출된 타깃 단어 벡터 시퀀스입니다.
  1. masked인 이유: 디코더에서 지금까지 출력한 값에만 attention을 적용하기 위해 붙여졌다. 아직 출력되지 않은 미래에 단어에 attention을 적용하면 안되기 때문이다.

  2. 디코더는 첫단어부터 순차적으로 단어를 출력한다. 디코더 또한 어텐션 병렬처리를 활용한다. 현재까지 출력된 값에 어텐션을 적용한다. 디코더는 디코더의 현재 상태를 query로 인코더의 최종 출력값을 key, value 값으로 사용한다. 현재 상태를 Query로 인코더에 질문하고 인코더에 출력값에서 중요한 정보를 key, value로 획득해서 decoder에 다음 단어에 가장 적합한 단어를 출력하는 방식이다.
    그 뒤는 인코더와 마찬가지로 feed forward를 거쳐 벡터로 출력한다.

벡터를 어떻게 실제 단어로 출력할까?
linear layer(softmax입력값으로 들어갈 로짓 생성)와 softmax layer(확률값 출력-가장 높은 확률값을 지닌 단어가 다음 단어가 된다.)를 이용한다. 마지막으로 label smoothing을 이용한다.

  • label smoothing
    출력값을 0~1 내에서 정답에 가까우면 1에 가까운 값 오답은 0에 가까운 값으로 변환시킨다. (단 0과 1이면 안됨.) 이는 모델 학습 시에 모델이 학습데이터에 너무 치중하여 학습하지 못하도록 보완하는 기술이다.
    레이블이 noise 한 경우, 즉 같은 입력값인데 다른 출력값이 학습데이터에 많을 경우 도움이 된다. 예를 들면, 원핫인코딩 사용시 고맙다와 감사합니다는 의미는 같은데 상이한 다른값이 되지만 smoothing을 통해 두 단어는 보다 가까운 벡터가 되고, 차이도 줄어들기에 효율적으로 학습할 수 있다.









GPT-1

GPT 현재까지 읽은 단어를 보고 다음 단어를 예측하도록 학습한다.
다음 문장을 예측하는 방식이기에 labeling 필요없다. 따라서 엄청난 양의 데이터가 필요하다.
선행 학습된 모델 자체로 여러 가지 목적의 자연어처리가 수행 가능하고 모델크기가 상당히 크다. fine tuning 필요없다.
왼->오른쪽으로 단방향 처리는 문장이해에 약점이 있을 수 있다. 또한, 한번 학습시키는데 엄청난 시간이 든다.









따라서 만들어진 새로운 모델이 bert!

BERT(bidirectional encoder representations from transformers)

  • 양방향으로 이해해서 숫자의 형태로 바꿔주는 형태이다.

  • bert는 동일한 문장 그대로 학습하되, 가려진 문장 예측하도록 한다.(masked token)

  • 한 문장말고 질의 및 응답 같은 두 문장도 받을 수도 있다. 질문과 응답 같이 주어진 질문에 적합하게 대답해야하는 문제에 해결가능하다. 양쪽 방향으로 처리하기에 질의 및 응답 구조 예측 가능한 것이다. 문장 간의 상관관계도 파악이 가능하여 문장 주제 찾기 또는 분류하기 가능하다.

  • Transformer 구조를 중점적으로 사용한 구조이다.

  • self attention layer를 여러 개 사용하여 문장에 포함되어 있는 token 사이의 의미 관계를 파악한다.

  • BERT는 decoder를 사용하지 않고, encoder를 학습시킨 후에(pre-training) 특정 task의 fine-tuning을 활용하여 결과물을 얻는 방법으로 사용된다.

  • pre-training
    사전에 아무런 정보없이 많은 양의 언어 데이터를 입력데이터로 pre-training 시킨다.
    ex) 아기가 한국어 배워서 어설프게 따라하기 시작
  • fine-tuning
    사전 데이터를 갖고 새로운 결과를 만들어내는 것이다. (재학습)
    ex) 고등학생이 한국어 배운 것으로 국어 지문 푸는 것

pre-training 과정에서 세가지 임베딩을 사용한다.

(token, segment, position embeddings)

  1. token embeddings
    wordpiece embedding으로 분리한다. 단어 의미로 분리가 아닌, 예를 들면 playing이란 단어를 play와 ing로 분류하는 방법이다. 장점 : 두 가지의 의미를 명확히 전달 가능하다. 신조어나 오탈자가 있는 단어도 학습단계에서 흔치않은 단어에 대한 예측이 향상된다.

Special Classification token(CLS)은 모든 문장의 가장 첫 번째(문장의 시작) 토큰으로 삽입된다. 이 토큰은 Classification task에서는 사용되지만, 그렇지 않을 경우엔 무시된다.

  1. segment embeddings : 두 문장이 입력될 때, 각 문장에 서로 다른 숫자를 더해주는 것, 딥러닝 모델에 두 개의 다른 문장이 있다는 것을 알려주기 위해 사용되는 임베딩이다.

Special Separator token(SEP)을 사용하여 첫 번째 문장과 두 번째 문장을 구별한다. 여기에 segment Embedding을 더해서 앞뒤 문장을 더욱 쉽게 구별할 수 있도록 도와줍니다. 이 토큰은 각 문장의 끝에 삽입된다.

  1. position embeddings
    각 토큰의 상대적 위치 정보 알려준다. 함수로는 sin,cos 이용한다.

포지셔닝 임베딩에서 상대적인 위치를 사용하는 이유는?
절대적 위치를 사용하면, 최장 길이의 문장을 세팅해야한다,

input으로 세 가지 임베딩을 더한 임베딩을 사용한다.



문장 표현을 학습하기 위해 두가지 unsupervised 방법을 사용한다.

  1. Masked Language Model
    문장에서 단어 중의 일부를 [Mask] 토큰으로 바꾼 뒤, 가려진 단어를 예측하도록 학습한다. 이 과정에서 BERT는 문맥을 파악하는 능력을 기르게 된다. 추가적으로 더욱 다양한 표현을 학습할 수 있도록 80%는 [Mask] 토큰으로 바꾸어 학습하지만, 나머지 10%는 token을 random word로 바꾸고, 마지막 10%는 원본 word 그대로를 사용하게 된다.

  2. Next Sentence Prediction
    다음 문장이 올바른 문장인지 맞추는 문제를 통해 두 문장 사이의 관계를 학습하게 된다.
    문장 A와 B를 이어 붙이는데, B는 50% 확률로 관련 있는 문장 또는 관련 없는 문장을 사용한다.

bert 코드 예시)

이런 데이터가 주어졌을 때, target 값 예측

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.layers import Dense, Input
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.models import Model
from tensorflow.keras.callbacks import ModelCheckpoint
import tensorflow_hub as hub
#tensorflow version 1.0.0일 때 돌아감
!pip install bert-tensorflow==1.0.0

#크롤링 하기
!wget --quiet https://raw.githubusercontent.com/tensorflow/models/master/official/nlp/bert/tokenization.py
from bert import tokenization

#bert encoding 하기 default 512는 out of memory가 512가 한계치라 그랬댕
def bert_encode(texts, tokenizer, max_len=512):
    all_tokens = []
    all_masks = []
    all_segments = []
    
    for text in texts:
        text = tokenizer.tokenize(text) #단어분리
            
        text = text[:max_len-2] #sep cls 자리 비워주기
        input_sequence = ["[CLS]"] + text + ["[SEP]"]
        pad_len = max_len - len(input_sequence) #자리 빈경우 패딩 처리하기
        
        tokens = tokenizer.convert_tokens_to_ids(input_sequence) #숫자로 바꾸기 token->vocab->ids
        tokens += [0] * pad_len #pad_len 개수만큼 0 뒤에 붙이기 token에 직접 패딩처리
        pad_masks = [1] * len(input_sequence) + [0] * pad_len #토큰인지 아닌지 알기 위해 1과 0으로 구성된 pad mask 생성
        segment_ids = [0] * max_len #문장 구분을 위한 부분
        
        all_tokens.append(tokens)
        all_masks.append(pad_masks) 
        all_segments.append(segment_ids)
    
    return np.array(all_tokens), np.array(all_masks), np.array(all_segments)

#bert layer 다운받기
module_url = "https://tfhub.dev/tensorflow/bert_en_uncased_L-24_H-1024_A-16/1"
bert_layer = hub.KerasLayer(module_url, trainable=True)

#모델 만들기
def build_model(bert_layer, max_len=512):
    input_word_ids = Input(shape=(max_len,), dtype=tf.int32, name="input_word_ids")
    input_mask = Input(shape=(max_len,), dtype=tf.int32, name="input_mask")
    segment_ids = Input(shape=(max_len,), dtype=tf.int32, name="segment_ids")

    _, sequence_output = bert_layer([input_word_ids, input_mask, segment_ids])
    clf_output = sequence_output[:, 0, :]
    out = Dense(1, activation='sigmoid')(clf_output) #출력층 정의 2개로 분류니까 sigmoid 사용
    
    model = Model(inputs=[input_word_ids, input_mask, segment_ids], outputs=out)
    model.compile(Adam(lr=2e-6), loss='binary_crossentropy', metrics=['accuracy'])
    
    return model

#tensorflow version이 바뀌어서 변경해줘야한다.
tf.gfile = tf.io.gfile
# Loading tokenizer from the bert layer
vocab_file = bert_layer.resolved_object.vocab_file.asset_path.numpy() #bert vocab 설정
do_lower_case = bert_layer.resolved_object.do_lower_case.numpy() #텍스트 소문자로 설정
tokenizer = tokenization.FullTokenizer(vocab_file, do_lower_case)

#Encoding the text into tokens, masks, and segment flags
train_input = bert_encode(train.text.values, tokenizer, max_len=160) #max(len(x) for x in train.text)으로 문자열 최대 길이 찾았을 때 157나옴
test_input = bert_encode(test.text.values, tokenizer, max_len=160)
train_labels = train.target.values

#Model: Build, Train, Predict, Submit
model = build_model(bert_layer, max_len=160)
model.summary()

train_history = model.fit(
    train_input, train_labels,
    validation_split=0.2,
    epochs=3,
    batch_size=2
) #batch_size 16로 하면 사양안좋아서 안돌아감

#predict
test_pred = model.predict(test값)












[참고]
https://www.youtube.com/watch?v=mxGCEWOxfe8
https://yonghyuc.wordpress.com/2020/03/04/batch-norm-vs-layer-norm/
https://hwiyong.tistory.com/392
https://www.kaggle.com/ratan123/in-depth-guide-to-google-s-bert

profile
공부한 것들을 정리하는 블로그

0개의 댓글