본문 바로가기
딥러닝 논문리뷰

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

by 빈이름 2023. 3. 4.

GPT에 이어 BERT도 리뷰해 보도록 하겠습니다.

1. 개요

GPT를 통해 라벨이 존재하지 않는 대량의 텍스트로 사전 학습을 진행하는 것이 실제로 큰 도움이 된다는 것을 알 수 있었습니다. 비지도 학습을 통해 언어의 정보를 학습시키는 방법은 크게 2가지 분류로 나눌 수 있습니다.

1. feature-base : 미리 학습된 고정된 언어 임베딩 정보를 여러가지 NLP task에 적용하는 방식입니다. 대표적으로 ELMo가 여기에 속합니다. 

2. fine-tuning : GPT와 같이 사전학습된 파라미터를 사용해 최소한의 모델 변형으로 fine-tuning을 하여 task에 적용하는 방식입니다.

그러나 이 2가지 방식 모두 문장을 순서대로 학습한다는 한계를 갖는다고 합니다. 예를 들어 GPT는 앞부터 뒤로 문장을 생성하는 방식으로 학습하기 때문에 앞의 토큰들의 정보만 사용해야 합니다. Attention 레이어를 사용하고 있지만 이를 충분히 활용하고 있지 못한 것입니다. BERT에서는 앞뒤 맥락을 모두 고려할 수 있도록 하는 새로운 학습 방식이 필요하다고 합니다.

BERT는 사전학습 방식으로 MLM을 새롭게 제시합니다. 이 방식은 문장에서 랜덤으로 몇 개의 단어를 마스킹합니다. 모델은 마스킹 된 단어를 예측하는 방식으로 학습됩니다. 문장의 중간이 가려져 있기 때문에 모델은 마스킹된 단어를 예측하는 과정에서 자연스럽게 문장 전체를 보고 예측을 하게 됩니다. 또, 추가적으로 문장 단위의 이해도를 더 높이기 위해 Next Sentence Prediction(NSP) 방식을 추가로 사용하여 함께 학습한다고 합니다.

2. 구조

BERT는 GPT와 같이 사전 비지도 학습 단계와 지도 학습 단계로 나눠집니다. 다만 비지도 학습 방식이 다를 뿐이죠. 문장 전체를 생성하지 않고 마스킹 된 단어를 예측하는 방식으로 학습됩니다. 문장을 생성할 필요가 없기 때문에 Trasnformer의 디코더가 아닌 인코더 구조를 사용한다고 표현 합니다.

BERT의 구조는 GPT와 크게 다르지 않습니다. 학습 방식이 다를뿐.

2-1. 비지도 학습

GPT와 다르게 BERT의 비지도 학습은 MLM과 NSP를 함께 수행합니다.

MLM : 전체 문장에서 15%의 단어를 마스킹을 합니다. 모델은 마스킹된 단어를 예측하는 방식으로 학습을 합니다. 마스킹에는 "<MASK>" 토큰이 사용됩니다.

 

input : "이순신은 <MASK> <MASK>의 <MASK>이다."

output : "이순신은 조선 중기의 무신이다."

 

그러나 "<MASK>" 토큰을 사용하는 방식은 문제가 있습니다. 실제로 모델이 수행해야 할 downstream task (지도 학습, fine-tuning, downstream task 모두 같은 과정을 말합니다.) 들에서는 이 "<MASK>" 토큰이 존재하지 않는다는 것입니다. 그렇기 때문에 모델이 비지도 학습과 지도 학습 사이에서 괴리감을 느낄 수 있습니다. 그렇기 때문에 논문의 저자들은 문장의 15%를 전부 "<MASK>" 토큰으로 대체하지 않습니다. 15%의 마스킹 대상 단어 중, 80%는 "<MASK>" 토큰으로 대체하고, 10%는 다른 단어 토큰으로 대체하고, 10%는 다른 단어 토큰으로 대체하지 않습니다.

 

input : "이순신은 편의점 <MASK>의 무신이다."

output : "이순신은 조선 중기의 무신이다."

 

이렇게 할 경우 모델은 "<MASK>" 토큰 뿐만이 아니라 다른 단어 토큰들도 문장에 올바르게 사용되었는지를 판별해야 합니다. 그렇기 때문에 fine-tuning 과정에서 "<MASK>" 토큰이 존재하지 않는 것에 더 대비할 수도 있고, 다른 단어 토큰들도 올바르게 사용됐는지 의심을 해야 하기 때문에 보다 어려운 학습을 하게 됩니다. 그리고 이런 더 어려운 학습은 모델의 성능을 끌어올리는데 도움이 됩니다.

NSP : BERT는 문장 2개를 입력 받습니다. NSP는 뒷 문장이 앞 문장과 이어지는 내용인지를 판별하는 task입니다. 논문의 저자들은 이 과정이 BERT가 두 문장의 관계를 학습하는데 도움이 된다고 주장합니다.

 

input : "이순신은 조선 중기의 무신이었다. <SEP> 본관은 덕수, 자는 여해, 시호는 충무였으며, 한성 출신이었다."

output : IsNext

 

2개의 문장이 서로 다른 문장이라는 것을 모델에 알리기 위해 우선 <SEP> 토큰으로 두 문장을 분리합니다. 그리고 추가로 Segment Embedding이 추가됩니다. 이는 Position Embedding처럼 해당 토큰이 '앞 문장'에 속하는지 '뒷 문장'에 속하는지에 대한 정보를 모델에 알려주기 위해 사용됩니다.

더보기

BERT 비지도 학습의 input 예시 코드 입니다.

class BERT_datasets(Datasets):
    def __init__(self):
        ...
    def __getitem__(self, idx):
        sentence = [self.cls_token] + self.sentences_A[idx] + [self.sep_token] + self.sentences_B[idx]
        segment = [1 for _ in range(len(self.sentences_A[idx])+1))]
        segment += [2 for _ in range(len(self.sentences_B[idx])+1))]
        position = list(range(len(sentence)))
        sentence, label = self.masking(sentence)
        is_next = True
        return sentence, torch.IntTensor(segment), torch.IntTensor(position), label, is_next
    
    def masking(self, sentence):
        label = torch.zeros(len(sentence)).int()
        for i, s in enumerate(sentence):
            masking = random.random()
            if masking < 0.15:
                label[i] = sentence[i]
                masking /= 0.15
                if masking < 0.8:
                    sentence[i] = self.mask_id
                elif masking < 0.9:
                    sentence[i] = random.randrange(self.tokenizer.vocab_size)
                else:
                    sentence[i] = sentence[i]
        sentence = torch.IntTensor(sentence)
        label = torch.IntTensor(label)

        return sentence, label

# ignore_index를 이용해 masking 부분만 학습.
criterion = nn.CrossEntropyLoss(ignore_index=pad_id)

2-2. 지도 학습

지도 학습에 사용되는 구조도 GPT와 크게 다르지 않습니다. 사전 학습에 사용된 Attention Block들을 그대로 활용하고, 마지막 linear layer만 각 downstream task에 알맞게 다시 구성하여 학습합니다. Text Entailment와 같이 2개의 문장을 입력 받아야 하는 task는 "<SEP>" 토큰을 통해 구분하여 한번에 입력합니다.

BERT는 분류 문제를 GPT와 다른 방식으로 수행합니다. GPT의 경우 순차적으로 단어 토큰을 생성하는 특성을 이용하여 마지막 입력 단어 토큰의 출력 벡터를 사용해 분류를 예측하였는데요, BERT는 이렇게 학습되지 않았기 때문에 다른 방식을 사용합니다. 사실 BERT는 비지도 학습에서 문장의 맨 앞에 "<CLS>" 라는 토큰을 추가하는데요, BERT는 이 "<CLS>" 토큰의 출력 벡터를 사용해 분류를 예측합니다. 사실 이 "<CLS>" 토큰이 사용되는 것이 옳은가에 대한 얘기는 계속해서 나왔는데요, BERT의 저자도 사실 큰 이유는 없다고 합니다(..) 하지만 대체로 괜찮은 성능이 나오기 때문에 현재까지도 downstream task에서 "<CLS>" 토큰을 많이 사용하고 있습니다.

BERT의 지도 학습 방식

3. 실험

실험은 GPT와의 비교를 위해 GPT와 같은 크기의 모델(BERT_base)과 더 큰 모델(BERT_large)을 나눠 실험을 진행했습니다. BERT_base는 12개의 transformer block과 768 임베딩 차원, 12개의 attention head를 사용했고, BERT_large는 24개의 transformer block과 1024 임베딩 차원, 16개의 attention head를 사용했습니다.

3-1. GLUE

GLUE는 NLP task들을 모아둔 benchmark 입니다. 모델의 성능 테스트를 위해 자주 사용되는 task입니다. BERT는 GLUE의 모든 task에서 기존 모델들보다 좋은 성능을 냈습니다. BERT_base는 GPT보다 더 높은 점수를 보였으며, BERT_large는 그보다도 더 높은 점수를 받았습니다. GPT와 BERT_base의 모델구조는 같고 둘의 학습 방식만 다른 것을 생각했을 때 이는 매우 놀라운 결과라고 생각됩니다. 또, 모델의 크기를 키우는 것이 성능 향상에 도움을 준다는 것도 알 수 있습니다.

3-2. SQUAD

SQUAD는 위키피디아 문서로부터 사람들이 직접 문서에서 질문과 답변을 작성한 QA 데이터셋입니다. BERT는 이를 분류 문제처럼 해결하고자 합니다. 질문과 답변을 "<SEP>" 토큰을 사이에 두고 함께 입력 받아 Transformer block들을 거치는 것까지는 동일합니다. 그리고 위키피디아 문서에서 정답 위치의 시작 인덱스와 끝 인덱스를 linear 레이어를 통해 분류하는 식으로 수행됩니다. 자세한건 나중에 QA에 관한 포스팅에서 다루도록 하겠습니다. BERT는 이런 방식을 통해 SQUAD에서도 가장 높은 점수를 달성했습니다.

4. 분석

4-1. MLM과 NSP

논문의 저자들은 BERT의 설계가 정말 효과가 있는지를 검증하기 위해서 추가로 실험을 진행했습니다. 우선 NSP와 MLM 방식이 효용성이 있는지를 실험하려고 합니다. 저자는 MLM의 강점이 GPT와 다르게 문장을 순차적이 아니라 한번에 보는 데에 있다고 했습니다. 이를 증명하기 위해 GPT와 같이 문장을 순차적으로 보는 방식의 실험(LTR)을 진행합니다. 그리고 NSP를 추가로 수행하는 것이 도움이 된다는 것을 증명하기 위해 NSP를 사용한 것과 사용하지 않은 결과도 함께 비교합니다. 결과는 아래와 같이 MLM과 NSP를 수행하지 않으면 성능이 저하되는 것을 확인할 수 있었습니다.

MLM과 NSP의 효용성을 검증하기 위한 실험

4-2. 모델 크기의 영향

BERT_large는 BERT_base보다 더 높은 점수를 받았습니다. 이 논문이 나오기 전에도 모델의 크기가 클수록 성능이 좋아진다는 연구는 있었지만 이는 데이터가 많은 task에 한해서였습니다. 그러나 BERT는 데이터가 적은 task에도 pre-train만 충분히 된다면 모델이 클수록 더 좋은 성능을 받을 수 있다는 것을 입증한 것이라고 합니다. 또 모델 크기를 키워도 성능 향상에 제약이 있었던 feature-base 방식과 다르게 fine-tuning 방식은 모델의 크기는 곧 성능이 될 수 있다는 가설(현재는 정설)도 제시합니다.

5. 결론

BERT는 MLM 방식을 통해 기존 GPT의 단방향적인 정보 처리의 한계를 해결하고 같은 조건에서 더 높은 성능의 모델을 얻는데 성공했습니다. MLM 방식은 지금까지도 굉장히 효과적은 pre-training 방식이라고 합니다. 개인적으로 같은 모델이라도 학습 방식에 따라 GPT와 BERT처럼 성능에 차이가 꽤 난다는 것이 놀라웠던 것 같습니다. GPT와 BERT까지 봤으면 최근의 언어 모델들을 이해하기 위한 기반을 갖췄다고 볼 수 있을 것 같습니다!

코드는 GPT와 유사하기 때문에 이번엔 따로 올리지 않고 다음에 huggingface와 함께 직접 데이터를 전처리하고 학습하는 코드를 따로 포스팅 해보도록 하겠습니다.