BM25 vs 벡터 검색 vs 하이브리드 검색

화상 기반 AI 법률 상담 플랫폼을 제작하면서 가장 먼저 부딪힌 문제는 바로 검색이었다.

사용자가 남긴 사건 설명을 어떻게 수만 건의 판례와 연결할 것인가?

처음에는 PostgreSQL의 ILIKE에 기대어 시작했지만 결과는 처참했다.

Recall@10 수치는 0%, 사실상 “검색 불능” 상태였다.

그래서 LawAId 프로젝트에서는 검색 성능을 끌어올리기 위해 BM25(키워드 검색), 벡터 검색(의미 검색), 그리고 두 가지를 결합한 하이브리드 검색을 실험했다.

최종적으로 Recall@10을 55%까지 끌어올렸고, 이 글은 그 과정을 정리한 글이다.


1. ILIKE: 단순 매칭의 한계

PostgreSQL의 ILIKE는 텍스트 안에 문자열이 있는지 단순히 확인한다.

예를 들어 "횡령"이라는 단어가 있어야만 검색된다.

  • 장점: 구현이 매우 간단하다.
  • 단점:
    • 동의어나 활용형 구분 불가 (예: “절도” vs “절도죄”)
    • 띄어쓰기에 민감
  • 실제 결과: Recall@10 = 0% → 실무에서 전혀 쓸 수 없었다.

2. BM25: 키워드 기반 확률적 검색

BM25는 정보 검색에서 가장 널리 쓰이는 문서 랭킹 함수다.

사용자 질의(Query)와 문서(Document)의 관련도를 수치 화해서 점수가 높은 문서를 위에 올린다.

핵심 원리

BM25는 TF-IDF의 발전형이다.

  • TF (Term Frequency): 단어가 문서에 얼마나 자주 나오나
  • IDF (Inverse Document Frequency): 흔하지 않은 단어일수록 가중치 ↑
  • 문서 길이 보정: 긴 문서가 불리하지 않도록 평균 길이에 맞춰 조정

즉, 문서 안에서 단어가 얼마나 “의미 있게” 등장하는지를 수치화한다.

예시

질의: "회사 돈을 개인적으로 사용"

  • "업무상 횡령 사건" → 핵심 단어 “횡령” 매칭, 점수 상승
  • "회사 자금을 개인적으로 사용" → 질의 단어 대부분 매칭, 최고 점수
  • "교통사고 과실 판례" → 관련 단어 없음, 점수 0

장단점

  • 장점:
    • 키워드 기반 정확도 높음
    • 속도 빠름
    • 점수 계산 방식이 직관적
  • 단점:
    • 문맥 이해 불가
    • 예: "횡령""재산 편취"를 같은 의미로 보지 못한다

3. 벡터 검색: 의미 기반 검색

BM25가 키워드 위주라면, 벡터 검색은 문맥 전체 의미를 본다.

문장이나 문서를 고차원 벡터로 바꾼 뒤, 질의 벡터와의 유사도를 계산하는 방식이다.

보통 코사인 유사도를 많이 쓴다.

특징

  • 동의어 대응: "횡령""배임" 같은 비슷한 표현도 매칭 가능
  • 문맥 이해: 단순 단어 매칭이 아니라 문장의 의미 반영
  • 자연어 질의 강점: “내가 회사 돈을 써버린 경우” 같은 표현도 검색 가능

한계

  • 리소스 부담: 고차원 연산으로 CPU/GPU 자원 많이 소모
  • 속도 느림: BM25보다 훨씬 무거움
  • 의미 손실: 하나의 벡터로 문서를 표현하다 보니 세부 정보가 사라질 수 있음

실제 적용

  • Sentence-BERT 기반 한국어 임베딩 모델 사용
  • PostgreSQL + pgvector 확장으로 벡터 검색 구현
  • FP16 양자화로 메모리 절감 (2.5GB → 1.3GB, 약 49% 감소)
  • 배치 처리로 OOM(Out Of Memory) 방지

4. 하이브리드 검색: 두 방식을 합치다

BM25와 벡터 검색은 성격이 다르다.

  • BM25는 법률 용어 같은 정확한 키워드 매칭에서 강하고,
  • 벡터 검색은 “말을 바꿔도 같은 의미”인 표현을 잘 잡아낸다.

두 방식 모두 강점이 뚜렷했기 때문에, 우리는 이들을 하나의 파이프라인으로 엮었다. 단순히 합치는 게 아니라, 후보군을 모으고 최종적으로 재정렬(re-ranking) 하는 구조였다.

Cross-Encoder의 역할

BM25와 벡터 검색만 합치면 여전히 순서가 뒤죽박죽이다.

여기서 Cross-Encoder가 최종 심판 역할을 한다.

  • Bi-Encoder(임베딩)는 쿼리와 문서를 각각 임베딩한 뒤 유사도를 계산하는 반면,
  • Cross-Encoder쿼리와 문서를 한 쌍으로 입력해 “이 둘이 얼마나 관련 있는지”를 직접 학습된 모델이 판단한다.

예를 들어 질의가 "회사 돈을 개인적으로 사용"일 때:

  • BM25는 "회사 자금을 개인적으로 사용" 같은 문서를 잘 올려주지만, "업무상 횡령 사건"은 점수가 낮을 수 있다.
  • 벡터 검색은 "업무상 횡령 사건"도 잘 잡아내지만, "개인적 비용 처리 사례"처럼 덜 중요한 문서도 끌어올린다.
  • Cross-Encoder는 이 둘을 비교해 실제 사용자의 의도와 더 가까운 문서를 위에 올린다.

최종 파이프라인 구조

  1. BM25 – 빠른 키워드 기반 후보 30개 확보
  2. 벡터 검색 – 의미 기반 후보 20개 확보
  3. 후보 합치기 – 두 결과를 합쳐 약 50개 후보군 생성
  4. Cross-Encoder 재정렬 – 쿼리와 문서 쌍별로 점수를 계산해 최종 Top-K 선정
# Simplified Example
bm25_results = bm25_service.search(query, top_n=30)
vector_results = vector_service.search(query, top_n=20)

# 후보군 합치기 (중복 제거 포함)
candidates = bm25_results + vector_results

# Cross-Encoder로 최종 점수 산출 및 재정렬
final_results = cross_encoder.rerank(query, candidates)

도입 효과

  • BM25의 정확성 + 벡터 검색의 의미 이해를 동시에 확보
  • Cross-Encoder로 잡음(noise)을 줄이고 최종적으로 사용자에게 가장 타당한 결과를 제공
  • Recall@10이 단일 BM25 대비 25%p 이상 향상

5. 성능 비교

직접 실험한 결과는 다음과 같다:

검색 방식 Recall@1 Recall@3 Recall@10 MRR
ILIKE 0% 0% 0% 0.000
BM25 0% 0% 30.0% 0.205
벡터 검색 0% 15.0% 15.0% 0.180
하이브리드 15.0% 30.0% 55.0% 0.250
  • Recall@K: 상위 K개의 결과 안에 정답(관련 문서)이 포함될 확률
  • MRR (Mean Reciprocal Rank): 정답이 몇 번째에 나왔는지를 역순위로 평균 낸 값 → 정답이 위에 있을수록 높음

➡️ 결론: 하이브리드 검색이 가장 높은 성능을 보였다.


6. 실무에서 배운 점과 주의사항

배운 점

  • 메모리 최적화의 중요성

GPU에는 FP16(반정밀도), CPU에는 INT8 동적 양자화를 적용해 리소스를 크게 줄일 수 있었다. 경량화는 단순한 최적화가 아니라 “서비스 가능성”을 좌우하는 핵심 요소였다.

  • 한국어 특화 처리의 필요성

복합 명사(예: 업무상횡령, 사기죄)와 법률 동의어(예: 횡령 ↔ 자금 유용)를 처리하지 않으면 검색 품질이 떨어졌다. 한국어 NLP에서는 일반 모델만 쓰기보다 도메인 맞춤 처리가 필수라는 걸 깨달았다.

  • 캐싱 전략의 효과

반복적으로 들어오는 쿼리를 캐싱하거나 BM25 인덱스를 직렬화해두니 응답 속도가 체감될 정도로 개선됐다. 작은 최적화가 전체 시스템의 사용자 경험에 크게 기여했다.

  • 하이브리드 접근의 실효성

단순 키워드 매칭(Recall@10 = 0%)에서 출발해 BM25와 벡터 검색을 결합하자 Recall@10이 55%까지 올라갔다. 서로 다른 방법을 조합하는 것이 실제 서비스 환경에서 훨씬 안정적이라는 점을 배웠다.


주의사항

  • 법률 용어의 의미적 차이 주의

예를 들어 횡령배임은 의미가 유사해 보이지만 법적으로는 전혀 다른 범죄다. 임베딩 모델이 의미적 유사성으로 연결할 수 있어도, 법률 도메인에서는 반드시 추가 필터링이 필요하다.

  • 실험 결과 해석 시 주의

Recall@10이나 MRR 같은 지표는 상대적인 성능 비교에는 좋지만, “사용자 만족도”와 1:1 대응되지 않는다. 지표만 보고 성능을 단정하기보다는 실제 사용 시나리오 테스트를 병행해야 한다.


7. 한계와 앞으로의 방향

  • 현재 한계: Recall@1 낮음(15%), 응답시간 길음(20~30초), 형사 사건 중심 데이터
  • 앞으로 개선:
    • 법률 특화 임베딩 모델 파인튜닝 → Recall@10 90% 목표
    • Cross-Encoder 고도화로 랭킹 정밀화
    • 고가용성 아키텍처 → Redis 세션 관리, Kubernetes 확장

8. 결론

법률 검색은 단순히 키워드 매칭만으로는 부족하다.

BM25는 키워드를 잘 잡고, 벡터 검색은 의미를 잡는다.

두 방식을 합친 하이브리드 접근법은 실제 서비스에서도 쓸 수 있을 만큼 안정적인 성능을 보여줬다.

검색에 정답은 없다. 하지만 도메인 특성을 고려한 하이브리드 전략이 현실적인 해답이었다.


참고 자료

  • Robertson & Zaragoza (2009). The Probabilistic Relevance Framework
  • Reimers & Gurevych (2019). Sentence Embeddings using Siamese BERT-Networks*
  • pgvector GitHub
위로 스크롤