멀티캠퍼스 데이터분석 6월 5일 수업 내용 — Word2Vec 이론 및 실습, 동시출현행렬, PMI/PPMI 직접 구현
Word2Vec
1. Word2Vec 이론 & 매개변수
2. Word2Vec 실습
동시출현행렬 & PMI
| 매개변수 | 기본값 | 설명 |
|---|---|---|
sentences | None | 토큰화된 문장 데이터 (2차원 리스트) |
vector_size | 100 | 임베딩 벡터 차원 수 (피처 수) |
window | 5 | 예측 시 고려할 주변 단어와의 거리 (문맥 크기) |
sg | 0 | 학습 방식 (0: CBOW / 1: Skip-gram) |
min_count | 5 | 최소 등장 빈도 — 이 값 미만 단어 제외 |
hs | 0 | 계산 방식 (0: Negative Sampling / 1: Hierarchical Softmax) |
epochs | 100 | 반복 학습 횟수 |
max_vocab_size | None | 메모리 제한 시 최대 단어 개수 |
seed | - | 재현 가능한 결과를 위한 시드값 |
workers | - | 학습에 사용할 CPU 스레드 수 |
CBOW (sg=0) | Skip-gram (sg=1) | |
|---|---|---|
| 방식 | 주변 단어 → 중심 단어 예측 | 중심 단어 → 주변 단어 예측 |
| 속도 | 빠름 | 느림 |
| 성능 | 빈출 단어에 유리 | 희귀 단어에 유리 |
| 권장 | 빠른 계산 필요 시 | 일반적으로 권장 |
| 속성 | 설명 |
|---|---|
wv | 학습된 단어 벡터 객체 |
wv['단어'] | 특정 단어의 임베딩 벡터 반환 |
wv.index_to_key | 등장 빈도 순 정렬된 단어 리스트 |
wv.key_to_index | 단어 → 인덱스 매핑 딕셔너리 |
corpus_total_words | 전체 학습 단어 수 |
vector_size | 벡터 차원 수 |
| 메서드 | 설명 |
|---|---|
wv.most_similar(word, topn=10) | 특정 단어와 가장 유사한 단어 topn개 반환 |
wv.similarity(word1, word2) | 두 단어의 코사인 유사도 계산 |
wv.get_vector(word) | 특정 단어의 벡터 반환 |
model.train() | 추가 데이터로 학습 |
model.save(path) | 학습된 모델 저장 |
Word2Vec.load(path) | 저장된 모델 로드 |
# !pip install gensim
from gensim.models import Word2Vec
from sklearn.svm import SVC
from konlpy.tag import Komoran
import numpy as np
docs = [
'오늘 날씨가 좋다 여행 가고 싶다',
'기온이 너무 올라서 아무것도 하기 싫다',
'수업이 너무 지루하고 졸리다',
'음식이 너무 맛이 없고 서비스도 별로다',
'영화가 너무 재미있어서 시간이 가는 줄 몰랐다'
]
target = [1, 0, 0, 0, 1]
komoran = Komoran()
allow_pos = ['NNP', 'NNG', 'VV', 'VA', 'SL', 'MAG']
tokens = []
for doc in docs:
words = []
for word, pos in komoran.pos(doc):
if pos in allow_pos:
words.append(word)
tokens.append(words)
print(tokens)
w2v = Word2Vec(
sentences = tokens,
vector_size = 100,
window = 5,
min_count = 1, # 데이터가 적으므로 1로 설정
sg = 1, # Skip-gram
epochs = 100,
seed = 42,
workers = 2
)
# wv 속성을 변수에 저장 (자주 사용되므로)
wv = w2v.wv
# 특정 단어의 벡터 확인
print(wv['여행'])
# 유사 단어 검색
print(wv.most_similar('음식', topn=3))
# 두 단어의 코사인 유사도
print(wv.similarity('여행', '음식'))
# 학습된 단어 수 확인
print(len(wv.index_to_key))
Word2Vec은 단어 단위 벡터를 반환합니다. 문장 벡터를 만들려면 문장 내 단어 벡터의 평균을 사용합니다.
vectors = []
for token in tokens:
vec = []
for word in token:
if word in wv.index_to_key:
vec.append(wv[word])
# 문장의 단어 벡터들을 평균 → 문장 벡터
vectors.append(np.mean(vec, axis=0))
print(np.array(vectors).shape) # (5, 100)
svc = SVC(random_state=42)
svc.fit(np.array(vectors), target)
💡 Word2Vec vs TF-IDF
TF-IDF Word2Vec 방식 통계 기반 (빈도) 딥러닝 기반 (문맥) 단어 간 의미 반영 X O 모르는 단어 처리 제외 min_count이하 제외출력 희소 행렬 밀집 벡터 (Dense)
window 범위 안에서 어떤 단어가 등장했는지 빈도수를 기록window=1일 때 '날씨가' → 주변 단어 ('오늘', '너무', '내일', '조금')| N-gram | 동시출현행렬 | |
|---|---|---|
| 반영 요소 | 단어 순서 | 단어 간 의미 관계 |
| 방향성 | 일방향 (앞→뒤) | 양방향 (주변 전체) |
import math
import pandas as pd
from konlpy.tag import Okt
docs = [
'오늘 날씨가 너무 좋다',
'오늘 기분이 정말 좋다',
'내일 날씨가 조금 흐리다',
'기분이 매우 나쁘다'
]
window_size = 2
okt = Okt()
tokens = [okt.morphs(doc) for doc in docs]
# 단어 사전 생성 (중복 제거, 정렬)
vocab = sorted(set(sum(tokens, [])))
vocab_dict = {word: idx for idx, word in enumerate(vocab)}
💡
list.extend()vslist.append()
append(item)→ 원소 하나를 리스트에 추가extend(iterable)→ 이터러블의 각 원소를 리스트에 추가 (리스트를 펼쳐서 추가)
# 0으로 채운 행렬 초기화
co_metric = [[0] * len(vocab) for _ in range(len(vocab))]
for token in tokens:
for idx, word in enumerate(token):
count_idx = vocab_dict[word]
# 윈도우 범위 설정
start = max(0, idx - window_size)
end = min(len(token), idx + window_size + 1)
for i in range(start, end):
if idx != i: # 자기 자신 제외
context = token[i]
context_idx = vocab_dict[context]
co_metric[count_idx][context_idx] += 1
co_df = pd.DataFrame(co_metric, index=vocab, columns=vocab)
co_df
total_count = co_df.sum().sum()
# P(word) : 각 단어의 전체 등장 비율
p_word = [sum(row) / total_count for row in co_metric]
# P(context) : 각 단어가 문맥으로 등장하는 비율
p_context = [sum(row) / total_count for row in co_df.T.values]
def calc_pmi(i, j):
p_wc = co_metric[i][j] / total_count
if p_wc == 0:
return 0
# PMI = log2( P(w,c) / (P(w) * P(c)) )
# 1e-12 → 수치 안정성을 위한 아주 작은 값 추가
return math.log2(p_wc / (p_word[i] * p_context[j]) + 1e-12)
# PMI 행렬 계산
pmi_metric = [
[calc_pmi(i, j) for j in range(len(vocab))]
for i in range(len(vocab))
]
pmi_df = pd.DataFrame(pmi_metric, index=vocab, columns=vocab)
# 색상 그라디언트로 시각화
pmi_df.style.background_gradient(cmap='Blues')
💡
1e-12를 더하는 이유
log2(0)는-∞가 되어 오류가 발생합니다.
극히 작은 값(1e-12)을 더해 수치 안정성을 확보합니다.
텍스트 → 토큰화 → 단어 사전(vocab) 생성
↓
동시출현행렬 생성 (window 범위 내 단어 빈도 카운트)
↓
P(w), P(c), P(w,c) 계산
↓
PMI = log2( P(w,c) / (P(w) × P(c)) )
↓
PPMI = max(PMI, 0) ← 음수를 0으로 대체
| 개념 | 설명 |
|---|---|
| Word2Vec | 딥러닝 기반 단어 임베딩 — 문맥 기반 의미 반영 |
CBOW (sg=0) | 주변 단어 → 중심 단어 예측 (빠름) |
Skip-gram (sg=1) | 중심 단어 → 주변 단어 예측 (일반적 권장) |
wv['단어'] | 특정 단어의 임베딩 벡터 반환 |
wv.most_similar() | 코사인 유사도 기반 유사 단어 탐색 |
wv.similarity(w1, w2) | 두 단어 간 코사인 유사도 |
wv.index_to_key | 빈도 순 정렬된 학습 단어 리스트 |
np.mean(vec, axis=0) | 문장 내 단어 벡터 평균 → 문장 벡터 |
| 동시출현행렬 | window 범위 내 단어 공동 등장 빈도 행렬 |
| PMI | 두 단어의 의미적 연관성 측정 |
| PPMI | PMI 음수값을 0으로 대체 |
list.extend(iterable) | 이터러블을 풀어서 리스트에 추가 |
sorted(set(sum(tokens, []))) | 중첩 리스트를 1차원으로 풀고 중복 제거 후 정렬 |
1e-12 | log(0) 방지를 위한 수치 안정성 값 |
df.style.background_gradient(cmap='Blues') | DataFrame 값에 색상 그라디언트 적용 |
model.save() / Word2Vec.load() | 모델 저장 & 로드 |