FAISS: knihovna pro rychlé a efektivní vyhledávání podobných vektorů

8. 7. 2025
Doba čtení: 21 minut

Sdílet

Autor: Root.cz s využitím DALL-E
Ukážeme si základní vlastnosti knihovny FAISS, která je určena pro vyhledávání vektorů (s vysokými počty dimenzí) na základě jejich podobnosti. Tato knihovna se používá třeba při zpracování přirozeného jazyka.

Obsah

1. FAISS: knihovna pro rychlé a efektivní vyhledávání podobných vektorů

2. Svět vektorových databází

3. Je FAISS vektorovou databází?

4. Řešený problém: nalezení nejbližších k bodů k zadaným souřadnicím

5. Příprava projektu

6. Instalace balíčků faiss-cpu a numpy do virtuálního prostředí

7. Definice souřadnic bodů v rovině

8. Konstrukce matice se souřadnicemi bodů

9. Konstrukce indexu pro vyhledávání na základě vzdálenosti

10. Nalezení nejbližších bodů k zadaným souřadnicím

11. Výpis souřadnic bodů nejbližších k zadaným souřadnicím

12. Vizualizace nejbližších nalezených bodů

13. Výběry vektorů na základě jejich podobnosti: volba metriky

14. Výběr podobných vektorů pomocí skalárního součinu: nekorektní verze

15. Normalizace vektorů v indexu

16. Rychlost vyhledání nejbližších bodů

17. Výsledky benchmarku

18. Obsah navazujícího článku

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. FAISS: knihovna pro rychlé a efektivní vyhledávání podobných vektorů

V současnosti můžeme sledovat poměrně výrazné a stále častější nasazování vektorových databází. Jedná se (většinou) o specializované databáze, které umožňují ukládat vektory numerických hodnot a především efektivně vyhledávat vektory podle jejich podobnosti (například se zadaným vzorem), přičemž podobností může být v tomto kontextu myšlena například vzdálenost koncových bodů vektorů v Eukleidovském prostoru, kosinus úhlu mezi vektory, výsledek skalárního součinu (pro normalizované vektory) atd.

Tyto operace se používají v mnoha oblastech, například při rozpoznávání a zpracování přirozeného jazyka (NLP – Natural Language Processing), při rozpoznávání obrázků, rozpoznávání hlasů, detekci anomálií, ale i (a to zejména) v souvislosti s velkými jazykovými modely (k tomuto tématu se ještě vrátíme v samostatném článku).

Poznámka: do této kategorie spadají i RAG databáze.

Na stránkách Roota jsme se již v článku pgvector: vektorová databáze postavená na Postgresu seznámili s vektorovou databází pgvector, která je postavena nad Postgresem. Dnes si popíšeme základní vlastnosti knihovny nazvané FAISS, což je jméno, které vzniklo ze sousloví Facebook AI Similarity Search (ovšem tato knihovna se používá i v mnoha projektech, které nemají s Facebookem nebo Metou cokoli společného). FAISS je šířena pod MIT licencí, což umožňuje její relativně snadné zařazení do dalších projektů.

2. Svět vektorových databází

S rozvojem systémů určených pro zpracování přirozeného jazyka i generativních AI (zejména velkých jazykových modelů) došlo i k velkému rozmachu vektorových databází. Dnes jich existuje minimálně několik desítek (pokud počítáme ty nejznámější a/nebo veřejně dostupné), ovšem spíše jich bude několik set.

V tabulce pod tímto odstavcem jsou vypsány ty nejznámější vektorové databáze, které pochopitelně mají rozdílné vlastnosti a každá z nich se hodí pro jiné způsoby nasazení:

Aerospike
AllegroGraph
Apache Cassandra
Azure Cosmos DB
Chroma
ClickHouse
Couchbase
CrateDB
DataStax
Elasticsearch
HAKES
HDF5 Query Indexing
JaguarDB
LanceDB
Lantern
LlamaIndex
MariaDB
Marqo
Meilisearch
Milvus
MongoDB Atlas
Neo4j
ObjectBox
OpenSearch
Oracle Database
Pinecone
Pixeltable (Incremental Embedding)
Postgres with pgvector
Qdrant
Redis Stack
Snowflake
SurrealDB
Typesense
Vespa
Weaviate

3. Je FAISS vektorovou databází?

V některých článcích se můžeme dočíst, že FAISS je vektorovou databází. Ovšem to ve skutečnosti není pravda, protože FAISS „pouze“ poskytuje algoritmy určené pro efektivní vyhledávání podobných vektorů a pro jejich shlukování (clustering). Neřeší ovšem problém uložení vektorů v nějakém strukturovaném formátu. Naopak, některé skutečné vektorové databáze, mezi které patří například Milvus nebo OpenSearch, využívají FAISS pro vyhledávání, ovšem způsob uložení a načítání vektorů řeší ve své vlastní režii.

Poznámka: na druhou stranu je však efektivní vyhledávání podobných vektorů ústředním prvkem mnoha systémů s AI, takže volba vhodné knihovny pro tyto operace bývá mnohem důležitější, než vlastní způsob uložení vektorů v databázi. Je tomu tak z toho důvodu, že vektory mají velký počet dimenzí (i několik tisíc) a samotných vektorů může být několik milionů, i více. Volba optimálního algoritmu (a popř. provedení výpočtů na GPU) je tedy v takových případech kritická.

4. Řešený problém: nalezení nejbližších k bodů k zadaným souřadnicím

V navazujícím kapitolách se s využitím knihovny FAISS pokusíme vyřešit následující problém: v 2D prostoru máme patnáct bodů a budeme chtít najít k bodů, které jsou neblíže k zadaným souřadnicím. Pokud bude k=1, tak budeme chtít nalézt nejbližší bod atd. Body jsou přitom v rovině rozmístěny následujícím způsobem a u vybraných bodů jsou zapsány i jejich souřadnice:

                                       │ y
                                       │
                                       │
                                       │
                                       │                [5,5]
                     o       o         │          o   o   o
                                       │          o   o   o
                         o             │          o   o   o
                      [-4,3]           │        [3,3]   [5,3]
                                       │
─────────────────────────────────────[0,0]──────────────────────────────────────
                                       │                                       x
                                       │              o
                                       │
                                       │          o       o
                                       │                [5,-5]
                                       │
                                       │
                                       │
                                       │

5. Příprava projektu

V prvním kroku si připravíme projekt v Pythonu a následně do něj nainstalujeme všechny potřebné knihovny. Pro vytvoření projektu použijeme nástroj PDM – viz též PDM: moderní správce balíčků a virtuálních prostředí Pythonu. Projekt bude vytvořen v novém (původně prázdném) adresáři:

$ mkdir faiss-demo
$ cd faiss-demo

Následně si necháme vytvořit projektový soubor nazvaný pyproject.toml. Na všechny odpovědi kladené nástrojem PDM můžeme s klidem odpovědět jen klávesou Enter:

$ pdm init
 
Creating a pyproject.toml for PDM...
Please enter the Python interpreter to use
 0. cpython@3.12 (/usr/bin/python)
 1. cpython@3.13 (/usr/bin/python3.13)
 2. cpython@3.12 (/usr/bin/python3.12)
 3. cpython@3.11 (/usr/bin/python3.11)
Please select (0):
 
 
Virtualenv is created successfully at /tmp/ramdisk/faiss-demo/.venv
Project name (faiss-demo): Project version (0.1.0): Do you want to build this project for distribution(such as wheel)?
If yes, it will be installed by default when running `pdm install`. [y/n] (n):
License(SPDX name) (MIT):
Author name (Pavel Tisnovsky):
Author email (tisnik@nikdo.nikde.com):
Python requires('*' to allow any) (==3.12.*):
Project is initialized successfully

Výsledný projektový soubor pyproject.toml by měl vypadat následovně (samozřejmě kromě jména, mailu a zvolené verze Pythonu):

[project]
name = "faiss-demo"
version = "0.1.0"
description = "Default template for PDM package"
authors = [
    {name = "Pavel Tisnovsky", email = "tisnik@nikdo.nikde.com"},
]
dependencies = []
requires-python = "==3.12.*"
readme = "README.md"
license = {text = "MIT"}
 
 
[tool.pdm]
distribution = false

6. Instalace balíčků faiss-cpu a numpy do virtuálního prostředí

Ve druhém kroku do virtuálního prostředí vytvořeného pro nový projekt přidáme dva balíčky – faiss-cpu a numpy. Druhý z balíčků byl přidán z toho důvodu, abychom mohli tvořit vektory, s nimiž bude faiss pracovat:

$ pdm add faiss-cpu numpy
 
Adding packages to default dependencies: faiss-cpu, numpy
  0:00:03 🔒 Lock successful.
Changes are written to pyproject.toml.
Synchronizing working set with resolved packages: 3 to add, 0 to update, 0 to remove
 
  ✔ Install packaging 25.0 successful
  ✔ Install numpy 2.3.1 successful
  ✔ Install faiss-cpu 1.11.0 successful
 
  0:00:05 🎉 All complete! 3/3

Projektový soubor nyní vypadá následovně:

[project]
name = "faiss-demo"
version = "0.1.0"
description = "Default template for PDM package"
authors = [
    {name = "Pavel Tisnovsky", email = "ptisnovs@redhat.com"},
]
dependencies = ["faiss-cpu>=1.11.0", "numpy>=2.3.1"]
requires-python = "==3.12.*"
readme = "README.md"
license = {text = "MIT"}
 
 
[tool.pdm]
distribution = false

Ověříme si, zda je modul faiss skutečně dostupný:

$ pdm run 
 
WARNING: No command is given, default to the Python REPL.
Python 3.12.10 (main, Apr 22 2025, 00:00:00) [GCC 14.2.1 20240912 (Red Hat 14.2.1-3)] on linux
Type "help", "copyright", "credits" or "license" for more information.
 
>>> import faiss
>>> help(faiss)

Měla by se zobrazit nápověda:

Help on package faiss:
 
NAME
    faiss
 
DESCRIPTION
    # Copyright (c) Facebook, Inc. and its affiliates.
    #
    # This source code is licensed under the MIT license found in the
    # LICENSE file in the root directory of this source tree.
 
PACKAGE CONTENTS
    _swigfaiss
    _swigfaiss_avx2
    _swigfaiss_avx512
    array_conversions
    class_wrappers
    contrib (package)
    extra_wrappers
    gpu_wrappers
    loader
    setup
    swigfaiss
    swigfaiss_avx2
    swigfaiss_avx512
 
CLASSES

7. Definice souřadnic bodů v rovině

Souřadnice bodů umístěných v rovině (viz čtvrtou kapitolu s jejich vizualizací) je možné uložit do běžných Pythonovských seznamů nebo do n-tic. Bodů je celkem patnáct a souřadnice v rovině mají jen dvě dimenze, takže reprezentace souřadnice je v tomto případě snadná:

# x-ove souradnice bodu v rovine
x = [-5, -4, -3,    3,  4,  5,    3, 3, 3, 4, 4, 4, 5, 5, 5]
 
# y-ove souradnice bodu v rovine
y = [ 5,  3,  5,   -5, -3, -5,    3, 4, 5, 3, 4, 5, 3, 4, 5]
Poznámka: vizuálně (mezerami) jsou odděleny jednotlivé kvadranty, do nichž jsou body umístěny.

Z těchto dvou oddělených seznamů je možné vytvořit seznam souřadnic (dvojic) velmi snadno:

# x-ove souradnice bodu v rovine
x = [-5, -4, -3,    3,  4,  5,   3, 3, 3,  4, 4, 4,  5, 5, 5]
 
# y-ove souradnice bodu v rovine
y = [ 5,  3,  5,   -5, -3, -5,   3, 4, 5,  3, 4, 5,  3, 4, 5]
 
print(x)
print(y)
 
# seznam souradnic
print(list(zip(x, y)))

Výsledky:

[-5, -4, -3, 3, 4, 5, 3, 3, 3, 4, 4, 4, 5, 5, 5]
[5, 3, 5, -5, -3, -5, 3, 4, 5, 3, 4, 5, 3, 4, 5]
 
[(-5, 5), (-4, 3), (-3, 5), (3, -5), (4, -3), (5, -5), (3, 3), (3, 4), (3, 5), (4, 3), (4, 4), (4, 5), (5, 3), (5, 4), (5, 5)]

8. Konstrukce matice se souřadnicemi bodů

Knihovna FAISS ovšem na svém vstupu neočekává standardní Pythonovské seznamy, ale (typicky) matice ve formátu podporovaném knihovnou Numpy (interně se jedná o obecnou strukturu reprezentující n-rozměrná pole). V takovém případě je matice homogenní, tj. všechny její prvky jsou stejného typu. Navíc musíme zaručit, že tento typ bude buď float32 nebo float16 popř. bfloat16. Ukažme si tedy, jakým způsobem lze vytvořit dvourozměrné pole souřadnic, ve kterém budou všechny prvky typu float32:

import numpy as np
 
# x-ove souradnice bodu v rovine
x = [-5, -4, -3,    3,  4,  5,   3, 3, 3,  4, 4, 4,  5, 5, 5]
 
# y-ove souradnice bodu v rovine
y = [ 5,  3,  5,   -5, -3, -5,   3, 4, 5,  3, 4, 5,  3, 4, 5]
 
# konstrukce 2D matice, v niz kazdy radek obsahuje souradnice jednoho bodu v
# rovine
points = np.column_stack((x,y)).astype("float32")
print(points)

Výsledek nyní bude vypadat následovně:

[[-5.  5.]
 [-4.  3.]
 [-3.  5.]
 [ 3. -5.]
 [ 4. -3.]
 [ 5. -5.]
 [ 3.  3.]
 [ 3.  4.]
 [ 3.  5.]
 [ 4.  3.]
 [ 4.  4.]
 [ 4.  5.]
 [ 5.  3.]
 [ 5.  4.]
 [ 5.  5.]]
Poznámka: tuto matici je již možné použít jako přímá vstupní data pro knihovnu FAISS.

9. Konstrukce indexu pro vyhledávání na základě vzdálenosti

Nyní již máme k dispozici vstupní vektory (v našem případě sadu patnácti dvouprvkových vektorů) připraveny ve formátu, který je kompatibilní s knihovnou FAISS. Tyto vektory nyní využijeme pro konstrukci takzvaného indexu. To je datová struktura umožňující rychlé vyhledávání nejbližších vektorů na základě zvolené metriky určující vzdálenosti (pojem vzdálenosti je zde ovšem zobecněn a nemusí znamenat pouze Eukleidovskou vzdálenost).

V dalším demonstračním příkladu se zkonstruuje index typu L2, což značí Eukleidovskou vzdálenost L2:

import faiss
import numpy as np
 
# x-ove souradnice bodu v rovine
x = [-5, -4, -3,    3,  4,  5,   3, 3, 3,  4, 4, 4,  5, 5, 5]
 
# y-ove souradnice bodu v rovine
y = [ 5,  3,  5,   -5, -3, -5,   3, 4, 5,  3, 4, 5,  3, 4, 5]
 
# konstrukce 2D matice, v niz kazdy radek obsahuje souradnice jednoho bodu v
# rovine
points = np.column_stack((x,y)).astype("float32")
print(points)
 
# konstrukce indexu pro vyhledavani na zaklade vzdalenosti
index = faiss.IndexFlatL2(2)
index.add(points)
 
print()
print("Dimension(s):         ", index.d)
print("Total values in index:", index.ntotal)
print("Is index trained:     ", index.is_trained)

Po konstrukci indexu si necháme vypsat jeho základní charakteristiky – počet dimenzí, počet záznamů v indexu a taktéž příznak, zda je index natrénován (viz navazující článek):

[[-5.  5.]
 [-4.  3.]
 [-3.  5.]
 [ 3. -5.]
 [ 4. -3.]
 [ 5. -5.]
 [ 3.  3.]
 [ 3.  4.]
 [ 3.  5.]
 [ 4.  3.]
 [ 4.  4.]
 [ 4.  5.]
 [ 5.  3.]
 [ 5.  4.]
 [ 5.  5.]]
 
Dimension(s):          2
Total values in index: 15
Is index trained:      True

10. Nalezení nejbližších bodů k zadaným souřadnicím

Nyní se konečně dostáváme k ústřednímu tématu dnešního článku, tedy k realizaci skriptu, který má nalézt nejbližších k bodů (nebo, chcete-li, vektorů) k zadaným souřadnicím v rovině. Začněme těmito souřadnicemi. I ty je nutné realizovat formou matice, v našem případě matice s jediným řádkem se souřadnicemi [3, 3]. A opět platí, že typ prvků této matice musí být buď float32 nebo float16 popř. bfloat16. Použijeme typ float32, tedy stejný typ, jaký mají prvky vstupní matice vektorů:

query_vector = np.array([[3, 3]]).astype("float32")
print(query_vector)

Následně si necháme vyhledat k nejbližších bodů (vektorů) k těmto souřadnicím. V praxi může k nabývat hodnoty od 1 (nejbližší bod) až po počet vstupních vektorů:

k = len(x)
distances, indices = index.search(query_vector, k)

Body/vektory budou v tomto případě vráceny v pořadí od nejbližšího k nejvzdálenějšímu (viz první hodnoty distances):

# tisk vysledku
print("Nearest neighbors:")
print("neighbour  distance  index")
print("--------------------------")
for i in range(k):
    print(f"{i+1:3}      {distances[0][i]:5}       {indices[0][i]:2}")

Výsledky budou vypadat následovně:

Nearest neighbors:
neighbour  distance  index
--------------------------
  1        0.0        6
  2        1.0        7
  3        1.0        9
  4        2.0       10
  5        4.0        8
  6        4.0       12
  7        5.0       11
  8        5.0       13
  9        8.0       14
 10       37.0        4
 11       40.0        2
 12       49.0        1
 13       64.0        3
 14       68.0        0
 15       68.0        5

A takto vypadá celý skript, který výše popsaný výpočet provádí:

import faiss
import numpy as np
 
# x-ove souradnice bodu v rovine
x = [-5, -4, -3,    3,  4,  5,   3, 3, 3,  4, 4, 4,  5, 5, 5]
 
# y-ove souradnice bodu v rovine
y = [ 5,  3,  5,   -5, -3, -5,   3, 4, 5,  3, 4, 5,  3, 4, 5]
 
# konstrukce 2D matice, v niz kazdy radek obsahuje souradnice jednoho bodu v
# rovine
points = np.column_stack((x,y)).astype("float32")
print(points)
 
# konstrukce indexu pro vyhledavani na zaklade vzdalenosti
index = faiss.IndexFlatL2(2)
index.add(points)
 
print()
print("Dimension(s):         ", index.d)
print("Total values in index:", index.ntotal)
print("Is index trained:     ", index.is_trained)
 
# vektor, ke kteremu budeme pocitat vzdalenost
query_vector = np.array([[3, 3]]).astype("float32")
print(query_vector)
 
# pocet nejblizsich bodu
k = len(x)
distances, indices = index.search(query_vector, k)
 
# tisk vysledku
print("Nearest neighbors:")
print("neighbour  distance  index")
print("--------------------------")
for i in range(k):
    print(f"{i+1:3}      {distances[0][i]:5}       {indices[0][i]:2}")

11. Výpis souřadnic bodů nejbližších k zadaným souřadnicím

V předchozím skriptu se vypisovaly pouze indexy nejbližších bodů/vektorů. Ovšem my máme k dispozici i původní souřadnice těchto bodů, takže si můžeme vypsat přímo tyto souřadnice:

Nearest neighbors:
neighbour  distance  coordinates
----------------------------------
  1        0.0       [3. 3.]
  2        1.0       [3. 4.]
  3        1.0       [4. 3.]
  4        2.0       [4. 4.]
  5        4.0       [3. 5.]
  6        4.0       [5. 3.]
  7        5.0       [4. 5.]
  8        5.0       [5. 4.]
  9        8.0       [5. 5.]
 10       37.0       [ 4. -3.]
 11       40.0       [-3.  5.]
 12       49.0       [-4.  3.]
 13       64.0       [ 3. -5.]
 14       68.0       [-5.  5.]
 15       68.0       [ 5. -5.]
Poznámka: zajisté jste si všimli, že vzdálenosti jsou vypočteny špatně. Například vzdálenost bodů [3, 3] a [4, 4] nemá být 2 ale √2. Je tomu tak schválně – aby se nemusela pomalu počítat odmocnina ze dvou, jsou namísto vzdáleností vypočteny jejich čtverce (tedy bez oné odmocniny). V praxi to většinou nevadí, protože vzdálenosti potřebujeme pouze porovnávat a odmocnina je monotonně rostoucí funkce.

Celý upravený skript nyní vypadá následovně:

import faiss
import numpy as np
 
# x-ove souradnice bodu v rovine
x = [-5, -4, -3,    3,  4,  5,   3, 3, 3,  4, 4, 4,  5, 5, 5]
 
# y-ove souradnice bodu v rovine
y = [ 5,  3,  5,   -5, -3, -5,   3, 4, 5,  3, 4, 5,  3, 4, 5]
 
# konstrukce 2D matice, v niz kazdy radek obsahuje souradnice jednoho bodu v
# rovine
points = np.column_stack((x,y)).astype("float32")
print(points)
 
# konstrukce indexu pro vyhledavani na zaklade vzdalenosti
index = faiss.IndexFlatL2(2)
index.add(points)
 
print()
print("Dimension(s):         ", index.d)
print("Total values in index:", index.ntotal)
print("Is index trained:     ", index.is_trained)
 
# vektor, ke kteremu budeme pocitat vzdalenost
query_vector = np.array([[3, 3]]).astype("float32")
print(query_vector)
 
# pocet nejblizsich bodu
k = len(x)
distances, indices = index.search(query_vector, k)
 
# tisk vysledku
print("Nearest neighbors:")
print("neighbour  distance  coordinates  ")
print("----------------------------------")
for i in range(k):
    print(f"{i+1:3}      {distances[0][i]:5}       {points[indices[0][i]]}")

12. Vizualizace nejbližších nalezených bodů

Upravme si nyní předchozí skript do takové podoby, aby vracel jen tři body nejbližší k souřadnicím [-4, 4]. Úprava je v tomto případě snadná. Modifikovaná místa jsou podtržena:

# vektor, ke kteremu budeme pocitat vzdalenost
query_vector = np.array([[-4, 4]]).astype("float32")
print(query_vector)
 
# pocet nejblizsich bodu
k = 3
distances, indices = index.search(query_vector, k)

Výsledkem výše uvedeného dotazu je trojice vektorů, které jsou nejpodobnější vektoru [-4, 4]:

[[-4.  4.]]
 
Nearest neighbors:
neighbour  distance  coordinates
----------------------------------
  1        1.0       [-4.  3.]
  2        2.0       [-5.  5.]
  3        2.0       [-3.  5.]

Což si pochopitelně můžeme vizualizovat v rovině. Hvězdičkou je naznačen bod/vektor z podmínky (pro který hledáme nejbližší sousedy), kolečkem vektory vrácené jako výsledek dotazu a tečkou ostatní vektory, které neodpovídají dotazu (nejsou vráceny):

                                       │ y
                                       │
                                       │
                                       │
                                       │
                     o       o         │          .   .   .
                         *             │          .   .   .
                         o             │          .   .   .
                                       │
                                       │
─────────────────────────────────────[0,0]──────────────────────────────────────
                                       │                                       x
                                       │
                                       │              .
                                       │
                                       │          .       .
                                       │
                                       │
                                       │
                                       │

13. Výběry vektorů na základě jejich podobnosti: volba metriky

Prozatím jsme pro výpočty podobnosti (resp. vzdálenosti) vektorů používali klasickou Eukleidovskou metriku, tedy skutečnou vzdálenost bodů v prostoru (v tomto případě vzdálenost koncových bodů vektorů v 2D rovině). Tato metrika se taktéž označuje L2 nebo přesněji L2, ovšem knihovna FAISS podporuje i odlišnou metriku založenou na výpočtu skalárního součinu (dot product). Vychází se přitom z faktu, že pokud mají dva vektory jednotkovou délku, bude jejich skalární součin největší v případě, že jsou vektory totožné, nulový ve chvíli, kdy jsou vektory na sebe kolmé a záporný tehdy, pokud je jeden vektor oproti druhému otočen o 180°. Jedná se o velmi často používanou metriku pro hledání podobných vektorů, ovšem v praxi nesmíme zapomenout na její základní vlastnosti:

  1. Všechny vektory musí být normalizovány na jednotkovou délku (kromě nulového vektoru)
  2. Jedná se o metriku, nikoli o vzdálenost. Naopak – čím podobnější jsou vektory, tím větší bude výsledek (opak vzdálenosti).
  3. Pozor je nutné dát na nulové vektory, které není možné normalizovat (speciální případ)

14. Výběr podobných vektorů pomocí skalárního součinu: nekorektní verze

Vyzkoušejme si nyní nahradit hledání podobných vektorů na základě vzdálenosti za hledání s využitím skalárního součinu. Ve skriptu prozatím provedeme jedinou změnu, a to konkrétně záměnu funkce pro konstrukci indexů. Namísto:

index = faiss.IndexFlatL2(2)

zavoláme:

index = faiss.IndexFlatIP(2)

Výsledky budou vypadat takto:

Nearest neighbors:
neighbour  distance  coordinates
----------------------------------
  1       30.0       [5. 5.]
  2       27.0       [5. 4.]
  3       27.0       [4. 5.]
  4       24.0       [5. 3.]
  5       24.0       [4. 4.]
  6       24.0       [3. 5.]
  7       21.0       [4. 3.]
  8       21.0       [3. 4.]
  9       18.0       [3. 3.]
 10        6.0       [-3.  5.]
 11        3.0       [ 4. -3.]
 12        0.0       [ 5. -5.]
 13        0.0       [-5.  5.]
 14       -3.0       [-4.  3.]
 15       -6.0       [ 3. -5.]

Například první výpočet vypadá následovně: 3×5+3×5=15+15=30. A poslední výpočet 3×3+3×(-5)=9–15=-6. Tedy čím podobnější jsou vektory, tím větší bude hodnota, zatímco u L2 metriky tomu bylo naopak!

Celý skript byl upraven do této podoby:

import faiss
import numpy as np
 
# x-ove souradnice bodu v rovine
x = [-5, -4, -3,    3,  4,  5,   3, 3, 3,  4, 4, 4,  5, 5, 5]
 
# y-ove souradnice bodu v rovine
y = [ 5,  3,  5,   -5, -3, -5,   3, 4, 5,  3, 4, 5,  3, 4, 5]
 
# konstrukce 2D matice, v niz kazdy radek obsahuje souradnice jednoho bodu v
# rovine
points = np.column_stack((x,y)).astype("float32")
print(points)
 
# konstrukce indexu pro vyhledavani na zaklade skalarniho soucinu
index = faiss.IndexFlatIP(2)
index.add(points)
 
print()
print("Dimension(s):         ", index.d)
print("Total values in index:", index.ntotal)
print("Is index trained:     ", index.is_trained)
 
# vektor, ke kteremu budeme pocitat vzdalenost
query_vector = np.array([[3, 3]]).astype("float32")
print(query_vector)
 
# pocet nejblizsich bodu
k = len(x)
distances, indices = index.search(query_vector, k)
 
# tisk vysledku
print("Nearest neighbors:")
print("neighbour  distance  coordinates  ")
print("----------------------------------")
for i in range(k):
    print(f"{i+1:3}      {distances[0][i]:5}       {points[indices[0][i]]}")

15. Normalizace vektorů v indexu

Výpočet metriky založené na skalárním součinu předpokládá, že vektory jsou již dopředu normalizovány, tj. že jejich délka je buď rovna jedné nebo nule. V praxi je tedy nutné již při konstrukci indexů použít normalizované vektory. To se provede relativně snadno (a zejména – pouze jedenkrát při inicializaci):

# normalizace
for i in range(len(points)):
   vector = points[i]
   normalized = np.linalg.norm(vector)
   vector /= normalized
   points[i] = vector

Index se zkonstruuje s využitím normalizovaných vektorů:

# konstrukce indexu pro vyhledavani na zaklade vzdalenosti
index = faiss.IndexFlatIP(2)
index.add(points)

I vektor, pro který hledáme nejshodnější „sousedy“ je nutné normalizovat a teprve poté provést vyhledávání:

# vektor, ke kteremu budeme pocitat vzdalenost
query_vector = np.array([[3, 3]]).astype("float32")
normalized = np.linalg.norm(query_vector)
query_vector /= normalized
print(query_vector)
 
# pocet nejblizsich bodu
k = len(x)
distances, indices = index.search(query_vector, k)

Výsledky vyhledávání budou (podle očekávání) odlišné:

Nearest neighbors:
neighbour  distance  coordinates
----------------------------------
  1      +1.0000     [0.7071068 0.7071068]
  2      +1.0000     [0.70710677 0.70710677]
  3      +1.0000     [0.70710677 0.70710677]
  4      +0.9939     [0.78086877 0.62469506]
  5      +0.9939     [0.62469506 0.78086877]
  6      +0.9899     [0.8 0.6]
  7      +0.9899     [0.6 0.8]
  8      +0.9701     [0.857493  0.5144958]
  9      +0.9701     [0.5144958 0.857493 ]
 10      +0.2425     [-0.5144958  0.857493 ]
 11      +0.1414     [ 0.8 -0.6]
 12      +0.0000     [-0.70710677  0.70710677]
 13      -0.0000     [ 0.70710677 -0.70710677]
 14      -0.1414     [-0.8  0.6]
 15      -0.2425     [ 0.5144958 -0.857493 ]
Poznámka: povšimněte si, že nyní dostáváme hodnoty v rozsahu –1..1.

Opět si ukažme úplný zdrojový kód takto upraveného skriptu:

import faiss
import numpy as np
 
# x-ove souradnice bodu v rovine
x = [-5, -4, -3,    3,  4,  5,   3, 3, 3,  4, 4, 4,  5, 5, 5]
 
# y-ove souradnice bodu v rovine
y = [ 5,  3,  5,   -5, -3, -5,   3, 4, 5,  3, 4, 5,  3, 4, 5]
 
# konstrukce 2D matice, v niz kazdy radek obsahuje souradnice jednoho bodu v
# rovine
points = np.column_stack((x,y)).astype("float32")
print(points)
 
# normalizace
for i in range(len(points)):
   vector = points[i]
   normalized = np.linalg.norm(vector)
   vector /= normalized
   points[i] = vector
 
print()
print("Normalized:")
print(points)
 
# konstrukce indexu pro vyhledavani na zaklade vzdalenosti
index = faiss.IndexFlatIP(2)
index.add(points)
 
print()
print("Dimension(s):         ", index.d)
print("Total values in index:", index.ntotal)
print("Is index trained:     ", index.is_trained)
 
# vektor, ke kteremu budeme pocitat vzdalenost
query_vector = np.array([[3, 3]]).astype("float32")
normalized = np.linalg.norm(query_vector)
query_vector /= normalized
print(query_vector)
 
# pocet nejblizsich bodu
k = len(x)
distances, indices = index.search(query_vector, k)
 
# tisk vysledku
print("Nearest neighbors:")
print("neighbour  distance  coordinates  ")
print("----------------------------------")
for i in range(k):
    print(f"{i+1:3}      {distances[0][i]:+7.4f}     {points[indices[0][i]]}")

16. Rychlost vyhledání nejbližších bodů

Při vyhledávání na základě podobnosti vektorů v rozsáhlých databázích je důležité, aby byl algoritmus vyhledávání dostatečně rychlý. V knihovně FAISS je z tohoto důvodu implementováno hned několik algoritmů používaných v různých situacích. Dnes se budeme primárně zabývat výchozím algoritmem, u kterého se pokusíme odhadnout (a příště i popsat) jeho časovou složitost. Vytvoříme proto benchmark, který bude pracovat s různě velkými indexy a bude postupně měřit dobu vyhledávání (a pro zajímavost i další časy trvání). Celý benchmark je prozatím poměrně naivní, ovšem pro prvotní odhad složitost může postačovat:

from time import time
import faiss
import numpy as np
 
import matplotlib.pyplot as plt
 
 
def similarity_search(n, k):
    DIMENSIONS=128
 
    t1 = time()
 
    # nahodne vektory
    data = np.random.rand(n, 128).astype('float32')
 
    t2 = time()
 
    # konstrukce indexu pro vyhledavani na zaklade vzdalenosti
    index = faiss.IndexFlatL2(DIMENSIONS)
    index.add(data)
 
    t3 = time()
 
    # vektor, ke kteremu budeme pocitat vzdalenost
    query_vector = np.random.rand(1, DIMENSIONS).astype("float32")
 
    # pocet nejblizsich bodu
    distances, indices = index.search(query_vector, k)
    t4 = time()
    assert len(distances) == k
    assert len(indices) == k
 
    return n, t2-t1, t3-t2, t4-t3
 
 
ns = []
ts_rand = []
ts_index = []
ts_search = []
 
for n in np.linspace(1000000, 10000000, 10):
    print(n)
    n, t_rand, t_index, t_search = similarity_search(int(n), 1)
    ns.append(n)
    ts_rand.append(t_rand)
    ts_index.append(t_index)
    ts_search.append(t_search)
 
 
plt.plot(ns, ts_rand, "r-", label="numpy.random.rand")
plt.plot(ns, ts_index, "b-", label="index creation")
plt.plot(ns, ts_search, "m-", label="similarity search")
 
# přidání legendy
plt.legend(loc="upper left")
 
# povolení zobrazení mřížky
plt.grid(True)
 
plt.savefig("faiss_benchmark_2.png")
 
# zobrazení grafu
plt.show()

17. Výsledky benchmarku

Numerické výsledky benchmarku vypadají následovně:

   n      Konstrukce matice   Konstrukce indexu    Vlastní vyhledávání
---------------------------------------------------------------------
 1000000  1.313993215560913   0.3532786369323731   0.03192019462585449
 2000000  2.543938875198364   0.7037720680236816   0.06934928894042969
 3000000  3.741727113723755   1.0349786281585693   0.09357905387878418
 4000000  4.819754600524902   1.3467643260955810   0.11976456642150879
 5000000  6.031541824340820   1.6814656257629395   0.14942240715026855
 6000000  7.295117855072022   2.1264710426330566   0.17904376983642578
 7000000  8.616289138793945   2.3469836711883545   0.23292398452758790
 8000000 11.428938865661621   2.7663018703460693   0.24178504943847656
 9000000 21.055070638656616   3.2713224887847900   0.27842283248901367
10000000 27.235011577606200   3.4759612083435060   0.29420256614685060

Lepší však bude si výsledky prohlédnou v grafické podobně. Nejprve všechny časy:

Benchmarky knihovny FAISS

Obrázek 1: Výsledek benchmarku se zobrazením všech tří časů.

Autor: tisnik, podle licence: Rights Managed

Ovšem nejvíce nás zajímají časy vyhledávání, takže si zobrazme pouze tyto naměřené výsledky:

Benchmarky knihovny FAISS

Obrázek 2: Čas vyhledání nejbližšího vektoru pro různé velikosti indexu.

Autor: tisnik, podle licence: Rights Managed
Poznámka: výsledky lze chápat jak v pozitivním, tak i v negativním smyslu. Pozitivní je, že na základě výsledků lze prohlásit, že vyhledávání má lineární složitost O(n), nikoli například O(n2) nebo dokonce O(kn). Ovšem to je současně i špatný výsledek, protože u velmi rozsáhlých databází bychom potřebovali složitost menší (O(log) atd.). Toho je možné částečně dosáhnout prostředky popsanými příště.

18. Obsah navazujícího článku

Knihovna FAISS není vývojáři oblíbena ani tak kvůli tomu, jaké operace umí realizovat (ostatně jednoduchý similarity search lze naprogramovat v řádu minut), ale jak rychle je dokáže provádět. K dispozici je totiž hned několik algoritmů výpočtů, které se vybírají na základě velikosti vstupní množiny vektorů, počtu dimenzí vektorů, taktéž na základě toho, jestli jsou vektory uloženy v operační paměti nebo přesahují její rozsah atd. Navíc je možné výsledky (tedy nejbližší vektory nalezené na základě zadané metriky) v případě potřeby pouze „odhadovat“, tj. nemusí se nalézt skutečně ty nejbližší vektory, ale vektory dostatečně blízké. To sice nemusí znít příliš prakticky, ale ve skutečnosti se tyto algoritmy v praxi poměrně často používají zejména při realizacích RAG databází. O těchto optimalizacích si řekneme podrobnější informace v navazujícím článku.

19. Repositář s demonstračními příklady

Demonstrační příklady vytvořené v Pythonu a popsané v dnešním článku najdete v repositáři https://github.com/tisnik/most-popular-python-libs/. Následují odkazy na jednotlivé příklady:

# Příklad Stručný popis Adresa
1 faiss-1.py seznamy souřadnic bodů v rovině https://github.com/tisnik/most-popular-python-libs/blob/master/faiss/faiss-1.py
2 faiss-2.py konstrukce matice se souřadnicemi bodů https://github.com/tisnik/most-popular-python-libs/blob/master/faiss/faiss-2.py
3 faiss-3.py konstrukce indexu pro vyhledávání na základě vzdálenosti https://github.com/tisnik/most-popular-python-libs/blob/master/faiss/faiss-3.py
4 faiss-4.py nalezení nejbližších bodů k zadaným souřadnicím – výpis indexů nalezených bodů https://github.com/tisnik/most-popular-python-libs/blob/master/faiss/faiss-4.py
5 faiss-5.py nalezení nejbližších bodů k zadaným souřadnicím – výpis souřadnic nalezených bodů https://github.com/tisnik/most-popular-python-libs/blob/master/faiss/faiss-5.py
6 faiss-6.py vyhledávání bodů na základě skalárního součinu bez normalizace vektorů https://github.com/tisnik/most-popular-python-libs/blob/master/faiss/faiss-6.py
7 faiss-7.py vyhledávání bodů na základě skalárního součinu s normalizací vektorů https://github.com/tisnik/most-popular-python-libs/blob/master/faiss/faiss-7.py
8 faiss-8.py jednoduchý benchmark rychlosti vyhledávání knihovnou FAISS https://github.com/tisnik/most-popular-python-libs/blob/master/faiss/faiss-8.py
9 pyproject.toml soubor s projektem a definicí závislostí https://github.com/tisnik/most-popular-python-libs/blob/master/faiss/pyproject.toml

20. Odkazy na Internetu

  1. FAISS (Facebook AI Similarity Search)
    https://en.wikipedia.org/wiki/FAISS
  2. FAISS documentation
    https://faiss.ai/
  3. Introduction to Facebook AI Similarity Search (Faiss)
    https://www.pinecone.io/le­arn/series/faiss/faiss-tutorial/
  4. Faiss: Efficient Similarity Search and Clustering of Dense Vectors
    https://medium.com/@pankaj_pan­dey/faiss-efficient-similarity-search-and-clustering-of-dense-vectors-dace1df1e235
  5. Top 8 Vector Databases in 2025: Features, Use Cases, and Comparisons
    https://synapsefabric.com/top-8-vector-databases-in-2025-features-use-cases-and-comparisons/
  6. Is FAISS a Vector Database? Complete Guide
    https://mljourney.com/is-faiss-a-vector-database-complete-guide/
  7. Vector database
    https://en.wikipedia.org/wi­ki/Vector_database
  8. Similarity search
    https://en.wikipedia.org/wi­ki/Similarity_search
  9. Nearest neighbor search
    https://en.wikipedia.org/wi­ki/Nearest_neighbor_search#Ap­proximation_methods
  10. Decoding Similarity Search with FAISS: A Practical Approach
    https://www.luminis.eu/blog/decoding-similarity-search-with-faiss-a-practical-approach/
  11. MetricType and distances
    https://github.com/facebo­okresearch/faiss/wiki/Metric­Type-and-distances
  12. RAG – Retrieval-augmented generation
    https://en.wikipedia.org/wi­ki/Retrieval-augmented_generation
  13. pgvector na GitHubu
    https://github.com/pgvector/pgvector
  14. Why we replaced Pinecone with PGVector
    https://www.confident-ai.com/blog/why-we-replaced-pinecone-with-pgvector
  15. PostgreSQL as VectorDB – Beginner Tutorial
    https://www.youtube.com/wat­ch?v=Ff3tJ4pJEa4
  16. What is a Vector Database? (neobsahuje odpověď na otázku v titulku :-)
    https://www.youtube.com/wat­ch?v=t9IDoenf-lo
  17. PGVector: Turn PostgreSQL Into A Vector Database
    https://www.youtube.com/wat­ch?v=j1QcPSLj7u0
  18. Milvus
    https://milvus.io/
  19. Vector Databases simply explained! (Embeddings & Indexes)
    https://www.youtube.com/wat­ch?v=dN0lsF2cvm4
  20. Vector databases are so hot right now. WTF are they?
    https://www.youtube.com/wat­ch?v=klTvEwg3oJ4
  21. Step-by-Step Guide to Installing “pgvector” and Loading Data in PostgreSQL
    https://medium.com/@besttechreads/step-by-step-guide-to-installing-pgvector-and-loading-data-in-postgresql-f2cffb5dec43
  22. Best 17 Vector Databases for 2025
    https://lakefs.io/blog/12-vector-databases-2023/
  23. Top 15 Vector Databases that You Must Try in 2025
    https://www.geeksforgeeks.org/top-vector-databases/
  24. Picking a vector database: a comparison and guide for 2023
    https://benchmark.vectorvi­ew.ai/vectordbs.html
  25. Top 9 Vector Databases as of Feburary 2025
    https://www.shakudo.io/blog/top-9-vector-databases
  26. What is a vector database?
    https://www.ibm.com/think/to­pics/vector-database
  27. SQL injection
    https://en.wikipedia.org/wi­ki/SQL_injection
  28. Cosine similarity
    https://en.wikipedia.org/wi­ki/Cosine_similarity
  29. Euclidean distance
    https://en.wikipedia.org/wi­ki/Euclidean_distance
  30. Dot product
    https://en.wikipedia.org/wi­ki/Dot_product
  31. Hammingova vzdálenost
    https://cs.wikipedia.org/wi­ki/Hammingova_vzd%C3%A1le­nost
  32. Jaccard index
    https://en.wikipedia.org/wi­ki/Jaccard_index
  33. Manhattanská metrika
    https://cs.wikipedia.org/wi­ki/Manhattansk%C3%A1_metri­ka
  34. pgvector: vektorová databáze postavená na Postgresu
    https://www.root.cz/clanky/pgvector-vektorova-databaze-postavena-na-postgresu/ 
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.