질의-문서의 상호작용 방식

Encoder, RAG

Bi-Encoder

질의(Query), 문서(Document)를 각각 독립적으로 인코딩한 뒤, 벡터 유사도로 관련성을 계산하는 구조. 두 입력이 독립적으로 인코딩되기 때문에 Bi-encoder이다. encoder란 문장을 입력받아 고정 길이 임베딩 벡터로 변환하는 신경망 모델이다.

query -> encoder -> q_vector
doc   -> encoder -> d_vector

score = similarity(q_vector, d_vector)

여기서 encoder는 보통 다음과 같은 모델이다.

  • BERT 기반 sentence encoder(문장 임베딩용으로 튜닝한 BERT)
  • SBERT(pair 유사도 학습으로 파인 튜닝한 모델)
  • E5(검색용으로 contrastive learning을 통해 학습된 임베딩 모델)
  • text-embedding(OpenAI, Google 등에서 제공하는 API 기반 임베딩 모델—문장을 벡터로 변환하는 서비스형 모델)

Bi-Encoder는 확장성(scalability)이 핵심인데, 문서가 n개라면,

  • Cross-Encoder는 질의마다 n번 forward pass—신경망에 입력을 넣고 출력을 n회 반복한다.
  • Bi-Encoder는 문서를 사전에 인코딩해 벡터로 저장해둘 수 있다. 검색 시에는 질의 벡터와 단순 유사도 계산만 하면 된다. 따라서 문서 수 n에 대해서 질의당 신경망 연산은 O(1)이며, 모델을 n번 돌릴 필요가 없다.

매우 빠르고, 대규모 검색에 강하며, 벡터 DB에 적합하다. RAG의 검색 과정에서 1차 필터링에 최적이다. 하지만 질의-문서 간 정밀 상호작용이 부족하고, 단어 순서나 문맥 차이 구분이 약하다. 따라서 ‘비슷하지만 실제로는 틀린 문서’를 걸러내기 어렵다.

Contrastive Learning

Bi-Encoder에서 기본적으로 차용되는 학습 방식.

similarity(q, positive) ↑
similarity(q, negative) ↓

이렇게 학습해서 의미적으로 관련된 쌍은 공간에서 가깝도록 만든다. 보통 cross-entropy 기반 손실(loss) 함수를 사용하고, 특히 InfoNCE loss 형태의 cross-entropy를 많이 사용한다.

손실(loss) 함수

신경망은 ‘예측이 정답과 얼마나 다른가?’의 목표를 위해, ‘다름의 정도’를 숫자로 계산하고 그 것을 손실(loss)이라고 한다. 정답과 완전히 같으면 0, 많이 틀릴 경우 그만큼 커진다. 모델은 이 손실을 최소화(minimize)하도록 학습한다. 따라서 손실 함수는 모델이 줄이려고 하는 값이다.

Cross-entropy는 ‘모델이 정답을 얼마나 확신하고 있는가’를 측정하는 함수다. 유사도 점수를 softmax로 확률처럼 만든다. 정답 문서 확률이 낮으면 손실이 커지고, 정답 확률이 1에 가까우면 손실이 작다. 그래서 contrastive learning에서 자연스럽게 쓰인다.

Dense와의 관계

Bi-Encoder는 상호작용 구조다. 그 위에 얹히는 표현은 Dense embedding이 일반적이고, Neural sparse인 SPLADE-like dual encoder도 가능하다.

Cross-Encoder

질의(Query)와 문서(Document)를 하나의 입력으로 함께 인코딩하고, Transformer가 두 텍스트를 동시에 보면서 직접 관련성 점수를 예측하는 구조. Top-K가 이미 어느 정도 줄어든 상황, 잘못된 문서를 최대한 제거해야 하는 상황, 검색 품질이 중요한 도메인 등에서 주로 사용한다.

[CLS] Query [SEP] Document [SEP]
      ↓
   Transformer
      ↓
 relevance score

Bi-Encoder는 벡터 유사도를 계산하지만, Cross-Encoder는 모델이 직접 점수를 출력한다.

Cross-attention

Transformer는 self-attention을 사용하는데, Cross-Encoder에서는 질의 토큰이 문서 토큰을, 문서 토큰이 질의 토큰을 참고한다. 즉, 토큰 단위에서 서로 직접 상호작용한다.

따라서 문서가 n개일 때 질의-문서 쌍마다 forward pass가 발생하고, 총 n번의 모델 실행이 발생한다. 그래서 대규모 전체 검색에는 부적합하며, GPU 효율성이 높고 RAG에서 reranking 계층에서 주로 채택된다.

Bi-Encoder는 질의는 질의끼리, 문서는 문서끼리만 self-attention이 일어난다. 하지만 Cross-Encoder는 질의와 문서가 하나의 시퀀스로 묶여 attention이 교차한다.

# 질의:
Ethereum nonce conflict

# 문서:
This document explains how transaction ordering affects nonce reuse.

Cross-Encoder는 ‘conflict’-’transaction ordering’과 같은 관계를 직접 attention으로 연결할 수 있고, 그래서 미묘한 문맥 차이까지 잡는다.

학습 방식

Cross-Encoder는 보통 다음 방식으로 학습한다:

  • Binary Classification(binary cross-entropy loss)
  • Regression(MSE loss)
  • Pairwise Ranking(Margin ranking loss 등)

ColBERT(Late Interaction Model)

1 문서 = 1 벡터, 1질의 = 1벡터가 아닌, 토큰 단위 벡터를 유지한 채로 비교한다. 즉, 문서와 질의를 하나의 벡터로 압축하지 않는다. 문서와 질의에서 각각 토큰별 벡터를 생성하고, 각 질의 토큰이 문서 토큰 중 가장 유사한 것과 매칭되고, 그 점수들을 합산한다. 따라서 dense, sparse 표현보다 기본적으로 연산 비용도 크고, 저장 공간도 비대해진다. (다만 sparse에서 vocab 크기 차원을 갖기에 반드시 큰 건 아니다.)

토큰 단위 세밀한 대응 관계가 중요할 때, 긴 문서에서 특정 구간만 관련될 때, 미묘한 의미 차이 구분이 중요할 때, 검색 품질이 곧 경쟁력이 될 때, 잘못된 문서 하나가 치명적일 때 사용하는 것이 좋다. 단순 FAQ, 작은 문서 집합, 속도 최우선 서비스에는 과하다. Dense 1차 필터 후 ColBERT rerank 방식도 있다.

질의와 문서가 각각 독립적으로 인코딩된 후에 마지막 단계에서 토큰 단위로 상호작용하기 때문에 Bi-Encoder 기반의 Late Interaction이다. 독립 인코딩은 유지하면서, 유사도 계산 단계에서 토큰 단위 상호작용을 수행한다.

Early Interation인 dense single-vector 검색에서는 문서 내부 정보가 이미 압축된 후에 비교가 일어난다. 상호작용도 벡터 하나 대 하나로 단순하다.