pgvector: vektorová databáze postavená na Postgresu

6. 3. 2025
Doba čtení: 26 minut

Sdílet

Autor: Depositphotos
Ukážeme si základní vlastnosti rozšíření Postgresu, které se jmenuje pgvector. Umožňuje používat PostgreSQL ve funkci vektorové databáze, například při zpracování přirozeného jazyka, rozpoznávání obrázků, detekci anomálií atd.

Obsah

1. pgvector – vektorová databáze postavená na Postgresu

2. Instalace rozšíření pgvector

3. Registrace rozšíření pgvector

4. Vytvoření tabulky pro 3D vektory

5. Operace zápisu a čtení 3D vektorů

6. Tabulka pro uložení 2D vektorů: základ pro další demonstrační příklady

7. Vizualizace výpočtů vzdáleností pro 2D vektory

8. Výběry vektorů na základě jejich podobnosti

9. L2 metrika pro nalezení podobných vektorů v klauzuli ORDER BY

10. L2 metrika pro nalezení podobných vektorů v klauzuli WHERE

11. Další metriky podporované rozšířením pgvector

12. Zpracování normalizovaných vektorů

13. Využití rozšíření pgvector z jazyka Python

14. Registrace rozšíření pgvector z Pythonu

15. Zápis nových záznamů obsahujících vektory

16. Dotazy vracející vektory

17. Výběr vektorů na základě jejich podobnosti

18. Použití různých metrik na nenormalizované i normalizované vektory

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

20. Odkazy na Internetu

1. pgvector – vektorová databáze postavená na Postgresu

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 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 atd. Tyto operace se používají v mnoha oblastech, například při rozpoznávání a zpracování přirozeného jazyka (NLP), 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).

Ovšem namísto specializovaných databází je možné zvolit technologii, která se osvědčuje už desítky let. Jedná se o relační databázi PostgreSQL, pro niž vzniklo rozšíření nazvané pgvector. Toto rozšíření umožňuje efektivní ukládání vektorů do tabulek, ale zejména podporuje i vyhledávání vektorů na základě jejich podobnosti. Dnes si ukážeme zcela základní operace podporované rozšířením pgvector i práci s vektory z Pythonu (tedy volání příkazů Postgresu z Pythonu přes balíček psycopg2). Příště si ukážeme pokročilejší operace, především způsob práce s indexy, které mají u tabulek s vektory poněkud specifický význam.

Příště si taktéž ukážeme, jak se pracuje s hodnotami typu half float, které jsou užitečné, protože umožňují pracovat s vektory o délce přibližně 4000 prvků i v rámci jediného osmikilobajtového bloku Postgresu (a mnohé LLM využívají poměrně dlouhé vektory).

2. Instalace rozšíření pgvector

Před vytvořením tabulky umožňující uložení vektorů je nutné si nainstalovat a zaregistrovat rozšíření pgvector (předpokladem je, že samotný PostgreSQL máte nainstalován). V tom nejjednodušším případě bude toto rozšíření dostupné ve formě standardního balíčku vaší distribuce Linuxu. Instalace je snadná, například:

$ sudo dnf install pgvector_17
Poznámka: v tomto případě 17 odpovídá verzi Postgresu, kterou zjistíte například příkazem:
$ postgres --version

Ovšem instalaci lze provést i z repositáře samotného Postgresu. Například pro ekosystém RPM jsou příslušné balíčky dostupné na stránce https://yum.postgresql.org/. Repositář je nutné nejprve přidat do seznamu aktivních repositářů a poté provést běžnou instalaci

Alternativně si můžeme rozšíření sami přeložit a nainstalovat postupem, který je popsaný zde:

cd /tmp
git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git
cd pgvector
make
make install

Po instalaci je vhodné pro jistotu zjistit, jaké soubory se vlastně nainstalovaly a do jakého místa. Rozšíření pgvector obsahuje jednu dynamickou knihovnu, několik SQL souborů a konfigurační soubor. Najděme nejdříve dynamickou knihovnu s tímto rozšířením. Její umístění zjistíme takto:

$ pg_config --pkglibdir
 
/usr/lib64/pgsql

Zjistíme obsah tohoto adresáře:

$ ls -l `pg_config --pkglibdir`
 
...
...
...
-rwxr-xr-x. 1 root root  15248 Feb 26  2024 utf8_and_iso8859_1.so
-rwxr-xr-x. 1 root root  35736 Feb 26  2024 utf8_and_iso8859.so
-rwxr-xr-x. 1 root root 174992 Feb 26  2024 utf8_and_johab.so
-rwxr-xr-x. 1 root root 142192 Feb 26  2024 utf8_and_sjis2004.so
-rwxr-xr-x. 1 root root  97160 Feb 26  2024 utf8_and_sjis.so
-rwxr-xr-x. 1 root root 179088 Feb 26  2024 utf8_and_uhc.so
-rwxr-xr-x. 1 root root  39800 Feb 26  2024 utf8_and_win.so
-rwxr-xr-x. 1 root root  15568 Feb 26  2024 uuid-ossp.so
-rwxr-xr-x. 1 root root 119528 Apr  3  2024 vector.so

Prakticky celé rozšíření pgvector je tedy realizováno v knihovně, jejíž velikost je přibližně 115 kB (to není na dnešní poměry mnoho).

Další soubory, které jsou nainstalovány, budou umístěny do adresáře:

$ pg_config --sharedir
 
/usr/share/pgsql

Opět se podívejme na jeho obsah (zkráceno):

$ ls -1 `pg_config --sharedir`/extension
 
vector--0.4.1--0.4.2.sql
vector--0.4.2--0.4.3.sql
vector--0.4.3--0.4.4.sql
vector--0.4.4--0.5.0.sql
vector--0.5.0--0.5.1.sql
vector--0.5.1--0.6.0.sql
vector--0.6.0--0.6.1.sql
vector--0.6.1--0.6.2.sql
vector--0.6.2.sql
vector.control
xml2--1.0--1.1.sql
xml2--1.1.sql
xml2.control

Všechny soubory vector*.sql patří do rozšíření pgvector.

Zbývá nám konfigurační soubor, jehož obsah si vypíšeme takto:

$ cat `pg_config --sharedir`/extension/vector.control
 
comment = 'vector data type and ivfflat and hnsw access methods'
default_version = '0.6.2'
module_pathname = '$libdir/vector'
relocatable = true

Do tohoto souboru přidejte řádek trusted = true, takže jeho nový obsah bude:

$ cat `pg_config --sharedir`/extension/vector.control
 
comment = 'vector data type and ivfflat and hnsw access methods'
default_version = '0.6.2'
module_pathname = '$libdir/vector'
relocatable = true
trusted = true
Poznámka: pokud tento řádek nepřidáte, nebude možné rozšíření pgvector aktivovat pro další uživatele databáze.

3. Registrace rozšíření pgvector

Posledním krokem, který je nutné udělat před založením „vektorové“ tabulky, je registrace rozšíření pro uživatele, který je přihlášen přes psql (ale i s využitím dalších klientů). Tato registrace probíhá příkazem CREATE (což může být poněkud matoucí):

test=> CREATE EXTENSION IF NOT EXISTS vector;

To, zda je rozšíření zaregistrováno, zjistíme takto:

test=> \dx
                             List of installed extensions
  Name   | Version |   Schema   |                     Description
---------+---------+------------+------------------------------------------------------
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
 vector  | 0.6.2   | public     | vector data type and ivfflat and hnsw access methods
(2 rows)
Poznámka: existují i další způsoby zjištění, jestli se pgvector používá, ale \dx je nejjednodušší.

4. Vytvoření tabulky pro 3D vektory

V tomto okamžiku již můžeme v tabulkách použít pro vybrané sloupce typ vector. Vyzkoušejme si to na jednoduchém příkladu; konkrétně na tabulce nazvané v3, která bude obsahovat sloupec id s jednoznačným identifikátorem (primární klíč) a další sloupec embedding s třídimenzionálními vektory, tj. vektory obsahujícími tři prvky. Takovou tabulku lze vytvořit následujícím příkazem:

test=> CREATE TABLE v3 (id bigserial PRIMARY KEY, embedding vector(3));
 
CREATE TABLE

Vypíšeme si všechny tabulky, které byly vytvořeny a do kterých je možné přistupovat:

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

Výpis struktury (schématu) právě vytvořené tabulky v3:

test=> \d+ v3
 
                                                          Table "public.v3"
  Column   |   Type    | Collation | Nullable |            Default             | Storage  | Compression | Stats target | Description
-----------+-----------+-----------+----------+--------------------------------+----------+-------------+--------------+-------------
 id        | bigint    |           | not null | nextval('v3_id_seq'::regclass) | plain    |             |              |
 embedding | vector(3) |           |          |                                | external |             |              |
Indexes:
    "v3_pkey" PRIMARY KEY, btree (id)
Access method: heap
Poznámka: povšimněte si, že je prozatím vytvořen pouze jediný index umožňující rychlý výběr záznamů na základě zadaného primárního klíče. To je v praxi nedostačující; podrobnosti si vysvětlíme příště.

5. Operace zápisu a čtení 3D vektorů

Pro zápis nových záznamů do tabulky v3 je nutné nějakým způsobem specifikovat i zapisované vektory. Musí se jednat o trojice hodnot (pro jednoduchost předpokládejme, že se prozatím bude jednat o trojici celých čísel). Taková trojice se zapíše do řetězce (!) s tímto formátem:

'[hodnota1, hodnota2, hodnota3]'

Můžeme si to otestovat na příkazu INSERT, kterým do tabulky vložíme čtyři vektory. V příkazu jsou specifikovány pouze hodnoty ukládané do sloupce embedding; zápis jednoznačných identifikátorů do sloupce id ponecháme na databázi:

test=> INSERT INTO v3 (embedding) VALUES ('[1,2,3]'), ('[1,2,2]'), ('[4,5,6]'), ('[5, 5, 5]');
 
INSERT 0 4

V dalším kroku se přesvědčíme, že do tabulky skutečně byly vloženy čtyři záznamy:

test=> SELECT * FROM v3;
 
 id | embedding
----+-----------
  1 | [1,2,3]
  2 | [1,2,2]
  3 | [4,5,6]
  4 | [5,5,5]
(4 rows)

Výpis hodnot pouze ze sloupce obsahující vektory:

test=> SELECT embedding FROM v3;
 
 embedding
-----------
 [1,2,3]
 [1,2,2]
 [4,5,6]
 [5,5,5]
(4 rows)

K dispozici jsou i všechny další klauzule podporované dotazem SELECT, například:

test=> SELECT * FROM v3 LIMIT 1;
 
 id | embedding
----+-----------
  1 | [1,2,3]
(1 row)
 
 
 
test=> select * from v3 order by id desc;
 id | embedding
----+-----------
  4 | [5,5,5]
  3 | [4,5,6]
  2 | [1,2,2]
  1 | [1,2,3]
(4 rows)

Ovšem navíc jsou k dispozici i speciální operátory určené pro zápis podmínek na základě vzdálenosti (resp. přesněji řečeno podobnosti) vektorů. Tomuto tématu se budeme podrobněji věnovat v navazujících kapitolách, protože se jedná o jednu z nejdůležitějších vlastností rozšíření pgvector.

6. Tabulka pro uložení 2D vektorů: základ pro další demonstrační příklady

Jeden z důvodů, proč vektorové databáze existují, je požadavek na nalezení podobných vektorů na základě zvolené metriky (těch se používá hned několik, jak si ostatně řekneme v dalším textu). Pro ilustraci, jakým způsobem se vlastně podobné vektory hledají, si vytvoříme tabulky, do které budou uloženy dvoudimenzionální vektory, tj. pouze dvojice numerických hodnot. Takové vektory lze snadno vizualizovat v rovině.

Tabulka se bude jmenovat v2 a bude obsahovat dvojici sloupců – jednoznačný identifikátor (a současně i primární klíč) a 2D vektor:

test=> CREATE TABLE v2 (id bigserial PRIMARY KEY, embedding vector(2));
CREATE TABLE

Následně do této tabulky vložíme patnáct záznamů. Nemusíme přitom volat příkaz INSERT patnáctkrát, protože v rámci jednoho příkazu lze do tabulky vložit větší množství záznamů:

test=> INSERT INTO v2 (embedding) VALUES ('[-5,  5]'), ('[-4,  3]'), ('[-3,  5]');
test=> INSERT INTO v2 (embedding) VALUES ('[ 3, -5]'), ('[ 4, -3]'), ('[ 5, -5]');
test=> INSERT INTO v2 (embedding) VALUES ('[ 3,  3]'), ('[ 3,  4]'), ('[ 3,  5]');
test=> INSERT INTO v2 (embedding) VALUES ('[ 4,  3]'), ('[ 4,  4]'), ('[ 4,  5]');
test=> INSERT INTO v2 (embedding) VALUES ('[ 5,  3]'), ('[ 5,  4]'), ('[ 5,  5]');

7. Vizualizace výpočtů vzdáleností pro 2D vektory

Tabulka s dvoudimenzionálními vektory nám poměrně dobře poslouží pro popis nových operátorů, které byly do databáze PostgreSQL přidány v rozšíření pgvector. Nejprve si vypišme obsah této tabulky. Není to nic těžkého:

test=> SELECT * FROM v2;
 
 id | embedding
----+-----------
  1 | [-5,5]
  2 | [-4,3]
  3 | [-3,5]
  4 | [3,-5]
  5 | [4,-3]
  6 | [5,-5]
  7 | [3,3]
  8 | [3,4]
  9 | [3,5]
 10 | [4,3]
 11 | [4,4]
 12 | [4,5]
 13 | [5,3]
 14 | [5,4]
 15 | [5,5]
(15 rows)

Na 2D vektory se můžeme (i když to není zcela přesné) dívat jako na koncové souřadnice orientovaných šipek vycházejících z počátku kartézské soustavy souřadnic. V případě, že si tyto souřadnice vykreslíme, získáme následující graf (do kterého jsem doplnil numerické souřadnice u vybraných koncových bodů):

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

8. Výběry vektorů na základě jejich podobnosti

Nyní se dostáváme k velmi důležité vlastnosti rozšíření pgvector. V rámci tohoto rozšíření je totiž umožněno v podmínkách v příkazech SELECT, DELETE a UPDATE použít nové operátory. Jeden z těchto operátorů se zapisuje těmito znaky:

<->

Tento operátor dokáže porovnat dvojici vektorů na základě jejich vzdálenosti, přičemž se pro výpočet vzdálenosti používá klasická Eukleidovská metrika, které zjistí vzdálenosti koncových bodů vektorů (v našem případě ve dvourozměrném prostoru). V navazujících kapitolách si ukážeme některá možná použití tohoto operátoru i operátorů dalších, pomocí nichž se vektory porovnávají na základě odlišných metrik.

9. L2 metrika pro nalezení podobných vektorů v klauzuli ORDER BY

Výše zmíněný operátor ↔ můžeme použít v klauzuli ORDER BY, která zajistí uspořádání vektorů na základě jejich vzdálenosti od zadaného vektoru (nebo, chcete-li, na základě podobnosti vektorů z tabulky se zadaným vektorem). Podívejme se na jednoduchý příklad:

test=> SELECT * FROM v2 ORDER BY embedding <-> '[-4,4]' limit 3;

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

 id | embedding
----+-----------
  2 | [-4,3]
  3 | [-3,5]
  1 | [-5,5]
(3 rows)

Což si pochopitelně můžeme vizualizovat v rovině (hvězdičkou je naznačen vektor z podmínky, kolečkem vektory vrácené jako výsledek dotazu a tečkou ostatní vektory, které neodpovídají dotazu):

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

Podobně můžeme získat pět vektorů, které jsou nejpodobnější vektoru [4, 4]:

test=> SELECT * FROM v2 ORDER BY embedding <-> '[4,4]' limit 5;
 id | embedding
----+-----------
 11 | [4,4]
 12 | [4,5]
 10 | [4,3]
  8 | [3,4]
 14 | [5,4]
(5 rows)

Výsledných pět vrácených vektorů si opět zobrazíme v 2D rovině:

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

Tři vektory nejpodobnější vektoru [3, 3]:

test=> SELECT * FROM v2 ORDER BY embedding <-> '[3,3]' limit 3;
 id | embedding
----+-----------
  7 | [3,3]
  8 | [3,4]
 10 | [4,3]
(3 rows)

V 2D rovině:

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

Tentýž dotaz, ovšem pro šest nejpodobnějších vektorů:

test=> SELECT * FROM v2 ORDER BY embedding <-> '[3,3]' limit 6;
 id | embedding
----+-----------
  7 | [3,3]
 10 | [4,3]
  8 | [3,4]
 11 | [4,4]
  9 | [3,5]
 13 | [5,3]
(6 rows)

Výsledek v 2D rovině:

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

10. L2 metrika pro nalezení podobných vektorů v klauzuli WHERE

Kromě vyhledání N nejbližších (nejpodobnějších) vektorů je možné podmínku se vzdáleností/podobností pochopitelně zapsat i do klauzule WHERE. To si můžeme snadno ukázat na dotazu, který nalezne všechny vektory, jejichž vzdálenost od vektoru [3,3] je menší než 3:

test=> SELECT * FROM v2 WHERE embedding <-> '[3,3]' < 3;
 
 id | embedding
----+-----------
  7 | [3,3]
  8 | [3,4]
  9 | [3,5]
 10 | [4,3]
 11 | [4,4]
 12 | [4,5]
 13 | [5,3]
 14 | [5,4]
 15 | [5,5]
(9 rows)

Výsledných devět vektorů si můžeme v rovině vizualizovat následovně:

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

Ovšem stejnou klauzuli pochopitelně můžeme použít i v příkazu DELETE, nikoli pouze v příkazu SELECT. Smažeme ty vektory, jejichž vzdálenost od [3,3] je ostře menší než 2. Celkem se smažou čtyři záznamy:

test=> DELETE FROM v2 WHERE embedding <-> '[3,3]' < 2;
DELETE 4

Po provedení tohoto příkazu již nebude tabulka v2 obsahovat 15 záznamů, ale pouze jedenáct záznamů:

test=> SELECT * FROM v2;
 
 id | embedding
----+-----------
  1 | [-5,5]
  2 | [-4,3]
  3 | [-3,5]
  4 | [3,-5]
  5 | [4,-3]
  6 | [5,-5]
  9 | [3,5]
 12 | [4,5]
 13 | [5,3]
 14 | [5,4]
 15 | [5,5]
(11 rows)

Vizuálně (smazané vektory jsou označeny křížkem):

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

11. Další metriky podporované rozšířením pgvector

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 prostoru). Tato metrika se taktéž označuje L2, ovšem pgvector podporuje i další metriky, které jsou vypsány v 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 (popíšeme si příště, 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ě)

Eukleidovskou vzdálenost již známe, ovšem pro úplnost si ukažme, jaké mohou být výsledky:

test=> SELECT embedding, embedding <-> '[3,3]' as L2_distance FROM v2;
 embedding |    l2_distance
-----------+--------------------
 [-5,5]    |  8.246211251235321
 [-4,3]    |                  7
 [-3,5]    |  6.324555320336759
 [3,-5]    |                  8
 [4,-3]    |  6.082762530298219
 [5,-5]    |  8.246211251235321
 [3,3]     |                  0
 [3,4]     |                  1
 [3,5]     |                  2
 [4,3]     |                  1
 [4,4]     | 1.4142135623730951
 [4,5]     |   2.23606797749979
 [5,3]     |                  2
 [5,4]     |   2.23606797749979
 [5,5]     | 2.8284271247461903
(15 rows)

Kosinus úhlů nám dává představu o tom, jak moc se liší úhel mezi dvojicí vektorů. Jedná se o velmi často používanou metriku při porovnávání tokenizovaných a vektorizovaných textů. Pro nenormalizované hodnoty však nedostaneme korektní výsledky, o čemž se lze snadno přesvědčit:

test=> SELECT embedding, embedding <=> '[3,3]' as cos_distance FROM v2;
 embedding |     cos_distance
-----------+----------------------
 [-5,5]    |                    1
 [-4,3]    |   1.1414213562373094
 [-3,5]    |    0.757464374963667
 [3,-5]    |    1.242535625036333
 [4,-3]    |   0.8585786437626906
 [5,-5]    |                    1
 [3,3]     |                    0
 [3,4]     | 0.010050506338833531
 [3,5]     | 0.029857499854668013
 [4,3]     | 0.010050506338833531
 [4,4]     |                    0
 [4,5]     | 0.006116265326381098
 [5,3]     | 0.029857499854668013
 [5,4]     | 0.006116265326381098
 [5,5]     |                    0
(15 rows)

Totéž platí i pro výpočet skalárního součinu. Pro nenormalizované hodnoty dostaneme výsledky, které nejsou příliš použitelné:

test=> SELECT embedding, embedding <#> '[3,3]' as inner_product_distance FROM v2;
 embedding | inner_product_distance
-----------+------------------------
 [-5,5]    |                     -0
 [-4,3]    |                      3
 [-3,5]    |                     -6
 [3,-5]    |                      6
 [4,-3]    |                     -3
 [5,-5]    |                     -0
 [3,3]     |                    -18
 [3,4]     |                    -21
 [3,5]     |                    -24
 [4,3]     |                    -21
 [4,4]     |                    -24
 [4,5]     |                    -27
 [5,3]     |                    -24
 [5,4]     |                    -27
 [5,5]     |                    -30
(15 rows)

12. Zpracování normalizovaných vektorů

Pro zjištění podobnosti vektorů se nejčastěji používá L2 metrika, L1 metrika, skalární součin a kosinus úhlu mezi vektory. Ovšem u posledních dvou metrik, tj. u skalárního součinu a kosinu úhlu, je nutné, aby vektory uložené v databázi byly normalizovány, tj. aby všechny měly délku rovnu 1 nebo 0. Takové vektory mám umístěny v tabulce nazvané normalized. Tato tabulka vznikla skriptem, který je ukázaný v patnácté kapitole a vektory zde uložené mají stejný směr, jako vektory z tabulky v2, ovšem jejich délka je rovna jedné:

test=> SELECT * FROM normalized;
 id |        embedding
----+--------------------------
  1 | [-0.70710677,0.70710677]
  2 | [-0.8,0.6]
  3 | [-0.51449573,0.8574929]
  4 | [0.51449573,-0.8574929]
  5 | [0.8,-0.6]
  6 | [0.70710677,-0.70710677]
  7 | [0.70710677,0.70710677]
  8 | [0.6,0.8]
  9 | [0.51449573,0.8574929]
 10 | [0.8,0.6]
 11 | [0.70710677,0.70710677]
 12 | [0.62469506,0.7808688]
 13 | [0.8574929,0.51449573]
 14 | [0.7808688,0.62469506]
 15 | [0.70710677,0.70710677]
(15 rows)

Rozdíl úhlů vektorů nyní bude vracet korektní výsledky (povšimněte si, že vektory mířící stejným směrem [1,1] mají nulový výsledek, protože se nevrací přímo kosinus úhlů, ale hodnota 1-kosinus, což se lépe interpretuje):

test=> SELECT embedding, embedding <=> '[0.8,0.6]' as cos_distance FROM v2;
 
        embedding         |     cos_distance
--------------------------+----------------------
 [-0.70710677,0.70710677] |                    1
 [-0.8,0.6]               |   1.1414213626001684
 [-0.51449573,0.8574929]  |   0.7574643856826806
 [0.51449573,-0.8574929]  |   1.2425356143173194
 [0.8,-0.6]               |   0.8585786373998318
 [0.70710677,-0.70710677] |                    1
 [0.70710677,0.70710677]  |                    0
 [0.6,0.8]                | 0.010050431991999509
 [0.51449573,0.8574929]   | 0.029857542730722608
 [0.8,0.6]                | 0.010050431991999509
 [0.70710677,0.70710677]  |                    0
 [0.62469506,0.7808688]   | 0.006116289048590184
 [0.8574929,0.51449573]   | 0.029857542730722608
 [0.7808688,0.62469506]   | 0.006116289048590184
 [0.70710677,0.70710677]  |                    0
(15 rows)

A konečně si můžeme nechat spočítat skalární součin. Povšimněte si, že se ve skutečnosti vrací hodnota s opačným znaménkem, takže pro nejpodobnější vektory získáme nejmenší hodnotu a naopak:

test=> SELECT embedding, embedding <#> '[3,3]' as inner_product_distance FROM normalized;
 
        embedding         | inner_product_distance
--------------------------+------------------------
 [-0.70710677,0.70710677] |                     -0
 [-0.8,0.6]               |     0.6000000238418579
 [-0.51449573,0.8574929]  |    -1.0289915800094604
 [0.51449573,-0.8574929]  |     1.0289915800094604
 [0.8,-0.6]               |    -0.6000000238418579
 [0.70710677,-0.70710677] |                     -0
 [0.70710677,0.70710677]  |     -4.242640495300293
 [0.6,0.8]                |     -4.200000286102295
 [0.51449573,0.8574929]   |     -4.115965843200684
 [0.8,0.6]                |     -4.200000286102295
 [0.70710677,0.70710677]  |     -4.242640495300293
 [0.62469506,0.7808688]   |     -4.216691970825195
 [0.8574929,0.51449573]   |     -4.115965843200684
 [0.7808688,0.62469506]   |     -4.216691970825195
 [0.70710677,0.70710677]  |     -4.242640495300293
(15 rows)

13. Využití rozšíření pgvector z jazyka Python

Ve druhé části dnešního článku si ukážeme základní způsoby využití rozšíření pgvector z programovacího jazyka Python. Demonstrační příklady budou postaveny nad balíčkem psycopg2, který však sám o sobě podporu pro práci s vektory neumožňuje, což se projeví například tak, že se namísto vektorů vrací řetězce, které vektory obsahují ve svém textu (a bylo by je tedy nutné parsovat). Aby bylo možné s rozšířením pgvector pracovat bez komplikací, je nutné doinstalovat Pythonovský balíček nazvaný taktéž pgvector. Je to snadné:

$ pip install --user pgvector
 
Collecting pgvector
  Downloading pgvector-0.3.6-py3-none-any.whl.metadata (13 kB)
Requirement already satisfied: numpy in /home/ptisnovs/.local/lib/python3.12/site-packages (from pgvector) (2.2.0)
Downloading pgvector-0.3.6-py3-none-any.whl (24 kB)
Installing collected packages: pgvector
Successfully installed pgvector-0.3.6
Poznámka: povšimněte si, že jednou ze závislostí tohoto balíčku je i NumPy. To je vlastně logické, protože právě díky NumPy je možné v Pythonu jednoduše pracovat jak s vektory, tak i s maticemi či s vícerozměrnými poli (vektory jsou v tomto kontextu pouze jedním specifickým typem n-rozměrného pole).

14. Registrace rozšíření pgvector z Pythonu

V úvodní části dnešního článku jsme si řekli, že rozšíření pgvector je nutné v PostgreSQL povolit (zaregistrovat) pro každou databázi. Tuto operaci je možné provést i přímo z Pythonu, protože příkaz CREATE EXTENSION vector můžeme zavolat přímo přes balíček psycopg2 (což se může hodit ve chvíli, kdy je databáze vytvářená programově a nikoli ručně z konzole psql). Registrace se provede snadno:

import psycopg2
 
from pgvector.psycopg2 import register_vector
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
with connection.cursor() as cursor:
    cursor.execute("CREATE EXTENSION IF NOT EXISTS vector")

V praxi by samozřejmě bylo mnohem lepší konfiguraci připojení k databázi (port, jméno uživatele, jméno databáze atd.) číst z nějakého konfiguračního souboru a v případě hesla ze zabezpečeného úložiště.

Poznámka: tento skript je možné pustit vícekrát, protože i když je rozšíření zaregistrováno, nedojde k žádné chybě ani k výjimce.

15. Zápis nových záznamů obsahujících vektory

Předpokládejme, že v databázi (schématu) je již vytvořena tabulka nazvaná v2, k čemuž můžeme použít příkaz uvedený v šesté kapitole. Připomeňme si, že tato tabulka má pouze dva sloupce – první obsahuje ID (automaticky generované) a druhý pak dvourozměrný vektor. Jakým způsobem se provede zápis nových vektorů do takové tabulky? Je to jednoduché, což je ostatně patrné i z následujícího zdrojového kódu. K databázi se nejdříve běžným způsobem připojíme a následně zavoláme funkci register_vector – to je důležité, protože jinak by nebylo možné vektory přímo zapisovat, ale ani číst. Následně můžeme zavolat příkaz:

INSERT INTO v2 (embedding) VALUES (%s)

tento příkaz obsahuje znak %s, za který je nutné doplnit příslušný vektor, tj. konkrétně hodnotu typu numpy.array obsahující dvojici numerických hodnot. Toto doplnění za nás provede psycopg2 automaticky – nemusíme (a ani bychom nikdy neměli) příslušný SQL příkaz „skládat“ ručně, což je nepraktické a navíc náchylné na SQL Injection. Použijeme tedy tento postup:

vector = np.array([x[i], y[i]])
 
cursor.execute("INSERT INTO v2 (embedding) VALUES (%s)", (vector, ))

Ve skriptu je ukázáno naplnění tabulky patnácti dvourozměrnými vektory, jejichž x-ové souřadnice jsou uloženy v seznamu x a y-ové souřadnice v seznamu y:

import psycopg2
 
import numpy as np
from pgvector.psycopg2 import register_vector
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
register_vector(connection)
 
x = [-5, -4, -3,    3,  4,  5,   3, 3, 3, 4, 4, 4, 5, 5, 5]
y = [ 5,  3,  5,   -5, -3, -5,   3, 4, 5, 3, 4, 5, 3, 4, 5]
 
with connection.cursor() as cursor:
    for i in range(len(x)):
        vector = np.array([x[i], y[i]])
        print(vector)
        cursor.execute("INSERT INTO v2 (embedding) VALUES (%s)", (vector, ))
    connection.commit()

Podobným způsobem můžeme naplnit tabulku nazvanou normalized taktéž patnácti vektory. Ty však nyní budou normalizovány tak, aby jejich délka byla rovna jedné (kromě nulového vektoru). Délka vektoru je vypočtena funkcí np.linalg.norm:

import psycopg2
 
import numpy as np
from pgvector.psycopg2 import register_vector
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
register_vector(connection)
 
x = [-5, -4, -3,    3,  4,  5,   3, 3, 3, 4, 4, 4, 5, 5, 5]
y = [ 5,  3,  5,   -5, -3, -5,   3, 4, 5, 3, 4, 5, 3, 4, 5]
 
with connection.cursor() as cursor:
    for i in range(len(x)):
        vector = np.array([x[i], y[i]], dtype="float")
        norm = np.linalg.norm(vector)
        vector /= norm
        print(vector)
        cursor.execute("INSERT INTO normalized (embedding) VALUES (%s)", (vector, ))
    connection.commit()

16. Dotazy vracející vektory

Další operací – vlastně nejčastější operací vůbec – je výběr vektorů, resp. přesněji řečeno provedení dotazů (query), které vrací záznamy obsahující vektory. Nejprve si vyzkoušejme prostý dotaz, který vrátí všechny záznamy z tabulky v2:

import psycopg2
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
with connection.cursor() as cursor:
    cursor.execute("SELECT * FROM v2")
    records = cursor.fetchall()
    for record in records:
        print(record)

Výsledky vypadají takto:

(1, '[-5,5]')
(2, '[-4,3]')
(3, '[-3,5]')
(4, '[3,-5]')
(5, '[4,-3]')
(6, '[5,-5]')
(7, '[3,3]')
(8, '[3,4]')
(9, '[3,5]')
(10, '[4,3]')
(11, '[4,4]')
(12, '[4,5]')
(13, '[5,3]')
(14, '[5,4]')
(15, '[5,5]')

Na první pohled by možná mohlo zdát, že je vše v pořádku, ovšem pokusme se zjistit typ hodnot, které čteme z druhého sloupce:

import psycopg2
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
with connection.cursor() as cursor:
    cursor.execute("SELECT * FROM v2")
    records = cursor.fetchall()
    for record in records:
        print(record, type(record[1]))

Nyní je z vypsaných informací patrné, že se sice vrací hodnoty ze sloupce embeddings, ovšem tyto hodnoty jsou typu řetězec (string):

(1, '[-5,5]') <class 'str'>
(2, '[-4,3]') <class 'str'>
(3, '[-3,5]') <class 'str'>
(4, '[3,-5]') <class 'str'>
(5, '[4,-3]') <class 'str'>
(6, '[5,-5]') <class 'str'>
(7, '[3,3]') <class 'str'>
(8, '[3,4]') <class 'str'>
(9, '[3,5]') <class 'str'>
(10, '[4,3]') <class 'str'>
(11, '[4,4]') <class 'str'>
(12, '[4,5]') <class 'str'>
(13, '[5,3]') <class 'str'>
(14, '[5,4]') <class 'str'>
(15, '[5,5]') <class 'str'>

Aby bylo možné korektně číst vektory, musíme zavolat již známou funkci register_vector z balíčku pgvector.psycopg2. Poté by již vše mělo být v pořádku, což si pochopitelně ověříme:

import psycopg2
 
from pgvector.psycopg2 import register_vector
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
register_vector(connection)
 
with connection.cursor() as cursor:
    cursor.execute("SELECT * FROM v2")
    records = cursor.fetchall()
    for record in records:
        print(record, type(record[1]))

Nyní se již vektory korektně vrací jako typ numpy.array a dokonce prvky tohoto jednorozměrného pole mají korektní typ float32:

(1, array([-5.,  5.], dtype=float32)) <class 'numpy.ndarray'>
(2, array([-4.,  3.], dtype=float32)) <class 'numpy.ndarray'>
(3, array([-3.,  5.], dtype=float32)) <class 'numpy.ndarray'>
(4, array([ 3., -5.], dtype=float32)) <class 'numpy.ndarray'>
(5, array([ 4., -3.], dtype=float32)) <class 'numpy.ndarray'>
(6, array([ 5., -5.], dtype=float32)) <class 'numpy.ndarray'>
(7, array([3., 3.], dtype=float32)) <class 'numpy.ndarray'>
(8, array([3., 4.], dtype=float32)) <class 'numpy.ndarray'>
(9, array([3., 5.], dtype=float32)) <class 'numpy.ndarray'>
(10, array([4., 3.], dtype=float32)) <class 'numpy.ndarray'>
(11, array([4., 4.], dtype=float32)) <class 'numpy.ndarray'>
(12, array([4., 5.], dtype=float32)) <class 'numpy.ndarray'>
(13, array([5., 3.], dtype=float32)) <class 'numpy.ndarray'>
(14, array([5., 4.], dtype=float32)) <class 'numpy.ndarray'>
(15, array([5., 5.], dtype=float32)) <class 'numpy.ndarray'>

17. Výběr vektorů na základě jejich podobnosti

Ukažme si další příklady založené na rozšíření pgvector a psycopg2. Provedeme výběr vektorů na základě jejich podobnosti s vektorem [3, 3], přičemž budeme chtít získat maximálně pět výsledků. Povšimněte si, že do metody cursor.execute můžeme předat běžný Pythonovský seznam, ale samozřejmě je podporován i datový typ numpy.array (připomínám, že parametry SQL dotazu se předávají formou n-tice a proto zápis vypadá takto ([3,3], ):

import psycopg2
 
from pgvector.psycopg2 import register_vector
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
register_vector(connection)
 
with connection.cursor() as cursor:
    cursor.execute("SELECT embedding FROM v2 ORDER BY embedding <-> %s::vector LIMIT 5", ([3,3], ))
    records = cursor.fetchall()
    for record in records:
        print(record[0])

Výsledky:

[3. 3.]
[3. 4.]
[4. 3.]
[4. 4.]
[3. 5.]

Složitější dotazy je vhodnější zapsat formou víceřádkového řetězce. Předchozí příklad tedy lze přepsat do této podoby a stále získáme stejné výsledky:

import psycopg2
 
from pgvector.psycopg2 import register_vector
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
register_vector(connection)
 
with connection.cursor() as cursor:
    query = """
        SELECT embedding
          FROM v2
         ORDER BY embedding <-> %s::vector
         LIMIT 5
    """
    cursor.execute(query, ([3,3], ))
    records = cursor.fetchall()
    for record in records:
        print(record[0])

Výběr všech vektorů, jejichž vzdálenost od vektoru [3,3] je menší než 2.5:

import psycopg2
 
from pgvector.psycopg2 import register_vector
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
register_vector(connection)
 
with connection.cursor() as cursor:
    query = """
        SELECT embedding
          FROM v2
         WHERE embedding <-> %s::vector < 2.5
    """
    cursor.execute(query, ([3,3], ))
    records = cursor.fetchall()
    for record in records:
        print(record[0])

Nyní by se mělo vrátit osm vektorů:

[3. 3.]
[3. 4.]
[3. 5.]
[4. 3.]
[4. 4.]
[4. 5.]
[5. 3.]
[5. 4.]

A konečně poslední příklad z této kategorie, ve kterém se postupně vypisují vektory s různou maximální vzdáleností od vektoru [3,3]. Společně s rostoucí maximální vzdáleností se pochopitelně zvětšuje počet vrácených vektorů:

import psycopg2
 
from pgvector.psycopg2 import register_vector
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
register_vector(connection)
 
query = """
    SELECT embedding
      FROM v2
     WHERE embedding <-> %s::vector < %s
"""
 
for distance in range(0, 10):
    print("Distance:", distance)
    with connection.cursor() as cursor:
        cursor.execute(query, ([3,3], distance))
        records = cursor.fetchall()
        for record in records:
            print(record[0])
    print("-"*50)

Výsledky pro různé maximální vzdálenosti jsou odděleny znaky „-“:

Distance: 0
--------------------------------------------------
Distance: 1
[3. 3.]
--------------------------------------------------
Distance: 2
[3. 3.]
[3. 4.]
[4. 3.]
[4. 4.]
--------------------------------------------------
Distance: 3
[3. 3.]
[3. 4.]
[3. 5.]
[4. 3.]
[4. 4.]
[4. 5.]
[5. 3.]
[5. 4.]
[5. 5.]
--------------------------------------------------
Distance: 4
[3. 3.]
[3. 4.]
[3. 5.]
[4. 3.]
[4. 4.]
[4. 5.]
[5. 3.]
[5. 4.]
[5. 5.]
--------------------------------------------------
Distance: 5
[3. 3.]
[3. 4.]
[3. 5.]
[4. 3.]
[4. 4.]
[4. 5.]
[5. 3.]
[5. 4.]
[5. 5.]
--------------------------------------------------
Distance: 6
[3. 3.]
[3. 4.]
[3. 5.]
[4. 3.]
[4. 4.]
[4. 5.]
[5. 3.]
[5. 4.]
[5. 5.]
--------------------------------------------------
Distance: 7
[-3.  5.]
[ 4. -3.]
[3. 3.]
[3. 4.]
[3. 5.]
[4. 3.]
[4. 4.]
[4. 5.]
[5. 3.]
[5. 4.]
[5. 5.]
--------------------------------------------------
Distance: 8
[-4.  3.]
[-3.  5.]
[ 4. -3.]
[3. 3.]
[3. 4.]
[3. 5.]
[4. 3.]
[4. 4.]
[4. 5.]
[5. 3.]
[5. 4.]
[5. 5.]
--------------------------------------------------
Distance: 9
[-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: podle očekávání s rostoucí maximální vzdáleností roste i počet vrácených vektorů.

18. Použití různých metrik na nenormalizované i normalizované vektory

A konečně se podívejme na použití různých metrik (viz kapitolu 11) v dotazech prováděných z Pythonu. Nejprve provedeme výběry z tabulky v2, která obsahuje nenormalizované vektory. Funkce list_by_distance je volána s různými připravenými dotazy a vrací podobnosti vektorů:

import psycopg2
 
from pgvector.psycopg2 import register_vector
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
register_vector(connection)
 
 
def list_by_distance(title, query):
    print(title)
    print("-" * 70)
    with connection.cursor() as cursor:
        cursor.execute(query, ([3,3], ))
        records = cursor.fetchall()
        for record in records:
            print(record[0], record[1])
    print()
 
 
query_l2_distance = """
        SELECT embedding, embedding <-> %s::vector as distance
          FROM v2
         ORDER BY distance
    """
 
 
query_cosine_distance = """
        SELECT embedding, embedding <=> %s::vector as distance
          FROM v2
         ORDER BY distance
    """
 
 
query_inner_product_distance = """
        SELECT embedding, (embedding <#> %s::vector) * -1 as distance
          FROM v2
         ORDER BY distance
    """
 
 
list_by_distance("L2", query_l2_distance)
list_by_distance("cosine", query_cosine_distance)
list_by_distance("inner product", query_inner_product_distance)

A takto by měly vypadat výsledky (pro [3,3] budou nulové, kromě výpočtu skalárního součinu):

L2
----------------------------------------------------------------------
[3. 3.] 0.0
[4. 3.] 1.0
[3. 4.] 1.0
[4. 4.] 1.4142135623730951
[3. 5.] 2.0
[5. 3.] 2.0
[4. 5.] 2.23606797749979
[5. 4.] 2.23606797749979
[5. 5.] 2.8284271247461903
[ 4. -3.] 6.082762530298219
[-3.  5.] 6.324555320336759
[-4.  3.] 7.0
[ 3. -5.] 8.0
[ 5. -5.] 8.246211251235321
[-5.  5.] 8.246211251235321
 
cosine
----------------------------------------------------------------------
[3. 3.] 0.0
[5. 5.] 0.0
[4. 4.] 0.0
[5. 4.] 0.006116265326381098
[4. 5.] 0.006116265326381098
[3. 4.] 0.010050506338833531
[4. 3.] 0.010050506338833531
[5. 3.] 0.029857499854668013
[3. 5.] 0.029857499854668013
[-3.  5.] 0.757464374963667
[ 4. -3.] 0.8585786437626906
[-5.  5.] 1.0
[ 5. -5.] 1.0
[-4.  3.] 1.1414213562373094
[ 3. -5.] 1.242535625036333
 
inner product
----------------------------------------------------------------------
[ 3. -5.] -6.0
[-4.  3.] -3.0
[-5.  5.] 0.0
[ 5. -5.] 0.0
[ 4. -3.] 3.0
[-3.  5.] 6.0
[3. 3.] 18.0
[3. 4.] 21.0
[4. 3.] 21.0
[4. 4.] 24.0
[5. 3.] 24.0
[3. 5.] 24.0
[4. 5.] 27.0
[5. 4.] 27.0
[5. 5.] 30.0

V dnešním posledním demonstračním příkladu provedeme tytéž dotazy, ovšem nyní z tabulky s normalizovanými vektory:

hacking_tip

import psycopg2
 
from pgvector.psycopg2 import register_vector
 
connection = psycopg2.connect(
    host="", port=5432, user="tester", password="123qwe", dbname="test"
)
 
register_vector(connection)
 
 
def list_by_distance(title, query):
    print(title)
    print("-" * 70)
    with connection.cursor() as cursor:
        cursor.execute(query, ([0.6, 0.8], ))
        records = cursor.fetchall()
        for record in records:
            print(record[0], record[1])
    print()
 
 
query_l2_distance = """
        SELECT embedding, embedding <-> %s::vector as distance
          FROM normalized
         ORDER BY distance
    """
 
 
query_cosine_distance = """
        SELECT embedding, embedding <=> %s::vector as distance
          FROM normalized
         ORDER BY distance
    """
 
 
query_inner_product_distance = """
        SELECT embedding, (embedding <#> %s::vector) * -1 as distance
          FROM normalized
         ORDER BY distance
    """
 
 
list_by_distance("L2", query_l2_distance)
list_by_distance("cosine", query_cosine_distance)
list_by_distance("inner product", query_inner_product_distance)

Výsledky nyní budou korektní i při použití skalárního součinu:

L2
----------------------------------------------------------------------
[0.6 0.8] 0.0
[0.62469506 0.7808688 ] 0.031238551948631895
[0.51449573 0.8574929 ] 0.1030360078106494
[0.70710677 0.70710677] 0.1417780269885387
[0.70710677 0.70710677] 0.1417780269885387
[0.70710677 0.70710677] 0.1417780269885387
[0.7808688  0.62469506] 0.2518836080769836
[0.8 0.6] 0.28284269614271473
[0.8574929  0.51449573] 0.3844675371382995
[-0.51449573  0.8574929 ] 1.1159777193238047
[-0.70710677  0.70710677] 1.3104034663729762
[-0.8  0.6] 1.4142136466667896
[ 0.8 -0.6] 1.4142136466667896
[ 0.70710677 -0.70710677] 1.5109078847197794
[ 0.51449573 -0.8574929 ] 1.6596968430299401
 
cosine
----------------------------------------------------------------------
[0.6 0.8] 0.0
[0.62469506 0.7808688 ] 0.00048792362213134766
[0.51449573 0.8574929 ] 0.005308210849761963
[0.70710677 0.70710677] 0.010050446094585386
[0.70710677 0.70710677] 0.010050446094585386
[0.70710677 0.70710677] 0.010050446094585386
[0.7808688  0.62469506] 0.03172260522842407
[0.8 0.6] 0.039999961853027344
[0.8574929  0.51449573] 0.07390761375427246
[-0.51449573  0.8574929 ] 0.6227031350135803
[-0.70710677  0.70710677] 0.8585786181264703
[-0.8  0.6] 1.0
[ 0.8 -0.6] 1.0
[ 0.70710677 -0.70710677] 1.1414213818735297
[ 0.51449573 -0.8574929 ] 1.3772968649864197
 
inner product
----------------------------------------------------------------------
[ 0.51449573 -0.8574929 ] -0.3772968649864197
[ 0.70710677 -0.70710677] -0.141421377658844
[ 0.8 -0.6] 0.0
[-0.8  0.6] 0.0
[-0.70710677  0.70710677] 0.141421377658844
[-0.51449573  0.8574929 ] 0.3772968649864197
[0.8574929  0.51449573] 0.9260923862457275
[0.8 0.6] 0.9600000381469727
[0.7808688  0.62469506] 0.9682773947715759
[0.70710677 0.70710677] 0.9899495244026184
[0.70710677 0.70710677] 0.9899495244026184
[0.70710677 0.70710677] 0.9899495244026184
[0.51449573 0.8574929 ] 0.994691789150238
[0.62469506 0.7808688 ] 0.9995120763778687
[0.6 0.8] 1.0

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

20. Odkazy na Internetu

  1. Vector database
    https://en.wikipedia.org/wi­ki/Vector_database
  2. Nearest neighbor search
    https://en.wikipedia.org/wi­ki/Nearest_neighbor_search#Ap­proximation_methods
  3. RAG – Retrieval-augmented generation
    https://en.wikipedia.org/wi­ki/Retrieval-augmented_generation
  4. pgvector na GitHubu
    https://github.com/pgvector/pgvector
  5. Why we replaced Pinecone with PGVector
    https://www.confident-ai.com/blog/why-we-replaced-pinecone-with-pgvector
  6. PostgreSQL as VectorDB – Beginner Tutorial
    https://www.youtube.com/wat­ch?v=Ff3tJ4pJEa4
  7. What is a Vector Database? (neobsahuje odpověď na otázku v titulku :-)
    https://www.youtube.com/wat­ch?v=t9IDoenf-lo
  8. PGVector: Turn PostgreSQL Into A Vector Database
    https://www.youtube.com/wat­ch?v=j1QcPSLj7u0
  9. Milvus
    https://milvus.io/
  10. Vector Databases simply explained! (Embeddings & Indexes)
    https://www.youtube.com/wat­ch?v=dN0lsF2cvm4
  11. Vector databases are so hot right now. WTF are they?
    https://www.youtube.com/wat­ch?v=klTvEwg3oJ4
  12. 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
  13. Best 17 Vector Databases for 2025
    https://lakefs.io/blog/12-vector-databases-2023/
  14. Top 15 Vector Databases that You Must Try in 2025
    https://www.geeksforgeeks.org/top-vector-databases/
  15. Picking a vector database: a comparison and guide for 2023
    https://benchmark.vectorvi­ew.ai/vectordbs.html
  16. Top 9 Vector Databases as of Feburary 2025
    https://www.shakudo.io/blog/top-9-vector-databases
  17. What is a vector database?
    https://www.ibm.com/think/to­pics/vector-database
  18. SQL injection
    https://en.wikipedia.org/wi­ki/SQL_injection
  19. Cosine similarity
    https://en.wikipedia.org/wi­ki/Cosine_similarity
  20. Hammingova vzdálenost
    https://cs.wikipedia.org/wi­ki/Hammingova_vzd%C3%A1le­nost
  21. Jaccard index
    https://en.wikipedia.org/wi­ki/Jaccard_index
  22. Manhattanská metrika
    https://cs.wikipedia.org/wi­ki/Manhattansk%C3%A1_metri­ka

Autor článku

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