Word Embedding과 Word2Vec

단어를 벡터로 표현하는 두 방식(One-Hot Encoding과 Word Embedding)을 비교하고, Word Embedding을 만드는 대표적 방법인 Word2Vec의 원리와 forward 구현까지 정리한다.

One-Hot Encoding과 Word Embedding#

One-Hot Encoding은 sparse representation이다. 자기 자신인 단어만 1이고 나머지는 모두 0인, 차원이 vocab_size인 벡터다.

문제는 단어 사이의 유사도를 표현할 수 없다는 것이다. 서로 다른 두 단어의 One-Hot 벡터의 내적은 항상 0이고, 유클리드 거리도 항상 2의 제곱근으로 일정하다. 즉 어떤 두 단어든 똑같이 멀리 떨어져 있다.

Word Embedding은 dense vector(distributed vector)다. 단어의 의미를 여러 차원(embedding dim)에 0이 아닌 값의 형태로 표현한다. 그래서 내적, 유클리드 거리, 코사인 유사도 등으로 두 단어 사이의 유사성을 표현할 수 있다. 의미적으로 유사한 단어들 간의 코사인 유사도는 1에 가까워진다.

Word2Vec#

Word2Vec은 Word Embedding을 만드는 대표적인 방법론이자 모델이다. 두 가지 예측 Task를 가진다.

  1. Skip-gram: 중심 단어를 기준으로 주변 단어를 예측하는 Task
  2. CBOW(Continuous Bag-of-Words): 주변 단어를 바탕으로 중심 단어를 예측하는 Task

Skip-gram이 많이 사용됐기 때문에 이 방법을 기준으로 살펴본다.

알고리즘#

먼저 주변 단어를 몇 개나 볼 것인지를 정하는 Window size를 정의한다. 가령 Window size가 3이라면 중심 단어의 왼쪽 3개, 오른쪽 3개를 주변 단어로 삼는다. 이렇게 문장의 단어들로부터 무수한 (중심 단어, 주변 단어) 쌍이 생기고, 이를 학습 데이터로 쓴다. 주변 단어의 개수만큼 학습 기회가 주어지는 셈이다.

학습은 다음과 같이 이루어진다.

  1. 입력 단어 X를 모델에 넣는다.
  2. 두 번의 선형 변환을 거친 출력이 나온다.
  3. 출력은 ground truth인 주변 단어와 비교되고, 그 차이로 loss를 얻어 역전파로 파라미터를 학습한다.

가중치 matrix가 곧 Word Embedding이다#

Word2Vec의 가장 중요한 부분은 모델이 학습한 가중치 matrix가 곧 Word Embedding이 된다는 것이다.

모델은 입력 단어 하나로 시작한다. 입력은 One-Hot의 형태로 들어가고, W1, W2, 그리고 softmax를 거친다.

보통의 뉴럴 네트워크는 선형 변환 뒤에 activation이 있지만, 이 모델은 W1 뒤에 activation이 없다. 그래서 Word2Vec을 일반적인 뉴럴 네트워크가 아니라 Word Embedding을 만드는 도구로 봐야 한다.

  • 학습을 거친 W1은 입력 단어의 의미를 담은 Word Embedding이 된다.
  • W2는 입력 단어에 대응하는 출력 단어의 Word Embedding이 된다.

차원도 중요하다. W1(vocab_size, embedding_dim)이다. One-Hot으로 들어온 입력을 받아 embedding_dim 차원으로 압축한다(One-Hot 입력은 곧 해당 단어의 행을 골라내는 것과 같다). W2에서는 압축된 정보를 다시 Vocab 차원에서 분류해야 하므로 W1과 반대 방향이다. 여기서 embedding_dim은 압축의 의미를 가지기 때문에 vocab_size보다 작게 잡는다.

두 단어가 유사할수록, 즉 입력 단어와 주변 단어가 유사할수록 (입력 단어, 주변 단어) 쌍이 나올 확률이 높다. 그래서 모델은 W1에 있는 입력 단어의 embedding과 W2에 있는 출력 단어의 embedding이 유사해지도록 학습된다. 예컨대 입력이 eat이면, 출력 쪽 W2apple, orange, rice 벡터가 입력 쪽 W1eat 벡터와 유사해진다.

Skip-gram / CBOW forward 구현#

직접 구현하면서 차원의 흐름을 따라가 보면 구조가 분명해진다.

Skip-gram의 forward는 다음과 같다.

  • 단어 embedding은 nn.Embedding으로 가져온다. 이때 nn.Embedding은 One-Hot이 아니라 정수 인덱스를 입력으로 받는다. sparse=True는 입력이 One-Hot이라는 뜻이 아니라, gradient를 sparse하게 만들어 큰 vocab에서 업데이트를 효율화하는 옵션이다.
  • 차원 흐름: (B,)(B, dim)(B, vocab_size)
  • 마지막 (B, vocab_size)의 각 행은 logit 값들이다.
  • CrossEntropyLoss가 logit을 받으면 알아서 softmax를 적용한 뒤 loss를 계산한다. 그래서 모델 마지막에 softmax를 직접 붙일 필요가 없다.
  • 다만 accuracy를 확인할 때는 argmax로 인덱스를 뽑아 정답과 비교한다.

CBOW의 forward는 입력이 여러 개라는 점이 다르다.

  • 차원 흐름: (B, W*2)(B, W*2, d_e)(B, d_e)(B, vocab_size)
  • 가운데에서 (B, W*2, d_e)(B, d_e)로 만드는 것은 2W개의 주변 단어 embedding을 하나로 합치는(평균/합) 과정이다.
  • 한 가지 구현상의 선택으로, window size에 따른 문장 범위를 초과하는 경우는 샘플에서 제외했다(padding은 구현하지 않음).

정리#

  • One-Hot은 sparse하고 유사도를 표현하지 못한다. Word Embedding은 dense하고 내적/코사인 유사도로 의미를 표현한다.
  • Word2Vec은 Skip-gram(중심→주변)과 CBOW(주변→중심)가 있고, 학습된 W1(입력 embedding)과 W2(출력 embedding)이 곧 Word Embedding이다. embedding_dim은 vocab_size보다 작다.
  • forward에서 nn.Embedding은 정수 인덱스를 받고, CrossEntropyLoss가 logit을 받아 softmax까지 처리한다.

댓글