Qdrant에서 벡터 검색은 어떻게 동작하나요?

아직 벡터 검색이 어떻게 동작하는지 파악 중이라면 계속 읽어 보세요. 이 문서는 벡터 검색이 어떻게 쓰이는지, 더 큰 생태계에서 Qdrant가 어떤 위치를 차지하는지, 그리고 기존 프로젝트를 Qdrant로 어떻게 확장할 수 있는지 설명해요.

> 출처: [Qdrant 공식 문서 — vector-search](https://qdrant.tech/documentation/overview/vector-search)

바로 코드를 작성하고 싶다면 'Complete Beginners' 튜토리얼을 방문해서 5~15분 안에 검색 엔진을 만들어 보세요. 벡터 검색이 왜 등장했는지 역사부터 차근차근 알아볼게요.

인간의 기억은 믿을 수 없어요. 그래서 '지식'을 글로 수집하기 시작한 이래로, 같은 책을 반복해서 읽지 않고 관련 콘텐츠를 찾는 방법을 고민해야 했죠. 그 덕분에 어떤 뛰어난 마음들이 역색인(inverted index) 을 도입했어요. 가장 단순한 형태는 책의 부록으로, 보통 맨 끝에 있으며 핵심 용어와 그 용어가 나오는 페이지 링크를 나열합니다. 용어는 알파벳 순으로 정렬되죠. 옛날에는 손으로 만들어 준비에 많은 노력이 드는 목록이었어요. 디지털화가 시작된 뒤로는 훨씬 쉬워졌지만, 여전히 같은 원리를 유지했어요. 그 방식은 잘 작동했고 지금도 작동합니다.

특정 책에서 특정 주제를 찾고 싶다면 관련 구문을 찾아 올바른 페이지로 빠르게 갈 수 있어요. 물론 올바른 용어를 안다는 전제하에요. 모른다면 여러 번 시도하고 실패하거나, 다른 누군가에게 올바른 질의를 만드는 걸 도와달라고 해야 해요.

역색인의 단순화된 예시.

시간이 흘러도 이 분야에는 오랫동안 큰 변화가 없었어요. 하지만 텍스트 데이터 수집이 더 빠른 속도로 늘어나기 시작했어요. 그래서 역색인 주변에도 많은 프로세스를 만들기 시작했습니다. 예를 들어 사용자가 많은 단어를 제공하게 하고 단어를 조각으로 쪼개기 시작했죠. 그러면 모든 질의 단어를 포함하지 않아도 그 일부를 포함한 문서를 찾을 수 있게 됐어요. 단어를 어근 형태로 변환해 더 많은 경우를 커버하고, 불용어(stopword)를 제거하는 작업도 시작했죠. 사실상 점점 더 사용자 친화적으로 변해 가고 있었습니다. 그래도 전체 과정의 아이디어는 중세부터 알려진 가장 단순한 키워드 기반 검색에 약간의 변형을 더한 것에서 나왔어요.

토큰화 과정과 추가적인 불용어 제거 및 단어의 어근 변환.

기술적으로 말하면 문서와 질의를 희소 벡터(sparse vector) 로 인코딩하는데, 각 위치가 전체 사전의 한 단어에 대응돼요. 입력 텍스트에 특정 단어가 있으면 그 위치에 0이 아닌 값이 생깁니다. 하지만 실제로 어떤 텍스트도 수백 개 이상의 서로 다른 단어를 포함하진 않아요. 그래서 대부분의 벡터는 수천 개의 0과 몇 개의 0이 아닌 값을 갖게 되죠. 그래서 이를 희소(sparse)라고 부릅니다. 이들은 가장 큰 중첩(overlap)을 가진 문서를 찾아 단어 기반 유사도를 계산하는 데 이미 사용될 수 있어요.

질의를 희소 형식으로 벡터화한 예시.

희소 벡터는 상대적으로 높은 차원을 가지며, 그 크기는 사전 크기와 같아요. 그리고 사전은 입력 데이터에서 자동으로 얻어집니다. 따라서 벡터가 있으면 그것을 만든 텍스트에 쓰인 단어를 부분적으로 재구성할 수 있어요.

바벨 탑 (The Tower of Babel)

역색인에서 새로운 문제를 발견할 때마다, 적어도 어느 정도는 문제를 해결하기 위한 새 휴리스틱을 내놓곤 했어요. 사람들이 같은 개념을 다른 단어로 표현할 수도 있다는 걸 깨달은 뒤로는 질의를 정규화된 형태로 변환하기 위해 동의어 목록을 만들기 시작했죠. 하지만 우리가 예측하지 못한 경우에는 그게 통하지 않습니다. 그래도 언어는 시대에 따라 변하므로 사전을 계속 손보고 유지해야 해요. 또 하나 어려운 문제는 다국어 시나리오에서 드러납니다. 옛 방식은 별도의 파이프라인을 구성하고 품질 유지를 위해 사람을 개입시켜야 해요.

바벨 탑, 피터르 브뤼헐.

표현 혁명 (The Representation Revolution)

NLP를 위한 머신러닝의 최신 연구는 주로 심층 언어 모델(Deep Language Model) 훈련에 집중돼 있어요. 이 과정에서 신경망은 대량의 텍스트 코퍼스를 입력으로 받아 단어를 벡터 형태의 수학적 표현으로 만듭니다. 이 벡터들은 비슷한 의미를 갖고 비슷한 맥락에서 나타나는 단어들이 함께 그룹화되어 유사한 벡터로 표현되도록 만들어져요. 또한 모든 단어 벡터의 평균을 내서 텍스트 전체(질의, 문장, 문단 같은)에 대한 벡터를 만들 수도 있어요.

신경망이 만든 이 밀집 벡터(dense vector) 를 다른 데이터 표현으로 사용할 수 있어요. 신경망은 어떤 위치에서도 0을 거의 만들지 않기 때문에 밀집(dense)이라고 불러요. 희소 벡터와 달리 차원이 상대적으로 낮아서 수백에서 수천 개 정도예요. 아쉽게도 벡터만 보고 문서의 내용을 이해하고 싶다면 더 이상 불가능합니다. 차원이 더 이상 특정 단어의 존재를 나타내지 않기 때문이에요.

밀집 벡터는 텍스트에 쓰인 단어가 아니라 의미를 포착할 수 있어요. 즉, 대규모 언어 모델(LLM)은 동의어를 자동으로 처리할 수 있습니다. 더구나 이런 신경망은 다국어 코퍼스로 훈련됐을 수 있어서, 서로 다른 언어로 쓰인 같은 문장을 유사한 벡터 표현(임베딩이라고도 해요)으로 변환해요. 그리고 데이터베이스 안의 다른 벡터까지의 거리를 계산해 서로 비교함으로써 유사한 텍스트 조각을 찾을 수 있습니다.

입력 질의가 서로 다른 단어를 담고 있어도, 신경 인코더가 문장의 의미를 포착할 수 있기 때문에 유사한 벡터 표현으로 변환됩니다. 이 특징은 동의어뿐 아니라 서로 다른 언어도 포착할 수 있어요.

벡터 검색은 임베딩 유사도에 기반해 유사한 객체를 찾는 과정이에요. 좋은 점은 신경망을 직접 설계하고 훈련할 필요가 없다는 거예요. HuggingFace나 SentenceTransformers 같은 라이브러리에서 미리 훈련된 모델이 많이 제공됩니다. 신경 모델을 직접 다루고 싶지 않다면 co.embed API 같은 SaaS 도구로 임베딩을 만들 수도 있어요.

왜 Qdrant인가? (Why Qdrant?)

벡터 검색의 어려움은 큰 객체 집합에서 유사한 문서를 찾아야 할 때 생겨요. 가장 가까운 예시를 찾으려면 순진한 접근법은 모든 문서까지의 거리를 계산하는 거예요. 수십, 수백 개 정도라면 괜찮을 수 있지만 그보다 많아지면 병목이 될 수 있죠. 관계형 데이터를 다룰 때는 전체 테이블 스캔을 피하기 위해 데이터베이스 인덱스를 설정해 속도를 높여요. 벡터 검색도 마찬가지예요. Qdrant는 완전한 기능을 갖춘 벡터 데이터베이스로, 그래프 같은 구조를 사용해 가장 가까운 객체를 아선형 시간(sublinear time) 에 찾음으로써 검색 속도를 높여요. 따라서 데이터베이스의 모든 객체에 대해 거리를 계산하는 게 아니라 일부 후보에 대해서만 계산합니다.

Qdrant로 벡터 검색하기. HNSW 그래프 덕분에 데이터베이스의 일부 객체에 대해서만 거리를 비교할 수 있어요.

규모에서 의미 검색(semantic search)을 수행할 때 — 텍스트에 대해 수행하는 벡터 검색을 우리가 그렇게 부르기도 해요 — 효과적으로 하려면 Qdrant 같은 특화된 도구가 필요합니다.

다음 단계 (Next Steps)

벡터 검색은 희소 방식의 흥미로운 대안이에요. 많은 휴리스틱을 수동으로 유지할 필요 없이 키워드 기반 검색이 가진 문제를 해결하죠. 단, 텍스트를 벡터로 변환할 추가 구성요소인 신경 인코더가 필요해요.

  • 튜토리얼 1 - Complete Beginners: 복잡한 배경에도 불구하고 벡터 검색은 설정이 매우 간단해요. Qdrant를 쓰면 5분 안에 검색 엔진을 가동할 수 있습니다.
  • 튜토리얼 2 - Question and Answer System: SaaS 도구를 골라 직접 모델을 만들지 않을 수도 있어요. Qdrant Cloud와 Cohere co.embed API로 벡터 검색 프로젝트를 설정하는 건 꽤 쉽습니다.

벡터 검색에는 또 흥미로운 점이 있어요. 데이터 유형을 벡터화해 줄 신경망이 있기만 하면 어떤 종류의 데이터든 검색할 수 있다는 거죠. 역방향 이미지 검색(reverse image search)을 생각해 보세요. 임베딩 벡터로 그것도 가능해요.

더 알아보기 (Learn more)