Усі уроки · 44
Векторні бази даних
Чому семантичному пошуку потрібна векторна база даних, як індекси ANN на кшталт HNSW швидко знаходять найближчих сусідів, яку базу вибрати — і робочий приклад пошуку за схожістю.
Спершу: що таке база даних?
База даних — це програма, уся робота якої — пам’ятати дані й відповідати на запитання про них. Ви один раз передаєте їй мільйон записів, а потім питаєте: «які записи підходять під це?» — і вона відповідає за мілісекунди, не перечитуючи все з нуля.
Два слова ви почуєте в розмові про будь-яку базу даних:
- Запит — запитання, яке ви їй ставите. «Усі замовлення за березень», «користувач із такою поштою», «п’ять об’єктів, найсхожіших на цей».
- Індекс — допоміжна структура, яку база заздалегідь будує, щоб відповідати, не перевіряючи кожен запис. Як предметний покажчик у кінці книжки: ви одразу відкриваєте потрібну сторінку, а не читаєте всі 400.
Звичайна база даних чудово справляється з точними запитаннями: це значення, ця дата, це ім’я користувача. Для запиту «знайди п’ять документів, близьких за змістом до цього абзацу» вона марна: у ньому немає нічого точного.
Векторна база даних створена саме для такого запитання: її записи — вектори, а запит звучить так: «що розташоване найближче до цього?»
Проблема: найближчі сусіди за великих обсягів
Ембеддинги розв’язують питання «що означає цей текст», а координати визначає модель, яку ви вибрали для їх побудови (Моделі ембеддингів). Наступна проблема — швидкість. Припустімо, у вас мільйон документів з ембеддингами й один вектор запиту. Які 5 документів найближчі до нього?
Простими словами: «найближчі» означає «найбільш схожі за змістом». У цьому весь фокус із уроку про ембеддинги: близькі вектори — це схожі тексти. Лишається тільки швидко їх знайти.
Очевидне рішення — порахувати відстань до кожного вектора й відсортувати — називається повним перебором (brute-force search), і для кількох тисяч векторів він годиться. На мільйоні він уже гальмує, а на сотні мільйонів безнадійний.
Саме це розв’язує векторна база даних: вона зберігає вектори й за мілісекунди відповідає на запит «дай K найближчих сусідів цього вектора» навіть за величезних обсягів. Робить вона це за допомогою індексу — структури даних, яка віддає трохи точності в обмін на велику швидкість.
Як працюють індекси (версія в один абзац)
Основний підхід — наближений пошук найближчих сусідів (Approximate Nearest Neighbor, ANN). На практиці вам трапляться два сімейства індексів:
- HNSW (Hierarchical Navigable Small World) будує багатошаровий граф, де кожен вектор з’єднано з кількома сусідніми. Запит починається з верхнього шару — там стрибки через увесь простір — і шар за шаром наближається до цілі: так ви спершу їдете шосе, потім міськими вулицями, а наприкінці шукаєте точну адресу. Швидко й точно, але потребує багато пам’яті.
- IVF (Inverted File) спершу розкладає всі вектори по кошиках: стандартний алгоритм k-means (k-середніх) ділить їх на задану кількість «районів». Потім запит переглядає лише кілька найближчих до нього кошиків замість усієї колекції (так у базах даних називають «один іменований набір записів»). Пам’яті потрібно менше, точність трохи нижча, налаштовувати легко.
Реалізовувати це не потрібно
Індекси ANN — результат десятиліть досліджень, і їх уже реалізовано за вас. Ваше завдання — знати, що вони існують, розуміти компроміс «точність в обмін на швидкість» і вибирати розумні параметри. Ось і все.
Пайплайн
Ось повний життєвий цикл системи векторного пошуку: кожен крок — окремий компонент, який ви побудуєте:
Завантажте свої тексти — звернення до підтримки, документацію продукту, статті.
Яку вибрати: просто візьміть одну
Усі варіанти добрі — і тому вибрати складніше, ніж мало б бути:
- Chroma — відкритий код, орієнтована на Python, налаштувань не потребує. Варіант за замовчуванням для навчання й прототипів.
- FAISS — бібліотека, а не база даних. Чиста швидкість, перевірена на практиці в Meta, сервера немає, і готового способу зберігати дані між запусками теж немає. Чудово підходить, коли ембеддинги живуть усередині вашого застосунку.
- LanceDB — вбудована база: дані зберігаються в локальних файлах, окремий сервер не потрібен. Проста й сучасна.
- Supabase pgvector — вектори просто в Postgres. Якщо ваші дані вже лежать у Postgres, а масштаб помірний, це шлях найменшого опору.
- Qdrant — відкритий код, висока швидкість, розвинена фільтрація. Популярний варіант для розміщення на власних серверах.
- Weaviate — повнофункціональна база з відкритим кодом і вбудованим гібридним пошуком: він поєднує старий добрий пошук за ключовими словами та векторний, тож одним запитом ви знаходите і точні артикули товарів, і наближені за змістом збіги.
- Pinecone — повністю керований сервіс: обслуговувати нічого не треба. Підходить, якщо простіше заплатити, ніж самому тримати інфраструктуру.
Коротка відповідь
Почніть із Chroma (або з pgvector, якщо ви вже користуєтеся Postgres). Вона спокійно справляється з мільйонами векторів — більше, ніж побачить більшість побічних проєктів. Переїзд потім — це скрипт перерахунку ембеддингів і пів дня роботи. Питання «яку векторну базу вибрати» варте хвилин роздумів, а не тижнів.
Реалізація: індексація та пошук
Суть вміщається приблизно у 20 рядків. Перший файл — математика, що лежить в основі: косинусна схожість, повний перебір. Другий файл — те саме зі справжньою векторною базою (Chroma): ANN-індекс замінює цикл.
Косинусна схожість в одному реченні: уявіть дві стрілки, що виходять з однієї точки, — одна для вашого запитання, інша для документа. Оцінка показує, наскільки вони вказують в одному напрямку: 1 означає «те саме», 0 — «жодного зв’язку». Довжина не має значення, і це саме те, що потрібно: запитання з двох слів і довгий абзац усе одно можуть бути про те саме, а отже, можуть опинитися сусідами.
1import numpy as np2 3docs = {4 #98c379">"forgot password": [0.9, 0.1, 0.0],5 #98c379">"account locked": [0.85, 0.15, 0.0],6 #98c379">"refund policy": [0.0, 0.9, 0.2],7 #98c379">"double charge": [0.05, 0.85, 0.1],8 #98c379">"track my package": [0.1, 0.0, 0.9],9}10query = np.array([0.88, 0.12, 0.02])11 12for name, vec in docs.items():13 a = np.array(vec)14 score = np.dot(a, query) / (np.linalg.norm(a) * np.linalg.norm(query))15 print(name, round(score, 3))Запустіть brute_force.py — і два документи про акаунт отримають найвищі оцінки,
точно так, як на карті змістів з уроку про ембеддинги. with_chroma.py улаштований
як код для робочої системи: ви віддаєте базі сирі тексти (ембеддинги вона будує
сама), а запит за мілісекунди повертає два найближчі звернення — важку роботу
виконує індекс. Замініть ці три документи своїми — і маєте
семантичний пошук; передайте результати LLM замість того, щоб виводити їх на екран,
— і маєте RAG.
Справжні ембеддинги, іграшковий приклад
У цьому прикладі використано «іграшкові» тривимірні вектори, щоб математика була на виду. Справжні ембеддинги мають від 384 до 3072 вимірів, але код лишається тим самим: numpy і векторним базам розмір не важливий.
Два практичні зауваження для реальної експлуатації:
- Завжди зберігайте вихідний текст поруч із кожним вектором: він знадобиться для відповіді, а сам по собі вектор нічого не говорить.
- Переіндексовуйте під час зміни моделі. Вектори різних моделей непорівнянні, тому після зміни моделі доведеться заново порахувати ембеддинги й перебудувати індекс.
Чому б просто не пройти циклом по всіх векторах і не порахувати відстань?
Що показує косинусна схожість?
Ви змінили модель ембеддингів. Що ламається?
Що потрібно зберігати поруч із кожним вектором?