본문 바로가기
Drawing (AI)/Paper review

BERT(2)

by 생각하는 이상훈 2023. 8. 13.
728x90

Intro

BERT(Bidirectional Encoder Representations from Transformers)는 2018년에 구글이 공개한 사전 훈련된 모델이다. 

BERT는 2018년에 공개되어 등장과 동시에 수많은 NLP task에서 최고 성능을 보여주면서 NLP분야의 한 획을 그은 모델로 평가받고 있다. BERT의 구조에 대해 이해하고, 간단하게 실습해보려한다.

BERT의 T가 transformer인 만큼 트랜스포머를 이용하여 구현되었다. 위키피디아의 25억개 단어, BooksCorpus의 8억개 단어와 레이블이 없는 텍스트 데이터로 pre-train이 되어있는 언어모델이다.

BERT가 높은 성능을 얻을 수 있었던 것은, 레이블이 없는 방대한 데이터로 사전 훈련된 모델을 가지고 Fine-tuning을 진행하는 해당 모델을 사용하면 성능이 높게 나오는 기존의 사례들을 참고하였기 때문이다.

 

BERT의 기본 구조는 트랜스포머의 인코더를 쌓아올린 구조입니다. 위에서 볼 수 있듯이 Base 버전에서는 총 12개를 쌓았고, Large 버전에서는 총 24개를 쌓았다. 추가로 Large 버전은 Base 버전보다 d_model의 크기나 셀프 어텐션 헤드(Self Attention Heads)의 수가 더 크다. 트랜스포머 인코더 층의 수를 L, d_model의 크기를 D, 셀프 어텐션 헤드의 수를 A라고 하였을 때 각각의 크기는 다음과 같다.

  • BERT-Base : L=12, D=768, A=12 : 110M개의 파라미터
  • BERT-Large : L=24, D=1024, A=16 : 340M개의 파라미터

Contextual Embedding

BERT의 입력은 앞서 배운 딥 러닝 모델들과 마찬가지로 임베딩 층(Embedding layer)를 지난 임베딩 벡터들이다. d_model을 768로 정의하였으므로, 모든 단어들은 768차원의 임베딩 벡터가 되어 BERT의 입력으로 사용된다. BERT는 내부적인 연산을 거친 후, 동일하게 각 단어에 대해서 768차원의 벡터를 출력한다. 위의 그림에서는 BERT가 각 768차원의 [CLS], I, love, you라는 4개의 벡터를 입력받아서(입력 임베딩) 동일하게 768차원의 4개의 벡터를 출력하는 모습(출력 임베딩)을 볼 수 있다.

BERT의 연산을 거친 후의 출력 임베딩은 문장의 문맥을 모두 참고한 문맥을 반영한 임베딩이 된다. 위의 좌측 그림에서 [CLS]라는 벡터는 BERT의 초기 입력으로 사용되었을 입력 임베딩 당시에는 단순히 embedding layer를 지난 임베딩 벡터였지만, BERT를 지나고 나서는 [CLS], I, love, you라는 모든 단어 벡터들을 모두 참고한 후에 문맥 정보를 가진 벡터가 된다. 위의 좌측 그림에서는 모든 단어를 참고하고 있다는 것을 점선의 화살표로 표현하였다.

이는 [CLS]라는 단어 벡터 뿐만 아니라 다른 벡터들도 전부 마찬가지이다. 가령, 위의 우측 그림에서 출력 임베딩 단계의 love를 보면 BERT의 입력이었던 모든 단어들인 [CLS], I, love, you를 참고하고 있습니다.

하나의 단어가 모든 단어를 참고하는 연산은 사실 BERT의 12개의 층에서 전부 이루어지는 연산이다. 그리고 이를 12개의 층을 지난 후에 최종적으로 출력 임베딩을 얻게되는 것이다. 가령, 위의 그림은 BERT의 첫번째 층에 입력된 각 단어가 모든 단어를 참고한 후에 출력되는 과정을 화살표로 표현하였다. BERT의 첫번째 층의 출력 임베딩은 BERT의 두번째 층에서는 입력 임베딩이 된다.

BERT는 기본적으로 트랜스포머 인코더를 12번 쌓은 것이므로 내부적으로 각 층마다 멀티 헤드 셀프 어텐션과 포지션 와이즈 피드 포워드 신경망을 수행하고 있고 이 셀프 어텐션을 통해서 모든 단어들을 참고하여 문맥을 반영한 출력 임베딩을 얻게 된다.


BERT의 서브워드 토크나이저 : WordPiece

BERT는 단어보다 더 작은 단위로 쪼개는 서브워드 토크나이저를 사용한다. BERT가 사용한 토크나이저는 WordPiece 토크나이저로 데이터 압축 알고리즘인 바이트 페어 인코딩(Byte Pair Encoding, BPE)의 유사 알고리즘이다. 동작 방식은 BPE와 조금 다르지만, 글자로부터 서브워드들을 병합해가는 방식으로 최종 단어 집합(Vocabulary)을 만드는 것은 BPE와 유사하다.

서브워드 토크나이저는 기본적으로 자주 등장하는 단어는 그대로 단어 집합에 추가하지만, 자주 등장하지 않는 단어의 경우에는 더 작은 단위인 서브워드로 분리되어 서브워드들이 단어 집합에 추가된다는 아이디어를 이용한 알고리즘이다. 이렇게 단어 집합이 만들어지고 나면, 이 단어 집합을 기반으로 토큰화를 수행한다. BERT의 서브워드 토크나이저도 이와 마찬가지로 동작한다. 그 과정을 따라가 보면 아래와 같다.

준비물 : 이미 훈련 데이터로부터 만들어진 단어 집합

1. 토큰이 단어 집합에 존재한다.
=> 해당 토큰을 분리하지 않는다.

2. 토큰이 단어 집합에 존재하지 않는다.
=> 해당 토큰을 서브워드로 분리한다.
=> 해당 토큰의 첫번째 서브워드를 제외한 나머지 서브워드들은 앞에 "##"를 붙인 것을 토큰으로 한다.

예를 들어 embeddings이라는 단어가 입력으로 들어왔을 때, BERT의 단어 집합에 해당 단어가 존재하지 않았다고 해보자. 만약, 서브워드 토크나이저가 아닌 토크나이저라면 여기서 OOV(Out-Of-Vocabulary) 문제가 발생한다. 하지만 서브워드 토크나이저의 경우에는 해당 단어가 단어 집합에 존재하지 않았다고 해서, 서브워드 또한 존재하지 않는다는 의미는 아니므로 해당 단어를 더 쪼개려고 시도한다. 만약, BERT의 단어 집합에 em, ##bed, ##ding, #s라는 서브 워드들이 존재한다면, embeddings는 em, ##bed, ##ding, #s로 분리된다. 여기서 ##은 이 서브워드들은 단어의 중간부터 등장하는 서브워드라는 것을 알려주기 위해 단어 집합 생성 시 표시해둔 기호이다. 이런 표시가 있어야만 em, ##bed, ##ding, #s를 다시 손쉽게 embeddings로 복원할 수 있기에 이용하는 것이다.


Position Embedding

트랜스포머에서는 포지셔널 인코딩(Positional Encoding)이라는 방법을 통해서 단어의 위치 정보를 표현했다. 포지셔널 인코딩은 사인 함수와 코사인 함수를 사용하여 위치에 따라 다른 값을 가지는 행렬을 만들어 이를 단어 벡터들과 더하는 방법이다. BERT에서는 이와 유사하지만, 위치 정보를 사인 함수와 코사인 함수로 만드는 것이 아닌 학습을 통해서 얻는 포지션 임베딩(Position Embedding)이라는 방법을 사용한다.

위의 그림은 포지션 임베딩을 사용하는 방법을 보여준다. 우선, 위의 그림에서 WordPiece Embedding은 우리가 이미 알고 있는 단어 임베딩으로 실질적인 입력이다. 그리고 이 입력에 포지션 임베딩을 통해서 위치 정보를 더해주어야 한다. 포지션 임베딩의 아이디어는 굉장히 간단한데, 위치 정보를 위한 임베딩 층(Embedding layer)을 하나 더 사용한다. 가령, 문장의 길이가 4라면 4개의 포지션 임베딩 벡터를 학습시킨다. 그리고 BERT의 입력마다 다음과 같이 포지션 임베딩 벡터를 더해주는 것이다.

  • 첫번째 단어의 임베딩 벡터 + 0번 포지션 임베딩 벡터
  • 두번째 단어의 임베딩 벡터 + 1번 포지션 임베딩 벡터
  • 세번째 단어의 임베딩 벡터 + 2번 포지션 임베딩 벡터
  • 네번째 단어의 임베딩 벡터 + 3번 포지션 임베딩 벡터

실제 BERT에서는 문장의 최대 길이를 512로 하고 있으므로, 총 512개의 포지션 임베딩 벡터가 학습된다. 결론적으로 현재 설명한 내용을 기준으로는 BERT에서는 총 두 개의 임베딩 층이 사용된다. 단어 집합의 크기가 30,522개인 단어 벡터를 위한 임베딩 층과 문장의 최대 길이가 512이므로 512개의 포지션 벡터를 위한 임베딩 층이다.

사실 BERT는 세그먼트 임베딩(Segment Embedding)이라는 1개의 임베딩 층을 추가적으로 사용한다.


BERT의 Pre-training

위의 그림은 BERT의 논문에 첨부된 그림으로 ELMo와 GPT-1, 그리고 BERT의 구조적인 차이를 보여준다. 가장 우측 그림의 ELMo는 정방향 LSTM과 역방향 LSTM을 각각 훈련시키는 방식으로 양방향 언어 모델을 만들었다. 가운데 그림의 GPT-1은 트랜스포머의 디코더를 이전 단어들로부터 다음 단어를 예측하는 방식으로 단방향 언어 모델을 만들었다. Trm은 트랜스포머를 의미한다. 단방향(→)으로 설계된 Open AI GPT와 달리 가장 좌측 그림의 BERT는 화살표가 양방향으로 뻗어나가는 모습을 보여준다. 이는 마스크드 언어 모델(Masked Language Model)을 통해 양방향성을 얻었기 때문이다. BERT의 사전 훈련 방법은 크게 두 가지로 나뉜다. 첫번째는 마스크드 언어 모델이고, 두번째는 다음 문장 예측(Next sentence prediction, NSP)이다.


Masked Language Model

BERT는 사전 훈련을 위해서 인공 신경망의 입력으로 들어가는 입력 텍스트의 15%의 단어를 랜덤으로 마스킹한다. 그리고 인공 신경망에게 이 가려진 단어들을(Masked words) 예측하도록 한다. 중간에 단어들에 구멍을 뚫어놓고, 구멍에 들어갈 단어들을 예측하게 하는 식이다. 예를 들어 '나는 [MASK]에 가서 그곳에서 빵과 [MASK]를 샀다'를 주고 '슈퍼'와 '우유'를 맞추게 한다. 더 정확히는 전부 [MASK]로 변경하지는 않고, 랜덤으로 선택된 15%의 단어들은 다시 다음과 같은 비율로 규칙이 적용된다.

  • 80%의 단어들은 [MASK]로 변경한다.
    Ex) The man went to the store → The man went to the [MASK]
  • 10%의 단어들은 랜덤으로 단어가 변경된다.
    Ex) The man went to the store → The man went to the dog
  • 10%의 단어들은 동일하게 둔다.
    Ex) The man went to the store → The man went to the store

이렇게 하는 이유는 [MASK]만 사용할 경우에는 [MASK] 토큰이 파인 튜닝 단계에서는 나타나지 않으므로 사전 학습 단계와 파인 튜닝 단계에서의 불일치가 발생하는 문제가 있기 때문이다. 이 문제을 완화하기 위해서 랜덤으로 선택된 15%의 단어들의 모든 토큰을 [MASK]로 사용하지 않는다. 이를 전체 단어 관점에서 그래프를 통해 정리하면 다음과 같다.

 

전체 단어의 85%는 마스크드 언어 모델의 학습에 사용되지 않는다. 마스크드 언어 모델의 학습에 사용되는 단어는 전체 단어의 15%이다. 학습에 사용되는 12%는 [MASK]로 변경 후에 원래 단어를 예측하고 1.5%는 랜덤으로 단어가 변경된 후에 원래 단어를 예측한다. 1.5%는 단어가 변경되지는 않았지만, BERT는 이 단어가 변경된 단어인지 원래 단어가 맞는지는 알 수 없다. 이 경우에도 BERT는 원래 단어가 무엇인지를 예측하도록 한다.

 

예시를 통해 이해해보면 조금 더 쉽다. 'My dog is cute. he likes playing'이라는 문장에 대해서 마스크드 언어 모델을 학습하고자 한다. 약간의 전처리와 BERT의 서브워드 토크나이저에 의해 이 문장은 ['my', 'dog', 'is' 'cute', 'he', 'likes', 'play', '##ing']로 토큰화가 되어 BERT의 입력으로 사용된다. 그리고 언어 모델 학습을 위해서 다음과 같이 데이터가 변경되었다고 가정해보자.

 

'dog' 토큰은 [MASK]로 변경

위 그림은 'dog' 토큰이 [MASK]로 변경되어서 BERT 모델이 원래 단어를 맞추려고 하는 모습을 보여준다. 여기서 출력층에 있는 다른 위치의 벡터들은 예측과 학습에 사용되지 않고, 오직 'dog' 위치의 출력층의 벡터만이 사용된다. 구체적으로는 BERT의 손실 함수에서 다른 위치에서의 예측은 무시한다. 출력층에서는 예측을 위해 단어 집합의 크기만큼의 Dense layer에 소프트맥스 함수가 사용된 1개의 층을 사용하여 원래 단어가 무엇인지를 맞추게 된다. 그런데 만약 'dog'만 변경된 것이 아니라 다음과 같이 데이터셋이 변경되었다면 어떨까? 이번에는 세 가지 유형 모두에 대해서 가정해자.

 

  • 'dog' 토큰은 [MASK]로 변경되었습니다.
  • 'he'는 랜덤 단어 'king'으로 변경되었습니다.
  • 'play'는 변경되진 않았지만 예측에 사용됩니다.

BERT는 랜덤 단어 'king'으로 변경된 토큰에 대해서도 원래 단어가 무엇인지, 변경되지 않은 단어 'play'에 대해서도 원래 단어가 무엇인지를 예측해야 한다. 'play'는 변경되지 않았지만 BERT 입장에서는 이것이 변경된 단어인지 아닌지 모르므로 마찬가지로 원래 단어를 예측해야 한다. BERT는 마스크드 언어 모델 외에도 다음 문장 예측이라는 또 다른 task를 학습한다.


다음 문장 예측(Next Sentence Prediction, NSP)

BERT는 두 개의 문장을 준 후에 이 문장이 이어지는 문장인지 아닌지를 맞추는 방식으로 훈련시킨다. 이를 위해서 50:50 비율로 실제 이어지는 두 개의 문장과 랜덤으로 이어붙인 두 개의 문장을 주고 훈련시킨다. 이를 각각 Sentence A와 Sentence B라고 하였을 때, 다음의 예는 문장의 연속성을 확인한 경우와 그렇지 않은 경우를 보여준다.

  • 이어지는 문장의 경우
    Sentence A : The man went to the store.
    Sentence B : He bought a gallon of milk.
    Label = IsNextSentence
  • 이어지는 문장이 아닌 경우 경우
    Sentence A : The man went to the store.
    Sentence B : dogs are so cute.
    Label = NotNextSentence

BERT의 입력으로 넣을 때에는 [SEP]라는 특별 토큰을 사용해서 문장을 구분한다. 첫번째 문장의 끝에 [SEP] 토큰을 넣고, 두번째 문장이 끝나면 역시 [SEP] 토큰을 붙여준다. 그리고 이 두 문장이 실제 이어지는 문장인지 아닌지를 [CLS] 토큰의 위치의 출력층에서 이진 분류 문제를 풀도록 한다. [CLS] 토큰은 BERT가 분류 문제를 풀기 위해 추가된 특별 토큰이다. 그리고 위의 그림에서 나타난 것과 같이 마스크드 언어 모델과 다음 문장 예측은 따로 학습하는 것이 아닌 loss를 합하여 학습이 동시에 이루어진다.

BERT가 언어 모델 외에도 다음 문장 예측이라는 task를 학습하는 이유는 BERT가 풀고자 하는 task 중에서는 QA(Question Answering)나 NLI(Natural Language Inference)와 같이 두 문장의 관계를 이해하는 것이 중요한 task들이 있기 때문이다.


세그먼트 임베딩(Segment Embedding)

앞서 언급했듯이 BERT는 QA 등과 같은 두 개의 문장 입력이 필요한 task를 풀기도 합니다. 문장 구분을 위해서 BERT는 세그먼트 임베딩이라는 또 다른 임베딩 층(Embedding layer)을 사용한다. 첫번째 문장에는 Sentence 0 임베딩, 두번째 문장에는 Sentence 1 임베딩을 더해주는 방식이며 임베딩 벡터는 두 개만 사용된다.

결론적으로 BERT는 총 3개의 임베딩 층이 사용된다.

  • WordPiece Embedding : 실질적인 입력이 되는 워드 임베딩. 임베딩 벡터의 종류는 단어 집합의 크기로 30,522개.
  • Position Embedding : 위치 정보를 학습하기 위한 임베딩. 임베딩 벡터의 종류는 문장의 최대 길이인 512개.
  • Segment Embedding : 두 개의 문장을 구분하기 위한 임베딩. 임베딩 벡터의 종류는 문장의 최대 개수인 2개.

주의할 점은 많은 문헌에서 BERT가 문장 중간의 [SEP] 토큰과 두 종류의 세그먼트 임베딩을 통해서 두 개의 문장을 구분하여 입력받을 수 있다고 설명하고 있지만, 여기서 BERT에 두 개의 문장이 들어간다는 표현에서의 문장이라는 것은 실제 우리가 알고 있는 문장의 단위는 아니다. 예를 들어 QA 문제를 푸는 경우에는 [SEP]와 세그먼트 임베딩을 기준으로 구분되는 [질문(Question), 본문(Paragraph)] 두 종류의 텍스트를 입력받지만, Paragraph 1개는 실제로는 다수의 문장으로 구성될수 있다. 다시 말해 [SEP]와 세그먼트 임베딩으로 구분되는 BERT의 입력에서의 두 개의 문장은 실제로는 두 종류의 텍스트, 두 개의 문서일 수 있다.

BERT가 두 개의 문장을 입력받을 필요가 없는 경우도 있다. 예를 들어 네이버 영화 리뷰 분류나 IMDB 리뷰 분류와 같은 감성 분류 태스크에서는 한 개의 문서에 대해서만 분류를 하는 것이므로, 이 경우에는 BERT의 전체 입력에 Sentence 0 임베딩만을 더해준다.


 

728x90