BERT와 이로부터 파생된 다양한 언어 모델에서는 가장 첫 위치에 문장 공통 토큰인 [CLS]를 두어 해당 위치의 임베딩 결과를 대표 임베딩으로 사용한다.
예컨대, BERT-base 모델은 토큰의 길이가 512이고 각 토큰 위치에서의 임베딩 output은 768차원 벡터이므로 sentence input 하나에 대한 output 텐서의 shape은 512*768이다.
이 중 가장 앞에 위치한 [CLS] 토큰의 768차원 벡터를 해당 sentence의 대표 임베딩 결과로 사용한다.
batch 내 각 문장에 대한 768 차원의 대표 임베딩 결과가 추출된 것이므로 최종 shape은 batch_size * 768 이 된다.
모든 sentence의 첫 번째 token은 언제나 [CLS](special classification token)이다. 이 [CLS] token은 transformer 전체층을 다 거치고 나면 token sequence의 결합된 의미를 가지게 되는데, 여기에 간단한 classifier를 붙이면 단일 문장, 또는 연속된 문장의 classification을 쉽게 할 수 있게 된다. 만약 classification task가 아니라면 이 token은 무시하면 된다.
정리하면 [CLS] 토큰은 sentence의 시작을 알려주는 토큰이면서 분류 Task에서는 sentence의 정보를 담아 최종적으로 label을 알려주는 역할을 한다.
분류 Task를 풀 때, 위 그림과 같이 [CLS] 토큰의 hidden state 값에 pooling layer를 추가해 output label 값을 출력한다.
여기서 pooling layer는 CLS 토큰 위의 layer를 의미하는데, pooling layer라는 이름을 사용하는 이유는 CNN에서의 pooling과 비슷한 의미를 지니기 때문인 것 같다. 즉, 768차원의 벡터를 6차원의 벡터(6개의 label로 분류하는 task라고 가정)로 pooling 한다는 의미인 것이다.(나의 개인적인 추측이다..)
자, 이제 [CLS] 토큰이 sentence의 정보를 어떻게 담고 있는 지 알아보자.
단순하게 설명하자면, [CLS] 토큰과 문장 내 다른 토큰들 사이의 self-attention 연산을 통해 정보를 가지게 되는 것이다.
그런데 여기서 의문점이 하나 생긴다.
예를 들어 [CLS], I, love, you라는 토큰들이 있다고 가정해 보자.
여기서 각각의 토큰들은 self-attention을 통해 각 토큰의 정보를 담고 있게 된다.
I는 I의 정보를 담은 표현 벡터를 가지고, love는 love의 정보를 담은 표현 벡터를 가지고, you 또한 마찬가지인 것처럼 말이다.
그럼 [CLS] 토큰도 [CLS]의 정보를 담은 표현 벡터를 가질 것인데, 이게 어떻게 sentence의 정보를 담게 되는 것일까?
다른 토큰들과 [CLS]토큰의 차이를 생각해 보면 다른 토큰들은 우리가 실제로 사용하는 언어 속의 토큰들이고, 일정한 패턴을 가진다.
이에 반해 [CLS] 토큰은 언어적 문법을 고려하지 않고 단순히 문장 맨 앞에 넣는 토큰이다.
이제 이 [CLS] 토큰이 self-attention layer를 거치면 문장 내에서 다른 토큰들을 골고루 적절히 반영한 weighted sum이 [CLS] 토큰의 representation으로 나올 것이라 기대할 수 있는 것이다. (어떠한 이론적 근거를 바탕으로 확실한 방법은 아닌 것 같다.)
어떤 사람들은 이러한 방법 외에 단순히 각각의 token vector 들을 average pooling하는 것이 더 좋을 것이라고도 한다.
I, love, you는 self-attention 연산 시 자기 자신과 전체 문장에 있는 단어와 연결하면서 자기 자신의 표현 벡터를 얻는 것이고,
[CLS]는 전체 문장에서 자신에 대응하는 단어가 없기 때문에 전체 문장의 표현만을 얻게 되는 느낌으로 이해하면 더욱 쉬울 것이다.
Reference
https://github.com/Gubuzeong/Getting-Started-with-Google-BERT/issues/3