Skip to main content

의미 유사도 (Semantic Similarity)

의미 유사도(Semantic Similarity)는 두 텍스트가 의미적으로 얼마나 유사한지를 수치로 측정하는 태스크입니다. 단순한 단어 겹침이 아니라 문장의 뜻을 비교하며, 검색 엔진, 중복 문서 탐지, 추천 시스템, 그리고 **RAG(Retrieval-Augmented Generation)**의 검색(Retrieval) 단계에서 핵심적으로 사용됩니다.

학습 목표

이 문서를 완료하면 다음을 수행할 수 있습니다.
  • 어휘 기반 유사도와 의미 기반 유사도의 차이를 설명할 수 있습니다
  • Sentence-BERT의 Siamese Network 구조를 이해합니다
  • 코사인 유사도로 문장 간 유사도를 계산할 수 있습니다
  • sentence-transformers 라이브러리로 시맨틱 서치를 구현할 수 있습니다
  • 임베딩 모델 선택 기준과 벤치마크를 이해합니다

왜 중요한가

의미 유사도는 현대 NLP 애플리케이션의 근간입니다.
활용 분야설명관련 탭
RAG 검색사용자 질문과 유사한 문서 검색RAG 탭
시맨틱 서치키워드가 아닌 의미로 검색-
중복 탐지유사 질문/문서 감지-
클러스터링유사 문서 그룹화-
추천 시스템콘텐츠 기반 유사 항목 추천-
RAG와의 직접 연결: RAG 파이프라인에서 “Retriever”는 사용자 질문과 의미적으로 유사한 문서를 검색합니다. 이때 문장 임베딩과 코사인 유사도가 핵심 기술이므로, 이 문서의 내용은 RAG 탭에서 직접 활용됩니다.

핵심 개념

어휘 유사도 vs 의미 유사도

# 어휘 기반 (Jaccard Similarity)
sent_a = set("오늘 날씨가 좋습니다".split())
sent_b = set("오늘 날씨가 맑습니다".split())
jaccard = len(sent_a & sent_b) / len(sent_a | sent_b)
print(f"Jaccard: {jaccard:.2f}")  # 0.60 — "좋습니다" ≠ "맑습니다"

# 의미 기반 (Sentence-BERT)
# "좋습니다"와 "맑습니다"가 날씨 맥락에서 유사하다는 것을 캡처
# → 의미 유사도: ~0.92
한계 예시: “나는 커피를 마신다”와 “나는 카페라테를 즐긴다”는 어휘 겹침이 적지만 의미가 유사합니다. 반면 “나는 커피를 마신다”와 “나는 커피를 만든다”는 어휘 겹침이 높지만 의미가 다릅니다.

Sentence-BERT: Siamese Network

일반 BERT로 문장 유사도를 계산하려면 두 문장을 하나의 입력으로 결합해야 합니다. N개 문장에 대해 O(N2)O(N^2)의 비교가 필요하여 10,000개 문장이면 약 5,000만 번의 추론이 필요합니다. **Sentence-BERT(SBERT)**는 Siamese Network 구조로 이 문제를 해결합니다. 각 문장을 독립적으로 고정 크기 벡터로 인코딩하고, 벡터 간 유사도를 계산합니다. 핵심 아이디어:
  • 두 BERT는 동일한 가중치를 공유 (Siamese = “쌍둥이”)
  • Mean Pooling으로 토큰 임베딩을 하나의 문장 벡터로 압축
  • 인코딩은 한 번만 수행하고, 유사도 계산은 벡터 연산으로 수행
  • 10,000개 문장 비교: BERT 10,000번 인코딩 + 벡터 비교 → 수 초 완료

코사인 유사도 (Cosine Similarity)

두 벡터 사이의 각도를 측정합니다. 벡터의 크기(길이)가 아닌 방향이 얼마나 유사한지를 나타냅니다. cos(u,v)=uvuv\cos(\mathbf{u}, \mathbf{v}) = \frac{\mathbf{u} \cdot \mathbf{v}}{\|\mathbf{u}\| \cdot \|\mathbf{v}\|}
  • 1.0: 완전히 같은 방향 (매우 유사)
  • 0.0: 직교 (관련 없음)
  • -1.0: 정반대 방향 (반대 의미)
import numpy as np

def cosine_similarity(u, v):
    """코사인 유사도 계산"""
    return np.dot(u, v) / (np.linalg.norm(u) * np.linalg.norm(v))

sentence-transformers로 실습

기본 사용법

from sentence_transformers import SentenceTransformer, util

# 다국어 문장 임베딩 모델 로딩
model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")

# 문장 인코딩
sentences = [
    "오늘 날씨가 정말 좋습니다",
    "오늘은 맑고 화창한 날입니다",
    "주식 시장이 크게 하락했습니다",
    "경제 전망이 어둡습니다",
]

embeddings = model.encode(sentences, convert_to_tensor=True)
print(f"임베딩 차원: {embeddings.shape}")  # (4, 384)

# 모든 쌍의 유사도 계산
cosine_scores = util.cos_sim(embeddings, embeddings)

for i in range(len(sentences)):
    for j in range(i + 1, len(sentences)):
        print(f"[{cosine_scores[i][j]:.4f}] "
              f"'{sentences[i]}' vs '{sentences[j]}'")
# [0.8923] '오늘 날씨가 정말 좋습니다' vs '오늘은 맑고 화창한 날입니다'
# [0.1234] '오늘 날씨가 정말 좋습니다' vs '주식 시장이 크게 하락했습니다'
# ...

시맨틱 서치 구현

import torch

# 검색 대상 문서 (코퍼스)
corpus = [
    "파이썬은 데이터 과학에 널리 사용되는 프로그래밍 언어입니다",
    "자바스크립트는 웹 개발의 핵심 언어입니다",
    "딥러닝은 인공 신경망을 활용한 머신러닝 기법입니다",
    "자연어 처리는 컴퓨터가 인간의 언어를 이해하는 기술입니다",
    "컨테이너 기술은 애플리케이션 배포를 간소화합니다",
    "벡터 데이터베이스는 임베딩 벡터의 유사도 검색에 특화되어 있습니다",
]

# 코퍼스 인코딩 (한 번만 수행)
corpus_embeddings = model.encode(corpus, convert_to_tensor=True)

def semantic_search(query, top_k=3):
    """시맨틱 서치: 의미적으로 유사한 문서 검색"""
    query_embedding = model.encode(query, convert_to_tensor=True)

    # 코사인 유사도 계산
    scores = util.cos_sim(query_embedding, corpus_embeddings)[0]

    # 상위 K개 결과
    top_results = torch.topk(scores, k=top_k)

    print(f"\n질의: '{query}'")
    print("-" * 50)
    for score, idx in zip(top_results.values, top_results.indices):
        print(f"  [{score:.4f}] {corpus[idx]}")

# 검색 테스트
semantic_search("NLP 기술이란?")
# [0.7832] 자연어 처리는 컴퓨터가 인간의 언어를 이해하는 기술입니다
# [0.5421] 딥러닝은 인공 신경망을 활용한 머신러닝 기법입니다
# [0.3156] 파이썬은 데이터 과학에 널리 사용되는 프로그래밍 언어입니다

semantic_search("유사한 문서 찾기")
# [0.6789] 벡터 데이터베이스는 임베딩 벡터의 유사도 검색에 특화되어 있습니다
# ...

임베딩 모델 비교

모델차원한국어속도STS 성능
paraphrase-multilingual-MiniLM-L12-v2384지원빠름중간
distiluse-base-multilingual-cased-v2512지원빠름중간
intfloat/multilingual-e5-large1024지원느림높음
BAAI/bge-m31024지원느림매우 높음
jhgan/ko-sbert-sts768전용중간높음 (한국어)
snunlp/KR-SBERT-V40K-klueNLI-augSTS768전용중간높음 (한국어)
임베딩 모델 선택은 RAG 검색 품질에 직접적인 영향을 미칩니다. 도메인과 언어에 맞는 모델을 선택하세요. 한국어 전용 모델(ko-sbert)이 다국어 모델보다 한국어에서 더 나은 성능을 보이는 경우가 많습니다. MTEB Leaderboard에서 최신 벤치마크를 확인할 수 있습니다.

AI/ML 활용 사례

중복 질문 탐지

def find_duplicates(questions, threshold=0.85):
    """유사 질문 그룹 탐지"""
    embeddings = model.encode(questions, convert_to_tensor=True)
    cosine_scores = util.cos_sim(embeddings, embeddings)

    duplicates = []
    for i in range(len(questions)):
        for j in range(i + 1, len(questions)):
            if cosine_scores[i][j] > threshold:
                duplicates.append((
                    questions[i], questions[j],
                    cosine_scores[i][j].item()
                ))
    return duplicates

questions = [
    "파이썬 설치 방법",
    "Python 어떻게 설치하나요?",
    "딥러닝이란 무엇인가요?",
    "딥러닝의 정의를 알려주세요",
]

for q1, q2, score in find_duplicates(questions):
    print(f"[{score:.3f}] '{q1}' ↔ '{q2}'")

문서 클러스터링

from sklearn.cluster import KMeans

# 문서 임베딩
documents = [...]  # 문서 리스트
embeddings = model.encode(documents)

# K-Means 클러스터링
kmeans = KMeans(n_clusters=5, random_state=42)
clusters = kmeans.fit_predict(embeddings)

# 클러스터별 문서 그룹화
for cluster_id in range(5):
    cluster_docs = [doc for doc, c in zip(documents, clusters)
                    if c == cluster_id]
    print(f"\n클러스터 {cluster_id}: {len(cluster_docs)}건")
    for doc in cluster_docs[:3]:
        print(f"  - {doc[:50]}...")
Mean Pooling: 모든 토큰 임베딩의 평균. 가장 일반적이고 안정적. CLS Pooling: [CLS] 토큰만 사용. BERT 원래 설계 의도이지만 SBERT에서는 Mean보다 성능이 낮은 경우가 많음. Max Pooling: 각 차원별 최댓값. 특정 태스크에서 효과적. 대부분의 경우 Mean Pooling이 권장됩니다.
꼭 그렇지는 않습니다. 384차원 MiniLM이 768차원 BERT-base보다 STS 태스크에서 높은 성능을 보이는 경우도 있습니다. 차원이 높으면 더 많은 정보를 담을 수 있지만, 저장 공간과 검색 속도 비용이 증가합니다. 벡터 DB에서 수백만 건을 다룬다면 384~512차원이 실용적입니다.
유클리드 거리: 벡터 간 직선 거리. 정규화되지 않은 임베딩에서 사용. 내적(Dot Product): 정규화된 벡터에서는 코사인 유사도와 동일. 정규화되지 않으면 벡터 크기도 반영. 맨해튼 거리: L1 노름 기반. 고차원에서 유클리드보다 안정적. 대부분의 문장 임베딩 모델은 코사인 유사도에 최적화되어 학습됩니다.

체크리스트

학습을 마치기 전에 아래 항목을 확인하세요.
  • 어휘 기반 유사도와 의미 기반 유사도의 차이를 설명할 수 있는가?
  • Sentence-BERT의 Siamese Network 구조와 장점을 이해하는가?
  • 코사인 유사도의 수식과 의미를 설명할 수 있는가?
  • sentence-transformers로 시맨틱 서치를 구현할 수 있는가?
  • 임베딩 모델 선택 시 고려해야 할 요소를 알고 있는가?

다음 문서