AI Agent/텍스트 분석과 LLM

5. 단어 임베딩 — Word2Vec

427paul 2026. 6. 24. 20:42

1. Word2Vec의 필요성 (TF-IDF의 한계)

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

리뷰1 = "배우의 연기가 정말 훌륭했어요"
리뷰2 = "주인공의 연기력이 정말 뛰어났어요"  # 사실상 같은 말
리뷰3 = "스토리가 지루하고 각본이 허술했어요"  # 완전히 다른 내용

vec = TfidfVectorizer(analyzer='char_wb', ngram_range=(2,4))
X   = vec.fit_transform([리뷰1, 리뷰2, 리뷰3])
유사도 = cosine_similarity(X)

print(f"리뷰1 ↔ 리뷰2 (배우연기 vs 주인공연기력): {유사도[0][1]:.4f}")
print(f"리뷰1 ↔ 리뷰3 (연기 vs 지루한스토리):   {유사도[0][2]:.4f}")
  • 의미 유사성 파악 불가: TF-IDF는 '배우'와 '주인공'처럼 의미는 같지만 형태가 다른 단어를 완전히 다른 특성으로 처리
  • 실험 결과: 사실상 같은 의미인 두 리뷰의 TF-IDF 코사인 유사도는 0.23으로 낮게 나오는 반면, Word2Vec은 이를 0.98 이상의 높은 유사도로 잡아냄
  • 한계점: 반의어 구분이나 정교한 문맥 파악은 여전히 부분적 해결에 그침
한계 예시 Word2Vec 해결 여부
의미 유사성 모름 "배우" ≠ "주인공" (같은 뜻) ✅ 해결
반의어 구분 어려움 "감동적"과 "지루하다"가 유사할 수 있음 부분적 해결
문맥 무시 "연기가 훌륭하다" vs "연기가 별로다" → 유사도 높음 ❌ 미해결 (BERT가 해결)

2. 핵심 개념: 분포 가설(Distributional Hypothesis)

  • 아이디어: "비슷한 문맥에서 함께 등장하는 단어는 비슷한 의미를 가진다"는 언어학적 가설을 신경망으로 구현한 것
  • 학습 데이터 설계: '연기', '배우', '감동'이 한 문장에 자주 묶여 등장하면 Word2Vec은 이들을 서로 가까운 벡터로 학습
문장1: [주인공, 연기, 훌륭하다, 감동, 자연스럽다]
문장2: [배우, 연기력, 뛰어나다, 캐릭터, 몰입]
문장3: [주연, 조연, 연기, 앙상블, 완벽하다]

→ "연기"와 "배우"가 비슷한 문맥(훌륭하다, 감동, 몰입)에서 등장
→ Word2Vec: 두 단어를 비슷한 벡터로 학습

3. 학습 방식: Skip-gram vs CBOW

  • CBOW:
    • 주변 단어들로 중심 단어를 예측
    • 속도가 빠르고 대용량 데이터에 적합
  • Skip-gram:
    • 중심 단어 하나로 주변 단어들을 예측
    • 학습은 더 느리지만, 데이터가 적거나 희귀 단어를 처리할 때 성능이 더 좋아 실무에서 권장
  CBOW Skip-gram
원리 주변 단어 → 중심 단어 예측 중심 단어 → 주변 단어 예측
속도 빠름 느림
희귀 단어 약함 강함
일반 권장 대용량 데이터 소용량 or 희귀어 포함
gensim 설정 sg=0 (기본값) sg=1

4. 단어 벡터와 유사도 측정

# 긍정 단어끼리
print(f"감동 ↔ 여운:      {model.wv.similarity('감동','여운'):.4f}")
print(f"훌륭하다 ↔ 완벽:  {model.wv.similarity('훌륭하다','완벽하다'):.4f}")
print(f"연기 ↔ 배우:      {model.wv.similarity('연기','배우'):.4f}")

# 부정 단어끼리
print(f"지루하다 ↔ 별로:  {model.wv.similarity('지루하다','별로'):.4f}")
print(f"실망 ↔ 아깝다:    {model.wv.similarity('실망','아깝다'):.4f}")

# 긍정 ↔ 부정 (낮아야 함)
print(f"감동 ↔ 지루하다:  {model.wv.similarity('감동','지루하다'):.4f}")
  • 밀집 벡터 (Dense Vector):
    • 모든 차원이 의미 있는 실수로 채워진 고정 크기 배열
    • 대부분 0인 희소 벡터(TF-IDF)보다 효율적
  • 코사인 유사도 (Cosine Similarity):
    • 벡터의 크기(리뷰의 길이)는 무시하고 방향만 비교하여 내용의 유사성을 측정
def 코사인(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# model.wv.similarity()와 동일한 결과
직접 = 코사인(model.wv["연기"], model.wv["배우"])
내장 = model.wv.similarity("연기", "배우")
# 둘 다 같은 값

5. 모델 주요 파라미터 및 튜닝

model = Word2Vec(
    sentences=문장들,
    vector_size=30,
    window=4,
    min_count=1,
    epochs=500,
    seed=42
)
  • vector_size:
    • 벡터 차원 수
    • 실습용 소규모 데이터는 20~50, 실전용 대용량은 200~300 권장
  • window:
    • 문맥 범위
    • 작으면 문법적 관계, 크면 의미적 관계 학습에 유리
  • min_count:
    • 최소 등장 횟수
    • 실습 데이터는 1을 사용해 모든 단어를 포함시키고, 대용량 데이터는 5 이상으로 설정해 노이즈를 줄임
  • epochs:
    • 데이터가 적을수록 반복 학습 횟수(100~500회)를 늘려야 수렴도가 높아짐
  • seed:
    • 재현성 보장
    • seed를 고정하면 매번 같은 초기값으로 시작 → 동일한 결과 재현 가능

6. 유사 단어 검색

검색_단어들 = ["왕", "영상미", "실망", "추천"]

for 기준 in 검색_단어들:
    유사어 = model.wv.most_similar(기준, topn=4)
    print(f"'{기준}' 유사어:", [w for w,_ in 유사어])
  • 내부 동작
    1. 입력 단어 ‘기준’(왕, 영상미, 실망, 추천)의 벡터를 가져옴
    2. 어휘에 있는 모든 단어의 벡터와 코사인 유사도 계산
    3. 유사도 내림차순 정렬
    4. 상위 4개 반환
  • OOV (Out-Of-Vocabulary):
    • 학습하지 않은 단어를 만나면 에러가 발생하는 문제
    • 데이터를 늘리거나 fastText를 사용하여 보완

7. 문서 벡터화 및 분류기 연결

def 문서_벡터(단어_리스트, model):
    벡터들 = [model.wv[단어] for 단어 in 단어_리스트 if 단어 in model.wv]
    if len(벡터들) == 0:
        return np.zeros(model.vector_size)  # 어휘에 없으면 영벡터
    return np.mean(벡터들, axis=0)          # 모든 단어 벡터의 평균
  • 단어 벡터 평균: 리뷰 내 모든 단어 벡터의 평균을 구해 리뷰 전체를 하나의 벡터(문서 벡터)로 변환

8. 일반적인 경향

상황 유리한 방법
데이터 적음 (수백 개) TF-IDF
데이터 많음 (수만 개↑) Word2Vec
어휘 변화 많음 Word2Vec
빠른 구현 필요 TF-IDF
의미 유사성 중요 Word2Vec
  • 사전 학습 모델 (Pre-trained): 뉴스나 위키피디아로 이미 학습된 한국어 Word2Vec 모델을 가져와 내 데이터에 맞춰 추가 학습(Fine-tuning)하는 것이 효율적
    모델 학습 데이터 특징
    Korean Word2Vec (뉴스) 국내 뉴스 기사 시사 어휘 풍부
    fastText 한국어 위키피디아 OOV 처리 가능
    KR-ELECTRA 대규모 코퍼스 문맥 이해 (BERT 계열)

9. 모델 저장과 로드

# 저장
model.save("/tmp/movie_review_w2v.model")

# 로드
불러온 = Word2Vec.load("/tmp/movie_review_w2v.model")

# 동일한지 확인
print(model.wv.similarity("연기", "배우"))
print(불러온.wv.similarity("연기", "배우"))
# → 완전히 같은 값

마무리

오늘 배운 흐름

TF-DIF 한계: "배우" ≠ "주인공"
    ↓
Word2Vec 핵심 아이디어
  "비슷한 문맥 = 비슷한 벡터"
    ↓
학습 (3줄)
  model = Word2Vec(sentences=..., vector_size=30, window=4, epochs=500)
    ↓
유사 단어 검색
  model.wv.most_similar("연기", topn=5)
    ↓
두 단어 유사도
  model.wv.similarity("연기", "배우")   # 0.99
    ↓
어휘 확인 (OOV 방지)
  if 단어 in model.wv:
    ↓
문서 벡터 (단어 벡터 평균)
  np.mean([model.wv[단어] for 단어 in 리뷰], axis=0)
    ↓
4주차 분류기 연결
  LogisticRegression().fit(문서벡터, 레이블)
    ↓
모델 저장 / 로드
  model.save() / Word2Vec.load()

핵심 개념 한 줄 요약

개념 한 줄 설명 현실 팁
분포 가설 비슷한 문맥 = 비슷한 의미 Word2Vec의 이론적 토대
Dense Vector 모든 차원이 의미 있는 벡터 TF-IDF(Sparse)와 반대
Skip-gram 중심 단어로 주변 예측 소규모 데이터에 권장
vector_size 벡터 차원 수 데이터 적으면 30~50
window 문맥 범위 보통 5 사용
epochs 반복 학습 횟수 데이터 적으면 늘리기
most_similar 유사 단어 검색 OOV 먼저 확인!
OOV 학습 데이터에 없는 단어 KeyError 주의
문서 벡터 단어 벡터 평균 단어 순서 무시하는 한계 있음

핵심 코드 치트시트

from gensim.models import Word2Vec
import numpy as np

# 학습
model = Word2Vec(sentences=[[단어들], ...],
                 vector_size=100, window=5,
                 min_count=2, epochs=10, seed=42)

# 단어 벡터
model.wv["연기"]

# 유사 단어
model.wv.most_similar("연기", topn=5)

# 두 단어 유사도
model.wv.similarity("연기", "배우")

# OOV 확인
if "단어" in model.wv:
    v = model.wv["단어"]

# 문서 벡터
def 문서_벡터(단어_리스트, model):
    벡터들 = [model.wv[w] for w in 단어_리스트 if w in model.wv]
    return np.mean(벡터들, axis=0) if 벡터들 else np.zeros(model.vector_size)

# 저장 / 로드
model.save("model.model")
model = Word2Vec.load("model.model")

 

https://github.com/427paul/AI_Agent/blob/main/Text%20_Analysis_and_LLM/%5BBDA%5D_5%EC%A3%BC%EC%B0%A8_%EB%8B%A8%EC%96%B4%EC%9E%84%EB%B2%A0%EB%94%A9.ipynb

 

AI_Agent/Text _Analysis_and_LLM/[BDA]_5주차_단어임베딩.ipynb at main · 427paul/AI_Agent

Contribute to 427paul/AI_Agent development by creating an account on GitHub.

github.com