NLP & LLM 시리즈의 네 번째 글이다.
BoW와 TF-IDF가 못 본 의미의 세계, 임베딩이 그 빈자리를 메운다.
지난 글에서 BoW와 TF-IDF의 결정적 한계를 짚었다. 단어의 의미를 모른다.
"좋다"와 "최고"가 비슷한 뜻이라는 걸 빈도 통계만으론 알 수 없고, "구매했어요"와 "샀어요"도 별개의 토큰일 뿐이다.
단어를 그저 번호표(442번 토큰, 53번 토큰)로 다루는 한 이 한계는 절대 못 넘는다.
워드 임베딩(Word Embedding)은 발상을 통째로 바꾼다.
단어를 번호표가 아니라 저차원 의미 공간의 좌표로 표현한다.
"좋다"의 좌표를 알면 그 근처에 "최고", "만족", "추천" 같은 단어들이 자연스럽게 모여 있어야 한다.
이 좌표를 어떻게 학습하느냐가 임베딩 모델들의 정체성이다.
이 글에선 세 가지 대표적인 단어 임베딩 모델 ─Word2Vec, FastText, GloVe─ 를 차례로 본다.
임베딩의 출발점: 분포 가설
모든 임베딩 모델의 뿌리에는 하나의 가설이 있다.
분포 가설(Distributional Hypothesis) 비슷한 문맥에서 등장하는 단어는 비슷한 의미를 가진다.
영국 언어학자 J.R. Firth가 1957년에 한 말이 가장 유명하다. "단어를 알려면 그 단어와 어울리는 친구들을 보라."
마스크 리뷰 데이터로 옮겨보면 직관적이다.
- "강추"는 항상 "좋아요", "만족", "사이즈", "구매" 같은 단어와 함께 등장한다.
- "환불"은 "불량", "실망", "교환", "취소"와 어울린다.
Word2Vec ─ 빈자리를 맞히며 좌표를 배운다
가장 고전적이고 영향력 있는 임베딩 모델이다. 2013년 구글이 발표한 이후로 사실상 NLP의 판도를 바꿨다.
본 발상은 단순하다. 빈자리 맞히기 게임을 신경망에게 시킨다.
"라이브 방송 보고 ___ 주문했어요"
이 빈자리에 들어갈 단어를 맞혀라. 정답은 학습 데이터에서 가져온다.
모델은 처음엔 엉뚱한 단어를 내놓다가, 수많은 문장을 보면서 점점 정확해진다.
그 과정에서 각 단어를 의미 공간의 적절한 좌표로 옮긴다.
빈자리 게임을 잘 풀려면 비슷한 의미의 단어가 비슷한 좌표에 있어야 모델 입장에서 효율적이기 때문이다.
Skip-gram과 CBOW
빈자리 게임을 어떤 방향으로 푸느냐에 따라 두 가지 변형이 있다.
- Skip-gram:
중심 단어 하나를 보고 주변 단어를 예측한다."보고"→ 주변엔 뭐가 있을까? → "방송", "주문" 예측 - CBOW(Continuous Bag of Words):
주변 단어들을 보고 중심 단어를 예측한다.["라이브", "방송". "주문"]→ 가운데에 뭐가 있을까? → "보고" 예측
둘 다 결과적으로 비슷한 좌표를 만들지만 트레이드오프가 다르다.
Skip-gram은 희귀한 단어도 잘 학습하는 대신 계산이 무겁고,
CBOW는 빠르지만 희귀 단어 처리가 약하다.
데이터가 작거나 희귀어가 중요하면 Skip-gram, 데이터가 크고 일반 성능만 필요하면 CBOW를 쓰는 게 보통이다.
gensim으로 학습해보기
파이썬에서 gensim 라이브러리로 단 몇 줄에 학습이 가능하다.
from gensim.models import Word2Vec
# 토큰화된 문장 리스트 준비
sentences = df['text_tag'].apply(lambda x: x.split()).tolist()
# Word2Vec 학습
model = Word2Vec(
sentences,
vector_size=100, # 임베딩 벡터 차원
window=5, # 주변 단어를 보는 창 크기
min_count=2, # 2번 미만 등장 단어는 제외
sg=1, # 1: Skip-gram, 0: CBOW
epochs=50 # 학습 반복 횟수
)
window=5는 "중심 단어 좌우로 5개씩의 단어까지 주변으로 본다"는 뜻이다.
vector_size=100은 각 단어를 100차원 좌표로 표현하겠다는 의미.
차원이 클수록 더 많은 정보를 담지만 학습 데이터가 충분해야 효과가 난다.
유사 단어 뽑아보기
학습이 끝나면 단어 사이 유사도가 좌표 거리로 계산 가능해진다.
model.wv.most_similar("강추", topn=5)
[('만족', 0.78),
('추천', 0.71),
('완전', 0.65),
('최고', 0.62),
('굿', 0.59)]
빈도 통계만 봐선 절대 묶이지 않을 단어들이 의미적으로 모이는 게 보인다.
BoW가 만든 442차원 희소 벡터엔 없던 정보가 100차원 임베딩엔 살아 있다.
wv에서 쓸 수 있는 것들
model.wv["강추"] # 강추의 벡터 좌표 (100차원 array)
model.wv.most_similar("강추", topn=5) # 유사어 5개
model.wv.similarity("강추", "만족") # 두 단어 사이 코사인 유사도
model.wv.doesnt_match(["강추","만족","최악"]) # 결이 다른 단어 찾기
model.wv.key_to_index # 단어 → 인덱스 사전
model.wv.index_to_key # 인덱스 → 단어 리스트
"강추" in model.wv # 단어가 vocab에 있는지
Word2Vec의 한계: 학습한 단어만 안다
빈자리 게임으로 배운 좌표는 학습 데이터에 등장한 단어들에 한해서만 정의된다.
학습 코퍼스에 한번도 안 나온 단어 ─ OOV(Out-of-Vocabulary) ─ 가 들어오면 좌표 자체가 없어서 모델이 대응을 못 한다.
신조어, 오타, 변형 표현이 많은 한국어 텍스트에선 꽤 큰 약점이다.
이 약점을 해결하려는 게 다음에 등장한 FastText다.
FastText ─ 단어를 더 잘게 쪼개서 본다
페이스북이 2016년 발표한 모델이다.
Word2Vec의 구조는 거의 그대로 빌려오되, 한 가지 차이를 둔다.
단어를 통째로 보지 않고 글자 n-gram 조각으로 쪼개서 본다.
예를 들어 "안녕하세요"를 3~5글자 n-gram으로 쪼개면:\
3-gram:안녕하, 녕하세, 하세요
4-gram:안녕하세, 녕하세요
5-gram:안녕하세요
이 모든 조각 각각에 임베딩 좌표를 학습한다.
그리고 "안녕하세요"라는 단어의 최종 좌표는 이 조각들의 좌표를 모두 더해서 만든다.
이 작은 변화가 큰 결과를 가져온다.
신조어와 OOV에 강하다
학습 때 "안녕하세요"를 한 번도 못 봤어도, "안녕", "하세", "세요" 같은 조각은 봤을 수 있다.
그러면 FastText는 그 조각들의 좌표를 조합해서 "안녕하세요"의 좌표를 추정해낸다.
Word2Vec이라면 좌표 없음으로 떨어졌을 단어를 어떻게든 표현하는 것이다.
마스크 리뷰 데이터에서 "강추했어요", "강추함", "강추요" 같은 변형이 자주 나오는데,
FastText는 이 셋의 좌표가 자연스럽게 비슷해진다. "강추"라는 공통 조각을 공유하기 때문이다.
학습코드
사용법은 Word2Vec와 거의 같다.
from gensim.models import FastText
ft_model = FastText(
sentences,
vector_size=100,
windows=5,
min_count=2,
min_n=2, # n-gram 최소 길이
max_n=6, # n-gram 최대 길이
sg=1,
epochs=50
)
min_n, max_n이 새로 등장한 파라미터다. n-gram을 어느 범위로 쪼갤지 정한다.
한국어는 글자 한자에 의미가 실리는 경우가 많아서 2~6정도가 무난하다.
GloVe ─ 전체 통계를 한 번에 본다
스탠포드가 2014년 발표한 모델이다. Word2Vec과 같은 시대 모델이지만 접근 방식이 정반대다.
Word2Vec과 FaastText는 윈도우 안의 주변 단어만 본다.
한 문장씩 흘려보내며 좁은 시야로 좌표를 조금씩 업데이트하는 식이다. 이걸 예측 기반(Predictive) 접근이라고 한다.
Glove는 코퍼스 전체를 한꺼번에 본다.
모든 단어 쌍이 함께 등장한 횟수를 거대한 동시 출현 행렬(Co-occurrence Matrix)로 미리 만든다.
그리고 이 행렬의 통계를 모두 만족하도록 임베딩 좌표를 학습한다.
이건 카운트 기반(Count-based) 접근이다.
무엇이 다른가
- Word2Vec: 문장 하나하나에서 윈도우만 보고 학습 → 지역 정보(local) 중심
- GloVe: 전체 코퍼스의 통계를 한 행렬에 담아 학습 → 전역 정보(global) + 지역정보
전체 통계를 한 번에 활용하기 때문에 학습이 더 안정적이고, 결과 좌표가 수학적 일관성을 가진다.
가장 유명한 예시가 GloVe로 학습된 영어 임베딩의 이런 성질이다:
king - man + woman ≈ queen
좌표끼리 더하고 뺐는데 의미가 통하는 결과가 나온다.
단어 사이 관계가 좌표 공간에 선형적으로 인코딩되어 있는 것.
다만 GloVe는 학습 비용이 크다.
동시 출현 행렬을 만드는 단계부터 부담이고, 그래서 보통은 직접 학습하기보다 이미 학습된 벡터(Stanford에서 동개)를 가져다 쓰는 경우가 많다.
세 모델 한눈에 비교
| 항목 | Word2Vec | FastText | GloVe |
| 학습 방식 | 예측 기반(윈도우) | 예측 기반(윈도우) + n-gram | 카운트 기반(전역 통계) |
| OOV 대응 | 불가 | 가능(n-gram 합성) | 불가 |
| 학습 속도 | 빠름 | 약간 느림 | 느림 |
| 전역 정보 활용 | 약함 | 약함 | 강함 |
| 한국어 친화도 | 보통 | 좋음(조사·어미 변형 강함) | 보통 |
| 대표 활용처 | 일반 NLP,EDA | 신조어 많은 SNS, 한국어 | 사전학습 벡터로 가져다 쓰기 |
실무에선 데이터 규모가 작거나 신조어가 많은 한국어 환경이라면 FastText가 가장 무난한 선택이다.
영어처럼 정제된 대규모 텍스트엔 GloVe 사전 학습 벡터가 흔히 활용되고,
가장 기본적인 임베딩을 빠르게 학습할 땐 Word2Vec이 여전히 좋은 출발점이다.
단어 임베딩의 본질적 한계
세 모델 모두 한 단어에 하나의 고정된 좌표를 부여한다. 이게 곧 한계가 된다.
"사과를 한 입 베어 물었다"
"사과를 받아들였다"
두 문장에서 "사과"는 완전히 다른 뜻이다.
그런데 Word2Vec, FastText, GloVe 어떤 모델로 학습해도 "사과"의 좌표는 하나뿐이다.
두 의미를 평균낸 어정쩡한 과표가 만들어진다.
문맥에 따라 단어의 의미가 달라지는 걸 표현하려면 단어가 등장한 문장의 맥락까지 좌표에 반영해야 한다.
이건 단어 임베딩 단계에서 풀리는 문제가 아니다.
학습된 임베딩을 신경망에 넣고, 신경망이 문맥을 보면서 좌표를 다시 조정하는 단계가 필요하다.
다음 글에선 그 단계로 넘어간다.
학습된 임베딩을 PyTorch의 nn.Embedding에 얹고,
BiLSTM 신경망을 통과시켜 문장 단위 의미를 잡아내는 분류 모델을 직접 만들어본다.
임베딩이 모델 안에 들어가는 순간이다.
정리
- 워드 임베딩은 단어를 저차원 의미 공간의 좌표로 표현한다. 비슷한 의미의 단어가 가까운 좌표를 갖는다.
- Word2Vec은 빈자리 맞히기 게임으로 좌표를 학습한다. Skip-gram과 CBOW 두 변형이 있다.
- FastText는 단어를 n-gram 조각으로 쪼개서 학습하므로 신조어와 변형 표현에 강하다.
- GloVe는 코퍼스 전체의 동시 출현 통계를 활용해 더 안정적인 좌표를 만든다.
- 셋 다 한 단어에 하나의 고정 좌표만 주는 한계가 있다. 문맥을 담으려면 신경망 단계로 가야 한다.
'[NLP] 자연어 처리 & LLM > 텍스트 데이터와 임베딩' 카테고리의 다른 글
| 임베딩을 신경망에 ─ Embedding + BiLSTM으로 감정 분류 (1) | 2026.05.14 |
|---|---|
| 벡터화의 시작 ─ BoW와 TF-IDF로 단어를 숫자로 바꾸기 (0) | 2026.05.14 |
| 형태소 분석기 vs 바이트 페어 ─ 한국어 토큰화의 두 갈래 길 (0) | 2026.05.14 |
| 한국어 텍스트를 모델에 넣기까지 ─ 토큰화와 클렌징의 첫걸음 (0) | 2026.05.13 |