들어가기전에..
[바미] 단어를 벡터로 바꾸는 Word2Vec에 대해 알아봅시다.
들어가기전에..자연어 처리(NLP)를 공부하다 보면 Word2Vec이라는 이름을 정말 자주 보게 됩니다.Word2Vec은 한 문장으로 말하면 아래와 같습니다. 단어를 숫자 벡터(임베딩)로 바꿔서, 의미가 비슷한
codesk.tistory.com
이전 글에서 Word2Vec을 통해 단어를 벡터(임베딩)로 바꾸면 의미가 비슷한 단어끼리 벡터 공간에서 가까워진다고 정리했습니다.
그렇다면 ‘가깝다’는 걸 우리는 어떤 기준으로 계산할까요? 궁금하지 않으신가요?
임베딩에서 가장 자주 쓰이는 기준이 바로 코사인 유사도(Cosine Similarity)입니다. 코사인 유사도는 한 문장으로 요약하면 다음과 같습니다.
두 벡터가 얼마나 ‘같은 방향’을 보고 있는지를 측정하는 지표입니다.
즉, 벡터의 길이(크기)보다는 방향이 중요할 때 사용하는 유사도라고 이해하시면 됩니다.
왜 코사인 유사도를 사용할까
텍스트 임베딩은 보통 문장이나 단어의 의미를 벡터로 표현합니다. 그런데 벡터를 비교할 때 단순히 값의 크기만 보면 의미가 왜곡될 수 있습니다.
예를 들어 어떤 모델은 문장이 길어지면 임베딩 벡터의 크기가 커질 수도 있고, 어떤 단어는 빈도나 학습 특성 때문에 벡터 크기가 다르게 나올 수도 있습니다. 이럴 때 크기 차이 때문에 의미 비교가 흔들리는 것을 줄이기 위해 코사인 유사도를 사용합니다.
코사인 유사도는 벡터의 크기를 정규화해서, 사실상 방향만 비교하기 때문입니다.
\[
\mathrm{cosine\_similarity}(A,B)=\frac{A\cdot B}{\|A\|\,\|B\|}
\]
- : 내적(dot product)
- ||A|| : 벡터의 크기(노름, norm)
직관적으로는 아래처럼 이해하시면 됩니다.
- 두 벡터가 완전히 같은 방향이면 각도 0˚→ cos 값 = 1
- 서로 직각이면 90˚ → cos 값 = 0
- 반대 방향이면 180˚ → cos 값 = -1
그래서 코사인 유사도의 값 범위는 보통 -1 ~ 1입니다.
예제로 보는 코사인 유사도
같은 방향 → 유사도 1
- A=(1,1)
- B=(2,2)
BB는 AA의 2배이지만 방향이 같기 때문에 코사인 유사도는 1에 가깝습니다.
직각 → 유사도 0
- A=(1,0)
- B=(0,1)
서로 완전히 다른 축(방향)을 보고 있으므로 유사도는 0입니다.
(3) 반대 방향 → 유사도 -1
- A=(1,1)
- B=(−1,−1)
정반대 방향이므로 유사도는 -1입니다.
코사인 유사도 vs 유클리드 거리 차이
임베딩 비교에서 자주 헷갈리는 부분이 유사도와 거리를 혼용하는 경우입니다. 두 지표는 비슷해 보이지만 관점이 다릅니다.
코사인 유사도
- 벡터의 방향이 중요할 때 강합니다.
- 벡터 크기 영향을 줄입니다(정규화).
- 텍스트 임베딩 비교에서 매우 흔하게 사용됩니다.
유클리드 거리(Euclidean distance)
- 벡터의 실제 위치/거리 차이를 봅니다.
- 크기 차이까지 포함해서 “가까움”을 판단합니다.
- 데이터가 같은 스케일/의미 있는 좌표계라는 전제가 있을 때 적합합니다.
텍스트 임베딩은 보통 “방향이 의미”로 해석되는 경우가 많기 때문에 코사인 유사도가 많이 쓰입니다.
그럼 어디에 쓰일까요?
단어/문장 유사도 검색
- “이 문장과 비슷한 문장을 찾아주세요”
- “이 단어와 비슷한 단어를 찾아주세요”
위처럼 유사도를 기반으로 검색/추천/중복 문장 제거 등에 활용됩니다.
RAG(검색 + 생성)에서 Top-K 문서 찾기
질의(query) 임베딩과 문서 임베딩을 만든 뒤, 코사인 유사도가 높은 문서를 Top-K로 가져와서 LLM 입력에 붙이는 방식이 흔합니다.
클러스터링/분류 전 단계 특징 비교
라벨이 없는 텍스트를 비슷한 그룹으로 묶거나(클러스터링), 분류 전에 데이터 성격을 파악할 때도 유용합니다.
실무에서 자주 하는 실수와 주의점들
0 벡터(모두 0)면 계산이 되지 않습니다
분모에 ||A||B|| 가 있기 때문에 벡터 크기가 0이면 나눗셈이 불가능합니다.
대부분 라이브러리는 이런 경우를 예외 처리하거나 0으로 처리합니다.
코사인 유사도는 '의미가 같다'의 증명이 아닙니다
유사도가 높아도 문장이 길이만 비슷하거나 표현 패턴이 비슷한 경우가 있을 수 있습니다. 또한 모델이 해당 도메인에 약하면 점수가 신뢰하기 어려울 수도 있습니다. 그래서 실제 서비스에서는 임계값(threshold)과 후처리(필터/재랭킹)를 함께 사용하는 경우가 많습니다.
임베딩 모델/도메인에 따라 점수 해석이 달라질 수 있습니다
같은 코사인 유사도라도 어떤 임베딩 모델을 쓰는지, 데이터 도메인이 맞는지에 따라 점수의 '의미'가 달라질 수 있습니다.
파이썬으로 코사인 유사도 계산하기
import numpy as np
def cosine_similarity(a, b):
a = np.array(a, dtype=float)
b = np.array(b, dtype=float)
denom = (np.linalg.norm(a) * np.linalg.norm(b))
if denom == 0:
return 0.0
return float(np.dot(a, b) / denom)
A = [1, 1]
B = [2, 2]
C = [1, 0]
print(cosine_similarity(A, B)) # 1에 가까움
print(cosine_similarity(A, C)) # 0.707...
마치며
이전 글에서 Word2Vec으로 '단어를 벡터로 바꾸는 방법'을 정리했다면 이번 글은 그 벡터들 사이의 '가까움'을 숫자로 계산하는 방법을 정리한 셈입니다.
여기서 기억할 건 하나입니다. 코사인 유사도는 벡터의 길이보다 방향을 봅니다. 이제 다음 단계로 실제 데이터에서 유사도 상위 단어를 뽑아보면서 점수 분포가 어떻게 나오는지 확인해보면 훨씬 빠르게 익숙해집니다.