pgvector, embedding a sémantické vyhledávání: sofistikovanější indexy a různé typy vektorů

Dnes
Doba čtení: 35 minut

Sdílet

Autor: Depositphotos
Po úvodní dvojici článků o PostgreSQL s rozšířením pgvector umožňujícím ukládání a vyhledávání vektorů se zaměříme na benchmarky. Vektory totiž mohou obsahovat prvky různých typů a při vyhledávání lze využít odlišné metriky.

Obsah

1. pgvector, embedding a sémantické vyhledávání (2. část: sofistikovanější indexy a různé typy vektorů)

2. Podporované typy prvků vektorů

3. Indexy založené na použití sofistikovanějších datových struktur

4. Rozdělení celého prostoru Voroného dekompozicí

5. Konstrukce indexu IVFFlat v Postgresu 

6. Index HNSW (Hierarchical Navigable Small World)

7. Základ pro benchmarky, které budeme provádět

8. Projektový soubor a spuštění benchmarku

9. Rychlost zápisu vektorů do tabulky

10. Vizualizace výsledků benchmarku v grafu

11. Vliv počtu dimenzí na rychlost zápisu

12. Benchmark a jeho výsledky

13. Časy vyhledávání vektorů na základě jejich podobnosti

14. Zdrojový kód benchmarku zjišťujícího časy vyhledávání

15. Výsledky benchmarku

16. Metriky podporované při testování podobnosti vektorů

17. Změření rychlosti vyhledávání s využitím různých metrik

18. Výsledky benchmarku

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

20. Odkazy na Internetu

1. pgvector, embedding a sémantické vyhledávání (2. část: sofistikovanější indexy a různé typy vektorů)

V článcích pgvector: vektorová databáze postavená na Postgresu a Rozšíření PostgreSQL jménem pgvector, embedding a sémantické vyhledávání (1. část) jsme se seznámili se základními vlastnostmi knihovny pgvector, která zajišťuje rozšíření možností databáze PostgreSQL o operace nad vektory. Připomeňme si, že díky tomuto rozšíření je možné do tabulek ukládat záznamy obsahující vektory s pevně zadanou délkou a typem prvků. Tyto vektory jsou ukládány v binární podobě, tj. například vektor se 100 prvky typu float32 bude uložen v 100×4=400 bajtech (nepoužívá se tedy textový formát ani JSON). Ovšem pgvector podporuje i vyhledávání vektorů na základě jejich podobnosti (vector similarity search), přičemž je možné využívat různé metriky (L1, L2, skalární součin, Hammingovu vzdálenost atd.). A navíc lze vyhledávání urychlit pomocí speciálních „vektorových“ indexů.

V dnešním článku se zaměříme na benchmarky. Vektory totiž mohou obsahovat prvky různých typů, při vyhledávání lze využít odlišné metriky atd. Tyto parametry pochopitelně mají vliv na rychlost práce s databází – a efektivní a rychlé vyhledávání vektorů je například v oblasti jazykových modelů kritickou operací.

2. Podporované typy prvků vektorů

Rozšíření pgvector podporuje zpracování vektorů s prvky několika typů. Tyto typy jsou vypsány v následující tabulce:

Jméno typu Způsob uložení Maximální počet dimenzí Stručný popis
vector 4×počet_dimenzí+8 2000 prvky vektorů jsou typu float32/single (4 bajty)
halfvec 2×počet_dimenzí+8 4000 prvky vektorů jsou typu half float (2 bajty)
bit počet_dimenzí/8+8 64000 každý prvek je uložen v jediném bitu
sparcevec 8×nenulové_prvky+16 1000 nenulových prvků uloženy jsou jen nenulové prvky
Poznámka: všechny dnešní benchmarky budou používat typ vector.

3. Indexy založené na použití sofistikovanějších datových struktur

V jazykových modelech, ale i v některých dalších oblastech informatiky, se můžeme setkat s datovými sadami, které obsahují desítky či dokonce stovky milionů vektorů. To znamená, že (v tomto případě zcela pochopitelně) existuje snaha o zmenšení času vyhledávání – ideálně změnou časové složitosti (z lineární složitosti k například logaritmické složitosti nebo složitosti s odmocninou), nebo alespoň sice se zachováním lineární složitosti, ovšem s jejím menším sklonem (což je ona někdy podceňovaná konstanta k ve vztahu k O(n)). I v těchto oblastech nám databáze PostgreSQL s rozšířením pgvector nabízí různá řešení.

V rámci dalších kapitol se seznámíme se dvěma různými technikami, které jsou založeny na použití sofistikovanějších datových struktur, než jsou pouhé sekvence (resp. v tomto případě tabulky) vektorů, které je nutné při vyhledávání vektorů na základě jejich podobnosti procházet sekvenčně, tj. prvek po prvku.

Poznámka: na tomto místě je vhodné poznamenat, že výkonnost pgvectoru může být horší v porovnání s některými specializovanými vektorovými databázemi. Z tohoto důvodu se pokusím v navazujícím článku o podrobnější porovnání různých vektorových databází z pohledu časové (a částečně i paměťové) složitosti.

4. Rozdělení celého prostoru Voroného dekompozicí

Jeden z dostupných a relativně často používaných algoritmů, které byly navrženy pro urychlení vyhledávání podobných vektorů (similarity search) spočívá v tom, že se n-dimenzionální prostor, ve kterém vektory vstupní datové sady leží, rozdělí do zadaného či automaticky vypočteného (odhadnutého) počtu oblastí. Pro rozdělení se přitom typicky používá Voroného dekompozice, i když by teoreticky bylo možné použít i další metody (možná zobecněný oktalový strom nebo podobné rozdělení n-rozměrného prostoru). Algoritmus vyhledávání vektorů na základě jejich podobnosti je v tom nejjednodušším případě upraven následujícím způsobem:

  1. Vstupem je vektor (dotaz) k němuž v datové (v případě pgvector tabulce) sadě hledáme nejbližší vektory
  2. Pro tento vstupní vektor je zjištěna oblast (Voroného dekompozice), ve které bude prováděno vyhledávání
  3. Následně jsou nejbližší vektory vyhledávány pouze v této oblasti

Problém spočívá v tom, že právě popsaný algoritmus nemusí vyhledat skutečně nejbližší vektory. Tato potenciálně problematická situace nastane v případě, že se vstupní vektor nachází blízko hranice oblasti a nejbližší vektor (vektory) by se tedy nacházel(y) těsně za touto hranicí. Proto může být algoritmus vyhledávání upraven do nepatrně odlišné podoby:

  1. Vstupem je vektor (dotaz) k němuž v datové sadě hledáme nejbližší vektory
  2. Pro tento vstupní vektor je zjištěna oblast (Voroného dekompozice), ve které bude prováděno vyhledávání
  3. Dále jsou zjištěno m sousedních oblastí k nalezené oblasti (m je většinou nějakým způsobem konfigurovatelné)
  4. Následně jsou nejbližší vektory vyhledávány v nalezené oblasti (bod 2) i oblastech sousedních (bod 3)

Pochopitelně platí, že čím větší je hodnota m, tím pomalejší, ale přesnější je vyhledání nejbližších vektorů. V limitním případě (m=celkový počet oblastí) se vlastně vrátíme k prohledávání celého prostoru.

5. Konstrukce indexu IVFFlat v Postgresu

Rozdělení n-dimenzionálního prostoru podle Voroného dekompozice je pochopitelně nutné nějakým způsobem realizovat. Podporu pro toto rozdělení programátorům sice nabízí samotné rozšíření pgvector, ovšem samotná konstrukce indexu se nepatrně zkomplikuje. Připomeňme si, že původně byla tabulka obsahující vektory zkonstruována následujícím způsobem:

CREATE TABLE IF NOT EXISTS vektoryXYZ (
    id bigserial PRIMARY KEY,
    embedding vector(počet_dimenzí) NOT NULL
);

Při aplikaci Voroného dekompozice je nutné index explicitně vytvořit, například následujícím způsobem:

CREATE INDEX ON vektoryXYZ
          USING ivfflat (embedding vector_l2_ops)
           WITH (lists = počet_oblastí);

V tomto případě je nutné explicitně specifikovat počet oblastí. Uvádí se, že pro databáze obsahující do milionu vektorů by měl počet oblastí zhruba odpovídat počtu vektorů podělených konstantou 1000. Pro větší databáze by se mělo jednat o hodnotu, která se blíží druhé odmocnině počtu vektorů. Ovšem pochopitelně vás nic neomezuje v provedení benchmarků pro různé počty oblastí a nalezení nejlepší hodnoty (tyto benchmarky se provedou jedenkrát, zatímco vektorová databáze bude používána opakovaně).

6. Index HNSW (Hierarchical Navigable Small World)

Dalším potenciálně užitečným typem indexu, které jsou nabízeny (nejenom) rozšířením pgvector (ale například i ve Faissu), je index označovaný zkratkou HNSW neboli Hierarchical Navigable Small World. Jak již název tohoto indexu naznačuje, je metoda HNSW založena na rozdělení celého prostoru do hierarchicky organizovaných menších částí (ty jsou nazývány „malé světy“, z čehož plyne i zkratka celého algoritmu). Samotné rozdělování prostoru je časově velmi náročné, ovšem na druhou stranu se urychlí vyhledávání podobných vektorů, a to o jeden až dva řády (do značné míry ovšem záleží na dalších parametrech). Samozřejmě se i s tímto indexem pojí určité další nevýhody.

Případný index se vytvoří na základě toho, jakou metriku budeme při vyhledávání používat:

CREATE INDEX ON vektoryXYZ
          USING hnsw (embedding vector_l2_ops);

Za slovo embedding se zapisuje operace podle příslušné metriky:

Metrika Slovo zapsané za embedding
L1 vector_l1_ops
L2 vector_l2_ops
skalární součin vector_ip_ops
kosinus vector_cosine_ops
Hammingova vzdálenost bit_hamming_ops
Jaccardova vzdálenost bit_jaccard_ops

Ovšem dají se specifikovat i další parametry indexu, například:

CREATE INDEX ON vektoryXYZ
          USING hnsw (embedding vector_l2_ops);
           WITH (m = 16, ef_construction = 64);

Vhodné nastavení parametrů lze opět zjistit s využitím benchmarků.

7. Základ pro benchmarky, které budeme provádět

V dalších kapitolách budeme spouštět různé varianty benchmarků, kterými si ověříme chování rozšíření pgvector pro relativně malé, ale i pro rozsáhlejší vektorové databáze. Všechny tyto benchmarky budou založeny na stejném základu, který je popsán v této kapitole. V benchmarku vždy vytvoříme novou tabulku se sloupcem s vektory a výpisem všech viditelných tabulek si ověříme, že tabulka existuje. Dále do této tabulky vložíme zadaný počet pseudonáhodných vektorů s předem známou dimenzí. Celý zdrojový kód je založen na Pythonovských knihovnách psycopg2 a pgvector:

# Rozšíření pgvector pro databázi PostgreSQL
#
# - příprava pro benchmark rozšíření pgvector
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
 
from time import time
 
import psycopg2
from pgvector.psycopg2 import register_vector
 
import numpy as np
 
# parametry benchmarku
DIMENSIONS = 16
VECTOR_COUNT = 10000
 
 
def create_vector_table(connection, dimensions, print_tables=True):
    """Vytvoření tabulky obsahující sloupec s vektory."""
    DROP_TABLE_STATEMENT = """
        DROP TABLE IF EXISTS v3
    """
 
    CREATE_TABLE_STATEMENT = """
        CREATE TABLE IF NOT EXISTS v3 (
            id bigserial PRIMARY KEY,
            embedding vector(%s) NOT NULL
        );
    """
 
    LIST_TABLES_QUERY = """
        SELECT table_schema,table_name
          FROM information_schema.tables
         WHERE table_schema='public'
         ORDER BY table_schema,table_name;
    """
 
    with connection.cursor() as cursor:
        cursor.execute(DROP_TABLE_STATEMENT)
        connection.commit()
 
        cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
        connection.commit()
 
        cursor.execute(LIST_TABLES_QUERY)
        tables = cursor.fetchall()
 
        if print_tables:
            print("Tables in database:")
            for table in tables:
                print(f"    {table[0]}: {table[1]}")
            print()
 
 
def fill_in_vector_table(connection, dimensions, vector_count):
    """Naplnění tabulky náhodnými vektory."""
    t1 = time()
    with connection.cursor() as cursor:
        # prozatím budeme tabulku naplňovat po jednotlivých záznamech
        for i in range(vector_count):
            # náhodný vektor
            vector = np.random.rand(dimensions).astype("float32")
            cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
        connection.commit()
    t2 = time()
    return t2 - t1
 
 
def print_vector_count(connection):
    """Tisk počtu vektorů uložených v tabulce."""
    with connection.cursor() as cursor:
        cursor.execute("SELECT count(*) FROM v3")
        count = cursor.fetchone()
        print(f"Vectors stored in table v3: {count[0]}")
 
 
def main():
    """Vstupní bod do benchmarku."""
    # připojení k databázi
    connection = psycopg2.connect(
        host="", port=5432, user="tester", password="123qwe", dbname="test"
    )
 
    try:
        # budeme používat rozšíření pgvector
        register_vector(connection)
 
        # vytvoření tabulky s vektory
        create_vector_table(connection, DIMENSIONS)
 
        # naplnění tabulky s vektory
        t = fill_in_vector_table(connection, DIMENSIONS, VECTOR_COUNT)
        print(f"Time to fill-in database: {t:3} seconds")
 
        # zjištění počtu skutečně uložených vektorů
        print_vector_count(connection)
    finally:
        connection.close()
 
 
# spuštění benchmarku
main()

8. Projektový soubor a spuštění benchmarku

Projektový soubor, který bude využit pro spuštění benchmarku, může vypadat následovně:

[project]
name = "sentence-transformer"
version = "0.1.0"
description = "Sentence transformer demos"
authors = [
    {name = "Pavel Tisnovsky", email = "ptisnovs@redhat.com"},
]
dependencies = [
    "datasets>=4.0.0",
    "pgvector>=0.4.1",
    "psycopg2>=2.9.10",
    "sentence-transformers>=5.0.0",
]
requires-python = ">=3.12"
readme = "README.md"
license = {text = "MIT"}
 
 
[tool.pdm]
distribution = false

Samotné spuštění benchmarku provedeme přes pdm nebo uv:

$ uv run index-benchmark-1.py 
  Tables in database:
    public: v3
 
Time to fill-in database: 0.02258753776550293 seconds
Vectors stored in table v3: 10

Nakonec si v konzoli PostgreSQL ověříme, zda byla tabulka v3 vytvořena a kolik obsahuje záznamů:

$ psql -U tester
 
Password for user tester:
 
psql (16.9)
Type "help" for help.

Výpis všech tabulek:

test=> \dt
 
              List of relations
 Schema |       Name        | Type  | Owner
--------+-------------------+-------+--------
 public | v3                | table | tester
(1 row)

Schéma tabulky s3 se vypíše takto:

test=> \d v3
 
                               Table "public.v3"
  Column   |    Type    | Collation | Nullable |            Default
-----------+------------+-----------+----------+--------------------------------
 id        | bigint     |           | not null | nextval('v3_id_seq'::regclass)
 embedding | vector(16) |           | not null |
Indexes:
    "v3_pkey" PRIMARY KEY, btree (id)

Zjištění počtu zapsaných vektorů:

test=> select count(*) from v3;
 
 count
--------
 100000
(1 row)

Jen pro zajímavost se podívejme na několik záznamů v tabulce v3:

test=> select * from v3 limit 10;
 
  1 | [0.16963236,0.95897835,0.51473874,0.45849234,0.113175556,0.68751436,0.9654627,0.8743154,0.92741364,0.36292967,0.6634899,0.9496507,0.17675222,0.54969317,0
.6603673,0.43664044,0.88731503,0.008242492,...
...
...
...
 10 | [0.7153847,0.7689986,0.75396407,0.05730126,0.74784076,0.34615812,0.250757,0.8888536,0.01584173,0.3476689,0.6566004,0.65356773,0.81164205,0.7884383,0.5832
1726,0.17815429,0.055939768,0.55867046,0.26687172,0.4249743,0.88436496,0.22805858,0.57775205,0.9806432,0.35423538,0.9512405,0.28012627,0.3412125,0.41170472,0.7
2094446,0.35541078,0.64600503,
(10 rows)
Poznámka: předpokladem je, že je rozšíření pgvector nainstalováno a inicializováno.

9. Rychlost zápisu vektorů do tabulky

V prvním reálném benchmarku změříme rychlost zápisu vektorů do prázdné tabulky. V benchmarku se bude postupně zvětšovat počet zapisovaných vektorů a bude se měřit čas zápisu všech těchto vektorů:

# Rozšíření pgvector pro databázi PostgreSQL
#
# - příprava pro benchmark rozšíření pgvector
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
# - měří se časy zápisu vektorů
 
from time import time
 
import psycopg2
from pgvector.psycopg2 import register_vector
 
import numpy as np
 
# parametry benchmarku
DIMENSIONS = 16
MAX_VECTOR_COUNT = 100000
STEPS = 10
 
 
def create_vector_table(connection, dimensions, print_tables=True):
    """Vytvoření tabulky obsahující sloupec s vektory."""
    DROP_TABLE_STATEMENT = """
        DROP TABLE IF EXISTS v3
    """
 
    CREATE_TABLE_STATEMENT = """
        CREATE TABLE IF NOT EXISTS v3 (
            id bigserial PRIMARY KEY,
            embedding vector(%s) NOT NULL
        );
    """
 
    LIST_TABLES_QUERY = """
        SELECT table_schema,table_name
          FROM information_schema.tables
         WHERE table_schema='public'
         ORDER BY table_schema,table_name;
    """
 
    with connection.cursor() as cursor:
        cursor.execute(DROP_TABLE_STATEMENT)
        connection.commit()
 
        cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
        connection.commit()
 
        cursor.execute(LIST_TABLES_QUERY)
        tables = cursor.fetchall()
 
        if print_tables:
            print("Tables in database:")
            for table in tables:
                print(f"    {table[0]}: {table[1]}")
            print()
 
 
def fill_in_vector_table(connection, dimensions, vector_count):
    """Naplnění tabulky náhodnými vektory."""
    t1 = time()
    with connection.cursor() as cursor:
        # prozatím budeme tabulku naplňovat po jednotlivých záznamech
        for i in range(vector_count):
            # náhodný vektor
            vector = np.random.rand(dimensions).astype("float32")
            cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
        connection.commit()
    t2 = time()
    return t2 - t1
 
 
def print_vector_count(connection):
    """Tisk počtu vektorů uložených v tabulce."""
    with connection.cursor() as cursor:
        cursor.execute("SELECT count(*) FROM v3")
        count = cursor.fetchone()
        print(f"Vectors stored in table v3: {count[0]}")
 
 
def main():
    """Vstupní bod do benchmarku."""
    # připojení k databázi
    connection = psycopg2.connect(
        host="", port=5432, user="tester", password="123qwe", dbname="test"
    )
 
    try:
        # budeme používat rozšíření pgvector
        register_vector(connection)
 
        # benchmark - naplnění tabulky s vektory
        print("Vector count", "Time")
        for vector_count in range(0, MAX_VECTOR_COUNT+1, MAX_VECTOR_COUNT//STEPS):
            # vytvoření tabulky s vektory
            create_vector_table(connection, DIMENSIONS, print_tables=False)
            t = fill_in_vector_table(connection, DIMENSIONS, vector_count)
            print(vector_count, t)
 
        # zjištění počtu skutečně uložených vektorů
        print_vector_count(connection)
    finally:
        connection.close()
 
 
# spuštění benchmarku
main()

Naměřené výsledky mohou vypadat následovně:

Vector count Time
0 0.0002460479736328125
10000 0.5639476776123047
20000 1.2714269161224365
30000 1.674407720565796
40000 2.2950122356414795
50000 2.6440303325653076
60000 3.145857334136963
70000 3.888411521911621
80000 5.093366622924805
90000 5.133985996246338
100000 5.900954723358154
Vectors stored in table v3: 100000

10. Vizualizace výsledků benchmarku v grafu

Výsledky benchmarků v tabulkové podobě se poměrně špatně čtou, resp. nemusí z nich být zcela zřejmé, jestli je čas zápisu vektorů lineární funkcí počtu zapisovaných vektorů či nikoli (tady pochopitelně závisí na tom, jaký index je použit). Proto si nyní benchmark z předchozí kapitoly upravíme do takové podoby, aby se na konci naměřené časy zobrazily formou grafu. Pro zobrazení grafu bude použita knihovna Matplotlib:

# Rozšíření pgvector pro databázi PostgreSQL
#
# - příprava pro benchmark rozšíření pgvector
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
# - měří se časy zápisu vektorů
# - vizualizace výsledků formou grafu
 
from time import time
 
import psycopg2
from pgvector.psycopg2 import register_vector
 
import matplotlib.pyplot as plt
import numpy as np
 
# parametry benchmarku
DIMENSIONS = 16
MAX_VECTOR_COUNT = 100000
STEPS = 10
 
 
def create_vector_table(connection, dimensions, print_tables=True):
    """Vytvoření tabulky obsahující sloupec s vektory."""
    DROP_TABLE_STATEMENT = """
        DROP TABLE IF EXISTS v3
    """
 
    CREATE_TABLE_STATEMENT = """
        CREATE TABLE IF NOT EXISTS v3 (
            id bigserial PRIMARY KEY,
            embedding vector(%s) NOT NULL
        );
    """
 
    LIST_TABLES_QUERY = """
        SELECT table_schema,table_name
          FROM information_schema.tables
         WHERE table_schema='public'
         ORDER BY table_schema,table_name;
    """
 
    with connection.cursor() as cursor:
        cursor.execute(DROP_TABLE_STATEMENT)
        connection.commit()
 
        cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
        connection.commit()
 
        cursor.execute(LIST_TABLES_QUERY)
        tables = cursor.fetchall()
 
        if print_tables:
            print("Tables in database:")
            for table in tables:
                print(f"    {table[0]}: {table[1]}")
            print()
 
 
def fill_in_vector_table(connection, dimensions, vector_count):
    """Naplnění tabulky náhodnými vektory."""
    t1 = time()
    with connection.cursor() as cursor:
        # prozatím budeme tabulku naplňovat po jednotlivých záznamech
        for i in range(vector_count):
            # náhodný vektor
            vector = np.random.rand(dimensions).astype("float32")
            cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
        connection.commit()
    t2 = time()
    return t2 - t1
 
 
def print_vector_count(connection):
    """Tisk počtu vektorů uložených v tabulce."""
    with connection.cursor() as cursor:
        cursor.execute("SELECT count(*) FROM v3")
        count = cursor.fetchone()
        print(f"Vectors stored in table v3: {count[0]}")
 
 
def display_graph(vectors_inserted, time_inserts):
    """Vykreslení grafu s časem zápisu n vektorů do tabulky."""
    plt.xlabel("# vectors")
    plt.ylabel("Time (sec)")
    plt.plot(vectors_inserted, time_inserts, "m-", label="insertion")
 
    # přidání legendy
    plt.legend(loc="upper left")
 
    # povolení zobrazení mřížky
    plt.grid(True)
 
    plt.savefig("insert-benchmark-3.png")
 
    # zobrazení grafu
    plt.show()
 
 
def main():
    """Vstupní bod do benchmarku."""
    # připojení k databázi
    connection = psycopg2.connect(
        host="", port=5432, user="tester", password="123qwe", dbname="test"
    )
 
    try:
        # budeme používat rozšíření pgvector
        register_vector(connection)
 
        # benchmark - naplnění tabulky s vektory
        print("Vector count", "Time")
        vectors_inserted = []
        time_inserts = []
 
        for vector_count in range(0, MAX_VECTOR_COUNT+1, MAX_VECTOR_COUNT//STEPS):
            # vytvoření tabulky s vektory
            create_vector_table(connection, DIMENSIONS, print_tables=False)
            t = fill_in_vector_table(connection, DIMENSIONS, vector_count)
            vectors_inserted.append(vector_count)
            time_inserts.append(t)
            print(vector_count, t)
 
        display_graph(vectors_inserted, time_inserts)
 
        # zjištění počtu skutečně uložených vektorů
        print_vector_count(connection)
    finally:
        connection.close()
 
 
# spuštění benchmarku
main()

Výsledky budou změřeny a zobrazeny jak pro databáze s relativně malým počtem vektorů, tak i pro rozsáhlejší databáze. Připomeňme si, že prozatím pracujeme s tabulkami bez „vektorových“ indexů:

Obrázek 1: Čas zápisu n vektorů do původně prázdné tabulky.

Obrázek 1: Čas zápisu n vektorů do původně prázdné tabulky. 

Autor: tisnik, podle licence: Rights Managed
Obrázek 2: Lineární závislost na počtu vektorů je patrná i u rozsáhlejších databází s miliony vektorů.

Obrázek 2: Lineární závislost na počtu vektorů je patrná i u rozsáhlejších databází s miliony vektorů. 

Autor: tisnik, podle licence: Rights Managed

11. Vliv počtu dimenzí na rychlost zápisu

Při vytváření tabulky v databázi PostgreSQL je nutné specifikovat počet dimenzí vektorů, tj. počet jejich prvků:

CREATE TABLE IF NOT EXISTS vektoryXYZ (
    id bigserial PRIMARY KEY,
    embedding vector(počet_dimenzí) NOT NULL
);

V praxi se počet dimenzí volí na základě toho, jaký úkol se vlastně řeší. V případě „běžné“ grafiky by se tedy použily dvě, tři nebo čtyři dimenze, ovšem například při práci s embedded modely je počet dimenzí většinou mnohem vyšší. Dimenze vektorů se v těchto případech většinou pohybuje v rozsahu 256 až 4096 (což už jsou specializované případy); typická hodnota dimenze u embedded modelů používaných v souvislosti s LLVM je 384, 512 nebo 768. A celkový počet vektorů se pohybuje od několika set tisíc (modely pro konkrétní malou oblast) až do stovek milionů (obecné embedded modely).

Počet dimenzí bude mít pravděpodobně vliv na rychlost vyhledávání (což si ověříme příště), ale ovlivní i rychlost zápisu záznamů do databáze. Pokusíme se zjistit, jestli je tato závislost lineární nebo „horší“.

12. Benchmark a jeho výsledky

Pro změření, jaký má vliv počet dimenzí vektorů na rychlost jejich zápisu do databáze bude benchmark nepatrně upraven. Nyní budeme postupně měnit počet dimenzí, zatímco počet zapisovaných vektorů bude konstantní. Počet dimenzí budeme měnit od 16 do 1024:

# parametry benchmarku
VECTOR_COUNT = 20000
MAX_DIMENSIONS = 1024
STEPS = 16

Změní se i kód benchmarku, a to konkrétně smyčka, která zjišťuje časy zápisu:

# benchmark - naplnění tabulky s vektory
print("Dimensions", "Time")
dimensions = []
time_inserts = []
 
for dimension in range(MAX_DIMENSIONS//STEPS, MAX_DIMENSIONS+1, MAX_DIMENSIONS//STEPS):
    # vytvoření tabulky s vektory
    create_vector_table(connection, dimension, print_tables=False)
    t = fill_in_vector_table(connection, dimension, VECTOR_COUNT)
    dimensions.append(dimension)
    time_inserts.append(t)
    print(dimension, t)

Upravený kód benchmarku bude vypadat následovně:

# Rozšíření pgvector pro databázi PostgreSQL
#
# - příprava pro benchmark rozšíření pgvector
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
# - průběžně se mění počet dimenzí
# - měří se časy zápisu vektorů
# - vizualizace výsledků formou grafu
 
from time import time
 
import psycopg2
from pgvector.psycopg2 import register_vector
 
import matplotlib.pyplot as plt
import numpy as np
 
# parametry benchmarku
VECTOR_COUNT = 20000
MAX_DIMENSIONS = 1024
STEPS = 16
 
 
def create_vector_table(connection, dimensions, print_tables=True):
    """Vytvoření tabulky obsahující sloupec s vektory."""
    DROP_TABLE_STATEMENT = """
        DROP TABLE IF EXISTS v3
    """
 
    CREATE_TABLE_STATEMENT = """
        CREATE TABLE IF NOT EXISTS v3 (
            id bigserial PRIMARY KEY,
            embedding vector(%s) NOT NULL
        );
    """
 
    LIST_TABLES_QUERY = """
        SELECT table_schema,table_name
          FROM information_schema.tables
         WHERE table_schema='public'
         ORDER BY table_schema,table_name;
    """
 
    with connection.cursor() as cursor:
        cursor.execute(DROP_TABLE_STATEMENT)
        connection.commit()
 
        cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
        connection.commit()
 
        cursor.execute(LIST_TABLES_QUERY)
        tables = cursor.fetchall()
 
        if print_tables:
            print("Tables in database:")
            for table in tables:
                print(f"    {table[0]}: {table[1]}")
            print()
 
 
def fill_in_vector_table(connection, dimensions, vector_count):
    """Naplnění tabulky náhodnými vektory."""
    t1 = time()
    with connection.cursor() as cursor:
        # prozatím budeme tabulku naplňovat po jednotlivých záznamech
        for i in range(vector_count):
            # náhodný vektor
            vector = np.random.rand(dimensions).astype("float32")
            cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
        connection.commit()
    t2 = time()
    return t2 - t1
 
 
def print_vector_count(connection):
    """Tisk počtu vektorů uložených v tabulce."""
    with connection.cursor() as cursor:
        cursor.execute("SELECT count(*) FROM v3")
        count = cursor.fetchone()
        print(f"Vectors stored in table v3: {count[0]}")
 
 
def display_graph(dimensions, time_inserts):
    """Vykreslení grafu s časem zápisu n-dimenzionálních vektorů do tabulky."""
    plt.xlabel("# dimensions")
    plt.ylabel("Time (sec)")
    plt.plot(dimensions, time_inserts, "m-", label="insertion")
 
    # přidání legendy
    plt.legend(loc="upper left")
 
    # povolení zobrazení mřížky
    plt.grid(True)
 
    plt.savefig("insert-benchmark-4.png")
 
    # zobrazení grafu
    plt.show()
 
 
def main():
    """Vstupní bod do benchmarku."""
    # připojení k databázi
    connection = psycopg2.connect(
        host="", port=5432, user="tester", password="123qwe", dbname="test"
    )
 
    try:
        # budeme používat rozšíření pgvector
        register_vector(connection)
 
        # benchmark - naplnění tabulky s vektory
        print("Dimensions", "Time")
        dimensions = []
        time_inserts = []
 
        for dimension in range(MAX_DIMENSIONS//STEPS, MAX_DIMENSIONS+1, MAX_DIMENSIONS//STEPS):
            # vytvoření tabulky s vektory
            create_vector_table(connection, dimension, print_tables=False)
            t = fill_in_vector_table(connection, dimension, VECTOR_COUNT)
            dimensions.append(dimension)
            time_inserts.append(t)
            print(dimension, t)
 
        display_graph(dimensions, time_inserts)
 
        # zjištění počtu skutečně uložených vektorů
        print_vector_count(connection)
    finally:
        connection.close()
 
 
# spuštění benchmarku
main()

Naměřené výsledky budou vypadat následovně:

Obrázek 3: Rychlost zápisu vektorů s různýcm počtem dimenzí do databáze.

Obrázek 3: Rychlost zápisu vektorů s různým počtem dimenzí do databáze. 

Autor: tisnik, podle licence: Rights Managed
Poznámka: závislost rychlosti zápisu na počtu dimenzí se v tomto případě jeví jako lineární, což velmi pravděpodobně souvisí s I/O operacemi a celkové velikosti tabulky při jejím uložení na disku.

13. Časy vyhledávání vektorů na základě jejich podobnosti

Nejprve si ověříme časovou složitost (konkrétně závislost času vyhledání na počtu uložených vektorů) pro tabulku, která nemá definován žádný speciální „vektorový“ index. Prvky vektorů budou datového typu float32, což sice znamená relativně velké nároky na diskový prostor zabraný tabulkami Postgresu, ovšem samotné výpočty (jsou prováděny na CPU!) jsou poměrně rychlé, protože lze k tomuto účelu využít instrukční sady SSEx či AVX s instrukcemi typu SIMD.

Poznámka: všechny benchmarky, s nimiž se v dalších kapitolách seznámíme, využívají ten nejprimitivnější způsob vyhledávání vektorů na základě jejich podobnosti. Způsob samotného výběru vektorů je dosti naivní – lineárně se prochází celou tabulkou. Až v navazujícím dílu se seznámíme s indexy založenými na sofistikovanějších datových strukturách, které umožňují rychlejší, ovšem (obecně) ne tak přesné vyhledávání.

14. Zdrojový kód benchmarku zjišťujícího časy vyhledávání

Jak jsme si již řekli v předchozí kapitole, budeme v benchmarku zjišťovat časy vyhledávání podobných vektorů. Použijeme přitom porovnání vektorů podle metriky L2 a necháme si vyhledat maximálně pět nejbližších vektorů:

SELECT embedding
  FROM v3
 ORDER BY embedding <-> %s::vector
 LIMIT 5

Funkce, která vyhledávání provádí a navíc i testuje, jestli se vrátil vyžadovaný počet nejbližších vektorů, vypadá takto:

def similarity_search(connection, dimensions, vector_count, repeat_count=1):
    """Vyhledávání na základě podobnosti vektorů."""
    query = """
        SELECT embedding
          FROM v3
         ORDER BY embedding <-> %s::vector
         LIMIT 5
    """
 
    t1 = time()
    with connection.cursor() as cursor:
        for i in range(repeat_count):
            vector = np.random.rand(dimensions).astype("float32")
            cursor.execute(query, (vector, ))
            records = cursor.fetchall()
            assert len(records) == min(vector_count, 5)
    t2 = time()
    return t2 - t1

Pochopitelně si ukážeme i celý zdrojový kód takto upraveného benchmarku:

# Rozšíření pgvector pro databázi PostgreSQL
#
# - příprava pro benchmark rozšíření pgvector
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
# - měří se časy vyhledávání vektorů
# - vizualizace výsledků formou grafu
 
from time import time
 
import psycopg2
from pgvector.psycopg2 import register_vector
 
import matplotlib.pyplot as plt
import numpy as np
 
# parametry benchmarku
DIMENSIONS = 256
MAX_VECTOR_COUNT = 10000
STEPS = 10
REPEAT_COUNT = 100
 
 
def create_vector_table(connection, dimensions, print_tables=True):
    """Vytvoření tabulky obsahující sloupec s vektory."""
    DROP_TABLE_STATEMENT = """
        DROP TABLE IF EXISTS v3
    """
 
    CREATE_TABLE_STATEMENT = """
        CREATE TABLE IF NOT EXISTS v3 (
            id bigserial PRIMARY KEY,
            embedding vector(%s) NOT NULL
        );
    """
 
    LIST_TABLES_QUERY = """
        SELECT table_schema,table_name
          FROM information_schema.tables
         WHERE table_schema='public'
         ORDER BY table_schema,table_name;
    """
 
    with connection.cursor() as cursor:
        cursor.execute(DROP_TABLE_STATEMENT)
        connection.commit()
 
        cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
        connection.commit()
 
        cursor.execute(LIST_TABLES_QUERY)
        tables = cursor.fetchall()
 
        if print_tables:
            print("Tables in database:")
            for table in tables:
                print(f"    {table[0]}: {table[1]}")
            print()
 
 
def fill_in_vector_table(connection, dimensions, vector_count):
    """Naplnění tabulky náhodnými vektory."""
    t1 = time()
    with connection.cursor() as cursor:
        # prozatím budeme tabulku naplňovat po jednotlivých záznamech
        for i in range(vector_count):
            # náhodný vektor
            vector = np.random.rand(dimensions).astype("float32")
            cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
        connection.commit()
    t2 = time()
    return t2 - t1
 
 
def similarity_search(connection, dimensions, vector_count, repeat_count=1):
    """Vyhledávání na základě podobnosti vektorů."""
    query = """
        SELECT embedding
          FROM v3
         ORDER BY embedding <-> %s::vector
         LIMIT 5
    """
 
    t1 = time()
    with connection.cursor() as cursor:
        for i in range(repeat_count):
            vector = np.random.rand(dimensions).astype("float32")
            cursor.execute(query, (vector, ))
            records = cursor.fetchall()
            assert len(records) == min(vector_count, 5)
    t2 = time()
    return t2 - t1
 
 
def print_vector_count(connection):
    """Tisk počtu vektorů uložených v tabulce."""
    with connection.cursor() as cursor:
        cursor.execute("SELECT count(*) FROM v3")
        count = cursor.fetchone()
        print(f"Vectors stored in table v3: {count[0]}")
 
 
def display_graph(vector_count, time_search):
    """Vykreslení grafu s časem vyhledávání n-dimenzionálních vektorů."""
    plt.xlabel("# vectors")
    plt.ylabel("Time (sec)")
    plt.plot(vector_count, time_search, "m-", label="similarity search")
 
    # přidání legendy
    plt.legend(loc="upper left")
 
    # povolení zobrazení mřížky
    plt.grid(True)
 
    plt.savefig("insert-benchmark-5.png")
 
    # zobrazení grafu
    plt.show()
 
 
def main():
    """Vstupní bod do benchmarku."""
    # připojení k databázi
    connection = psycopg2.connect(
        host="", port=5432, user="tester", password="123qwe", dbname="test"
    )
 
    try:
        # budeme používat rozšíření pgvector
        register_vector(connection)
 
        # benchmark - vyhledávání v tabulce s vektory
        print("Vector count", "Time")
        vectors_inserted = []
        time_search = []
 
        for vector_count in range(0, MAX_VECTOR_COUNT+1, MAX_VECTOR_COUNT//STEPS):
            # vytvoření tabulky s vektory
            create_vector_table(connection, DIMENSIONS, print_tables=False)
            fill_in_vector_table(connection, DIMENSIONS, vector_count)
            t = similarity_search(connection, DIMENSIONS, vector_count, REPEAT_COUNT)
            vectors_inserted.append(vector_count)
            time_search.append(t)
            print(vector_count, t)
 
        display_graph(vectors_inserted, time_search)
 
        # zjištění počtu skutečně uložených vektorů
    finally:
        connection.close()
 
 
# spuštění benchmarku
main()

15. Výsledky benchmarku

Výsledky benchmarku, který byl uveden v předchozí kapitole, jsou rozděleny na malé a středně velké databáze:

Obrázek 4: Rychost vyhledávání na základě podobnosti vektorů pro relativně malé databáze.

Obrázek 4: Rychlost vyhledávání na základě podobnosti vektorů pro relativně malé databáze. 

Autor: tisnik, podle licence: Rights Managed
Obrázek 5: Rychlost vyhledávání podobných vektorů pro rozsáhlejší databáze.

Obrázek 5: Rychlost vyhledávání podobných vektorů pro rozsáhlejší databáze. 

Autor: tisnik, podle licence: Rights Managed
Poznámka: můžeme zde vidět přibližně lineární závislost času vyhledávání na počtu vektorů – což je u rozsáhlých databází velký problém!

16. Metriky podporované při testování podobnosti vektorů

Prozatím jsme v benchmarku 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 prostoru). Tato metrika se taktéž označuje L2, ovšem rozšíření pgvector podporuje i další metriky, které jsou vypsány v následující tabulce:

Operátor Stručný popis
<-> Eukleidovská vzdálenost
<#> skalární součin (jedna z nejdůležitějších matematických operací vůbec; vrací se hodnota s otočeným znaménkem!)
<=> 1 – kosinus úhlu mezi vektory (rozsah –1 až 1 za předpokladu, že jsou vektory normalizovány!)
<+> Manhattanská metrika (vzdálenost při pohybu pouze po mřížce)
<~> Hammingova vzdálenost (popíšeme si příště, používá se například u vektorizovaného textu)
<%> Jaccardův index (opět si popíšeme příště)
Poznámka: to, které metriky lze použít, záleží na typu prvků vektorů. Například Hammingovu vzdálenost a Jaccardův index lze využít jen pro vektory bitů (těmi se budeme zabývat v samostatném článku, protože se jedná o poměrně rozsáhlé téma).

17. Změření rychlosti vyhledávání s využitím různých metrik

V dnešním posledním benchmarku otestujeme, jak se bude rychlost vyhledávání vektorů na základě jejich podobnosti lišit podle toho, jaká je použita metrika. Ve skutečnosti nemůžeme změřit všechny metriky, protože některé z nich jsou určeny například jen pro binární vektory. Budeme tedy měřit rychlost vyhledávání při využití metriky L2, výsledků skalárního součinu a výpočtu kosinu dvou vektorů (skutečně vrácené vektory budou odlišné):

def distance_function_to_operator(distance_function):
    operators = {
            "l2": "<->",
            "inner product": "<#>",
            "cosine": "<=>",
    }
    return operators[distance_function]

Funkce pro nalezení maximálně pěti nejbližších vektorů bude používat dotaz s proměnným operátorem:

def similarity_search(connection, dimensions, vector_count, repeat_count=1, distance_function="l2"):
    """Vyhledávání na základě podobnosti vektorů."""
    operator = distance_function_to_operator(distance_function)
 
    query = f"""
        SELECT embedding
          FROM v3
         ORDER BY embedding {operator} %s::vector
         LIMIT 5
    """
 
    t1 = time()
    with connection.cursor() as cursor:
        for i in range(repeat_count):
            vector = np.random.rand(dimensions).astype("float32")
            cursor.execute(query, (vector, ))
            records = cursor.fetchall()
            assert len(records) == min(vector_count, 5)
    t2 = time()
    return t2 - t1

Následuje výpis celého zdrojového kódu dnešního posledního benchmarku:

Školení Zabbix

# Rozšíření pgvector pro databázi PostgreSQL
#
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
# - měří se časy vyhledávání vektorů podle různých kritérií
# - vizualizace výsledků formou grafu
 
from time import time
 
import psycopg2
from pgvector.psycopg2 import register_vector
 
import matplotlib.pyplot as plt
import numpy as np
 
# parametry benchmarku
DIMENSIONS = 256
MAX_VECTOR_COUNT = 10000
STEPS = 10
REPEAT_COUNT = 100
 
 
def create_vector_table(connection, dimensions, print_tables=True):
    """Vytvoření tabulky obsahující sloupec s vektory."""
    DROP_TABLE_STATEMENT = """
        DROP TABLE IF EXISTS v3
    """
 
    CREATE_TABLE_STATEMENT = """
        CREATE TABLE IF NOT EXISTS v3 (
            id bigserial PRIMARY KEY,
            embedding vector(%s) NOT NULL
        );
    """
 
    LIST_TABLES_QUERY = """
        SELECT table_schema,table_name
          FROM information_schema.tables
         WHERE table_schema='public'
         ORDER BY table_schema,table_name;
    """
 
    with connection.cursor() as cursor:
        cursor.execute(DROP_TABLE_STATEMENT)
        connection.commit()
 
        cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
        connection.commit()
 
        cursor.execute(LIST_TABLES_QUERY)
        tables = cursor.fetchall()
 
        if print_tables:
            print("Tables in database:")
            for table in tables:
                print(f"    {table[0]}: {table[1]}")
            print()
 
 
def fill_in_vector_table(connection, dimensions, vector_count):
    """Naplnění tabulky náhodnými vektory."""
    t1 = time()
    with connection.cursor() as cursor:
        # prozatím budeme tabulku naplňovat po jednotlivých záznamech
        for i in range(vector_count):
            # náhodný vektor
            vector = np.random.rand(dimensions).astype("float32")
            cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
        connection.commit()
    t2 = time()
    return t2 - t1
 
 
def distance_function_to_operator(distance_function):
    operators = {
            "l1": "<+>",
            "l2": "<->",
            "inner product": "<#>",
            "cosine": "<=>",
    }
    return operators[distance_function]
 
 
def similarity_search(connection, dimensions, vector_count, repeat_count=1, distance_function="l2"):
    """Vyhledávání na základě podobnosti vektorů."""
    operator = distance_function_to_operator(distance_function)
 
    query = f"""
        SELECT embedding
          FROM v3
         ORDER BY embedding {operator} %s::vector
         LIMIT 5
    """
 
    t1 = time()
    with connection.cursor() as cursor:
        for i in range(repeat_count):
            vector = np.random.rand(dimensions).astype("float32")
            cursor.execute(query, (vector, ))
            records = cursor.fetchall()
            assert len(records) == min(vector_count, 5)
    t2 = time()
    return t2 - t1
 
 
def print_vector_count(connection):
    """Tisk počtu vektorů uložených v tabulce."""
    with connection.cursor() as cursor:
        cursor.execute("SELECT count(*) FROM v3")
        count = cursor.fetchone()
        print(f"Vectors stored in table v3: {count[0]}")
 
 
def display_graph(distance_functions, vector_count, time_search):
    """Vykreslení grafu s časy vyhledávání n-dimenzionálních vektorů."""
    plt.xlabel("# vectors")
    plt.ylabel("Time (sec)")
    for distance_function in distance_functions:
        plt.plot(vector_count, time_search[distance_function], label=distance_function)
 
    # přidání legendy
    plt.legend(loc="upper left")
 
    # povolení zobrazení mřížky
    plt.grid(True)
 
    plt.savefig("insert-benchmark-6.png")
 
    # zobrazení grafu
    plt.show()
 
 
def main():
    """Vstupní bod do benchmarku."""
    # připojení k databázi
    connection = psycopg2.connect(
        host="", port=5432, user="tester", password="123qwe", dbname="test"
    )
 
    distance_functions = (
        #"l1",
        "l2",
        "cosine",
        "inner product",
    )
 
    try:
        # budeme používat rozšíření pgvector
        register_vector(connection)
 
        # benchmark - vyhledávání v tabulce s vektory
        print("Vector count", "Function", "Time")
        vectors_inserted = []
        time_search = {}
 
        # příprava slovníku seznamů
        for distance_function in distance_functions:
            time_search[distance_function] = []
 
        for vector_count in range(0, MAX_VECTOR_COUNT+1, MAX_VECTOR_COUNT//STEPS):
            # vytvoření tabulky s vektory
            create_vector_table(connection, DIMENSIONS, print_tables=False)
            fill_in_vector_table(connection, DIMENSIONS, vector_count)
 
            for distance_function in distance_functions:
                t = similarity_search(connection, DIMENSIONS, vector_count, REPEAT_COUNT, distance_function)
                time_search[distance_function].append(t)
                print(vector_count, distance_function, t)
 
            vectors_inserted.append(vector_count)
 
        display_graph(distance_functions, vectors_inserted, time_search)
    finally:
        connection.close()
 
 
# spuštění benchmarku
main()

18. Výsledky benchmarku

Opět se podívejme na výsledky benchmarku, který ukazuje, jak se liší (nebo naopak neliší) časy porovnávání vektorů s využitím různých metrik. Výsledky jsou rozděleny na „malé“ a středně velké až rozsáhlé databáze:

Obrázek 6: Rychlost vyhledávání s využitím odlišných metrik, výsledky pro malé databáze.

Obrázek 6: Rychlost vyhledávání s využitím odlišných metrik, výsledky pro malé databáze. 

Autor: tisnik, podle licence: Rights Managed
Obrázek 7: Rychlost vyhledávání s využitím odlišných metrik, výsledky pro rozsáhlejší databáze.

Obrázek 7: Rychlost vyhledávání s využitím odlišných metrik, výsledky pro rozsáhlejší databáze.  

Autor: tisnik, podle licence: Rights Managed
Poznámka: výsledky zhruba odpovídají očekávání, protože nejvíce času se tráví při procházení vektory a jen relativně malé množství času výpočty. Porovnání na základě skalárního součinu je rychlejší, protože zde jsou výpočty nejjednodušší (například není nutné počítat délky vektorů atd.).

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

Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python s nainstalovanými balíčky psycopg2 a pgvector byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V tabulce zobrazené níže jsou odkazy na jednotlivé příklady:

# Demonstrační příklad Stručný popis příkladu Cesta
1 create_extension.py registrace rozšíření vector pro uživatele připojeného k databázi https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/cre­ate_extension.py
2 insert_into_v2.py zápis 2D vektorů do tabulky v2 https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/in­sert_into_v2.py
3 insert_normalized.py zápis normalizovaných 2D vektorů do tabulky normalized https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/in­sert_normalized.py
       
4 read_vectors1.py přečtení vektorů bez jejich konverze na skutečné vektory https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/re­ad_vectors1.py
5 read_vectors2.py důkaz, že přečtené vektory jsou vráceny jako řetězce https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/re­ad_vectors2.py
6 read_vectors3.py přečtení vektorů z databáze ve formě N-dimenzionálních polí https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/re­ad_vectors3.py
       
7 select_by_distance1.py výběr vektorů na základě jejich odlišnosti od zadaného vektoru https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/se­lect_by_distance1.py
8 select_by_distance2.py dtto, ale s lepším naformátováním SQL dotazu https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/se­lect_by_distance2.py
9 select_by_distance3.py výběr vektorů s nejmenší odlišností (vzdáleností) https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/se­lect_by_distance3.py
10 select_by_distance4.py výběr vektorů se zadanou nejmenší odlišností (vzdáleností) https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/se­lect_by_distance4.py
       
11 various_distances.py použití různých metrik na nenormalizované vektory https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/va­rious_distances.py
12 various_distances_normalized.py použití různých metrik na normalizované vektory https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/va­rious_distances_normalized­.py
       
13 create_table_v2.py vytvoření tabulky se sloupcem, který bude obsahovat dvouprvkové vektory https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/cre­ate_table_v2.py
14 create_table_v384.py vytvoření tabulky se sloupcem, který bude obsahovat vektory s 384 prvky https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/cre­ate_table_v384.py
16 create_embeddings_one_sentence.py vektorizace jediné věty na základě zvoleného modelu https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/cre­ate_embeddings_one_senten­ce.py
17 create_embeddings.py vektorizace většího počtu vět na základě zvoleného modelu https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/cre­ate_embeddings.py
18 sentence_into_v384.py vložení vektoru reprezentujícího jedinou větu do tabulky se sloupcem typu vektor https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/sen­tence_into_v384.py
19 sentences_into_v384.py vložení většího množství vět do tabulky se sloupcem typu vektor https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/sen­tences_into_v384.py
20 sentence_similarity1.py sémantické vyhledávání přes rozšíření pgvector https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/sen­tence_similarity1.py
       
21 create_table_v384B.py vytvoření tabulky se sloupcem typu vektor a sloupcem s původním textem https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/cre­ate_table_v384B.py
22 sentences_into_v384b.py vložení vektorizovaného i původního textu do tabulky v384b https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/sen­tences_into_v384b.py
23 sentence_similarity2.py sémantické vyhledávání přes rozšíření pgvector https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/sen­tence_similarity2.py
       
24 index-benchmark-1.py základ pro všechny další benchmarky https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/index-benchmark-1.py
25 index-benchmark-2.py benchmark pro změření rychlosti zápisu vektorů do tabulky https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/index-benchmark-2.py
26 index-benchmark-3.py vizualizace benchmarku pro změření rychlosti zápisu vektorů do tabulky https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/index-benchmark-3.py
27 index-benchmark-4.py změření rychlosti zápisů vektorů do tabulky v závislosti na počtu dimenzí https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/index-benchmark-4.py
28 index-benchmark-5.py změření rychlosti vyhledávání vektorů na základě jejich podobnosti https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/index-benchmark-5.py
29 index-benchmark-6.py změření rychlosti vyhledávání s využitím různých metrik podporovaných rozšířením pgvector https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/index-benchmark-6.py
       
30 pyproject.toml soubor s definicí Pythonovského projektu https://github.com/tisnik/most-popular-python-libs/blob/master/pgvector/py­project.toml

20. Odkazy na Internetu

  1. pgvector: vektorová databáze postavená na Postgresu
    https://www.root.cz/clanky/pgvector-vektorova-databaze-postavena-na-postgresu/
  2. Rozšíření PostgreSQL jménem pgvector, embedding a sémantické vyhledávání (1. část)
    https://www.root.cz/clanky/rozsireni-postgresql-jmenem-pgvector-embedding-a-semanticke-vyhledavani-1-cast/
  3. FAISS: knihovna pro rychlé a efektivní vyhledávání podobných vektorů
    https://www.root.cz/clanky/faiss-knihovna-pro-rychle-a-efektivni-vyhledavani-podobnych-vektoru/
  4. FAISS: knihovna pro rychlé a efektivní vyhledávání podobných vektorů (2. část)
    https://www.root.cz/clanky/faiss-knihovna-pro-rychle-a-efektivni-vyhledavani-podobnych-vektoru-2-cast/
  5. Knihovna FAISS a embedding: základ jazykových modelů
    https://www.root.cz/clanky/knihovna-faiss-a-embedding-zaklad-jazykovych-modelu/
  6. Knihovna FAISS a embedding: základ jazykových modelů (2. část)
    https://www.root.cz/clanky/knihovna-faiss-a-embedding-zaklad-jazykovych-modelu-2-cast/
  7. FAISS (Facebook AI Similarity Search)
    https://en.wikipedia.org/wiki/FAISS
  8. FAISS documentation
    https://faiss.ai/
  9. Introduction to Facebook AI Similarity Search (Faiss)
    https://www.pinecone.io/le­arn/series/faiss/faiss-tutorial/
  10. 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
  11. Cosine Distance vs Dot Product vs Euclidean in vector similarity search
    https://medium.com/data-science-collective/cosine-distance-vs-dot-product-vs-euclidean-in-vector-similarity-search-227a6db32edb
  12. F16C
    https://en.wikipedia.org/wiki/F16C
  13. FP16 (AVX-512)
    https://en.wikipedia.org/wiki/AVX-512#FP16
  14. 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/
  15. Is FAISS a Vector Database? Complete Guide
    https://mljourney.com/is-faiss-a-vector-database-complete-guide/
  16. Vector database
    https://en.wikipedia.org/wi­ki/Vector_database
  17. Similarity search
    https://en.wikipedia.org/wi­ki/Similarity_search
  18. Nearest neighbor search
    https://en.wikipedia.org/wi­ki/Nearest_neighbor_search#Ap­proximation_methods
  19. Decoding Similarity Search with FAISS: A Practical Approach
    https://www.luminis.eu/blog/decoding-similarity-search-with-faiss-a-practical-approach/
  20. MetricType and distances
    https://github.com/facebo­okresearch/faiss/wiki/Metric­Type-and-distances
  21. RAG – Retrieval-augmented generation
    https://en.wikipedia.org/wi­ki/Retrieval-augmented_generation
  22. pgvector na GitHubu
    https://github.com/pgvector/pgvector
  23. Why we replaced Pinecone with PGVector
    https://www.confident-ai.com/blog/why-we-replaced-pinecone-with-pgvector
  24. PostgreSQL as VectorDB – Beginner Tutorial
    https://www.youtube.com/wat­ch?v=Ff3tJ4pJEa4
  25. What is a Vector Database? (neobsahuje odpověď na otázku v titulku :-)
    https://www.youtube.com/wat­ch?v=t9IDoenf-lo
  26. PGVector: Turn PostgreSQL Into A Vector Database
    https://www.youtube.com/wat­ch?v=j1QcPSLj7u0
  27. Milvus
    https://milvus.io/
  28. Vector Databases simply explained! (Embeddings & Indexes)
    https://www.youtube.com/wat­ch?v=dN0lsF2cvm4
  29. Vector databases are so hot right now. WTF are they?
    https://www.youtube.com/wat­ch?v=klTvEwg3oJ4
  30. 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
  31. Best 17 Vector Databases for 2025
    https://lakefs.io/blog/12-vector-databases-2023/
  32. Top 15 Vector Databases that You Must Try in 2025
    https://www.geeksforgeeks.org/top-vector-databases/
  33. Picking a vector database: a comparison and guide for 2023
    https://benchmark.vectorvi­ew.ai/vectordbs.html
  34. Top 9 Vector Databases as of Feburary 2025
    https://www.shakudo.io/blog/top-9-vector-databases
  35. What is a vector database?
    https://www.ibm.com/think/to­pics/vector-database
  36. SQL injection
    https://en.wikipedia.org/wi­ki/SQL_injection
  37. Cosine similarity
    https://en.wikipedia.org/wi­ki/Cosine_similarity
  38. Euclidean distance
    https://en.wikipedia.org/wi­ki/Euclidean_distance
  39. Dot product
    https://en.wikipedia.org/wi­ki/Dot_product
  40. Hammingova vzdálenost
    https://cs.wikipedia.org/wi­ki/Hammingova_vzd%C3%A1le­nost
  41. Jaccard index
    https://en.wikipedia.org/wi­ki/Jaccard_index
  42. Manhattanská metrika
    https://cs.wikipedia.org/wi­ki/Manhattansk%C3%A1_metri­ka
  43. FAISS (Facebook AI Similarity Search)
    https://en.wikipedia.org/wiki/FAISS
  44. FAISS documentation
    https://faiss.ai/
  45. Introduction to Facebook AI Similarity Search (Faiss)
    https://www.pinecone.io/le­arn/series/faiss/faiss-tutorial/
  46. 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
  47. Cosine Distance vs Dot Product vs Euclidean in vector similarity search
    https://medium.com/data-science-collective/cosine-distance-vs-dot-product-vs-euclidean-in-vector-similarity-search-227a6db32edb
  48. F16C
    https://en.wikipedia.org/wiki/F16C
  49. FP16 (AVX-512)
    https://en.wikipedia.org/wiki/AVX-512#FP16
  50. 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/
  51. Is FAISS a Vector Database? Complete Guide
    https://mljourney.com/is-faiss-a-vector-database-complete-guide/
  52. FAISS and sentence-transformers in 5 Minutes
    https://www.stephendiehl.com/pos­ts/faiss/
  53. Sentence Transformer: Quickstart
    https://sbert.net/docs/qu­ickstart.html#sentence-transformer
  54. Sentence Transformers: Embeddings, Retrieval, and Reranking
    https://pypi.org/project/sentence-transformers/
  55. uv
    https://docs.astral.sh/uv/
  56. A Gentle Introduction to Retrieval Augmented Generation (RAG)
    https://wandb.ai/cosmo3769/RAG/re­ports/A-Gentle-Introduction-to-Retrieval-Augmented-Generation-RAG---Vmlldzo1MjM4Mjk1
  57. The Beginner’s Guide to Text Embeddings
    https://www.deepset.ai/blog/the-beginners-guide-to-text-embeddings
  58. What are Word Embeddings?
    https://www.youtube.com/wat­ch?v=wgfSDrqYMJ4
  59. How to choose an embedding model
    https://www.youtube.com/wat­ch?v=djp4205tHGU
  60. What is a Vector Database? Powering Semantic Search & AI Applications
    https://www.youtube.com/wat­ch?v=gl1r1XV0SLw
  61. How do Sentence Transformers differ from traditional word embedding models like Word2Vec or GloVe?
    https://zilliz.com/ai-faq/how-do-sentence-transformers-differ-from-traditional-word-embedding-models-like-word2vec-or-glove
  62. BERT (language model)
    https://en.wikipedia.org/wi­ki/BERT_(language_model)
  63. Levenštejnova vzdálenost
    https://cs.wikipedia.org/wi­ki/Leven%C5%A1tejnova_vzd%C3%A1le­nost
  64. pgvector Tutorial: Integrate Vector Search into PostgreSQL
    https://www.datacamp.com/tu­torial/pgvector-tutorial
  65. pgvectorbench
    https://github.com/pgvector­Bench/pgvectorBench
  66. pgvector 0.4.0 performance
    https://supabase.com/blog/pgvector-performance
  67. IVFFlat (Inverted File Flat) Index
    https://skyzh.github.io/write-you-a-vector-db/cpp-05-ivfflat.html
  68. HNSW (Hierarchical Navigable Small Worlds) Index
    https://skyzh.github.io/write-you-a-vector-db/cpp-06–02-hnsw.html
  69. Vector Indexing: Your Guide to Understanding and Implementation
    https://www.teradata.com/insights/ai-and-machine-learning/what-is-vector-index
  70. Vector Indexing Explained: Everything You Need to Know
    https://www.datastax.com/guides/what-is-a-vector-index
  71. Voroného diagram
    https://cs.wikipedia.org/wi­ki/Voron%C3%A9ho_diagram
  72. Matplotlib Home Page
    http://matplotlib.org/
  73. matplotlib (Wikipedia)
    https://en.wikipedia.org/wi­ki/Matplotlib

Autor článku

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