본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 06. 21. 18:23

벡터 검색만으로는 부족하다 ― BM25 / RRF / 하이브리드 검색 (RAG 제2회)

요약

RAG 시스템에서 벡터 검색의 한계를 극복하기 위한 키워드 검색(BM25)과 하이브리드 검색의 필요성을 설명합니다. 정확한 식별자(Identity) 검색과 의미적 유사성(Similarity) 검색의 차이를 분석합니다.

핵심 포인트

  • 벡터 검색은 의미적 유사성(Similarity)에는 강하나 정확한 문자열 일치에는 취약함
  • BM25는 희귀 단어의 중요도를 반영하여 키워드 기반의 정확한 검색(Identity)을 지원함
  • 효과적인 RAG를 위해서는 BM25와 벡터 검색을 결합한 하이브리드 검색이 필수적임
  • 에러 코드, 버전 번호 등 고유 명사 검색에는 키워드 기반 방식이 더 유리함

RAG를 만들면, 우선 벡터 검색 (Vector Search)을 넣고 싶어집니다.

문장을 임베딩 (Embedding)으로 만들어서, 가까운 것을 찾는다.

의미가 비슷한 문서를 찾아낼 수 있다.

키워드가 완전히 일치하지 않아도, 관련 있는 단편을 찾아낼 수 있다.

이것은 확실히 편리합니다.

하지만, 여기서 한 가지 큰 함정이 있습니다.

벡터 검색은 「의미가 가까운 것」을 찾는 데는 능숙하지만, 「바로 그 문자열이 들어있는 것」을 찾는 데는 항상 능숙하다고 할 수 없습니다.

예를 들어, 다음과 같은 질문을 생각해 봅시다.

ERR_PAYMENT_GATEWAY_TIMEOUT

이 발생했을 때의 복구 절차는 무엇인가.

v3.2

의 롤백 (Rollback) 절차는 어디에 있는가.

payment_v2_enforce

라는 피처 플래그 (Feature Flag)를 무효화하는 조건은 무엇인가.

이 경우, 원하는 것은 「대략 비슷한 이야기」가 아닙니다.

에러 코드, 버전 번호, 기능명이 일치하는 문서입니다.

RAG의 사고는 여기서 발생합니다.

비슷하지만 다른 문서를 찾아낸다.

활성화 절차와 무효화 절차를 섞는다.

v3.1v3.2를 가까운 것으로 취급한다.

에러 코드의 앞부분이 비슷하다는 이유만으로, 다른 장애 대응책을 가져온다.

이런 실수는 모델이 얼마나 똑똑한가만으로는 방지할 수 없습니다.

애초에 모델에 전달하기 전의 검색 단계에서 다른 것을 가져오고 있기 때문입니다.

제2회에서는 RAG의 토대가 되는 검색을 살펴봅니다.

키워드 검색, 벡터 검색, 그리고 RRF에 의한 하이브리드 검색 (Hybrid Search)입니다.

검색에서 말하는 「관련되어 있다」에는 적어도 2가지 종류가 있습니다.

하나는, 같은 것을 찾는 것입니다.

에러 코드, 함수명, 상품명, 버전, 설정 키, 문서 ID.

이것은 문자열 그 자체에 의미가 있습니다.

또 다른 하나는, 가까운 의미를 찾는 것입니다.

「장애 시의 복구」라고 적힌 문서를 「rollback」이라는 단어로 찾아낸다.

「청구가 이중으로 발생함」이라고 적힌 보고를 「결제의 중복」이라는 질문으로 찾아낸다.

이것은 표현이 달라도 의미가 가까우면 됩니다.

이 두 가지는 비슷해 보이지만 다릅니다.

전자는 identity입니다.

「이것 그 자체」를 찾고 있습니다.

후자는 similarity입니다.

「이것과 유사한 것」을 찾고 있습니다.

BM25와 같은 키워드 검색은 identity에 강합니다.

벡터 검색은 similarity에 강합니다.

이 둘을 섞어서 생각하면, RAG는 갑자기 불안정해집니다.

BM25는 검색의 세계에서 상당히 오랫동안 사용되어 온 기법입니다.

지금도 Elasticsearch 등의 검색 기반에서 중요한 역할을 맡고 있습니다.

구조를 대략적으로 말하자면, BM25는 「문서 안에 등장하는 단어」를 봅니다.

단, 단순히 출현 횟수를 세는 것만은 아닙니다.

중요한 것은 희귀한 단어입니다.

어떤 문서에도 등장하는 단어는 별로 단서가 되지 않습니다.

「시스템」, 「대응」, 「확인」만으로는 어떤 문서를 찾고 있는지 알 수 없습니다.

반대로, 거의 나오지 않는 단어는 강력한 단서로 사용할 수 있습니다.

ERR_PAYMENT_GATEWAY_TIMEOUT이나 payment_v2_enforce와 같은 문자열은, 그것이 등장하는 것만으로도 상당한 정보량이 있습니다.

BM25는 이러한 희귀어를 강력하게 봅니다.

이것이 IDF의 감각입니다.

또 하나 중요한 것은, 같은 단어가 몇 번이나 등장했다고 해서 무한히 평가를 높이지 않는 것입니다.

어떤 문서에 rollback이 1번 나온다.

다른 문서에 rollback이 30번 나온다.

30번 나오는 쪽이 더 중요해 보입니다.

하지만 30배 중요하다고는 할 수 없습니다.

어느 정도 나와 있다면, 「이 문서는 rollback에 대해 이야기하고 있다」라는 판단에는 충분합니다.

그 이상은 가중치를 천천히 늘리는 것만으로 충분합니다.

BM25의 term frequency saturation은 이 감각에 가깝습니다.

단어의 출현 횟수를 그대로 믿지 않고, 어느 지점부터는 증가폭을 둔화시킵니다.

즉, BM25는 단순한 키워드 일치가 아닙니다.

희귀어를 강력하게 보고, 긴 문서가 지나치게 유리해지지 않도록 하며, 같은 단어의 반복을 과대평가하지 않습니다.

오래된 기술이라고 생각하기 쉽지만, RAG에서는 오히려 이 부분이 효과적입니다.

왜냐하면, RAG의 입력에는 버전, 에러 코드, 설정명, 함수명, 제품명과 같이 「의미로 뭉뚱그려서는 안 되는 것」이 많기 때문입니다.

벡터 검색은 문장을 고정 길이의 수치로 변환합니다.

그 수치들 사이의 거리를 보고, 의미가 가까운 문서를 찾습니다.

이것은 매우 편리합니다.

사용자가 사용한 단어와 문서에 적혀 있는 단어가 다르더라도 찾아낼 수 있습니다.

표현의 변화(variation)에 강합니다.

자연어 질문에 대해 후보를 넓게 수집할 수 있습니다.

다만, 여기에는 피할 수 없는 성질이 있습니다.

긴 문장을 고정 길이의 벡터(vector)로 만든다는 것은 정보를 압축한다는 뜻입니다.

압축하는 이상, 세부 사항은 어디선가 뭉뚱그려집니다.

예를 들어, 활성화(enable) 절차와 비활성화(disable) 절차를 다룬 문서가 있다고 가정해 봅시다.

둘 다 같은 기능명, 같은 화면명, 같은 설정 항목, 같은 주의 사항을 포함하고 있습니다.

다른 점은 enable인지 disable인지 하는 점입니다.

인간에게는 이 부분이 결정적입니다.

하지만 문서 전체로 보면, 두 문서는 상당히 유사합니다.

벡터 공간(vector space)에서 가깝게 나타나는 것은 어떤 의미에서는 자연스러운 일입니다.

그것은 임베딩 (embedding)의 실패라기보다, 압축의 성질입니다.

그래서 벡터 검색만으로 RAG를 만들면 다음과 같은 사고가 발생합니다.

유사하지만 답은 다르다.

주제는 가깝지만 조건이 다르다.

문맥은 같지만 조작이 반대다.

LLM에 전달하기 전에 여기서 섞여 버리면, 생성 측의 부담이 커집니다.

모델은 '그럴듯한 근거'를 보고 말기 때문입니다.

여기서 중요한 것은 BM25와 벡터 검색 중 어느 쪽이 더 우월한가 하는 이야기가 아닙니다.

보고 있는 대상이 다릅니다.

BM25는 희귀한 문자열에 민감합니다.

그 대신, 유의어(paraphrasing)에는 약합니다.

벡터 검색은 의미의 유사성에 강합니다.

그 대신, 세밀한 식별자(identifier)를 뭉뚱그릴 때가 있습니다.

예를 들어, 질문이 다음과 같다고 가정해 봅시다.

v3.2의 배포를 롤백(rollback)하는 절차를 찾고 싶다.

이 질문에는 두 가지 성분이 있습니다.

롤백(rollback)은 의미의 문제입니다.

문서 측에서는 rollback이라고 적혀 있을 수도 있고, 복구revert라고 적혀 있을 수도 있습니다.

한편, v3.2는 정체성(identity)의 문제입니다.

v3.1도 아니고 v3.3도 아닙니다.

즉, 실제 검색 쿼리는 대략 섞여 있습니다.

일부는 의미로 찾고 싶고,

일부는 문자열로 놓치고 싶지 않습니다.

그래서 하이브리드 검색(hybrid search)이 필요합니다.

그렇다면 BM25와 벡터 검색의 결과를 어떻게 섞을까요?

가장 먼저 떠오르는 것은 점수를 더하는 것입니다.

BM25의 스코어(score)를 0.5배 하고, 벡터 유사도(vector similarity)를 0.5배 하여 더합니다.

혹은 0.7 대 0.3과 같이 가중치를 정합니다.

하지만 이것은 간단하지 않습니다.

BM25의 스코어와 코사인 유사도(cosine similarity)의 스코어는 애초에 같은 척도가 아닙니다.

한쪽은 키워드 일치로부터 나온 검색 스코어입니다.

다른 한쪽은 벡터 공간상의 거리입니다.

숫자로서 크더라도 의미는 다릅니다.

BM25의 검색 스코어와 코사인 유사도를 그대로 더해도 된다고 말할 수 없습니다.

게다가 가중치는 쿼리에 따라 달라집니다.

에러 코드를 찾는다면 BM25를 강화하고 싶을 것입니다.

'이 장애와 유사한 과거 사례'를 찾는다면 벡터 검색을 강화하고 싶을 것입니다.

매번 그것을 올바르게 조정하는 것은 실제 시스템에서는 상당히 어렵습니다.

여기서 등장하는 것이 RRF입니다.

RRF는 Reciprocal Rank Fusion의 약자입니다.

여러 검색 결과를 점수가 아닌 순위(rank)로 융합합니다.

생각 방식은 단순합니다.

BM25가 상위에 올린 문서.

벡터 검색도 상위에 올린 문서.

이처럼 여러 검색 방법에서 상위에 올라오는 문서는 신뢰해도 좋을 것 같습니다.

반대로, 한쪽에서만 갑자기 1위로 올라온 문서는 조금 신중하게 보고 싶습니다.

그것이 정말 강력한 근거인지, 아니면 한쪽 검색 방법의 특성인지 알 수 없기 때문입니다.

RRF의 기본 형태는 다음과 같은 식입니다.

score(d) = Σ 1 / (k + rank(d))

문서 d가 각각의 검색 결과에서 몇 위인지 확인합니다.

순위가 높을수록 더 큰 점수를 받습니다.

여러 검색 결과에서 상위에 있으면 점수가 더해집니다.

여기서 자주 사용되는 k=60은 이론상의 마법의 숫자가 아닙니다.

실험상 넓은 범위에서 안정적으로 유지되기 쉬운 값으로 사용되어 온 것입니다.

중요한 것은 RRF가 절대적인 스코어를 너무 믿지 않는 것입니다.

BM25의 점수와 벡터 유사도를 억지로 같은 척도로 맞추지 않습니다.

각각의 검색 방법이 낸 순위를 보고 상위 일치 항목을 잡아냅니다.

이러한 절충안이 RAG에서는 상당히 실용적입니다.

실제 RAG에서는 검색을 대략 다음과 같은 흐름으로 구성하는 경우가 많습니다.

  • BM25로 후보를 추출
  • 벡터 검색 (Vector Search)으로 후보를 추출
  • RRF로 후보를 통합
  • 필요하다면 리랭커 (Reranker)로 순위를 재조정
  • 상위 결과만을 LLM에 전달

BM25는 에러 코드나 고유명사를 잡아냅니다.

벡터 검색은 표현의 변경(Paraphrasing)이나 의미가 유사한 문서를 잡아냅니다.

RRF는 두 결과의 순위를 통합합니다.

그 뒤에 리랭커 (Reranker)를 배치하기도 합니다.

리랭커는 쿼리 (Query)와 문서를 함께 읽어 더욱 세밀하게 관계를 판정합니다.

다만, 리랭커는 계산 비용 (Computational Cost)이 높습니다.

처음부터 모든 문서에 적용할 수 있는 것이 아닙니다.

BM25와 벡터 검색으로 후보를 좁히고, RRF로 통합한 뒤, 이후의 소수 후보에 대해서만 사용합니다.

이 순서에는 이유가 있습니다.

저렴한 방법으로 넓게 수집하고, 순위를 정렬한 뒤, 마지막에 무거운 방법으로 세밀하게 살피는 것입니다.

RAG의 검색 부분에서는 이러한 단계적 설계가 상당히 효과적입니다.

물론, 하이브리드 검색 (Hybrid Search)을 한다고 해서 모든 것이 끝나는 것은 아닙니다.

예를 들어, 완전히 에러 코드만을 찾고 있는 경우입니다.

이때는 BM25만 사용하는 편이 더 정확하게 맞을 수 있습니다.

벡터 검색을 섞으면 유사한 장애명이나 비슷한 설명문이 후보에 포함됩니다.

사람이 읽는다면 아직 괜찮을지 몰라도, LLM에 전달하면 노이즈 (Noise)로 작용하게 됩니다.

또한, BM25와 벡터 검색이 거의 동일한 순위를 반환하는 경우도 있습니다.

그럴 때 RRF만으로는 차이를 만들기 어렵습니다.

구현 측면에서 동점일 때는 BM25를 우선한다거나, 날짜를 확인한다거나, 문서의 신뢰도를 확인하는 등의 규칙이 필요할 수도 있습니다.

즉, RRF는 편리한 부품이지만 모든 판단을 대신해 주는 것은 아닙니다.

검색에서 가장 먼저 살펴봐야 할 것은 쿼리의 성격입니다.

식별자 (Identifier)를 찾고 있는가.

의미가 유사한 사례를 찾고 있는가.

그 둘 모두인가.

이 부분을 간과한 채 "일단 벡터 검색"이나 "일단 하이브리드"로 접근하면, 원인을 알 수 없는 실패가 늘어납니다.

과거의 검색 결과는 사람이 읽었습니다.

순위가 다소 낮더라도 사람이 스크롤하며 다시 선택할 수 있었습니다.

하지만 RAG에서는 검색 결과가 그대로 LLM의 입력값으로 들어갑니다.

상위에 들어온 문서가 정답의 근거로 취급됩니다.

즉, 검색의 실패는 곧 답변의 실패로 직결됩니다.

이 점이 일반적인 검색 엔진과 RAG의 큰 차이점입니다.

RAG에서 "모델이 틀렸다"라고 생각되는 문제 중 일부는 사실 검색의 문제입니다.

필요한 문서가 포함되지 않았거나.

유사하지만 다른 문서가 포함되었거나.

중요한 식별자를 놓쳤거나.

잡음이 너무 많거나.

생성 (Generation)을 수정하기 전에 먼저 검색을 살펴봐야 할 상황이 많습니다.

그리고 검색을 볼 때는 BM25와 벡터 검색을 경쟁시키는 것이 아니라, 역할을 나누어 생각해야 합니다.

BM25는 문자열의 날카로움을 가지고 있습니다.

벡터 검색은 의미의 확장성을 가지고 있습니다.

RRF는 이 두 가지를 억지로 동일한 점수로 밀어넣지 않고, 순위의 합의로서 다룹니다.

이 세 가지를 구분하는 것만으로도 RAG의 설계는 상당히 명확해집니다.

다음 회차에서는 여기서 조금 반대 방향으로 나아갑니다.

정말로 벡터 검색이나 인덱스 (Index)가 필요한가.

에이전트 (Agent)가 여러 번 검색을 다시 수행할 수 있다면, grep이나 파일 검색과 같은 저수준 (Low-level) 도구가 더 강력한 상황도 있습니다.

제3회에서는 grep과 벡터 검색의 활용 구분법을 다룹니다.

검색 알고리즘뿐만 아니라, 에이전트에게 결과를 어떻게 보여줄 것인지까지 포함하여 생각합니다.

―― AI 미래 편집실 「AI 워치」

AI 자동 생성 콘텐츠

본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0