사전 벡터 검색
알고리즘

벡터 검색

gabury1고친 사람 github-actions[bot]

벡터 검색은 뜻이 비슷한 글을 찾아 줍니다. 글을 미리 숫자 목록으로 바꿔 둡니다. 검색할 때는 검색어의 숫자 목록과 가장 가까운 것을 고릅니다. 그래서 낱말이 하나도 안 겹쳐도 뜻이 맞는 글이 나옵니다.

쉽고 빠른 이해

검색어와 뜻이 가까운 글을 골라 줍니다. 「차량 수리」로 찾으면 「자동차 정비 안내」라는 글이 맨 위에 옵니다. 두 제목에 같은 낱말은 하나도 없습니다.

낱말이 같은지만 보는 검색은 같은 뜻을 다른 말로 적은 글을 놓칩니다. 사람은 같은 것을 여러 말로 부르므로 이런 글이 적지 않습니다. 벡터 검색은 글자 대신 뜻을 견줘서 이 틈을 메웁니다.

  1. 글마다 뜻을 담은 숫자 목록을 만들어 저장해 둡니다
  2. 검색어가 오면 같은 방식으로 숫자 목록을 만듭니다
  3. 저장된 목록 가운데 검색어의 목록과 가장 가까운 몇 개를 고릅니다

대가도 있습니다. 먼저 오류 코드나 상품 모델명처럼 글자가 딱 맞아야 하는 검색에 약합니다.

맞는 글이 없어도 가장 가까운 글을 내놓습니다. 동떨어진 글이 결과에 섞일 수 있다는 뜻입니다.

글이 아주 많으면 전부 견줄 수 없습니다. 그때는 계산을 줄이는 대신 가장 가까운 글을 가끔 놓치는 방법을 씁니다.

상세

벡터 검색은 검색어와 뜻이 가까운 데이터를 찾는 계산 절차입니다. 입력은 검색어 하나와 저장된 데이터 여럿입니다. 출력은 검색어와 가장 가까운 데이터 k 개입니다. k 는 몇 개를 돌려받을지 부르는 쪽이 정하는 수입니다.

뜻을 숫자 목록으로 옮기는 임베딩

벡터는 숫자를 정해진 개수만큼 늘어놓은 목록입니다. [0.9, 0.1] 은 숫자 둘짜리 벡터입니다. 숫자의 개수를 차원이라고 부릅니다. 이 벡터는 2차원입니다.

글을 벡터로 바꾸는 일이 임베딩입니다. 뜻을 숫자로 옮겨 두어야 두 글의 뜻이 가까운지를 계산으로 잴 수 있습니다.

바꾸는 일은 임베딩 모델이 맡습니다. 임베딩 모델은 많은 글을 미리 학습한 신경망입니다. 신경망은 예시 데이터를 보며 입력에서 출력을 내는 규칙을 스스로 맞춰 가는 계산 모델입니다. 학습을 거친 모델은 뜻이 비슷한 글에 서로 가까운 벡터를 줍니다.

「자동차 정비 안내」와 「차량 수리」는 낱말이 다릅니다. 그래도 둘은 가까운 벡터를 받습니다. 「라면 끓이는 법」은 두 글과 먼 벡터를 받습니다. 벡터 검색은 이 가까움을 검색에 씁니다.

실무에서 쓰는 임베딩은 숫자가 수백 개를 넘는 벡터입니다. 숫자 하나하나가 무슨 뜻인지는 사람이 정하지 않습니다. 모델이 학습하면서 정한 것이라 사람이 읽고 풀기도 어렵습니다.

색인 단계와 검색 단계

벡터 검색은 두 단계로 나뉩니다. 앞 단계는 글이 들어올 때 미리 벡터로 바꿔 저장해 둡니다. 나중에 빨리 찾으려고 데이터를 미리 정리해 두는 이 일을 색인이라고 부릅니다.

뒤 단계는 검색어가 올 때 돕니다. 검색어를 벡터로 바꿉니다. 저장된 벡터 가운데 가장 가까운 k 개를 고릅니다. 그 벡터가 가리키는 글이 검색 결과입니다.

flowchart TD
    subgraph 색인단계["색인 단계 · 글이 들어올 때"]
        D1["글"] --> M1["임베딩 모델"]
        M1 --> V1["글 벡터"]
        V1 --> S["벡터 저장소"]
    end
    subgraph 검색단계["검색 단계 · 검색어가 올 때"]
        Q1["검색어"] --> M2["임베딩 모델"]
        M2 --> V2["검색어 벡터"]
        V2 --> K["가장 가까운 벡터 k 개"]
    end
    S --> K
    K --> R["그 벡터의 글을 돌려준다"]

글 벡터는 원래 글을 가리키는 번호와 함께 벡터 저장소에 쌓입니다. 이 저장소를 맡는 데이터베이스가 벡터 데이터베이스입니다. 벡터를 쌓아 두는 데서 그치지 않고 가까운 벡터를 찾는 기능까지 갖췄습니다.

두 단계는 같은 임베딩 모델을 써야 합니다. 모델이 다르면 같은 뜻이어도 전혀 다른 벡터가 나옵니다. 그러면 두 벡터가 가까운지 재도 뜻이 가까운지 알 수 없습니다. 모델을 바꾸면 저장된 글을 전부 다시 벡터로 바꿔야 하는 까닭입니다.

가까움을 재는 세 방법

순위를 매기려면 두 벡터가 얼마나 가까운지를 숫자 하나로 나타내야 합니다. 벡터는 공간 안의 화살표로 볼 수 있습니다. [0.9, 0.1] 은 원점에서 오른쪽으로 0.9, 위로 0.1 만큼 간 화살표입니다.

화살표 둘의 가까움을 재는 방법은 흔히 셋입니다.

방법 재는 것 가까운 쪽
코사인 유사도 두 화살표가 가리키는 방향이 얼마나 같은가 클수록 가깝다 · 가장 큰 값은 1
내적 같은 순번의 숫자끼리 곱해 모두 더한 값 클수록 가깝다
유클리드 거리 두 화살표 끝점 사이의 곧은 거리 작을수록 가깝다

코사인 유사도는 화살표의 길이를 빼고 방향만 봅니다. 내적은 방향과 길이를 함께 봅니다. 그래서 길이가 긴 벡터는 내적에서 점수를 더 받습니다.

모든 벡터의 길이를 1 로 맞춰 두면 세 방법이 같은 순위를 냅니다. 길이를 1 로 맞추는 일을 벡터 정규화라고 부릅니다. 정규화한 벡터끼리는 내적이 곧 코사인 유사도입니다.

어느 방법을 쓸지는 임베딩 모델이 정합니다. 모델이 학습할 때 쓴 방법을 검색에도 씁니다.

글 셋에 매기는 순위

설명을 위해 벡터를 2차원으로 줄여 봅니다. 검색어는 「차량 수리」이고 글은 셋입니다. 벡터 값은 임베딩 모델이 이렇게 줬다고 정한 것입니다.

아래 함수는 코사인 유사도를 구합니다. 내적을 두 벡터 길이의 곱으로 나눈 값입니다. 오른쪽 주석이 그 줄이 내는 값입니다.

Python
def cosine(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    len_a = sum(x * x for x in a) ** 0.5
    len_b = sum(y * y for y in b) ** 0.5
    return dot / (len_a * len_b)

q = [0.9, 0.1]   # 차량 수리
a = [0.8, 0.2]   # 자동차 정비 안내
b = [0.6, 0.5]   # 자전거 수리
c = [0.1, 0.9]   # 라면 끓이는 법

cosine(q, a)     # 0.99
cosine(q, b)     # 0.83
cosine(q, c)     # 0.22

순위는 「자동차 정비 안내」, 「자전거 수리」, 「라면 끓이는 법」 순입니다. k 가 2 면 앞의 둘을 돌려줍니다.

1위 글은 검색어와 겹치는 낱말이 하나도 없습니다. 낱말이 같은지로 점수를 매기는 BM25(Best Matching 25)라면 이 글은 0점입니다. 거꾸로 「수리」가 겹치는 「자전거 수리」가 BM25 에서는 1위가 됩니다.

전부 견주는 정확한 검색

가장 쉬운 방법은 검색어 벡터를 저장된 벡터 전부와 견주는 것입니다. 그리고 가장 가까운 k 개를 남깁니다. 이 방법은 가장 가까운 k 개를 언제나 빠짐없이 찾습니다.

주어진 점과 가장 가까운 점을 찾는 문제가 최근접 이웃 검색입니다. 벡터 하나를 공간의 점 하나로 보면 벡터 검색이 바로 이 문제입니다.

가까운 점을 하나가 아니라 k 개 찾으면 k-최근접 이웃(k-NN, k-Nearest Neighbors) 검색입니다. 전부 견주는 방법은 이 문제를 정확하게 풉니다.

비용은 저장된 벡터 수 n 과 차원 d 가 정합니다. 벡터 둘을 견주는 데 곱셈이 d 번 듭니다. 이것을 n 번 하므로 검색 한 번에 O(n·d) 가 듭니다. O(n·d) 는 비용이 n 과 d 의 곱에 비례한다는 뜻입니다.

가까운 k 개는 크기가 k 인 힙에 담으며 추립니다. 힙은 가장 큰 값을 늘 꼭대기에 두는 트리입니다. 여기서는 지금까지 모은 후보 가운데 가장 먼 것이 꼭대기에 옵니다. 새 벡터가 그 꼭대기보다 가까우면 꼭대기를 빼고 새 벡터를 넣습니다.

힙에 한 번 넣고 빼는 비용은 log k 에 비례합니다. 벡터 하나를 견주는 비용은 d 에 비례합니다. log k 는 d 보다 훨씬 작으므로 전체 비용은 견주는 쪽을 따라 O(n·d) 로 적습니다.

저장에 드는 공간도 O(n·d) 입니다. 벡터 n 개를 숫자 d 개씩 담아 두기 때문입니다.

글이 수억 개면 검색 한 번에 벡터 수억 개를 견줘야 합니다. 요청마다 이 일을 하면 응답이 너무 늦습니다.

차원이 낮으면 k-d 트리 같은 색인이 잘 듭니다. k-d 트리는 공간을 구역으로 잘게 나눠 둡니다. 검색할 때는 검색어에서 먼 구역을 열지 않고 건너뜁니다.

차원이 수백을 넘으면 이 건너뛰기가 통하지 않습니다. 검색어와 모든 점 사이의 거리가 서로 비슷해지기 때문입니다. 어느 구역도 멀다고 버릴 수 없으니 대부분을 견주게 됩니다. 차원이 높아질수록 공간을 나누는 방법이 이렇게 힘을 잃는 현상을 차원의 저주라고 부릅니다.

근사 최근접 이웃 검색

그래서 큰 데이터에서는 정확도를 조금 내주고 속도를 삽니다. 가장 가까운 k 개를 대부분 찾되 가끔 놓치는 것을 받아들입니다. 이런 검색을 근사 최근접 이웃 검색(ANN, Approximate Nearest Neighbor)이라고 부릅니다.

얼마나 놓치는지는 재현율로 잽니다. 정확한 검색이 찾을 k 개 가운데 근사 검색도 찾은 몫입니다. 더 많은 벡터를 살펴보면 재현율이 오릅니다. 대신 검색은 느려집니다. 둘 사이의 균형은 색인의 설정값으로 고릅니다.

근사 색인은 크게 두 갈래입니다. 하나는 가까운 벡터끼리 선으로 이어 두고 선을 따라 걷는 그래프 방식입니다. 다른 하나는 벡터를 묶음으로 나눠 두고 검색어와 가까운 묶음만 여는 방식입니다.

그래프 방식의 대표는 HNSW(Hierarchical Navigable Small World, 계층형 탐색 가능 스몰 월드)입니다. 벡터마다 가까운 벡터 몇 개와 선으로 잇습니다.

검색은 한 벡터에서 출발합니다. 검색어에 더 가까운 이웃으로 옮겨 갑니다. 더는 가까워지지 않을 때까지 이를 되풀이합니다.

HNSW 는 이런 그래프를 층으로 여럿 쌓습니다. 위층에는 벡터가 몇 개만 있어서 한 걸음에 멀리 건너뜁니다. 아래층으로 내려갈수록 벡터가 많아져 촘촘하게 좁힙니다. 맨 아래층에는 모든 벡터가 있습니다.

한 층에서 더 가까워지지 않으면 그 벡터에서 한 층 내려가 걷기를 잇습니다. 위층에서 큰 걸음으로 검색어 근처까지 가 두므로 아래층에서는 조금만 걸으면 됩니다.

flowchart TD
    subgraph 위층["위층 · 벡터 몇 개만"]
        A2["출발 벡터"] --> B2["검색어에 더 가까운 벡터"]
    end
    subgraph 중간층["중간층 · 벡터가 더 많다"]
        B1["같은 벡터에서 이어 걷기"] --> C1["더 가까운 벡터"]
    end
    subgraph 아래층["맨 아래층 · 모든 벡터"]
        C0["같은 벡터에서 이어 걷기"] --> D0["가장 가까운 후보 k 개"]
    end
    B2 -->|"더 가까워지지 않으면 한 층 내려간다"| B1
    C1 -->|"더 가까워지지 않으면 한 층 내려간다"| C0

묶음 방식의 대표는 IVF(Inverted File, 역파일)입니다. IVF 는 먼저 벡터를 묶음 여럿으로 나눕니다. 그리고 묶음마다 속한 벡터 목록을 적어 둡니다.

묶음은 k-평균 군집화로 나눕니다. k-평균 군집화는 가까운 벡터끼리 한 묶음이 되도록 나누는 절차입니다. 묶음마다 한가운데를 대표하는 가운데 벡터도 하나씩 정합니다.

검색할 때는 검색어와 가운데 벡터가 가까운 묶음 몇 개만 엽니다. 그 안의 벡터만 견주므로 계산이 크게 줍니다. 가장 가까운 벡터가 옆 묶음에 들어 있으면 놓칩니다.

방법 검색 한 번에 견주는 벡터 결과 더 드는 것
전부 견주기 전부 · O(n·d) 언제나 정확하다 없다
HNSW 선을 따라 걸으며 만난 벡터만 가끔 놓친다 선을 담는 메모리 · 그래프를 짓는 시간
IVF 연 묶음 안의 벡터만 가끔 놓친다 묶음을 나누는 시간 · 가운데 벡터

표의 둘째 열이 속도를 가릅니다. 근사 방식은 전체 가운데 일부만 견주므로 전부 견주기보다 훨씬 적게 계산합니다. 그 대가가 셋째 열의 놓침과 넷째 열의 준비 비용입니다.

낱말로 찾는 검색과 나눠 맡는 일

이 절은 벡터 검색을 낱말로 찾는 검색과 견줍니다. 둘은 잘 잡는 검색이 서로 다릅니다. 그 차이 때문에 둘을 함께 쓰기도 합니다.

낱말로 찾는 검색이 전문 검색입니다. 글 본문에 검색어의 낱말이 들어 있는지를 봅니다.

전문 검색은 역색인으로 후보를 찾습니다. 역색인은 낱말마다 그 낱말이 든 글 목록을 적어 둔 색인입니다. 검색어의 낱말로 그 목록을 바로 꺼낼 수 있습니다. 순위는 BM25 같은 점수로 매깁니다.

아래 표가 두 검색을 견줍니다.

전문 검색 벡터 검색
견주는 것 낱말이 같은가 뜻이 가까운가
잘 잡는 검색 오류 코드 · 상품 모델명 · 사람 이름 다른 말로 적은 같은 뜻 · 문장으로 묻는 검색어
놓치는 검색 같은 뜻을 다른 낱말로 적은 글 글자가 딱 맞아야 하는 이름과 번호
맞는 글이 없을 때 결과가 비기도 한다 그래도 가장 가까운 k 개를 내놓는다
결과가 나온 까닭 맞은 낱말을 짚을 수 있다 벡터가 가깝다는 것 말고는 짚기 어렵다

벡터 검색은 가까운 순서만 매기므로 동떨어진 글도 k 개 안에 들 수 있습니다. 그래서 점수가 일정 값보다 낮은 결과를 걸러 내는 문턱을 따로 두기도 합니다.

둘은 서로 못 잡는 것을 잡습니다. 그래서 두 검색을 함께 돌린 뒤 결과를 합치기도 합니다. 이 방식이 하이브리드 검색입니다.

두 점수는 척도가 달라 바로 더할 수 없습니다. BM25 점수는 위로 끝이 정해져 있지 않습니다. 코사인 유사도는 1 을 넘지 않습니다.

그 대신 점수는 버리고 순위만 가지고 합치는 상호 순위 융합(RRF, Reciprocal Rank Fusion)을 씁니다. 글마다 두 검색에서 받은 순위를 보고 앞 순위일수록 큰 값을 줍니다. 두 값을 더해 새 순위를 매기므로 두 검색 모두에서 앞에 선 글이 맨 위로 옵니다.

벡터 공간 모델과 가르는 선

글을 벡터로 나타내는 생각 자체는 오래됐습니다. 벡터 공간 모델은 낱말 하나마다 벡터의 숫자 하나를 맡깁니다. 이 절에서는 숫자 하나가 들어가는 곳을 칸이라고 부릅니다. 칸 수가 곧 앞에서 본 차원입니다.

칸에는 그 낱말이 이 글에서 얼마나 중요한지를 나타내는 무게를 적습니다. 이 글에 자주 나오는 낱말일수록 무게가 큽니다. 어느 글에나 나오는 흔한 낱말은 무게가 작습니다. 이렇게 무게를 매기는 대표 방법이 TF-IDF(Term Frequency-Inverse Document Frequency, 낱말 빈도-역문서 빈도)입니다.

이런 벡터는 칸이 낱말 수만큼 많습니다. 글 하나에 든 낱말은 그중 몇 개뿐이라 대부분의 칸이 0 입니다. 이렇게 대부분이 0 인 벡터를 희소 벡터라고 부릅니다.

임베딩은 칸 수가 낱말 수보다 훨씬 적습니다. 대신 거의 모든 칸에 값이 있습니다. 이런 벡터를 밀집 벡터라고 부릅니다.

희소 벡터는 낱말이 겹쳐야 가까워집니다. 밀집 벡터는 낱말이 달라도 뜻이 같으면 가까워집니다.

오늘날 벡터 검색이라고 하면 대개 밀집 벡터로 뜻을 견주는 검색을 가리킵니다. 이 항목도 그 뜻으로 썼습니다. 희소 벡터로 찾는 검색은 낱말이 겹쳐야 점수가 나므로 전문 검색에 가깝습니다.

쓰이는 곳

임베딩 모델만 있으면 무엇이든 벡터로 바꿀 수 있습니다. 그래서 글 말고도 쓰임이 넓습니다.

쓰임 벡터로 바꾸는 것 찾는 것
검색 증강 생성 사내 문서 · 도움말 질문과 뜻이 가까운 문서
추천 시스템 상품 · 사용자 이 사용자가 좋아한 것과 가까운 상품
이미지 검색 사진 올린 사진과 비슷한 사진
중복 탐지 글 · 질문 이미 올라온 것과 거의 같은 글

검색 증강 생성(RAG, Retrieval-Augmented Generation)은 대규모 언어 모델이 답하기 전에 관련 문서를 먼저 찾아 함께 건네는 방식입니다. 이 문서를 찾는 단계에서 벡터 검색을 씁니다.

질문은 문장으로 옵니다. 문서는 다른 낱말로 적혀 있는 일이 많습니다. 이런 검색에는 뜻으로 찾는 편이 잘 맞습니다.

관련 항목

벡터 검색이 속하는 상위 분류

검색 · 정보 검색 · 시맨틱 검색 · 최근접 이웃 검색 · 유사도 검색

벡터 검색이 기대는 표현과 모델

벡터 · 임베딩 · 임베딩 모델 · 문장 임베딩 · 밀집 벡터 · 희소 벡터 · 신경망 · 트랜스포머

벡터 사이 가까움을 재는 척도

코사인 유사도 · 내적 · 유클리드 거리 · 맨해튼 거리 · 벡터 정규화 · 거리 함수

벡터 검색을 빠르게 하는 근사 색인

근사 최근접 이웃 검색 · HNSW · IVF · 곱 양자화 · 지역 민감 해싱 · k-d 트리 · k-평균 군집화 · 차원의 저주 · 힙

벡터 검색과 겨루거나 함께 쓰는 검색 방식

전문 검색 · BM25 · TF-IDF · 벡터 공간 모델 · 역색인 · 하이브리드 검색 · 상호 순위 융합 · 재순위화 · SPLADE

벡터 검색을 구현한 저장소와 라이브러리

벡터 데이터베이스 · FAISS · pgvector · Apache Lucene · Elasticsearch · OpenSearch · Milvus · Qdrant · 검색 엔진

벡터 검색을 쓰는 응용

검색 증강 생성 · 추천 시스템 · 이미지 검색 · 중복 탐지 · 대규모 언어 모델

벡터 검색의 결과를 재는 지표

재현율 · 정밀도 · nDCG · 평균 역순위 · 지연 시간

다른 이름: Vector Search · 벡터 유사도 검색 · Vector Similarity Search