Sentence Transformer
SBERT (Sentence-BERT)
문장 전체를 고정 크기의 밀집 벡터(Dense Vector)로 변환하는 임베딩 모델입니다. 시맨틱 검색, 문장 유사도 계산, 클러스터링, RAG 파이프라인 구축에 널리 활용됩니다.
SBERT (Sentence-BERT)
문장 전체를 고정 크기의 밀집 벡터(Dense Vector)로 변환하는 임베딩 모델입니다. 시맨틱 검색, 문장 유사도 계산, 클러스터링, RAG 파이프라인 구축에 널리 활용됩니다.
Sentence Transformer는 2019년 Reimers와 Gurevych가 발표한 "Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks" 논문에서 제안된 문장 임베딩 프레임워크입니다. 기존 BERT 모델은 문장 유사도를 계산하려면 두 문장을 함께 입력해야 해서 N개 문장의 모든 쌍을 비교하는 데 O(N^2) 시간이 걸렸습니다. Sentence Transformer는 각 문장을 독립적으로 임베딩하여 코사인 유사도로 빠르게 비교할 수 있게 만들어, 대규모 시맨틱 검색을 실용적으로 가능하게 했습니다.
Sentence Transformer의 핵심 아키텍처는 Siamese/Triplet Network 구조입니다. 동일한 BERT(또는 다른 Transformer) 모델을 공유하면서, 두 문장이 유사하면 임베딩이 가까워지고 다르면 멀어지도록 학습합니다. 학습에는 NLI(Natural Language Inference) 데이터셋과 STS(Semantic Textual Similarity) 벤치마크가 주로 사용됩니다. 출력 임베딩은 [CLS] 토큰 또는 Mean Pooling을 통해 생성되며, 일반적으로 384~1024 차원의 밀집 벡터입니다.
현재 가장 널리 사용되는 모델은 all-MiniLM-L6-v2와 all-mpnet-base-v2입니다. MiniLM 모델은 6개 레이어, 384차원 임베딩으로 매우 빠르며(CPU에서 초당 5,000~14,000 문장), 품질도 실용적인 수준입니다. MPNet 모델은 12개 레이어, 768차원으로 MTEB(Massive Text Embedding Benchmark) 리더보드에서 최고 수준의 품질을 제공하지만 4~5배 느립니다. 공식 문서에 따르면 "mpnet이 최고 품질, MiniLM이 5배 빠르면서도 좋은 품질"이라고 설명합니다.
Sentence Transformer의 주요 활용 사례는 시맨틱 검색(키워드가 아닌 의미 기반 검색), RAG(Retrieval-Augmented Generation) 파이프라인의 Retriever, 중복 문서 탐지, 클러스터링(뉴스 기사 그룹화 등), 추천 시스템입니다. LangChain, LlamaIndex 등 LLM 프레임워크에서 기본 임베딩 모델로 자주 사용되며, FAISS, Pinecone, Weaviate 등 벡터 데이터베이스와 결합하여 수억 건의 문서에서도 밀리초 단위 검색이 가능합니다.
Sentence Transformer를 사용한 문장 임베딩 및 유사도 계산 예제입니다.
# pip install sentence-transformers
from sentence_transformers import SentenceTransformer, util
import numpy as np
# 모델 로드 (첫 실행 시 자동 다운로드)
# 빠른 모델: all-MiniLM-L6-v2 (384차원, 빠름)
# 고품질 모델: all-mpnet-base-v2 (768차원, 느리지만 정확)
model = SentenceTransformer('all-MiniLM-L6-v2')
# 문장 리스트
sentences = [
"인공지능이 세상을 바꾸고 있습니다.",
"AI 기술이 우리의 삶을 혁신하고 있습니다.",
"오늘 날씨가 매우 좋습니다.",
"머신러닝은 데이터에서 패턴을 학습합니다.",
]
# 문장 임베딩 생성
embeddings = model.encode(sentences, convert_to_tensor=True)
print(f"임베딩 shape: {embeddings.shape}") # (4, 384)
# 코사인 유사도 계산
cosine_scores = util.cos_sim(embeddings, embeddings)
print("\n유사도 행렬:")
print(cosine_scores.numpy().round(3))
# 쿼리와 가장 유사한 문장 찾기
query = "딥러닝 기술의 발전"
query_embedding = model.encode(query, convert_to_tensor=True)
# 모든 문장과의 유사도 계산
similarities = util.cos_sim(query_embedding, embeddings)[0]
# 결과 정렬 및 출력
print(f"\n쿼리: '{query}'")
print("유사한 문장 순위:")
for idx in similarities.argsort(descending=True):
print(f" {similarities[idx]:.3f}: {sentences[idx]}")
대규모 시맨틱 검색 (FAISS 연동)
# pip install sentence-transformers faiss-cpu
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
model = SentenceTransformer('all-MiniLM-L6-v2')
# 대규모 문서 코퍼스 (예시)
corpus = [
"Python은 인기 있는 프로그래밍 언어입니다.",
"JavaScript는 웹 개발에 필수적입니다.",
"딥러닝은 신경망을 사용한 머신러닝 기법입니다.",
"자연어 처리는 텍스트 데이터를 분석합니다.",
"컴퓨터 비전은 이미지를 이해하는 AI 분야입니다.",
# ... 수백만 개의 문서
]
# 코퍼스 임베딩 (한 번만 실행, 저장해두고 재사용)
corpus_embeddings = model.encode(corpus, convert_to_numpy=True)
corpus_embeddings = corpus_embeddings.astype('float32')
# FAISS 인덱스 생성 (L2 거리 기반)
dimension = corpus_embeddings.shape[1] # 384
index = faiss.IndexFlatIP(dimension) # Inner Product (코사인 유사도용 정규화 필요)
# 정규화 후 인덱스에 추가
faiss.normalize_L2(corpus_embeddings)
index.add(corpus_embeddings)
# 검색 함수
def search(query: str, top_k: int = 3):
query_embedding = model.encode([query], convert_to_numpy=True).astype('float32')
faiss.normalize_L2(query_embedding)
scores, indices = index.search(query_embedding, top_k)
results = []
for score, idx in zip(scores[0], indices[0]):
results.append({
'document': corpus[idx],
'score': float(score)
})
return results
# 검색 테스트
query = "AI가 이미지를 분석하는 방법"
results = search(query, top_k=3)
print(f"검색 쿼리: '{query}'")
for i, result in enumerate(results, 1):
print(f"{i}. [{result['score']:.3f}] {result['document']}")
커스텀 모델 파인튜닝
# pip install sentence-transformers datasets
from sentence_transformers import SentenceTransformer, InputExample, losses
from sentence_transformers.evaluation import EmbeddingSimilarityEvaluator
from torch.utils.data import DataLoader
# 기본 모델 로드
model = SentenceTransformer('all-MiniLM-L6-v2')
# 학습 데이터 준비 (문장 쌍 + 유사도 점수)
train_examples = [
InputExample(texts=["문장 A1", "문장 B1"], label=0.9), # 매우 유사
InputExample(texts=["문장 A2", "문장 B2"], label=0.1), # 다름
# ... 더 많은 데이터
]
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
# 손실 함수: CosineSimilarityLoss (연속 점수용)
train_loss = losses.CosineSimilarityLoss(model)
# 평가 데이터 (선택적)
evaluator = EmbeddingSimilarityEvaluator.from_input_examples(
train_examples[:100],
name='dev'
)
# 파인튜닝
model.fit(
train_objectives=[(train_dataloader, train_loss)],
epochs=3,
warmup_steps=100,
evaluator=evaluator,
evaluation_steps=500,
output_path='./my-finetuned-model'
)
# 파인튜닝된 모델 로드
model = SentenceTransformer('./my-finetuned-model')
Sentence Transformers는 오픈소스 무료입니다. Apache 2.0 라이선스로 상업적 사용이 가능합니다.
| 모델 | 차원 | 속도 (CPU) | MTEB 점수 | 특징 |
|---|---|---|---|---|
| all-MiniLM-L6-v2 | 384 | ~14,000 sent/s | ~56 | 5x 빠름 |
| all-MiniLM-L12-v2 | 384 | ~7,500 sent/s | ~57 | L6보다 약간 정확 |
| all-mpnet-base-v2 | 768 | ~2,800 sent/s | ~59 | 최고 품질 |
| paraphrase-MiniLM-L6-v2 | 384 | ~14,000 sent/s | ~55 | 패러프레이즈 특화 |
| multi-qa-MiniLM-L6-cos-v1 | 384 | ~14,000 sent/s | ~53 | QA 검색 특화 |
| distiluse-base-multilingual-cased-v2 | 512 | ~4,000 sent/s | ~51 | 다국어 (50+ 언어) |
* 속도는 CPU(i7) 기준, GPU 사용 시 10배+ 빨라짐
용도별 추천 모델
| 용도 | 추천 모델 | 이유 |
|---|---|---|
| 프로덕션 시맨틱 검색 | all-MiniLM-L6-v2 | 속도/품질 균형, 낮은 메모리 |
| 최고 정확도 필요 | all-mpnet-base-v2 | MTEB 최고 점수 |
| 한국어 지원 | distiluse-base-multilingual | 다국어 지원 |
| RAG 파이프라인 | multi-qa-MiniLM-L6 | 질문-문서 매칭 특화 |
| 중복 탐지 | paraphrase-MiniLM-L6 | 패러프레이즈 인식 특화 |
"검색 품질보다 속도가 중요하면 MiniLM, 정확도가 더 중요하면 mpnet 모델을 쓰세요. MiniLM이 5배 빠르지만 품질 차이는 크지 않아요."
"RAG 시스템에서 chunk 임베딩할 때 Sentence Transformer 써서 FAISS에 넣으면 밀리초 단위로 검색됩니다."
"Sentence Transformer로 GPT처럼 텍스트 생성할 수 있어요." → Sentence Transformer는 임베딩 전용 모델입니다. 텍스트 생성은 못 합니다.