NLP

NLP 문장 수준 임베딩 - 02

ELMo(Embedding from Language Models)

  • 미국 연구기관 Allen Institute for Artificial Intelligence와 미국 워싱턴대학교 공동연구팀이 발표한 문장 임베딩 기법이다. Computer vision 분야에서 널리 쓰이고 있었던 Transfer leaning을 자연어 처리에 접목하여 주목받았다. Transfer learning이란 이미 학습된 모델을 다른 Deep learning 모델의 입력값 또는 부분으로 재사용하는 기법을 일컫는다. ELMo가 제안된 이후 자연어 처리 분야에서는 모델을 Pretrain한 후 이를 각종 DownStream Task에 적용하는 양상이 일반화됐다. BERT(Bidirectional Encoder Representations from Transfomer), GPT(Generative Pre-Training)등이 이 방식을 따른다. Pretrain한 모델을 downstream task에 맞게 업데이트하는 과정을 Fine-tuning이라고 한다.

  • ELMo는 Language Model이다. 단어 sequence가 얼마나 자연스러운지 확률값을 부여한다. 예를들어, ‘발 없는 말이 천리’라는 단어 sequence 다음에 ‘간다’라는 단어가 자주 등장했다면, 모델은 ‘발 없는 말이 천리’를 일력박아 ‘간다’를 출력해야 한다.

  • ELMo는 크게 3가지 요소로 구성돼 있다.

    • 1) 문자 단위 Convolution Layer
      • 각 단어 내 문자들 사이의 의미적, 문법적 관계를 도출한다.
    • 2) 양방향 LSTM Layer
      • 단어들 사이의 의미적, 문법적 관계를 추출해내는 역할을 한다.
    • 3) ELMo Layer
      • 문자 단위 convolution Layer와 양방야 LSTM Layer는 ELMo를 Pretrain하는 과정에서 학습된다. 하지만 ELMo Layer는 Pretrain이 끝난 후 구체적인 DownStream task를 수행하는 과정에 학습된다.
      • 문자단위 conv layer와 양방향 LSTM layer의 출력벡터등을 가중합하는 방식으로 계산된다. 이들 가중치들은 downstream task의 학습 손실을 최소화하는 방향으로 업데이트되면서 학습된다.

문자 단위 Convolution Layer

  • ELMo 입력은 문자다 구체적으로는 유니코드 ID이다. 그러므로 corpus를 해당 단어를 유니코드로 변환해야한다. 한글 유니코드 블록은 UTF-8에서 3byte로 표현되기 때문에 예를 들어 ‘밥’이라는 단어의 유니코드를 10진수로 바꾸면 3가지 숫자가 된다. 여기서 단어(문자가 아님)의 시작과 끝을 알게하기 위해 BOW와 EOW에 해당하는 값을 유니코드 앞 뒤로 붙인다. 이후에 문자 임베딩 행렬에서 각각의 ID에 해당하는 행 벡터를 참조해 붙인다. 문자의 길이가 각각 다르므로 처음에 문자의 최대 길이를 정해주면 그에따른 padding처리를 해준다. Convolution filter의 크기는 (같이 보고 싶은 문자길이) $\times$ (문자 임베딩의 차원 수)가 된다. 이를통해 피처맵을 얻고 여기서 max pooling을 해주어 결과를 낸다.

  • 위에서 같이 보고 싶은 문자길이를 조정해 가면서 여러개의 filter를 사용해 얻은 풀링 벡터들을 concatenate한 뒤 highway network와 projection(차원 조정)을 한다.

양방향 LSTM, 스코어 레이어

  • 문자 단위 convolution layer가 반환 한 단어벡터 sequence(맨 하단의 보라색 벡터들)에서 시작과 끝을 알이는 , 토큰을 앞 뒤로 붙인 뒤 학습 시킨다. 순방향 LSTM layer와 역방향 LSTM layer에 모두 위의 벡터 sequence들을 입력하는데 각각 n개의 LSTM layer를 구성한다. ELMo 기본 모델은 n=2로 설정하고 있다. ELMo에는 LSTM layer에 residual connection 구조를 적용시켜 일부 계산 노드를 생략하게하여 효율적인 Gradient 전파를 돕는다. 프리트레인 단계에서 Word2vec에서 사용되었던 negative sampling 기법이 사용된다.

ELMo 양방향 LSTM 및 출력 레이어

ELMo 레이어

  • ELMo 입베딩은 Pretrain이 끝나고 구체적인 DownStream task를 학습하는 과정에서 도출되며, 각 layer별 hidden state를 가중합한 결과이다. 임의의 task를 수행하기 위한 문장 k번째 Token의 ELMo 임베딩의 구체적인 수식은 다음과 같다.

    • $h_{k,j}^{LM}$ : k번째 Token의 j번째 layer의 순방향, 역방향 LSTM hidden state를 concatenate한 벡터를 의미한다.
    • $s_{j}^{task}$ : j번째 layer가 해당 task 수행에 얼마나 중요한지를 의미하는 scalar값이다. downstream task를 학습하는 과정에서 loss를 최소화하는 방향을 업데이트한다.
    • $\gamma^{task}$ : ELMo 벡터의 크기를 scaling하여 해당 task 수행을 돕는 역할을 한다.
    • L : 양방향 LSTM layer 개수(보통 2로 설정함)
      • j=0 -> 문자 단위 convolution Layer
      • j=1 -> 양방향 LSTM layer의 첫번째 출력
      • j=2 -> 양방향 LSTM layer의 두번째 출력

트랜스포메 네트워크

  • 트랜스포머 네트워크는 구글 연구 팀이 NIPS에 공개한 딥러닝 아키텍처다. 뛰어난 성능으로 주목받았다. 이후 발표된 GPT, BERT 등 기법은 트랜스포머 블록을 기본 모델로 쓰고 있다. 크게 두가지 작동원리로 나눌수 있다. Multi-head Attentionfeedforward Network이다.

트랜스포머 구조

Scaled Dot-Product Attention

  • Scaled Dot-Product Attention의 입력(x)는 기본적으로 행렬 형태를 가지며 그 크기는 입력 문장의 단어수 $\times$ 입력 임베딩의 차원 수이다. 트랜스 포어믜 Scaled Dot-Product Attention 매커니즘은 query, key, value 3가지 사이의 관계가 핵심이다. 입력행렬 X와 Query, Key, Value에 따르는 가중치 행렬($W^{q}, W^{k}, W^{v}$)을 각각 곱해 계산한다. 이후 query와 key가 얼마나 유사한지를 구하기 위해 두 벡터간 내적을 구해 코사인 유사도를 구한다. 이를 통해 어떤 query와 key가 특정 task 수행에 중요한 역할을 하고 있다면 트랜스포머 블록은 이들 사이의 내적값을 키우는 방식으로 학습한다. 아래 식에서 제곱근 스케일을 하는 이유는 query-key 내적 행렬의 분산을 줄이게 돼 softmax 함수의 gradient가 지나치게 작아지는 것을 방지할 수 있기 때문이다. softmax 노드의 gradient는 softmax 확률 벡터 y의 개별 요소 값에 아주 민감하기 때문에 softmax 확률 벡터의 일부 값이 지나치게 작다면 gradient vanishing 문제가 나타날 수 있다.
Scaled Dot-Product Attention
소프트맥스 노드의 gradient

Scaled Dot-Product Attention

  • 아래 그림은 Scaled Dot-Product Attention 기법으로 Query, Key, Value 사이의 관계들이 농축된 새로운 Z를 만드는 예시이다. 파란색 선으로 둘러싸인 행렬은 Query, Key 내적을 $\sqrt(d_{k})$로 나눈 뒤 softmax 함수를 취한 결과이다. 이 행렬의 행은 Query 단어들에 대응하며, 열은 Key 단어들에 대응한다. 아래 그림처럼 Query와 Key값이 동일한 attention을 self-attention이라고 한다. 이는 같은 문장 내 모든 단어 쌍 사이의 의미적, 문법적 관계를 포착해낸다는 의미이다. softmax를 취한 결과는 확률이 된다. 따라서 각 행의 합은 1이다. '드디어-금요일'값이 가장 높아 벡터 공간상에서도 가까이 있을 가능성이 높고 두 단어사이의 관계가 task 수행(번역, 분류등)에 중요하다는 이야기이다. 마지막으로는 softmax 확률을 가중치 삼아 각 값 벡터들을 가중합하는 것과 같다. 새롭게 만들어진 '드디어'에 해당하는 벡터는 해당 문장 내 단어 쌍간 관계가 모두 농축된 결과이다.

Scaled Dot-Product Attention

  • self-attention은 RNN, CNN보다 장점이 많다. CNN의 경우 사용자가 지정한 window내의 context만 살피기 때문에 문장이 길고 처음 단어와 마지막 단어 사이의 연관성 파악이 task 수행에 중요한 데이터라면 해결하기 어렵다. RNN은 sequence의 길이가 길어질수록 gradient vanishing이 일어나기 쉽기 때문에 처음 입력받았던 단어를 기억하기 쉽지 않다. 하지만 self-attention은 문장 내 모든 단어쌍 사이의 관계를 늘 전체적으로 파알할 수 있다.

Multi-Head Attention

  • Scaled Dot-Product Attention을 여러 번 시행하는 것을 가리킨다. 동일한 문장을 여러 명의 독자가 동시에 분석해 최선의 결과를 내려고 하는 것에 비유할 수 있다. Multi-Head attention의 계산 과정은 아래의 수식과 그림과 같이 이루어진다. Query, Key, Value를 Scaled Dot-Product를 통해 얻은 Attention Value를 concatenate한다. 그 후 여기에 $W^{0}$를 내적해 Multi-Head Attention 수행 결과 행렬의 크기를 Scaled Dot-Product Attention의 입력 행렬과 동일하게 맞춘다.
Multi-Head Attention 수식

Multi-Head Attention

Position-wise Feedforward Networks

  • Multi-Head Attention Layer의 입력 행렬과 출력 행렬의 크기는 입력 단어 수 $\times$ 히든 벡터 차원 수로 동일하다. Position-wise Feedforward Networks Layer에서는 Multi-Head Attention Layer의 출력 행렬을 행 벡터 단위로, 다시 말해 단어 벡터 각각에 관해 아래의 수식을 적용한다. Multi-Head Layer의 출력 행렬 가운데 하나의 단어 벡터를 x라고 하자. 이 x에 관해 두 번의 선형변환을 하는데 그 사이에 activation을 해서 적용한다.
Position-wise Feedforward Networks 수식

트랜스포머의 학습 전략

  • 트랜스포머의 학습 전략은 warm up이다. 아래 그림과 같이 사용자가 정한 step수에 이르기 까지 learning rate를 높였다가 step 수를 만족하면 조금씩 떨어끄리는 방식이다. 대규모 데이터, 큰 모델 학습에 적합하다. 이 전략은 BERT 등 이후 제안된 모델에도 널리 쓰이고 있다. 이밖에 Layer Normalization 등도 트랜스포머의 안정적인 학습에 기여하고 있는 것으로 보인다. 좀더 자세한 사항을 알고 싶다면 여기를 눌러 공부해보자.

warm up 트랜스포머의 learning rate 조정 방식

BERT(Bidirectional Encoder Representations from Transformer)

  • BERT는 구글에서 공개한 모델이다. 성능이 뛰어나 널리 쓰이고 있다.

BERT, ELMo, GPT

  • BERT의 성공 비결은 그 performance가 검증된 트랜스포머 블록을 썼을뿐더러 모델의 속성이 양방향을 지향한다는 점에 있다. 아래 그림은 BERT 이전의 모델인 GPT(Generative Pre-Training)와 ELMo 모델과의 차이점을 시각화한 것이다. GPT는 단어 sequence를 왼쪽에서 오른쪽으로 한 방향으로만 보는 아키텍쳐이다. ELMo는 Bi-LSTM Layer의 상단은 양방향이지만 중간 Layer는 역시 한 방향인 모델이다. 반면 BERT의 경우 모든 Layer에서 양방향 성질을 잃지 않고 있다.

BERT, GPT, ELMo의 아키텍쳐

  • BERT와 GPT 모델은 모두 트랜스포머 블록을 사용하고 있다. GPT는 주어진 단어 sequence를 가지고 그 다음 단어를 예측하는 과정에서 학습하는 Language Model이기 때문에 입력 단어 이후의 단어를 모델에게 알려주는 것을 하지 못한다. 따라서 아래 그림 중 1번에 속한다. 이 문제를 극복하기 위해 Masked Language Model이 제안되었다. 주어진 sequence 다음 다음를 맞추는 것에서 벗어나, 일단 문장 전체를 모델에 알려주고, Masking에 해당하는 단어가 어떤 단어일지 예측하는 과정에서 학습해보자는 아이디어이다. 이는 아래 그림 중 2번에 속한다. Masked Language Model Task에서는 모델에 문장 전체를 다 주어도 반칙이 될 수 없다. BERT 모델은 빈칸을 채워야 하기 때문이다.

양방향, 단바향 Language Model

  • 아래 그림은 GPT가 Scaled Dot-Product Attention을 하는 과정을 도식화한 것이다. 예측해야 할 단어를 보지 않기 위해 softmax score 행렬의 일부 값을 0으로 만든다. 예를 들어, 입력 문장이 ‘뜨끈한, 국밥, 한 그릇’이고 이번에 예측해야 할 단어가 ‘국밥’이라면 GPT는 이전 단어인 ‘뜨끈한’만 참고해야 한다. 반면 BERT는 빈칸만 맞추면 되기 때문에 문장 내 단어 쌍 사이의 관계를 모두 볼 수 있다.

GPT의 학습

BERT의 학습

  • BERT 임베딩을 각종 Downstream Task에 적용해 실험한 결과 BERT의 임베딩 품질이 GPT보다 좋음을 입증했다. 또한 같은 BERT 모델이더라도 Pre-Train을 할 때 한 방향(Left-to-Right)만 보게 할 경우 그 성능이 기본 모델 대비 크게 감소하는 것을 확인할 수 있다. 그만큼 모델이 양방향 전후 context를 모두 보게 하는 것이 중요하다는 이야기이다.

Pre-Train Task와 학습 데이터 구축

  • BERT의 Pre-Train Task에는 크게 Masked Language Model과 다음 문장인지 여부 맞추기(NSP, Next Sentence Prediction)로 되어있는데, 이 두가지로 인해 BERT가 양방향 모델이 될 수 있었다.

  • Masked Language Model Task 수행을 위한 학습 데이터는 다음과 같이 만든다.

    • 1) 학습 데이터 한 문장 Token의 15%를 Masking한다.

    • 2) Masking 대상 Token 가운데 80%는 실제 빈칸으로 만들고, 모델은 그 빈칸을 채운다.

      • ex) 뜨끈한 국밥 [Mask] 하는게 낫지 -> 한 그릇
    • 3) Masking 대상 Token 가운데 10%는 랜덤으로 다른 Token으로 대체하고, 모델은 해당 위치의 정답 단어가 무엇일지 맞추도록 한다.

      • ex) 뜨끈한 국밥 [한 개] 하는게 낫지 -> 한 그릇
    • 4) Masking 대상 Token 가운데 10%는 Token을 그대로 두고, 모델은 해당 위치의 정답 단어가 무엇일지 맞추도록 한다.

      • ex) 뜨끈한 국밥 [한 그릇] 하는게 낫지 -> 한 그릇
    • 위와 같이 학습 데이터를 만들게 되면 우리는 다음과 같은 점들을 기대하게 된다.

      • ‘뜨끈한 국밥 [Mask] 하는게 낫지’의 빈칸을 채워야 하기 때문에 문장 내 어느 자리에 어떤 단어를 사용하는게 자연스러운지 앞뒤 문맥을 읽어낼 수 있게 된다.

      • ‘뜨끈한 국밥 한 그릇 하는게 낫지’, ‘뜨끈한 국밥 한 개 하는게 낫지’를 비교해 보면서 주어진 문장이 의미/문법상 비문인지 아닌지 분별할 수 있게 된다.

      • 모델은 어떤 단어가 Masking 될지 전혀 모르기 때문에 문장 내 모든 단어 사이의 의미적, 문법적 관계를 세밀히 살피게 된다.

  • 다음 문자인지 여부(NSP)를 맞추기 위한 학습 데이터는 다음과 같이 만든다.

    • 1) 모든 학습 데이터는 1건당 문장 2 개로 구성된다.
    • 2) 이 가운데 절반은 동일한 문서에서 실제 이어지는 문장을 2 개 뽑고, 그 정답으로 True를 부여한다.
    • 3) 나머지 절반은 서로 다른 문서에서 문장 1개씩 뽑고, 그 정답으로 False를 부여한다.
    • 4) max_num_tokens를 정의한다.
      • 학습 데이터의 90%는 max_num_tokens가 사용자가 정한 max_sequence_length가 되도록 한다.
      • 나머지 10%는 max_num_tokens가 max_sequence_length보다 짧게 되도록 랜덤으로 정한다.
    • 5) 이전에 뽑은 문장 2 개의 단어 총 수가 max_num_token을 넘지 못할 때까지 두 문장 중 단어 수가 많은 쪽을 50%의 확률로 문장 맨 앞 또는 맨 뒤 단어 하나씩 제거한다.

    • 이같이 학습 데이터를 만들면 우리는 다음과 같은 점들을 기대하게 된다.

      • 모델은 ‘내일은 비가 올 것이다’, ‘우산을 챙겨야 할 것 같다’가 이어진 문장인지 아닌지 반복 학습한다. 따라서 문장 간 의미 관계를 이해할 수 있다.

      • NSP Task가 너무 쉬워지는 것을 방지하기 위해 문장 맨 앞 또는 맨 뒤쪽 단어 일부를 삭제했기 때문에 일부 문장 성분이 없어도 전체 의미를 이해하는 데 큰 무리가 없다.

      • 학습 데이터의 10%는 사용자가 정한 최대 길이(max_sequence_length)보다 짧은 데이터로 구성돼 있기 때문에 학습 데이터에 짧은 문장이 포함돼 있어도 성능이 크게 떨어지지 않는다.

BERT 모델의 문장 구조

  • BERT 모델은 트랜스포머 Encoder를 일부 변형한 아키텍쳐이다. 참고로 GPT는 트랜스포머의 Decoder 구조를 일부 변형한 아키텍쳐이다. Original 트랜스포머와 차이점을 위주로 설명할 것이다.

  • BERT 모델은 문장의 시작을 알리는 [CLS], 문장의 종결을 의미하는 [SEP], 마스크 Token [MASK], 배치 데이터의 길이를 맞춰주기 위한 [PAD] 등의 4가지 스페셜 Token을 사용한다. BERT 모델의 입력 Layer을 시각화하면 다음과 같다.

  • 우선 입력 Token에 해당하는 Token 벡터를 참조해 Token 임베딩을 만든다. 여기에 첫번째 문장인지, 두 번째 문장인지에 해당하는 segment 임베딩을 참조해 더해준다. 마지막으로 입력 Token의 문장 내 절대적인 위치에 해당하는 Position Embedding을 더 한다. 이렇게 3개 임베딩을 더한 각각의 벡터에 Layer Normalization을 하고 Dropout을 시행해 첫 번째 트랜스포머 블록의 입력 행렬을 구성한다. 아래 그림처럼 Token 수가 11개인 문장이라면 트랜스포머 블록의 입력행렬의 크기는 11$\times$ Hidden 차원수가 된다. Token, Segment, Position 벡터를 만들 때 참조하는 행렬은 Pre-Train Task 수행을 잘하는 방향으로 다른 학습 parameter와 함께 업데이트된다.

BERT의 입력 Layer

  • BERT가 사용하는 트랜스포머 블록에서 Original 트랜스포머와 가장 큰 차이점을 보이는 대목은 Position-wise Feedforward Networks 부분이다. Activation function을 기존의 ReLU 대신 GELU(Gaussian Error Linear Units)를 사용한다. 정규분포의 누적분포함수(cumulative distribution functions)인 GELU는 ReLU보다 0 주위에서 부드럽게 변화해 학습 성능을 높인다.

  • Original 트랜스포머와 BERT가 가장 크게 차이를 보이는 또 하나의 부분은 마지막 Prediction Layer이다. Mask Language Model, NSP를 수행하기 위해서이다. Mask Language Model과 관련된 Layer는 실제 모델에서 Pre-Train이 끝나면 그 역할을 다하고 제거되어 Transfer learning의 Pine Tuning할 경우에는 사용되지 않는다.

    • Mask Language Model Layer의 입력은 마지막 트랜스포머 블록의 Mask 위치에 해당하는 Token 벡터이다. 예를 들면, BERT 모델의 입력 문장이 ‘너 오늘 [MASK] 몇시에 할 꺼야’이고, 띄어쓰기 기준으로 Token을 나눈다면 3번째 벡터가 Input_tensor가 된다. 이 벡터를 입력 당시와 동일한 차원 수로 선형변환을 한 뒤 Layer Normalization을 시행한다. 이후 Vocabulary 수 만큼으로 Projection하기 위해 가중치 벡터인 output_weights를 곱하고 output_bias를 더해 logit 벡터를 만든다. 여기서 주목할 점은 입력 Layer에서 Token 벡터를 만들 때 참조하는 행렬을 output_weights로 재사용한다는 점이다. BERT-base 다국어 모델의 단어 수가 10만 개 안팎인 점을 고려하면 계산, 메모리 효율성을 모두 달성하기 위한 전략이라고 생각할 수 있다.

    • NSP Layer의 입력은 마지막 트랜스포머 블록의 첫 번째 Token([[CLS]])에 해당하는 벡터이다. 이 벡터를 2차원으로 projection하는 가중치 행렬 output_weights를 곱하고, 여기에 2차원 크기의 바이어스 벡터를 더한 뒤 softmax함수를 취한다. 이 확률 벡터와 정답(True or False)과 비교해 CrossEntropy를 구하고 이를 최소화하는 방향으로 Parameter들을 업데이트한다.

Pre-Train Tutorial

  • BERT 모델을 Pre-Train하려면 GPU가 여러 개 있어야 한다. GPU 8개를 썼을 때 12개 Layer 크기의 기본 모델(BERT-base)를 Pre-Train 하는데 10~15일 정도 소요된다. 리소스가 많지 않은 분들은 이미 공개돼 있는 BERT Pre-Train 모델을 사용하는 것을 추천한다.

  • 자연어 처리 연구자 오연택 님께서 한국어 BERT Pre-Train 모델을 공개했다. Pre-Train 과정 및 hyper parameter 세팅 등 자세한 내용은 여기에서 확인 해볼 수 있다.