데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      VLM을 이용한 시각적 문서 검색: ColPali와 ColQwen

      juunho 26.08.17
      103 1 1
      DEVOTEE 요약
      기존 텍스트 기반 문서 검색은 시각적 맥락 정보를 놓쳐 한계가 있습니다. 이를 극복하기 위해 VLM 기반 시각적 문서 검색이 주목받으며, ColPali 및 ColQwen 모델은 Late Interaction Multi-Vector 방식을 활용해 문서와 질의를 세밀하게 비교합니다. T월드 배너 데이터로 구축된 베이스라인은 시각 정보만으로도 질의를 성공적으로 찾아냈으나, 긴 이미지 처리와 결과 중복 문제를 해결하기 위해 청킹 및 그룹핑 전략을 도입했습니다.
      DEVOTEE 추천 블로그

      들어가며

      안녕하세요, SK AX 배준호 매니저입니다.

      이번 글에서는 질의와 관련된 문서를 찾는 문서 검색(Document Retrieval)에 대한 내용을 다뤄보려고 합니다.

      문서 검색은 RAG(Retrieval-Augmented Generation)를 비롯한 다양한 Agent 시스템의 출발점이 되는 기술이며, 지금까지는 텍스트 기반 방식이 주로 활용되었습니다.


      하지만 실제 업무 문서에는 텍스트뿐 아니라 표 · 그림 · 차트 · 강조 박스와 같은 시각적 요소가 함께 의미를 형성하고 있어, 문서를 텍스트로 변환하는 경우 시각적 맥락에 담긴 핵심 정보를 놓칠 위험이 있습니다.

      이에 최근에는 VLM(Vision-Language Model)으로 페이지 이미지 자체를 임베딩하여 검색하는 시각적 문서 검색(Visual Document Retrieval)이 주목받고 있습니다.


      이러한 흐름에 맞춰, 본문에서는 기존 텍스트 기반 문서 검색 파이프라인의 한계를 먼저 살펴보고, ColBERT의 Late Interaction 방식을 문서 이미지에 적용한 ColPali와 ColQwen 모델을 소개드리겠습니다.

      또한 구체적인 예시를 위해 T 월드 이벤트 배너 데이터를 이용하여 시각적 문서 검색 Baseline을 구성한 과정도 정리해보겠습니다.


      1. 텍스트 기반 문서 검색 파이프라인의 한계

      문서 검색에서는 문서를 먼저 텍스트로 변환한 뒤 검색하는 방식이 가장 보편적인 접근입니다.

      일반적으로는 레이아웃 분석을 통해 제목, 본문, 표, 머리글 등의 영역을 구분하고, OCR을 이용하여 문서에서 문자를 추출합니다.

      이후 이미지나 도표에 대해서는 캡셔닝을 추가하고, 최종적으로 텍스트 임베딩을 생성하여 질의와의 유사도를 계산합니다.


      이 방식은 문서를 검색 가능한 텍스트 형태로 만들 수 있다는 장점이 있지만, 단계가 늘어날수록 각 단계에서 발생한 오류가 이후 단계로 전파될 수 있습니다.

      특히 OCR 인식 오류, 레이아웃 분석의 불일치, 청킹 및 캡셔닝 과정에서의 정보 왜곡이 누적되면서 전체 결과의 품질을 떨어뜨리게 됩니다.


      더욱 중요한 위험은 시각적 구조와 함께 표현되는 문서의 정보를 텍스트로 재구성한다는 점 자체에 있습니다.

      표에서 특정 값이 어떤 행과 열에 위치하는지, 이미지와 캡션이 어떤 관계를 가지는지, 인포그래픽이 어떤 흐름으로 정보를 전달하는지는 텍스트만으로 완전히 복원하기 어렵습니다.


      아래의 그림은 실제 이벤트 배너의 요금 안내 예시입니다.


      정상가에 그어진 취소선은 이 가격이 더 이상 유효하지 않다는 표시이지만, 텍스트로 추출할 경우 결과에 남지 않아 맥락이 사라지게 됩니다.

      금액과 티켓의 대응 관계 역시 같은 행에 놓여 있다는 배치만으로 표현되어 있어, 텍스트로 변환한 뒤에는 순서로 판단할 수밖에 없습니다.


      2. VLM 기반 검색 모델의 발전: BiPali, ColPali, ColQwen

      2.1 VLM의 문서 표현 방식

      텍스트 변환의 한계를 보완하기 위해, 이미지와 텍스트를 함께 처리할 수 있도록 설계된 VLM을 활용하는 접근이 등장했습니다.

      초기에는 CLIP이나 SigLIP과 같이 이미지와 텍스트를 각각 인코딩한 뒤 대조 학습(Contrastive Learning)으로 두 임베딩을 같은 공간에 정렬하는 방식이 사용되었습니다.

      이 계열의 모델들은 현재까지도 다양한 멀티모달 모델에서 비전 인코더로 활용될 만큼 강력한 성능을 보였습니다.

      그러나 이미지 한 장을 하나의 벡터로 요약하는 구조적 특성상, 문서 내부의 세부적인 텍스트 구조나 레이아웃을 정밀하게 표현하는 데에는 한계가 있었습니다.


      이를 해소하기 위해 최근에는 Vision Transformer(ViT)와 언어 모델을 결합한 형태의 VLM이 주로 사용되고 있습니다.

      ColPali의 기반 모델인 PaliGemma에서는 SigLIP 방식으로 학습된 ViT가 이미지를 여러 개의 patch로 나누어 임베딩을 생성하고, 이를 언어 모델의 입력 차원에 맞게 변환한 뒤 텍스트 토큰과 함께 처리합니다.

      이 과정에서 이미지 patch도 텍스트 토큰과 마찬가지로 문맥화된(contextualized) 표현을 얻게 됩니다.


      다만 이 접근은 문서 이해 자체에는 강점이 있지만, 정보 검색(Information Retrieval)에 최적화된 형태는 아니었습니다.

      검색에서는 대규모 문서 집합을 미리 인덱싱해두고, 질의가 입력되었을 때 문서의 어느 부분이 대응되는지를 효율적으로 계산하는 것이 중요하기 때문입니다.

      즉, 시각적 문서 검색의 핵심은 VLM이 이미지를 얼마나 잘 이해하는가를 넘어, 이해한 결과를 검색에 적합한 표현으로 어떻게 변환할 것인가에 있습니다.


      2.2 BiPali: 단일 벡터 기반 Bi-Encoder

      이러한 배경에서 PaliGemma를 검색 모델로 확장한 초기 형태가 BiPali 모델입니다.

      BiPali는 문서 페이지 이미지를 VLM에 통과시킨 뒤 언어 모델이 출력한 patch 임베딩을 평균 풀링(Mean Pooling)하여 하나의 벡터로 만들고, 이를 질의와 비교하는 Bi-Encoder 구조를 사용합니다.

      텍스트 검색에서 보편적인 Bi-Encoder 방식을 문서 이미지에 그대로 적용한 형태입니다.


      이 방식은 계산 효율성 측면에서 장점이 있지만, 페이지 전체를 하나의 벡터로 압축하는 과정에서 정보 손실이 발생하여 질의의 특정 표현을 문서 내의 시각 요소와 세밀하게 대응시키기 어려웠습니다.

      이는 ColPali 논문의 Ablation Study 결과에서도 나타났습니다.

      아래 ViDoRe 벤치마크의 평균 nDCG@5를 보면, 성능 향상의 대부분을 단일 벡터에서 Multi-Vector와 Late Interaction 구조로 바꾸면서 얻었다는 결과를 확인할 수 있습니다.

      모델

      구조

      평균 nDCG@5

      SigLIP

      단일 벡터

      51.4

      BiSigLIP

      단일 벡터 + 문서 데이터 파인튜닝

      58.6

      BiPali

      단일 벡터 + 언어 모델

      58.8

      ColPali

      Multi-Vector + Late Interaction

      81.3


      2.3 ColPali: Late Interaction 기반 Multi-Vector 검색

      ColPali는 Bi-Encoder에서 발생하는 정보 손실을 막기 위해 ColBERT 계열의 Late Interaction 방식을 도입했습니다.

      이는 문서와 질의를 각각 하나의 벡터로 축약하지 않고, 여러 개의 벡터로 유지한 상태에서 비교하는 방식입니다.


      구체적으로, ColPali는 언어 모델의 출력 토큰 임베딩에 projection layer를 추가하여 각 토큰을 128차원의 저차원 벡터로 변환합니다.

      이때 텍스트 토큰과 이미지 patch 토큰이 모두 대상이 되며, 생성된 임베딩은 하나로 합쳐지지 않고 Multi-Vector 표현으로 유지됩니다.

      검색 시에는 질의의 각 토큰마다 문서 내 모든 벡터와의 유사도를 계산한 뒤 그중 최댓값만을 선택하고, 이를 모든 질의 토큰에 대해 합산하여 최종 점수를 산출합니다.

      최댓값을 선택한다는 의미에서 MaxSim이라 부르며, 논문에서는 다음과 같이 정의하고 있습니다.


      Late Interaction 구조에서는 질의와 문서의 유사도를 한 번에 계산하는 것이 아니라, 질의의 각 표현이 문서 내에서 유사도가 가장 높은 지점을 개별적으로 찾게 됩니다.

      그 결과 표 · 이미지 · 인포그래픽과 같이 문서의 일부 영역에만 있는 시각 정보도 검색에 반영될 수 있습니다.

      이에 더하여 ColPali는 텍스트 질의와 문서 이미지를 공통 표현 공간에서 비교할 수 있도록 설계되어 있습니다.

      질의는 텍스트 입력으로, 문서는 이미지 입력으로 처리되지만 동일한 언어 모델과 동일한 projection layer를 통과하기 때문에 두 표현이 자연스럽게 같은 공간에 놓이게 됩니다.


      두 방식의 차이를 그림으로 나타내면 다음과 같으며, 우측 행렬의 수치는 구조를 설명하기 위한 예시 값입니다.



      2.4 ColQwen: Backbone 변경에 따른 차이

      ColQwen은 Late Interaction과 Multi-Vector 표현이라는 기본 구조를 유지한 채 backbone을 Qwen 계열의 VLM으로 바꾼 모델이며,

      ColQwen2-v1.0은 Qwen2-VL-2B-Instruct를 기반으로 합니다.

      ColPali와 ColQwen의 가장 큰 차이는 이미지를 입력받는 방식에 있습니다.

      ColPali가 모든 이미지를 448×448로 고정하여 항상 같은 수의 토큰으로 표현하는 반면,

      ColQwen2는 입력 이미지의 크기에 따라 생성되는 visual token의 수가 달라지며 최대 768개로 제한됩니다.

      이번 Baseline에서 사용한 데이터는 세로로 긴 형태였기 때문에, 입력 크기에 유연하게 대응할 수 있는 ColQwen2를 사용했습니다.


      3. 시각적 문서 검색 Baseline 구성

      3.1 데이터 특성

      앞에서 살펴본 개념을 시각적 문서 검색의 특성이 잘 드러나는 T 월드 이벤트 배너 데이터에 적용해 보았습니다.

      이벤트의 경우 의미가 몇 줄의 문구만으로 결정되지 않고, 할인 배지, 색상 대비, 배너 구성, 카테고리 이미지와 같은 요소들이 함께 작용합니다.

      또한 일반적인 문서와 달리, 높이가 수천 px에 달하고 긴 경우 11,000px를 넘어 한 장의 웹페이지에 가까운 형태입니다.

      이러한 특성은 이후 4장에서 다룰 문제의 원인이 되었습니다.


      각 이미지와 함께 진행 중 여부(event_status), 메인 배너와 상세 이미지의 구분(page_kind), 원본 이벤트 식별자와 같은 메타데이터를 수집했습니다.

      이 값들은 Qdrant의 payload에 저장되어 검색에 조건을 설정하거나 결과를 원본 이벤트 단위로 복원할 때 사용할 수 있습니다.


      3.2 전체 구조

      Baseline의 전체 흐름은 다음과 같습니다.

      1. 인덱싱: ColQwen2로 이미지의 patch 단위 Multi-Vector를 생성하여 Qdrant에 적재

      2. 검색: 자연어 질의를 ColQwen2로 임베딩한 뒤 MaxSim으로 비교

      3. 후처리: 결과를 원본 단위로 묶고 점수가 낮은 항목을 정리


      설계에서 가장 중요하게 고려한 부분은 검색에 사용하는 신호를 시각 정보 하나로 제한하는 것이었습니다.

      OCR 결과나 텍스트 메타데이터를 함께 사용하면 검색 결과는 더 좋아질 수 있지만, 어떤 신호가 그 결과를 만들었는지 구분하기 어려워지기 때문입니다.

      그래서 Baseline에서는 ColQwen2가 생성한 질의 임베딩과 이미지의 patch 단위 표현만으로 비교했습니다.

      다만 이후 Hybrid 방식으로 확장할 것을 고려해, 컬렉션 스키마에는 텍스트 벡터 필드를 named vector로 함께 정의할 수 있도록 구성했습니다.


      3.3 문서 이미지의 Multi-Vector 임베딩

      이제부터는 실제 코드 예제와 함께 진행해보겠습니다.

      먼저 ColQwen2 모델과 프로세서를 로드하고, 이미지를 patch 단위 임베딩으로 변환합니다.

      import torch
      from PIL import Image
      from colpali_engine.models import ColQwen2, ColQwen2Processor
      
      MODEL_ID = "vidore/colqwen2-v1.0"
      
      model = ColQwen2.from_pretrained(
          MODEL_ID, torch_dtype=torch.bfloat16, device_map="cuda:0"
      ).eval()
      
      # max_num_visual_tokens: 이미지 한 장에 허용할 최대 visual token 수
      processor = ColQwen2Processor.from_pretrained(MODEL_ID, max_num_visual_tokens=768)
      
      
      def embed_images(paths, batch_size=4):
          # 문서 이미지를 patch 단위 Multi-Vector로 임베딩
          images = [Image.open(p).convert("RGB") for p in paths]
          all_embeddings = []
      
          for i in range(0, len(images), batch_size):
              batch = processor.process_images(images[i:i + batch_size]).to(model.device)
              with torch.no_grad():
                  outputs = model(**batch)
      
              # 배치 패딩 위치는 영벡터로 출력되므로 attention_mask로 유효 구간만 남김
              for embedding, mask in zip(outputs, batch["attention_mask"]):
                  valid = embedding[mask.bool()]
                  all_embeddings.append(valid.cpu().float().numpy().tolist())
      
          return all_embeddings
      
      
      vectors = embed_images(["TW000001910.jpg"])
      print(len(vectors[0]), len(vectors[0][0]))   # (시퀀스 길이, 128)

      위 코드 예제의 출력 형태를 보면, 이미지 한 장은 하나의 벡터가 아니라 (시퀀스 길이 × 128) 형태의 행렬로 출력되는 것을 확인할 수 있습니다.

      이와 같이 문서를 하나의 벡터로 요약하지 않고 행렬을 그대로 유지하는 것이 Late Interaction 구조의 핵심입니다.

      이를 통해 질의의 각 표현이 이미지의 어느 영역과 대응되는지를 개별적으로 계산할 수 있게 됩니다.


      추가적으로 시퀀스 길이는 동적 해상도 처리 때문에 입력 이미지의 크기에 따라 달라지며, patch 개수와 동일하지는 않습니다.

      이는 ColQwen2의 이미지 프로세서가 이미지 앞뒤에 <|im_start|>user … Describe the image.<|im_end|> 형태의 고정 프롬프트를 추가하기 때문이며,

      출력 길이는 이미지 patch 수에 프롬프트 토큰 수가 더해진 값이 됩니다.


      3.4 Qdrant Multi-Vector Collection 구성

      이번 Baseline에서는 Late Interaction을 지원하는 벡터 DB인 Qdrant의 multivector 기능을 사용했습니다.

      Qdrant에서는 컬렉션을 생성할 때 comparator를 MAX_SIM으로 지정하면, 하나의 포인트에 저장된 벡터 집합과 질의 벡터 집합을 MaxSim 방식으로 비교할 수 있습니다.

      from qdrant_client.http.models import (
          Distance, VectorParams, PointStruct,
          MultiVectorConfig, MultiVectorComparator,
      )
      
      client.create_collection(
          collection_name="event_collection",
          vectors_config={
              "img_vec": VectorParams(
                  size=128,                     # ColQwen2가 출력하는 patch 임베딩 차원
                  distance=Distance.COSINE,     
                  multivector_config=MultiVectorConfig(
                      comparator=MultiVectorComparator.MAX_SIM
                  ),
              )
              # Hybrid 검색 확장 시 텍스트 벡터 필드(text_vec)를 함께 정의
          },
      )

      적재 단계에서는 이미지 한 장의 patch 임베딩 전체를 하나의 포인트에 담습니다.

      일반적인 벡터 검색과 달리 vector 필드에 단일 벡터가 아니라 벡터의 리스트가 입력됩니다.

      points = []
      
      for record, vector in zip(records, vectors):
          # uuid로 유니크 ID 부여
          point_id = uuid.uuid5(uuid.NAMESPACE_URL, f"event_collection:{record['event_id']}")
      
          points.append(
              PointStruct(
                  id=str(point_id),
                  vector={"img_vec": vector},          # (시퀀스 길이 x 128) 형태의 Multi-Vector
                  payload={
                      "event_id": record["event_id"],
                      "title": record["title"],
                      "event_status": record["event_status"],     
                      "page_kind": record["page_kind"],           
                  },
              )
          )
      
      client.upsert(collection_name="event_collection", points=points)

      3.5 질의 임베딩과 검색

      검색 단계에서는 사용자의 자연어 질의를 따로 가공하지 않고, 곧바로 Multi-Vector 표현으로 변환합니다.

      from qdrant_client.http.models import Filter, FieldCondition, MatchValue
      
      
      def embed_query(query: str):
          # 사용자의 텍스트 질의를 검색용 Multi-Vector로 변환
          batch = processor.process_queries([query]).to(model.device)
          with torch.no_grad():
              output = model(**batch)
          return output[0].cpu().float().numpy().tolist()
      
      
      # 수집 시 저장한 메타데이터를 payload 필터로 사용
      query_filter = Filter(
          must=[
              FieldCondition(key="event_status", match=MatchValue(value="ongoing")),
              FieldCondition(key="page_kind", match=MatchValue(value="main")),
          ]
      )
      
      user_query = query    # 사용자로부터 입력받는 질의
      
      hits = client.query_points(
          collection_name="event_collection",
          query=embed_query(user_query),
          using="img_vec",              # MaxSim comparator가 설정된 벡터 필드
          limit=12,
          query_filter=query_filter,
          with_payload=True,
      )
      
      results = [
          {
              "id": p.id,
              "score": p.score,
              "source_id": p.payload.get("source_id") or p.payload["event_id"],
              "payload": p.payload,
          }
          for p in hits.points
      ]

      질의는 텍스트로 입력되고 문서는 이미지로 입력되지만, 앞서 살펴본 것처럼 두 표현이 동일한 공간에 놓이기 때문에 별도의 캡셔닝이나 OCR 없이 직접 비교할 수 있습니다.


      아래는 "크리스마스때 갈만한 겨울 이벤트"로 검색한 실제 결과이며, 상위 세 건은 모두 이월드 일루미네이션 이벤트입니다.

      배너에 적힌 문구는 "이월드 일루미네이션 오픈 기념! 자유이용권 50% 할인", "국내야경명소 NO.1" 등으로, 질의에 사용한 "크리스마스"와 "겨울"은 등장하지 않았습니다.

      그럼에도 산타 복장의 캐릭터, 눈 내리는 밤하늘, 조명이 켜진 놀이공원 야경과 같은 시각 요소를 통해 이 배너가 검색된 것을 확인할 수 있습니다.


      이러한 방식으로 시각적 문서 검색의 기본적인 형태를 구성할 수 있었으나, 실제 사용 시 몇 가지 문제들을 발견했습니다.

      세로로 긴 배너는 검색에 잘 잡히지 않았고, 이를 해결하려 이미지를 나누자 이번에는 같은 이벤트가 반복 노출되었으며, 관련 없는 결과를 걸러낼 기준도 필요했습니다.


      4. 검색 단위와 후처리 설계

      4.1 이미지 크기와 Visual Token 제한

      메인 배너를 그대로 인덱싱했을 때, 페이지 하단의 유의사항이나 기간처럼 일부 영역에만 있는 정보는 검색 결과에 거의 반영되지 않는 문제가 발생했습니다.


      원인은 max_num_visual_tokens가 적용되는 방식에 있었습니다.

      ColQwen2에서 visual token의 수는 리사이즈된 이미지의 (너비 / 28) × (높이 / 28), 즉 면적에 비례하며, 이 수가 최대 768개로 제한되어 있으므로 원본이 클수록 그만큼 축소된 상태로 입력됩니다.

      ColQwen2의 동적 해상도 처리는 큰 이미지를 축소한 뒤 가변 크기의 grid 하나로 처리하기 때문에, 제한을 넘어서는 정보는 축소 과정에서 사라지는 현상이 발생하게 됩니다.

      예를 들어 840×9,610px 크기의 배너는 이 제한에 맞춰 폭이 224px까지 약 3.8배 축소되어, 배너의 한글 본문은 물론 할인율 배지조차 판독이 어려운 크기가 되었습니다.


      이를 해결하기 위해 visual token 제한을 늘릴 수도 있지만, 토큰 수가 면적에 비례하므로 효과가 크지 않습니다.

      또한 제한을 33% 늘리게 되면 저장해야 할 벡터는 그대로 33% 증가하며, ColQwen2-v1.0이 최대 768개의 visual token을 기준으로 학습된 모델이라는 점도 함께 고려해야 합니다.


      반면 이미지를 분할해 입력하면 각 chunk마다 제한이 새로 적용되므로, 더 높은 해상도를 유지할 수 있습니다.

      따라서 visual token 제한은 모델의 기본값인 768을 유지하며 chunking 설계에 집중했습니다.


      4.2 Chunking과 결과 그룹핑

      세로 길이가 일정 기준을 넘는 이미지는 여러 개의 chunk로 나누어 인덱싱했습니다.

      분할 시 chunk 경계가 배지나 문구의 중앙을 지나가는 경우 그 정보가 어느 chunk에도 온전히 남지 않으므로,

      텍스트 청킹에서와 마찬가지로 인접한 chunk가 경계 영역을 공유하도록 오버랩을 설정했습니다.


      각 chunk의 payload에는 원본 식별자와 잘라낸 세로 구간을 남겨, 검색은 chunk 단위로 하되 사용자에게는 매칭된 chunk와 원본 이미지를 함께 보여줄 수 있도록 했습니다.

      다만 chunking을 적용하면 같은 원본에서 나온 여러 chunk가 동시에 상위 결과에 등장하면서, 사용자 입장에서는 같은 이벤트가 반복되는 것처럼 보이기 쉽습니다.

      실제로 "경품으로 뭐 주는 이벤트 있어"로 검색해보니 상위 세 건이 모두 같은 배너의 서로 다른 chunk였고, 앞서 3.5의 결과도 마찬가지였습니다.


      이를 줄이기 위해 한 원본의 chunk들을 하나의 그룹으로 묶고, 그중 가장 점수가 높은 결과를 대표로 삼았습니다.

      검색엔진에서 field collapsing이라 부르는 처리와 같은 방식이며, Qdrant에서도 Grouping API로 이 기능을 제공합니다.

      이때 나머지 chunk를 버리지 않고 함께 유지한 이유는, 하나의 이벤트에서 여러 chunk가 동시에 매칭되었다는 사실 자체가 유의미한 신호이기 때문입니다.



      4.3 MaxSim 점수와 임계값

      검색 서비스에서는 순위와 별개로 질의와 관련 없는 결과를 걸러내는 기준이 필요합니다.

      텍스트 기반의 벡터 검색에서는 코사인 유사도 점수에 임계값(threshold)을 두는 방식이 일반적이지만, MaxSim 점수에는 그대로 적용하기 어렵습니다.


      MaxSim 점수는 질의 토큰별로 문서 벡터들과의 코사인 유사도 중 최댓값을 구해 모두 더한 값입니다.

      개별 유사도는 1을 넘지 않지만 합산 결과는 질의 토큰 수에 비례하므로, 같은 문서라도 질의가 길수록 점수가 커지게 됩니다.

      따라서 하나의 절대 점수를 임계값으로 고정하면, 경우에 따라 아무것도 걸러내지 못하거나 유효한 결과를 모두 잘라낼 가능성이 있습니다.


      이번 Baseline에서는 절대 점수 대신 최상위 점수 대비 비율을 기준으로 사용했습니다.

      예를 들어 기준을 0.8로 두면 최상위 점수의 80% 이상인 결과만 남고, 기준선은 점수의 절대 크기와 무관하게 질의마다 새로 정해집니다.

      다만 관련 문서가 없는 질의에서는 최상위 결과부터 낮은 점수를 받기 때문에, 점수 스케일에 맞춘 절대 하한을 함께 두어 보완했습니다.


      마치며

      이번 글에서는 시각적으로 풍부한 문서를 검색할 때 기존의 텍스트 중심 접근만으로는 어떤 한계가 발생하는지,

      그리고 이를 보완하기 위해 ColPali와 ColQwen이 어떤 방식으로 등장했는지 살펴보았습니다.

      이 접근의 핵심은 문서를 텍스트로 변환하지 않고 페이지 이미지 자체를 임베딩하는 데에 있습니다.

      여기에 Late Interaction을 결합하면 질의의 각 표현이 이미지의 특정 영역과 대응되므로, 표, 차트, 스캔 문서, 배너처럼 시각 요소가 의미를 형성하는 문서에서 명확한 이점을 얻을 수 있습니다.


      또한 이벤트 배너 검색에 적용해 Baseline을 구성해 본 결과, 검색 단위와 후처리를 어떻게 설계하느냐에 따라 검색 품질이 크게 달라진다는 것을 확인했습니다.

      세로로 긴 이미지는 visual token 제한을 늘리는 것보다 chunk로 분할해 입력하는 방식이 효과적이었고, 검색 결과를 어떤 단위로 묶고 제거할 것인지가 체감 품질에 큰 영향을 미쳤습니다.


      다만 시각적 문서 검색을 적용할 경우 페이지 한 장이 하나의 벡터가 아니라 수백 개의 벡터로 표현되는 만큼, 인덱스 크기가 커지는 trade-off가 있습니다.

      실제로 논문에서는 ColPali가 페이지당 257.5KB를 사용하는 반면 동일한 문서의 텍스트 임베딩은 10KB 이하인 것으로 보고되어 있습니다.

      대규모 문서 집합을 검색해야 하는 production 환경에서는 저장량을 줄이는 이진 양자화(Binary Quantization), 페이지당 벡터 수를 줄이는 토큰 풀링, 단일 벡터 모델로 후보를 좁힌 뒤 Multi-Vector로 리랭킹하는 2-stage 구성 등이 논의되고 있습니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      juunho 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기