Closed4

pgvectorを試す

kun432kun432

Mac上のDockerで。

docker run --rm -d \
    --name pgvector-test \
    -p 5432:5432 \
    -e POSTGRES_PASSWORD=postgres \
    pgvector/pgvector:pg17

別ターミナルを開いて、コンテナに入る

docker exec -ti pgvector-test bash

psqlで接続

psql -U postgres 
出力
psql (17.0 (Debian 17.0-1.pgdg120+1))
Type "help" for help.

postgres=#

データベースを作成する

CREATE DATABASE vectordb;

作成したデータベースを選択

\c vectordb

Getting Startedをさらっと。まず、作成したデータベースでpgvectorを有効化

CREATE EXTENSION vector;

サンプルでテーブルを作成

CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));

ベクトルデータを登録

INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');

検索

SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
出力
 id | embedding
----+-----------
  1 | [1,2,3]
  2 | [4,5,6]
(2 rows)

類似度指標は以下の通り

  • <->: L2(ユークリッド)距離
  • <#>: 内積
  • <=>: コサイン距離
  • <+>: L1(マンハッタン)距離

ドキュメントの流れだとここからいろいろ細かい操作について記載があるけど、それはおいおい。

一旦コンテナから抜ける。

ここからはJupyterLabコンテナを作成して、Pythonから操作してみる。

作業ディレクトリ作成

mkdir pgvector-test && cd pgvector-test

JupyterLabのDockerを起動。

docker run --rm \
    -p 8888:8888 \
    -u root \
    -e GRANT_SUDO=yes \
    -v .:/home/jovyan/work \
    quay.io/jupyter/minimal-notebook:latest

以下はJupyterLab上での作業

pandasとpsycopg2をインストール。後で使うのでopenaiも。

!pip install pandas psycopg2-binary openai

Getting Startedで作成したテーブルに接続して検索してみる。

import psycopg2

conn = psycopg2.connect(
    user='postgres',
    password='postgres',
    database='vectordb',
    host='host.docker.internal',
    port=5432     
)

cur = conn.cursor()

cur.execute("SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5")

records = cur.fetchall()

print(records)

cur.close()
conn.close()
結果
[(1, '[1,2,3]'), (2, '[4,5,6]')]
kun432kun432

実際にありそうなサンプルを試してみる。

京都大学大学院情報学研究科知能情報学コース言語メディア研究室 (https://nlp.ist.i.kyoto-u.ac.jp/)様が公開されている尼崎市のFAQデータセットを使用してテーブルを作成する。

https://arxiv.org/abs/1905.02851

https://nlp.ist.i.kyoto-u.ac.jp/EN/index.php?BERT-Based_FAQ_Retrieval

日本語の論文はおそらくこちらだと思う

https://www.anlp.jp/proceedings/annual_meeting/2019/pdf_dir/F5-1.pdf

データセットをダウンロードして、pandasデータフレームを作成。今回は100件だけ。

!wget https://tulip.kuee.kyoto-u.ac.jp/localgovfaq/localgovfaq.zip
!unzip localgovfaq.zip
!pip install pandas
import pandas as pd

def file2list(filename: str, prefix: str = "") -> tuple:
    """Q/AファイルをIDとコンテンツに分割、それぞれを配列で返す"""
    contents = []
    ids = []
    try:
        with open(filename, 'r') as file:
            for line in file:
                line = line.strip().replace(" ", "")
                id, content = line.split('\t')
                if prefix:
                    id = f"{prefix}_{id}"
                contents.append(content)
                ids.append(id)
    except Exception:
        raise

    return contents, ids

questions, ids = file2list("localgovfaq/qas/questions_in_Amagasaki.txt")
answers, _ = file2list("localgovfaq/qas/answers_in_Amagasaki.txt")

raw_df = pd.DataFrame({'QA_ID': ids, 'QUESTION': questions, 'ANSWER': answers})
df = raw_df.head(100)
df

OpenAIのAPIキーをセット

import getpass
import os

os.environ["OPENAI_API_KEY"] = getpass.getpass('OPENAI_API_KEY')

データフレームの回答のEmbeddingsを生成して追加する。

from openai import OpenAI
from tqdm import tqdm

tqdm.pandas()

client = OpenAI()

def get_embedding(text):
    response = client.embeddings.create(
        input=text,
        model="text-embedding-3-small"
    )
    return response.data[0].embedding

df['embedding'] = df['ANSWER'].progress_apply(get_embedding)
df

ではこのデータをいれるためのテーブルを作成する。

import psycopg2

create_table_query = """
    CREATE TABLE IF NOT EXISTS faq (
        id bigserial PRIMARY KEY,
        qa_id int,
        answer text,
        embedding vector(1536)
    );
"""

with psycopg2.connect(
    user='postgres',
    password='postgres',
    database='vectordb',
    host='host.docker.internal',
    port=5432
) as conn:
    with conn.cursor() as cur:
        cur.execute(create_table_query)
        conn.commit()

psqlで接続して確認してみる。

docker exec -ti pgvector-test bash
psql -U postgres vectordb
 \dt
結果
         List of relations
 Schema | Name  | Type  |  Owner
--------+-------+-------+----------
 public | faq   | table | postgres
 public | items | table | postgres
(2 rows)
 \d faq
結果
                                Table "public.faq"
  Column   |     Type     | Collation | Nullable |             Default
-----------+--------------+-----------+----------+---------------------------------
 id        | bigint       |           | not null | nextval('faq_id_seq'::regclass)
 qa_id     | integer      |           |          |
 answer    | text         |           |          |
 embedding | vector(1536) |           |          |
Indexes:
    "faq_pkey" PRIMARY KEY, btree (id)

ではデータを登録。

import psycopg2
from psycopg2.extras import execute_values

with psycopg2.connect(
    user='postgres',
    password='postgres',
    database='vectordb',
    host='host.docker.internal',
    port=5432
) as conn:
    with conn.cursor() as cur:
        for _, row in tqdm(df.iterrows(), total=len(df)):
            insert_query = "INSERT INTO faq (qa_id, answer, embedding) VALUES (%s, %s, %s)"
            cur.execute(insert_query, (row['QA_ID'], row['ANSWER'], row['embedding']))
        conn.commit()

検索してみる。

query_text = "地域総合センター今北へはどう行けばいいですか?"
query_embedding = get_embedding(query_text)

# PostgreSQLでの類似検索
with psycopg2.connect(
    user='postgres',
    password='postgres',
    database='vectordb',
    host='host.docker.internal',
    port=5432
) as conn:
    with conn.cursor() as cur:
        search_query = """
            SELECT qa_id, answer, embedding <=> %s::vector AS distance
            FROM faq
            ORDER BY distance
            LIMIT 5;
        """
        cur.execute(search_query, (query_embedding, ))
        results = cur.fetchall()

for result in results:
    qa_id, answer, distance = result
    print(f"Distance: {distance}, QA_ID: {qa_id}, Answer: {answer}")
    print("-----")
結果
Distance: 0.40620364528286224, QA_ID: 1, Answer: ■地域総合センター今北には、十分な駐車場がございませんので、市バスをご利用ください。JR沿線からは「立花駅」、阪急沿線からは「塚口駅」「武庫之荘駅」、阪神沿線からは「尼崎駅」「武庫川駅」「出屋敷駅」へお越しいただき、市バスをご利用ください。どちらの駅からいらっしゃいますか?。1.JR立花駅から(位置的には、南西へ徒歩約10分です。)。2.阪急塚口駅(南)から。3.阪急武庫之荘駅(南)から。4.阪神尼崎駅(北)から。5.阪神武庫川駅から。6.阪神出屋敷駅(北)から。<改>。【関連するFAQ】。地域総合センターについて知りたい。<改>。【お問い合わせ】。地域総合センター今北。尼崎市西立花町3丁目14-1。電話06-6416-5729。
-----
Distance: 0.49538776305986054, QA_ID: 48, Answer: ■地域総合センター上ノ島本館及び分館には十分な駐車設備がございませんので、次のとおり阪神バス(尼崎市内線)をご利用ください。以下のいずれの駅からの行き方について知りたいですか。1.阪急塚口駅から。2.JR立花駅から。3.阪神尼崎駅から。<改>。【関連するFAQ】。地域総合センターについて知りたい。<改>。【お問い合わせ】。地域総合センター上ノ島本館。尼崎市南塚口町8丁目7-25。電話06-6429-7640。
-----
Distance: 0.5507588593794948, QA_ID: 58, Answer: ■以下の場所で配布しています。【尾浜庁舎(道路維持担当窓口)】。【尼崎市役所本庁舎】。【尼崎市立魚つり公園】。【支所内地域振興センター】。【尼崎スポーツの森】。【尼崎の森中央緑地】。【尼崎総合庁舎】。【尼崎港管理事務所】。【北堀キャナルベース】。【尼ロック防災展示室】。【尼崎市内警察署】。【尼崎防犯協会】。【尼崎交通安全協会】。【消費生活センター】。【一般社団法人兵庫県自転車防犯登録会】。【JR尼崎駅案内所i+PlusJR尼崎駅構内(改札外)】。【あまらぶアートラボA-lab】。【中央公園内まちなかみどころご案内コーナー】。【阪神沿線レンタサイクル】。【尼崎市記念公園運動施設】。【スポーツクラブWOODY】。【サンシビック尼崎】。【猪名川町スポーツセンター】。【市内の体育館】。各場所の詳細については次のリンクをご覧ください。[URL]。【お問い合わせ】。尼崎市コールセンター。電話06-6375-5639。
-----
Distance: 0.5657978018642713, QA_ID: 33, Answer: 以下のいずれに該当されますか。1.引っ越し先が介護保険に関する施設以外の住宅。2.引っ越し先が介護保険に関する施設。3.受給資格証明書をもらっていない。<改>。■参考。介護保険事業担当でいう「介護保険に関する施設」とは、住所地特例対象施設のことをいい以下のとおりです。・介護保険3施設(特別養護老人ホーム、老人保健施設、介護療養型医療施設)。※ただし、地域密着型介護老人福祉施設(入所定員が30人未満の特別養護老人ホーム)については、住所地特例の対象外となります。(1)特定施設(介護保険法第8条第11項)。・養護老人ホーム(老人福祉法第20条の4)。・軽費老人ホーム(老人福祉法第20条の6)…ケアハウス等。・有料老人ホーム(老人福祉法第29条第1項)。・有料老人ホームに該当するサービスを提供しているサービス付き高齢者向け住宅(高齢者の居住の安定確保に関する法律第5条第1項)。※特定施設は要介護認定の有無に関わらず、住所地特例の対象となります。ただし地域密着型特定施設(介護専用型で入居定員30人未満)については住所地特例の対象外となります。<改>。詳しくは介護保険事業担当にお問い合わせください。■問合せ時間。午前8時45分~午後5時30分。ただし、窓口の取扱時間は午前9時~午後5時30分。■休日。土・日曜日、祝日、年末年始(12月29日~1月3日)。■連絡先。【介護保険事業担当保険料担当】。電話06-6489-6376。【北部保健福祉センター】。尼崎市南塚口町2-1-1。電話06-4950-0562。【南部保健福祉センター】。尼崎市竹谷町2-183。電話06-6415-6279。<改>。【関連するFAQ】。尼崎市内から市外へ住所を移すときはどうしたらいいですか?(転出届)。
-----
Distance: 0.5857635336204101, QA_ID: 24, Answer: 以下のいずれに該当されますか。1.引っ越し先が市内の介護保険に関する施設以外の住宅。2.引っ越し先が市内の介護保険に関する施設。3.受給資格証明書をもらっていない。<改>。■参考。介護保険事業担当でいう「介護保険に関する施設」とは、住所地特例対象施設のことをいいます。・介護保険3施設(特別養護老人ホーム、老人保健施設、介護療養型医療施設)。※ただし、地域密着型介護老人福祉施設(入所定員が30人未満の特別養護老人ホーム)については、住所地特例の対象外となります。・特定施設(介護保険法第8条第11項)。(1)養護老人ホーム(老人福祉法第20条の4)。(2)軽費老人ホーム(老人福祉法第20条の6)…ケアハウス等。(3)有料老人ホーム(老人福祉法第29条第1項)。(4)有料老人ホームに該当するサービスを提供しているサービス付き高齢者向け住宅(高齢者の居住の安定確保に関する法律第5条第1項)。※特定施設は要介護認定の有無に関わらず、住所地特例の対象となります。ただし地域密着型特定施設(介護専用型で入居定員30人未満)については住所地特例の対象外となります。<改>。詳しくは介護保険事業担当へお問い合わせください。■問合せ時間:午前8時45分~午後5時30分。ただし、窓口の取扱時間は、午前9時~午後5時30分。■休日:土・日曜日、祝日、年末年始(12月29日~1月3日)。■連絡先。【介護保険事業担当資格・保険料担当】。電話06-6489-6376。【北部保健福祉センター】。尼崎市南塚口町2-1-1。電話06-4950-0562。【南部保健福祉センター】。尼崎市竹谷町2-183。電話06-6415-6279。【お問い合わせ】。健康福祉局福祉部。介護保険事業担当資格・保険料担当。電話06-6489-6376。<改>。【関連するFAQ】。市外から尼崎市内へ住所を移すときはどうしたらいいですか?(転入届)。
-----
kun432kun432

まとめ

自分の過去の経験においてRDBとはあまり深く関わってこなかったところもあって、まだまだ理解が足りてないとは思うけど、一旦の流れだけは押さえれたので良しとする。

このスクラップは20日前にクローズされました