[Spring AI] 텍스트 유사도란? — DTM, TF-IDF, 코사인 유사도, 자카드 유사도, HNSW
by 우와한개발자
1. 텍스트 기반 유사도
- 임베딩 모델 없이 텍스트 자체를 비교하는 방식.
- 임베딩 모델이 나오기 전에 검색에서 많이 쓰였다.
1) 자카드 유사도 (Jaccard Similarity)
- 두 문장에서 공통으로 등장하는 단어 비율로 측정
- 단어 집합 기반으로 순서나 빈도는 무시한다.
문장 A: {"강아지", "뛴다"}
문장 B: {"강아지", "달린다"}
자카드 유사도 = 공통 단어 수 / 전체 단어 수
= 1 / 3 = 0.33
def jaccard_similarity(a, b):
set_a = set(a.split())
set_b = set(b.split())
intersection = set_a & set_b
union = set_a | set_b
return len(intersection) / len(union)
a = "강아지 뛴다"
b = "강아지 달린다"
print(jaccard_similarity(a, b)) # 0.333...
2) DTM (Document-Term Matrix)
- 문서에 단어가 몇 번 등장하는지 행렬로 표현
- 단순 빈도 기반이라 흔한 단어(은, 는, 이, 가)도 중요하게 취급되는 한계가 있다.
문서1: "강아지 뛴다 강아지"
문서2: "고양이 달린다"
강아지 뛴다 고양이 달린다
문서1 [ 2, 1, 0, 0 ]
문서2 [ 0, 0, 1, 1 ]
from sklearn.feature_extraction.text import CountVectorizer
docs = ["강아지 뛴다 강아지", "고양이 달린다"]
vectorizer = CountVectorizer()
dtm = vectorizer.fit_transform(docs)
print(vectorizer.get_feature_names_out()) # 단어 목록
print(dtm.toarray()) # 행렬
3) TF-IDF
- DTM에서 발전한 방식
- 자주 등장하지만 흔한 단어는 중요도를 낮추고, 특정 문서에서만 자주 나오는 단어는 중요도를 높여 가중치를 부여한다.
(1) TF (Term Frequency)
- 특정 문서에서 단어가 얼마나 자주 등장하는지
(2) IDF (Inverse Document Frequency)
- 전체 문서에서 단어가 얼마나 희귀한지
- 모든 문서에 등장하는 흔한 단어일수록 낮은 값
(3) TF-IDF = TF × IDF
- 특정 문서에서 자주 나오면서 다른 문서에서는 잘 안 나오는 단어일수록 높은 값
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
docs = ["강아지가 뛴다", "개가 달린다", "오늘 날씨 맑음"]
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(docs)
# 문서 간 코사인 유사도 계산
similarity = cosine_similarity(tfidf_matrix)
print(similarity)
2. 벡터 기반 유사도
- 임베딩으로 변환된 벡터끼리 비교하는 방식.
1) 코사인 유사도 (Cosine Similarity)
- 두 벡터 사이의 각도로 유사도 측정
- 벡터의 크기는 무시하고 방향만 봄
- 범위 0~1, 1에 가까울수록 유사하다.
- 텍스트 의미 검색(RAG)에 가장 많이 쓰인다.
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
a = np.array([[0.12, 0.87, 0.34]])
b = np.array([[0.13, 0.85, 0.36]])
print(cosine_similarity(a, b)) # 1에 가까운 값
2) 유클리드 거리 (Euclidean Distance)
- 두 벡터 사이의 직선 거리로 측정
- 값이 0에 가까울수록 유사
- 벡터 크기가 의미있을 때 적합하다.
- 이미지/추천 시스템에 사용한다.
from scipy.spatial.distance import euclidean
a = [0.12, 0.87, 0.34]
b = [0.13, 0.85, 0.36]
print(euclidean(a, b)) # 0에 가까울수록 유사
3) 내적 (Dot Product)
- 두 벡터를 곱해서 합산한 값으로 측정
- 벡터 크기에 영향을 받기 때문에 단독으로 쓰면 유사도가 왜곡될 수 있다.
- 벡터를 정규화(크기를 1로 맞춤)한 상태에서 사용하면 코사인 유사도와 동일한 결과를 얻는다.
- 정규화된 벡터 환경에서 코사인 유사도를 더 빠르게 계산할 수 있어 대규모 고속 검색에 적합하다.
import numpy as np
a = np.array([0.12, 0.87, 0.34])
b = np.array([0.13, 0.85, 0.36])
print(np.dot(a, b)) # 값이 클수록 유사
3. 텍스트 기반 vs 벡터 기반 유사도 비교
| 방식 | 분류 | 임베딩 필요 | 적합한 상황 |
| 자카드 유사도 | 텍스트 기반 | X | 단순 키워드 매칭 |
| DTM / TF-IDF | 텍스트 기반 | X | 검색 엔진 전통 방식 |
| 코사인 유사도 | 벡터 기반 | O | RAG, 의미 검색 표준 |
| 유클리드 거리 | 벡터 기반 | O | 이미지, 추천 시스템 |
| 내적 | 벡터 기반 | O | 정규화된 환경에서 고속 검색 |
4. 인덱스 방식
- 수백만 개의 벡터를 전부 순회하며 유사도를 계산하면 너무 느리기 때문에 인덱스를 사용해 탐색 범위를 줄인다.
1) HNSW (Hierarchical Navigable Small World)
- 고차원 벡터 공간에서 쿼리 벡터와 가장 유사한 항목을 초고속으로 찾아내는 그래프 기반의 근사 최근접 이웃(ANN) 검색 알고리즘
- 계층적 그래프 구조로 벡터를 연결해 가장 가까운 벡터를 빠르게 탐색한다.
- 장점 : 검색 속도 빠름, 정확도 높음
- 단점 : 메모리 사용량 많음, 인덱스 구축 시간 있음
- pgvector 기본 설정값
2) IVFFlat (Inverted File Index)
- 벡터를 클러스터로 나눠두고 가까운 클러스터만 탐색
- 장점 : 메모리 효율적
- 단점 : HNSW보다 정확도 낮음
5. 임베딩 / Vector DB / 텍스트 유사도의 관계
1) 파이프라인
- 셋은 독립적인 개념이 아니라 하나의 파이프라인이다
텍스트
→ 임베딩 모델이 벡터로 변환
→ Vector DB에 저장
→ 검색 시 코사인 유사도로 비교
→ 의미적으로 유사한 문서 반환
- 임베딩 : 텍스트를 벡터로 변환하는 과정/모델
- Vector DB : 변환된 벡터를 저장하고 유사도 검색을 수행하는 저장소
- 텍스트 유사도 : 임베딩 + Vector DB를 통해 얻는 최종 결과
2) RAG와의 연결
- 임베딩이 없으면 텍스트를 벡터로 못 바꾸고, 벡터가 없으면 Vector DB에 저장도 유사도 비교도 못한다
- RAG는 이 파이프라인을 이용해서 질문과 의미적으로 유사한 문서를 찾고 LLM에 context로 전달한다
'AI > Spring AI' 카테고리의 다른 글
| [Spring AI] Tool Calling이란? — 함수 호출, JSON 스펙, @Tool, @ToolParam (0) | 2026.06.04 |
|---|---|
| [Spring AI] RAG(검색 증강 생성)란? — 외부 문서 기반 답변 (0) | 2026.06.04 |
| [Spring AI] 임베딩과 Vector DB란? — 벡터 변환, pgvector, 도커 구축 (0) | 2026.06.04 |
| [Spring AI] On-Premise LLM 구현하기 — Ollama, Docker, Llama (0) | 2026.06.02 |
| [Spring AI] Spring AI ChatMemory — Advisor 기반 문맥 기억 챗봇 구현 (0) | 2026.06.02 |
블로그의 정보
우와한개발자 님의 블로그
우와한개발자