Day 3까지 배운 TF-IDF는 단어를 빈도로만 본다. “curry on fire”라는 문장에서 “fire”가 재난인지, 스포츠에서 “잘하고 있다”는 의미인지를 구별하지 못한다. 이날의 핵심 질문은 하나다: 컴퓨터가 단어의 “의미”를 어떻게 이해할 수 있을까?

1. 한국어 형태소 분석

이게 뭔지: 한국어 문장을 의미 있는 최소 단위(형태소)로 쪼개는 작업.
왜 필요한지: 영어는 공백이 단어 경계지만, 한국어는 조사와 어미가 붙어있다. “집중할”은 “집중(명사) + 할(보조동사)“이다. 쪼개지 않으면 “집중할”과 “집중하다”를 다른 단어로 취급한다.

from konlpy.tag import Mecab

tagger = Mecab()
sentence = '언제나 현재에 집중할 수 있다면 행복할 것이다.'

tagger.morphs(sentence)
# ['언제나', '현재', '에', '집중', '할', '수', '있', '다면', '행복', '할', '것', '이', '다', '.']

tagger.nouns(sentence)    # ['현재', '집중', '수', '행복'] — 명사만
tagger.pos(sentence)      # [('언제나', 'MAG'), ('현재', 'NNG'), ('에', 'JKB'), ...]

주요 품사 태그: NNG(일반 명사), VV(동사), MAG(부사), JKB(부사격 조사)

2. 불용어 정의 및 키워드 추출

왜 불용어가 도메인마다 다른가: 금융 텍스트에서 “투자”, “주식”은 중요하지만, 영화 리뷰에서는 자주 나오지만 의미가 없는 단어들이다. 불용어는 고정된 목록이 아니라 분석 목적에 맞게 정의해야 한다.

# 영화 리뷰 도메인 불용어 예시
stop_words = ['영화', '전', '난', '일', '걸', '뭐', '줄', '만', '건', '분']

네이버 영화 리뷰 약 100,000건 분석 예시:

from konlpy.tag import Mecab
from collections import Counter

tagger = Mecab()
nouns = []

for review in reviews:
    for noun in tagger.nouns(review):
        if noun not in stop_words and len(noun) > 1:  # 1글자 명사 제외
            nouns.append(noun)

nouns_counter = Counter(nouns)
top_nouns = dict(nouns_counter.most_common(50))
# {'연기': 9174, '최고': 8811, '평점': 8513, '스토리': 7165, ...}

50개 단어만 봐도 “사람들이 영화 리뷰에서 무엇을 중요하게 보는지”가 드러난다.

3. 시각화 방법 비교

같은 빈도 데이터를 세 가지 방식으로 시각화할 수 있다.

# 1. 수평 막대 그래프 — 숫자 비교에 정확
import matplotlib.pyplot as plt
plt.barh(list(top_nouns.keys()), list(top_nouns.values()))
plt.show()

# 2. 워드클라우드 — 분포를 한눈에
from wordcloud import WordCloud
wc = WordCloud(font_path='./font/NanumBarunGothic.ttf')
wc.generate_from_frequencies(top_nouns)
plt.imshow(wc)
plt.show()

# 3. 트리맵 — 계층적 구조를 크기로 표현
import squarify
squarify.plot(label=list(top_nouns.keys()), sizes=list(top_nouns.values()))
plt.show()
막대 그래프
장점: 정확한 수치 비교 가능 / 단점: 항목이 많으면 복잡
워드클라우드
장점: 직관적, 보기 쉬움 / 단점: 정확한 숫자 파악 어려움
트리맵
장점: 비율 시각화 명확 / 단점: 항목 이름이 겹칠 수 있음

4. 텍스트 유사도 계산

이게 뭔지: 벡터로 변환된 두 텍스트 사이의 “거리” 또는 “방향 차이”를 수치화하는 것.

코사인 유사도 — 가장 많이 쓰이는 방법

이게 뭔지: 두 벡터가 이루는 각도의 코사인 값. 크기는 무시하고 방향만 비교한다.
왜 텍스트에 적합한가: 긴 문서는 단어 개수가 많아서 벡터 크기도 크다. 코사인 유사도는 크기를 무시하므로 문서 길이 차이에 영향받지 않는다.

각도 0°  → cos(0°) = 1  → 완전히 같은 방향 (최대 유사)
각도 90° → cos(90°) = 0  → 무관
각도 180° → cos(180°) = -1 → 반대 방향
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import TfidfVectorizer

s1 = 'The iPhone was made by Apple.'
s2 = 'I love Apple iPhone.'
s3 = 'Facebook was created by Meta.'

tfidf = TfidfVectorizer().fit_transform([s1, s2, s3])
sim = cosine_similarity(tfidf)

# s1 vs s2: 0.486 (Apple, iPhone 공유)
# s1 vs s3: 0.000 (공통 단어 없음)

유클리디안 거리와 자카드 유사도

import numpy as np

# 유클리디안: 두 점 사이의 직선 거리
euclidean = np.linalg.norm(v1 - v2)

# 자카드: 교집합 / 합집합 (단어 존재 여부만 봄, 빈도 무시)
def jaccard(set1, set2):
    return len(set1 & set2) / len(set1 | set2)

5. Word2Vec — 의미를 학습하는 임베딩

TF-IDF의 한계: “커피”와 “카페인”이 실제로 관련 있어도, 같은 문서에 함께 등장하지 않으면 TF-IDF 벡터에서는 전혀 다른 단어로 취급된다.

Word2Vec의 핵심 아이디어: “비슷한 문맥에서 등장하는 단어는 비슷한 의미를 가진다.” “커피”가 항상 “마시다”, “아침”, “카페인”과 함께 나온다면, 이 단어들의 벡터를 가깝게 학습한다.

from gensim.models import Word2Vec

sentences = [
    ['나는', '아침에', '커피를', '마신다'],
    ['따뜻한', '커피를', '마시면', '하루가', '시작된다'],
    ['나는', '점심에', '국밥을', '먹는다']
]

model = Word2Vec(
    sentences,
    window=5,    # 앞뒤 5개 단어를 "문맥"으로 사용
    min_count=1, # 1회 이상 등장한 단어 포함
    sg=0         # sg=0: CBOW (문맥→중심 단어), sg=1: Skip-Gram (중심→문맥)
)

word_vector = model.wv['커피']   # 100차원 벡터
model.wv.similarity('커피', '차') # 유사도 점수 (0~1)

여기서 windowsg 파라미터를 같이 이해하면 Word2Vec이 어떻게 학습되는지 훨씬 선명해진다.

  • window: 중심 단어 주변 몇 개 단어까지 문맥으로 볼지 정하는 값
  • sg=0: CBOW, 주변 단어로 중심 단어를 예측
  • sg=1: Skip-Gram, 중심 단어로 주변 단어를 예측

CBOW는 빠르고 안정적이고, Skip-Gram은 느리지만 드문 단어 학습에 더 강한 편이다. 즉, Word2Vec은 단어를 그냥 번호로 바꾸는 것이 아니라, 문맥 예측 문제를 풀게 하면서 의미를 벡터 공간에 압축하는 방식이다.

TF-IDF

벡터 형태: 단어 수 차원 (sparse)

단어 의미: 빈도만 반영

"커피"와 "카페인": 별개 단어

데이터 요구량: 적어도 가능

Word2Vec

벡터 형태: 고정 차원 100~300 (dense)

단어 의미: 문맥 관계 반영

"커피"와 "카페인": 벡터 공간에서 가까움

데이터 요구량: 충분한 코퍼스 필요

왜 dense embedding이 중요한가

TF-IDF 벡터는 대부분 값이 0인 sparse vector다. 반면 Word2Vec은 비교적 짧은 길이의 dense vector를 만든다. dense embedding의 장점은 두 가지다.

  • 저장 공간과 계산량이 줄어든다.
  • 단어 간 의미적 유사성이 벡터 거리로 표현된다.

그래서 이후의 딥러닝 모델들은 one-hot보다 embedding을 먼저 거쳐 입력을 처리하는 경우가 많다.

6. RNN/LSTM — 순서를 고려한 학습

왜 필요한가: Word2Vec은 단어의 의미를 벡터로 표현하지만, 문장 안에서의 순서를 무시한다. “나는 커피를 좋아한다”와 “커피가 나를 좋아한다”를 구별하지 못한다. RNN은 앞 단어의 정보를 뒤로 전달하면서 순서를 학습한다.

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, LSTM, Dense

model = Sequential([
    # One-hot(vocab_size 차원) → 100차원 dense 벡터로 변환
    # 이 과정 자체가 Word2Vec과 같은 임베딩 학습
    Embedding(vocab_size, 100, input_length=seq_length - 1),
    
    LSTM(50),  # 이전 단어들의 정보를 상태(state)로 유지하며 다음 단어 예측
    
    Dense(vocab_size, activation='softmax')  # 다음 단어 확률 분포
])

model.compile(optimizer='adam', loss='categorical_crossentropy')
model.fit(X_train, y_train, epochs=10)

여기서 Embedding 층은 고정 사전이 아니라 학습 가능한 파라미터 층이다. 즉, 모델이 다음 단어를 잘 맞히도록 훈련되는 과정에서 각 단어의 임베딩도 함께 좋아진다. 이 점에서 Word2Vec과 LSTM 언어모델은 서로 이어지는 개념이다.

Day 4를 표현 학습의 흐름으로 정리하면

  • 키워드 추출: 많이 나온 단어를 본다.
  • TF-IDF: 중요한 단어에 가중치를 준다.
  • Word2Vec: 단어 의미를 벡터 공간에 담는다.
  • RNN/LSTM: 단어 순서와 문맥 흐름까지 학습한다.

즉, Day 4는 단어 빈도 중심의 표현에서 의미와 순서 중심의 표현으로 넘어가는 전환점에 가깝다.

RNN
구조 단순, 속도 빠름 / 긴 문장에서 기울기 소실 취약 / 짧은 시퀀스에 적합
LSTM
3개 게이트로 긴 의존성 처리 / 구조 복잡, 속도 느림 / 긴 문장 의존성 필요 시
GRU
2개 게이트, LSTM보다 간단 / LSTM보다 빠름 / 빠른 학습이 필요한 경우

핵심 정리

TF-IDF (Day 3까지)
  → 단어 빈도 기반
  → 의미 관계 없음
  → 소규모 데이터에서도 동작

Word2Vec (Day 4)
  → 문맥 기반 의미 학습
  → 의미 유사 단어가 가까운 위치
  → 충분한 코퍼스 필요

RNN/LSTM (Day 4 후반)
  → 단어 순서까지 고려
  → 문장 전체 맥락 이해
  → 더 많은 데이터와 연산 필요

“curry on fire” 문제: TF-IDF는 “fire”라는 단어 하나를 보고 분류한다. Word2Vec은 “curry”, “goal”, “tournament” 같은 주변 단어들을 함께 보고 스포츠 문맥임을 인식한다. LSTM은 문장 전체의 순서를 고려해서 판단한다.

Community

Comments

0 comments

Comments appear immediately. Use report if something needs review.

No comments yet.