Closed4

ベクトルデータベース「Weaviate」を試す 8:Multiple vectors/Named vectors

kun432kun432

スキーマのところで触れたけども、Weaviateでは登録したデータ構造から自動でスキーマが作成され、ベクトル化モジュールを有効にした場合、デフォルトだとすべてのプロパティを使ってベクトルデータが作成される。

で、自分で特定のデータのみをベクトル化の対象としたい場合は、各プロパティごとにベクトル化する・しないを設定することになる。

いずれの場合、ベクトルデータの中身が何であれ、1オブジェクトに対して1ベクトルデータになる。ただ、いろいろやっていくと、各プロパティごとにベクトル化して、プロパティごとに検索したくなることもある。これを実現するのが、Named vectors/Multiple vectorsになる。

https://weaviate.io/developers/weaviate/config-refs/schema/multi-vector#syntax

まずデータを用意する。以下のデータセットを使用。

https://linecorp.com/ja/csr/newslist/ja/2020/260

!wget https://d.line-scdn.net/stf/linecorp/ja/csr/dataset_.zip
!unzip dataset_.zip
import pandas as pd

df = pd.read_excel("dataset_.xlsx")
df.drop(columns=["ID","カテゴリ2","出典","<参考>UMカテゴリタグ","<参考>UMサービスメニュー\n(標準的な行政サービス名称)"], inplace=True)
df.rename(columns={
    'サンプルID': 'faq_id',
    'サンプル 問い合わせ文': 'question',
    'サンプル 応答文': 'answer',
    'カテゴリ1': 'category',
}, inplace=True)

faq_objs = df.to_dict(orient='records')
print(len(faq_objs))
print(faq_objs[0])

データセットの中身

662
{'faq_id': 1, 'question': '母子手帳を受け取りたいのですが、手続きを教えてください。', 'answer': '窓口で妊娠届をご記入いただき、母子手帳をお渡しします。\n住民票の世帯が別の方が代理で窓口に来られる場合は、委任状が必要になります。\n\n▼詳しくはこちら\n(自治体HP内関連ページのURL)', 'category': '妊娠・出産'}

クライアントを初期化。今回、Weaviateのクラスタには、Embeddedのv1.24.4を使う。Named vectors/Multiple vectorsを使った場合にスコア(certainty)を取得できないバグがあって、すでにv1.24.2で修正済なのだけど、WCSではその修正が含まれていないv1.24.1が最新になっているため。WCS以外のDocker-Composeとかでも問題ないと思う。(ただし、WCSでNamed vectors/Multiple vectorsを使った場合でも、検索自体は問題なくできるし、スコアもdistanceなら取得できる、certaintyを使おうとするとコケるだけ)

import weaviate
import weaviate.classes as wvc
import os
from google.colab import userdata

client = weaviate.connect_to_embedded(
    version="1.24.4",
    headers={
        "X-OpenAI-Api-Key": userdata.get('OPENAI_API_KEY')
    }
)

コレクションを作成。ここでNamed vectors/Multiple vectorsを使ってスキーマを設定する。

faq = client.collections.create(
    name="FAQ",
    vectorizer_config=[
        wvc.config.Configure.NamedVectors.text2vec_openai(
            name="answer_vector",
            source_properties=["answer"],
            vectorize_collection_name=False,        
        ),
        wvc.config.Configure.NamedVectors.text2vec_openai(
            name="question_vector",
            source_properties=["question"],
            vectorize_collection_name=False,        
        ),
    ],
    properties=[
        wvc.config.Property(
            name="faq_id",
            data_type=wvc.config.DataType.INT
        ),
        wvc.config.Property(
            name="answer",
            data_type=wvc.config.DataType.TEXT,
        ),
        wvc.config.Property(
            name="question",
            data_type=wvc.config.DataType.TEXT,
        ),
        wvc.config.Property(
            name="category",
            data_type=wvc.config.DataType.TEXT,
        ),
    ],
)

Named vectorsは、プロパティから名前付きベクトルデータを作成する。これを各プロパティごとに作ることでMultiple vectorsになるという次第。

Named vectorsの設定は、プロパティのスキーマではなく、ベクトル化モジュールのスキーマで行うことになる。pythonクライアントでのベクトル化モジュールの設定は通常wvc.config.Configure.Vectorizerを使用するが、Named vectorsの場合はwvc.config.Configure.NamedVectorsで行う。
nameに名前付きベクトルデータの名称を指定してsource_propertiesで対象となるプロパティを指定する。実際に検索で使用する際にはsource_propertiesで指定したプロパティ名ではなく、nameを指定して行うことになる。ここでは、質問と回答をNamed vectorsとして登録した。

なお、Named vectorsはそれぞれ独立しているので、(やるかどうかは別にして)例えば、questionにはOpenAIのEmbeddingモデルを使用して、answerにはCohereのEmbeddingモデルを使用する、みたいなこともできる。

ではデータを登録する。個々にベクトルデータを作成することになるので、これまでよりも多少時間がかかる。

question_objs = list()
for i, d in enumerate(faq_objs):
    question_objs.append({
        "faq_id": d["faq_id"],
        "answer": d["answer"],
        "question": d["question"],
        "category": d["category"],
    })

faq.data.insert_many(question_objs)

では検索してみる。基本的な使い方はこれまでのベクトル検索と同じだけども、NamedVectorsの場合はどの名前付きベクトルデータを検索するかを必ずtarget_vectorで指定する必要がある。

response = faq.query.near_text(
    query="母子手帳を受け取りたいのですが、手続きを教えてください。",
    include_vector=True,
    target_vector="question_vector",
    limit=5,
    return_metadata=wvc.query.MetadataQuery(distance=True, certainty=True),
)

for r in response.objects:
    print(r.metadata.certainty)
    print(r.metadata.distance)
    print(r.properties)
    print()
0.9999998807907104
2.980232238769531e-07
{'answer': '窓口で妊娠届をご記入いただき、母子手帳をお渡しします。\n住民票の世帯が別の方が代理で窓口に来られる場合は、委任状が必要になります。\n\n▼詳しくはこちら\n(自治体HP内関連ページのURL)', 'faq_id': 1, 'question': '母子手帳を受け取りたいのですが、手続きを教えてください。', 'category': '妊娠・出産'}

0.9659749865531921
0.06805002689361572
{'answer': '母子手帳は、○○市役所本庁舎△△階××課窓口、◎◎出張所、………(その他の受け取り場所を適宜記載)………で受け取れます。\n\n▼詳しくはこちら\n(自治体HP内関連ページのURL)', 'faq_id': 2, 'question': '母子手帳の受け取り場所はどこですか?', 'category': '妊娠・出産'}

0.9594100117683411
0.08117997646331787
{'answer': '母子手帳は、妊娠届の内容を確認させていただき、その場でお渡しします。\n\n▼詳しくはこちら\n(自治体HP内関連ページのURL)', 'faq_id': 3, 'question': '母子手帳はすぐに発行してもらえますか?', 'category': '妊娠・出産'}

0.956933319568634
0.08613336086273193
{'answer': '母子手帳の申請には診断書はいりませんが、妊娠届に診断を受けた病院名・医師名を記入していただきます。', 'faq_id': 37, 'question': '母子手帳の申請には医師の診断書が必要ですか?', 'category': '妊娠・出産'}

0.9566235542297363
0.08675283193588257
{'answer': '産前は母子手帳以外の手続きは特にありません。\n産後に、出生の届出や出生通知書の提出、(自治体が行う出産助成等)の申請をお願いします。', 'faq_id': 36, 'question': '母子手帳の他に産前に市役所でやるべき手続きはありますか?', 'category': '妊娠・出産'}

同じクエリを今度はtarget_vector="answer_vector"に対して行ってみる。

response = faq.query.near_text(
    query="母子手帳を受け取りたいのですが、手続きを教えてください。",
    include_vector=True,
    target_vector="answer_vector",     # 検索対象をanswer_vectorにする。
    limit=5,
    return_metadata=wvc.query.MetadataQuery(distance=True, certainty=True),
)

for r in response.objects:
    print(r.metadata.certainty)
    print(r.metadata.distance)
    print(r.properties)
    print()
0.9523594379425049
0.09528106451034546
{'answer': '母子手帳の申請には診断書はいりませんが、妊娠届に診断を受けた病院名・医師名を記入していただきます。', 'faq_id': 37, 'question': '母子手帳の申請には医師の診断書が必要ですか?', 'category': '妊娠・出産'}

0.9474475979804993
0.10510480403900146
{'answer': '母子手帳は、妊娠届の内容を確認させていただき、その場でお渡しします。\n\n▼詳しくはこちら\n(自治体HP内関連ページのURL)', 'faq_id': 3, 'question': '母子手帳はすぐに発行してもらえますか?', 'category': '妊娠・出産'}

0.945560097694397
0.10887980461120605
{'answer': '母子手帳をなくしたときは、再交付を受けてください。\nお子さんが出生前の母子手帳については、(再交付を受けられる場所)で再交付を受けられます。\nお子さんが出生後の母子手帳については、(再交付を受けられる場所)で受けられます。\n申請の際はご本人確認できるものをお持ちください。\n\n◆お問い合わせ\n(自治体の担当課等の名称)\n(電話番号)/(開庁時間)', 'faq_id': 108, 'question': '母子手帳をなくした場合は再発行できますか?', 'category': '妊娠・出産'}

0.9443613290786743
0.11127734184265137
{'answer': '産前は母子手帳以外の手続きは特にありません。\n産後に、出生の届出や出生通知書の提出、(自治体が行う出産助成等)の申請をお願いします。', 'faq_id': 36, 'question': '母子手帳の他に産前に市役所でやるべき手続きはありますか?', 'category': '妊娠・出産'}

0.9381375312805176
0.12372499704360962
{'answer': '母子手帳は、○○市役所本庁舎△△階××課窓口、◎◎出張所、………(その他の受け取り場所を適宜記載)………で受け取れます。\n\n▼詳しくはこちら\n(自治体HP内関連ページのURL)', 'faq_id': 2, 'question': '母子手帳の受け取り場所はどこですか?', 'category': '妊娠・出産'}

検索結果が異なっているのがわかる。当然ながら、質問をベクトル化したデータに対して、質問をクエリすると圧倒的に高い類似度で返って来ている。

kun432kun432

一般的なベクトルデータベースでは(自分の知る限り)1オブジェクト=1ベクトルデータだし、ベクトル空間も分けるとなると、おそらく別の枠組みが必要になるので、管理が複雑化するのではないかと思う。

Multiple vectors/Named vectorsを使うと、これを1つのオブジェクトとして効率よく管理できる。また、Multiple vectors/Named vectorsを使った場合でもキーワード検索は普通に使えるし、ハイブリッド検索とも組み合わせることができるので、柔軟に使えるのではないかと思う。

このスクラップは2ヶ月前にクローズされました