BERT는 대표적인 NLU 태스크에 적합한 pre-trained 모델이다. bert는 양방향 인코더를 활용해 레이블이 없는 자연어를 학습시켜 단순하게 마지막에 가까운 레이어만 fine tuning하면 당시 QA 등의 다양한 SOTA를 달성할 수 있었다. BERT는 현재까지도 강한 영향력을 가지는 모델이므로 하나씩 살펴보도록 하자.
버트 이전의 상황을 요약해보자면, 사전학습된 language representation을 활용하여 다운스트림 태스크를 수행하는 방법론에는 "feature-based"와 "Fine-tuning", 두가지 접근법이 있었다. Feature based한 접근법을 택한 모델 중 유명한 것이 ELMo가 될 것이다. 모델의 입력으로 사용하는 임베딩 벡터를 좀 더 정교하게 만들기 문맥 정보를 활용한 모델이기 때문이다. 반대로 Fine tuning한 접근법을 사용하는 모델로는 GPT가 있을 것이다. 범용적으로 사용할 수 있는 대형 모델을 만들고, 여기에 아주 조금의 파라미터만 tuning하면 다운스트림 태스크에 적합하다는 접근법을 취하고 있기 때문이다.
물론 GPT가 예나 지금이나 좋은 성능을 보이는 것은 사실이나 논문의 저자들 입장에서는 Fine tuning에서 pre-trained representation의 성능이 충분히 발휘되지 못한다고 판단했다고 한다. 그 이유로 단일 방향으로 구성된 모델 구조로 인해 사전학습 시 구성할 수 있는 모델의 구조가 제한된다는 점을 지적한다. 즉, 단일 방향 학습으로 구성하다보니 Next Token Prediction 태스크를 수행하기 위해 모델이 recursive하게 구성되고, 이는 모델이 문맥 전반의 정보를 파악하지 못하도록 구조가 짜여지게 강요되는 측면이 있다고 본 것이다. 특히 이런 구조가 QA와 같은 토큰 단위의 태스크 수행시 매우 치명적이라고 보았다.
이에 bert는 양방향 구조를 가질 수 있도록 학습 과정을 뒤집어놓았다. 나중에 다루겠지만, 입력 토큰의 일부를 마스킹하여 모델이 마스킹 된 부분을 맞추도록 하는 Masked Language Model을 사용한다.
Bert는 인코더만 가지고 오기 때문에, 레이블이 없는 데이터를 이용해 학습하게 된다. 이와 같이 레이블이 없는 비지도 학습 기반의 변수 추출 방법론들은 매우 다양하다. 가장 쉽게는 TF-IDF도 이러한 모델이 될 수 있을 것이고, Word2Vec과 같은 임베딩 방법론도 이러한 관점에서 만들어졌다고 볼 수 있다.
그 중에서 논문이 강조하고 있는 모델은 ELMo이다..
ELMo에 대해 CS224N에서도 다루었지만 간략히 정리해보자면, ELMo는 문장을 Bi-LSTM을 이용해 양방향 정보를 모아 문맥 정보를 추출해내 토큰들의 임베딩 벡터를 생성하는 역할을 한다. 이때 여러 레이어를 쌓고, 각 레이어의 정보를 평균을 내거나 특정 방법을 통해 하나의 임베딩 벡터로 만들게 된다.
- 밤을 구워먹었는데 너무 고소하고 맛있었다.
- 서울의 밤은 찬란히 빛나지만 너무 어두워.
이때 문맥정보를 추출해 임베딩 벡터를 생성한다는 의미는 동일한 단어라도 문맥에 따라 다른 임베딩 벡터를 만들어준다는 뜻이다. 예를 들어 위의 두 문장은 BoW 관점에서 보면 동일한 단어인 "밤"을 사용하고 있지만, 사실 전혀 다른 뜻을 가진 단어이다. 이를 단순히 word2vec과 같은 임베딩 모델은 구분할 수 없다. 하지만 ELMo는 문맥을 파악하여 각 토큰의 임베딩을 생성하기 때문에, 1번과 2번 문장에서 "밤"에 해당하는 임베딩 벡터가 전혀 다르게 생성되게 된다. 즉, ELMo는 모델의 입력값 혹은 변수라 할 수 있는 임베딩 벡터를 만드는 방법에 집중한 모델이라 할 수 있는 것이다.
또다른 중요한 연구들은 비지도 학습으로 사전학습을 도입한 모델들이다. 가장 대표적인 모델로는 GPT 시리즈가 있는데, GPT는 일단 대량의 비지도 데이터를 대형 모델이 학습하여 일종의 LM을 만들면, 해당 모델의 마지막 단을 간단히 수정하거나 아주 조금의 레이어만 덧붙여서 fine tuning을 해주면 손쉽게 각 다운스트림 태스크에 SOTA를 달성하는 좋은 성능을 보일 수 있다는 것이다.
이때, GPT는 트랜스포머의 인코더 단을 떼어다 학습시키게 된다. 왜냐하면 비지도 데이터를 사용하기 때문에 로 학습할만한 레이블이 없다. 이는 결국 seq2seq 태스크로 해결할 수 없기 때문에, 인코더만 떼어다가 자신의 문장을 생성하도록 하는 auto encoder 목적함수를 가지게 된다.
버트는 크게 두 개로 나누어 생각할 수 있다고 논문에서는 소개하고 있다. pre-training과 fine-tuning이 그 둘로 우리가 버트라고 부르는 모델은 pretrained 모델을 일컫는 용어다. 큰흐름을 간략히 보면 다음과 같다. 우선 레이블이 없는 데이터를 이용해 비지도 학습으로 사전학습을 시킨 버트를 만든다. 그리고 나서 이전에 사전학습한 버트를 초기값으로 설정한 후, 각 다운스트림 태스크에 맞게 최소한의 모델 구조만 변경시키게 된다. 이때 각 다운스트림 태스크에 맞는 모델들의 공통점은 동일한 초기값인 사전학습된 모델을 사용한다는 점과 대부분의 레이어 구조가 동일하다는 점에 있다.
Model Architecure
모델 구조를 우선 살펴보자면, 거의 그대로 트랜스포머의 인코더를 가져왔다. 이때 논문에서는 Multi-layer Bidirectional Transformer Encoder라고 표현하고 있는데, 인코더 구조를 생각해보면 모든 시점의 토큰에 attention하여 정보를 취합하기 때문에 이런 식으로 표현하고 있다. 특별히 기존의 트랜스포머 인코더 구조에서 달라진 점이 있지는 않다.
이때 하이퍼 파라미터는 다음과 같이 설정되었다.
Input/Output Representations
버트는 조금 복잡한 input을 가지게 되는데 총 세가지(Token Embedding, Segment Embedding, Position Embedding)을 가지게 된다. 하나씩 이야기하자. 이때 중요한 점은 버트는 두 문장을 입력으로 삼을 수 있다. 이는 학습 과정에서 좀 더 자세히 이야기하겠지만, 두 문장이 연속으로 이어져있는 것을 기본적인 입력으로 삼는다. 이때 두 문장은 구분하고, 문장의 정보를 요약하는 것이 버트의 임베딩에서 중요한 부분이다.
종합하면 총 3개의 임베딩 벡터를 각 시점마다 가지게 되며 각각 단어, 위치, 소속 문장의 정보를 가지고 있다. 세 임베딩 벡터를 더하여 최종적인 input으로 삼는다.
일반적으로 NLP에서 사전학습된 모델을 사용하려면 가장 간단하게 생각하면 다음 단어를 예측하는 태스크를 수행하여 학습을 진행해야 할 것이다. 왜냐하면 문장은 앞에서 뒤로 작성되고, 이전의 정보를 토대로 다음단어가 작성되기 때문이다. 실제로 이에 맞추어 GPT 등의 모델이 학습되었다. 하지만 버트는 이런 식으로는 문장의 정보가 충분히 학습될 수 없기 때문에, 양방향에서 정보가 흘러오도록 모델을 구성하고, 이에 맞추어 태스크를 수정하였다.
Task #1: Masked LM
본래의 lm은 uni-directional할 수 밖에 없다. 왜냐하면 다음 단어를 예측하는 과정에서 해당 단어 혹은 그 이후의 정보가 사용되면 안되기 때문이다. 하지만 bi-dirctional한 모델 구조가 당연히 더 강력한 성능을 보일 수 밖에 없기 때문에, 버트는 기존의 LM이 수행하던 다음 단어 예측 대신에 Masked LM으로 태스크를 변경하였다.
Masked LM은 각 시퀀스에서 약 15%의 단어를 마스킹하여 지워버린다. 그리고 마지막 레이어의 해당 단어의 위치에 softmax 함수를 이용하여 해당 단어를 예측하도록 한다. 이를 통해 모델은 information leakage 없이 각 단어의 관계를 학습할 수 있을 것이다.
fine-tuning하는 과정에선 각 다운스트림 태스크에 맞게 모델을 조정하는 과정이기 때문에, masking을 사용하지 않는다. 이는 pre training과 fine tuning 시 모델 동작이 달라지는 문제점을 야기할 수 있다. 그래서 실제로는 15%의 확률로 선택된 토큰들에 대해 다시 세 경우로 나누어 처리한다.
Task #2: Next Sentence Prediction(NSP)
트랜스포머의 경우 의 형식으로 인코더 디코더 구조를 가지고 있다. 이때 한 문장을 넣어서 한 문장을 출력하게 된다. 버트 역시 트랜스포머 인코더 구조를 취하고 있기 때문에, 한 문장을 입력값으로 하여 입력 문장과 동일한 문장을 출력값으로 가지는 것이 자연스러울 것이다. 하지만 많은 NLP 태스크들(질문에 적절한 답변을 고르는 Question & Answering 등)이 두 문장 간의 관계를 파악하는데 초점을 맞추고 있다. 즉, 하나의 문장을 입력값으로 사전학습 시키는 것으로는 많은 다운스트림 태스크에 대한 모델의 성능을 보장하기 힘들다는 것이다.
이를 보완하고자, 버트는 두 문장을 입력으로 하여 두 문장이 자연스럽게 이어지는 여부를 판단하는 NSP 태스크를 수행했다. 이때, 앞 문장을 A, 뒷문장을 B라고 하면 다음과 같이 선택되었다.
상당히 단순한 태스크인데, 이를 통해 NLI나 QA에서 좋은 성능을 확보할 수 있었다고 한다. 또한, NSP 태스크는 표상 학습(representation learning) 목적함수와 관련이 있다고 한다.
Pre-training data
사전 학습은 BooksCorpus 데이터(80억 단어)와 English Wikipedia(250억 단어)를 이용했다고 한다. 또한 위키피디아 데이터에 포함된 다양한 표, 리스트, 제목 등의 본문 외의 요소들은 제외했다고 한다. 또한 버트가 NSP 태스크를 수행하여 문장 간 관계를 파악해야 하므로 단일 문장으로 구성된 데이터셋보다는 문단 단위로 구성된 데이터셋을 이용해야 했다고 한다.
버트의 사전학습 과정을 요약하면, 다음과 같은 요소로 구성되어 있다.
대용량의 데이터를 통해 사전학습한 모델을 각 다운스트림 태스크에 맞게 fine tune 하기 위해서는 어떤 점을 고려해야 할까. 일반적으로 한 문장의 짝(text pair)를 다루는 태스크의 경우 cross attention을 통해 이를 해결한다. 하지만 버트는 이미 사전학습 과정에서 두 문장을 입력값으로 하여 서로 attention하도록 bidirectional하게 구성하여 간단하게 적용이 가능하다. 버트의 입력값으로 사용되는 문장 와 문장 는 다음과 같이 세부 태스크의 text pair와 유사하다
이와 같은 다양한 태스크에서 pair 데이터가 입력으로 들어가면 output layer에서 토큰들의 표상(token representation)이 토큰 단위 태스크에 그대로 입력값으로 사용될 수 있다. 이와 같이 토큰 단위로 표상을 활용하는 태스크들은 문장 태깅이나 QA 등이 있을 것이다. [CLS] 토큰은 감성분석이나 entailment와 같은 문장 단위의 태스크에서 표상으로 사용될 수 있다. 사전학습에 비해 fine tuning은 빠르게 수행될 수 있다는 점 역시 명심하자.
GLUE는 대표적인 NLU를 평가할 수 있는 데이터셋이다. 각 태스크에 맞게 lr을 조절했고, 의 경우엔 데이터셋이 작을 경우 학습이 불안정한 경우가 있어, 랜덤하게 학습을 종료하고 동일한 체크포인트에서 data shuffling을 다르게 하여 학습하는 방법을 사용했다고 한다.
결론은 모든 태스크에서 이전의 SOTA를 넘어선 기염을 토했다. 그냥 모델이 겁나게 좋다는 말인데, 이는 도 SOTA를 달성하는 모습을 보이고 있다.
SQuAD는 질의응답 데이터 셋이다. input으로 질문과 본문을 함께 넣었고, 출력으로 응답의 시작점과 종료점을 찾도록 설계했다고 한다. 구체적으론 시작점은 start vector를 만들고, 각 토큰과 내적하여 score를 구성한후 P_i = {e^{S \cdot T_i} \over \sum_j e^{S \cdot T_j}로 시작점을 선택하고, 로 최종 응답 span 후보를 선택하여 가장 점수가 높은 span을 선택했다고 한다. 위의 표에서 나타난 것처럼 SQuAD v1.1에서도 버트가 SOTQ를 기록했다.
SQuAD v1.1의 단점을 개선한 SQuAD v2.0에서도 인간에 비해 못하지만 SOTA를 기록했다. SQuAD v2.0의 특징인 답변이 없는 본문에 대해서는 시작과 종료가 모두 [CLS]토큰을 선택하도록 했다고 한다.
SWAG는 일반 상식을 이용해 주어진 문장과 이어지는 문장을 선택하는 객관식 데이터셋이다. 이때 주어진 문장을 문장으로 각 보기 문장을 문장으로 하여, 각 보기마다 [CLS] 토큰을 생성하도록 한다. 그리고, fine tuning에 사용되는 파라미터로 벡터 C를 도입하여 각 [CLS]토큰과 내적하고 소프트맥스 레이어를 통과시켜 문제를 풀도록 했다고 한다. 이 역시 SOTA를 기록했다.