BERT와 Sentence Transformers 이전, 텍스트를 벡터로 바꾸는 방법은 단순했다. 단어 하나에 정수 인덱스를 부여하거나, 전체 어휘 크기의 원-핫 벡터(one-hot vector)를 사용했다. 원-핫 벡터에서 “고양이"와 “강아지"는 완전히 직교하는 벡터다. 두 단어가 의미적으로 가깝다는 정보가 전혀 없다.
분포 가설(Distributional Hypothesis)은 다르게 말한다. “같은 맥락에서 등장하는 단어는 비슷한 의미를 갖는다.” “나는 오늘 사과를 먹었다"와 “나는 오늘 배를 먹었다"에서 사과와 배는 같은 위치에 등장한다. 이 맥락 정보를 학습하면 두 단어가 비슷한 벡터를 갖게 된다.
Word2Vec (2013)
Google의 Tomas Mikolov 등이 발표했다. 두 가지 학습 방식을 제안했다.
Skip-gram
중심 단어가 주어졌을 때 주변 단어를 예측한다.
문장: "나는 오늘 맛있는 사과를 먹었다"
윈도우 크기 = 2
중심 단어 "사과" → 예측 대상: [오늘, 맛있는, 를, 먹었다]
중심 단어 "먹었다" → 예측 대상: [맛있는, 사과를, .]
신경망은 단순하다. 입력층(원-핫 벡터) → 은닉층(임베딩 벡터) → 출력층(소프트맥스). 은닉층의 가중치 행렬이 곧 단어 임베딩이다.
학습이 끝나면 은닉층 가중치를 추출해 단어 벡터로 사용한다. 이 벡터가 Word2Vec 임베딩이다.
CBOW (Continuous Bag of Words)
Skip-gram의 반대다. 주변 단어들이 주어졌을 때 중심 단어를 예측한다. Skip-gram보다 학습이 빠르지만 희귀 단어에서 성능이 낮다.
[오늘, 맛있는, 를, 먹었다] → "사과" 예측
Negative Sampling
소프트맥스는 전체 어휘에 대해 계산하므로 어휘가 수십만 개면 연산이 느리다. Negative Sampling은 정답 단어와 랜덤하게 뽑은 소수의 오답 단어만 비교한다. 어휘 전체를 보지 않고 이진 분류(정답 단어인가 아닌가)로 단순화한다. 학습 속도가 수십 배 빨라진다.
의미 연산
학습된 벡터에서 가장 유명한 발견이다.
vec("왕") - vec("남자") + vec("여자") ≈ vec("여왕")
vec("파리") - vec("프랑스") + vec("한국") ≈ vec("서울")
벡터 공간에서 단어 간 관계가 방향으로 인코딩된다. 성별, 국가-수도, 시제 같은 관계가 일관된 방향으로 표현된다. 이것이 단어 임베딩이 단순한 인덱스와 다른 핵심이다.
from gensim.models import Word2Vec
# 학습
sentences = [["나는", "사과를", "먹었다"], ["그는", "배를", "좋아한다"]]
model = Word2Vec(sentences, vector_size=100, window=5, min_count=1)
# 유사 단어 검색
model.wv.most_similar("사과")
# 벡터 연산
model.wv.most_similar(positive=["왕", "여자"], negative=["남자"])
GloVe (2014)
Stanford의 Pennington, Socher, Manning이 발표했다. Global Vectors for Word Representation의 약자다.
Word2Vec은 로컬 맥락(윈도우 내 단어)만 본다. GloVe는 전체 말뭉치에서 단어 쌍이 얼마나 자주 함께 등장하는지(동시 출현 행렬, co-occurrence matrix)를 먼저 만들고, 이 전역 통계를 압축하는 벡터를 학습한다.
동시 출현 행렬
나는 사과 배 먹었다 좋아한다
나는 0 2 1 2 1
사과 2 0 0 2 0
배 1 0 0 0 2
먹었다 2 2 0 0 0
좋아한다 1 0 2 0 0
X_ij = 단어 i의 맥락에서 단어 j가 등장한 횟수.
GloVe의 목표는 두 단어 벡터의 내적이 동시 출현 횟수의 로그에 근사하도록 학습하는 것이다.
w_i · w_j + b_i + b_j ≈ log(X_ij)
전역 통계를 활용하므로 희귀 단어와 고빈도 단어 모두에서 안정적인 성능을 보인다.
Word2Vec vs GloVe
| 항목 | Word2Vec | GloVe |
|---|---|---|
| 학습 방식 | 로컬 윈도우, 온라인 | 전역 동시 출현 행렬 |
| 메모리 | 적음 | 행렬 저장 필요 |
| 대형 말뭉치 | 효율적 | 행렬 크기가 문제 |
| 성능 | 비슷 | 비슷 |
실무에서는 둘 다 비슷하게 쓰였다. 사전 학습된 벡터(Wikipedia, Google News 등으로 학습)를 다운로드해 사용하는 것이 일반적이었다.
FastText (2016)
Facebook이 발표한 Word2Vec의 확장이다. 단어 전체 대신 n-gram 문자 단위로 임베딩한다.
"eating" → ["eat", "ati", "tin", "ing", "<ea", "ng>", ...]
단어 벡터 = n-gram 벡터들의 합
이 접근의 장점은 두 가지다. 학습 데이터에 없는 단어(OOV, Out-Of-Vocabulary)도 문자 n-gram으로 벡터를 만들 수 있다. 형태소가 풍부한 언어(한국어, 터키어 등)에서 어간이 같은 단어들이 비슷한 벡터를 갖는다.
"먹다", "먹었다", "먹을", "먹고" → 모두 "먹" n-gram 공유 → 비슷한 벡터
한계와 BERT로의 전환
Word2Vec과 GloVe는 단어 하나에 벡터 하나다. “배"라는 단어는 과일(배), 신체(배), 교통수단(배) 중 어느 뜻이든 같은 벡터를 갖는다. 문맥에 따라 의미가 달라지는 다의어를 표현할 수 없다.
또한 문장 전체의 의미를 하나의 벡터로 표현하지 못한다. 문장 내 단어 벡터를 평균내는 방식을 쓰지만, 어순과 문법 정보가 사라진다.
이 두 한계를 해결한 것이 ELMo(2018)와 BERT(2018)다. ELMo는 같은 단어라도 문맥에 따라 다른 임베딩을 부여하는 문맥 의존 임베딩(contextual embedding)을 처음 선보였고, BERT가 이를 트랜스포머 기반으로 완성했다.
트레이드오프
Word2Vec 임베딩은 가볍고 빠르다. 100차원 벡터로도 충분히 의미 관계를 포착한다. BERT 계열이 수백 배 더 크고 느린 것을 고려하면, 리소스가 제한적이거나 단순 유사도 계산이 목적이라면 Word2Vec이 여전히 실용적인 선택이다.
그러나 문장 수준의 이해가 필요하거나 다의어 처리가 중요하면 단어 임베딩만으로는 부족하다. 현대 NLP 파이프라인에서 Word2Vec은 사전처리나 가벼운 피처 추출에 남아있고, 주요 태스크는 문맥 임베딩 모델이 담당한다.