ベクトルデータベース「Weaviate」を試す 8:Multiple vectors/Named vectors
前回の続き
Part3のスキーマのところで少しだけ触れたMultiple vectorsについてやってみる。
スキーマのところで触れたけども、Weaviateでは登録したデータ構造から自動でスキーマが作成され、ベクトル化モジュールを有効にした場合、デフォルトだとすべてのプロパティを使ってベクトルデータが作成される。
で、自分で特定のデータのみをベクトル化の対象としたい場合は、各プロパティごとにベクトル化する・しないを設定することになる。
いずれの場合、ベクトルデータの中身が何であれ、1オブジェクトに対して1ベクトルデータになる。ただ、いろいろやっていくと、各プロパティごとにベクトル化して、プロパティごとに検索したくなることもある。これを実現するのが、Named vectors/Multiple vectorsになる。
まずデータを用意する。以下のデータセットを使用。
!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': '妊娠・出産'}
検索結果が異なっているのがわかる。当然ながら、質問をベクトル化したデータに対して、質問をクエリすると圧倒的に高い類似度で返って来ている。
一般的なベクトルデータベースでは(自分の知る限り)1オブジェクト=1ベクトルデータだし、ベクトル空間も分けるとなると、おそらく別の枠組みが必要になるので、管理が複雑化するのではないかと思う。
Multiple vectors/Named vectorsを使うと、これを1つのオブジェクトとして効率よく管理できる。また、Multiple vectors/Named vectorsを使った場合でもキーワード検索は普通に使えるし、ハイブリッド検索とも組み合わせることができるので、柔軟に使えるのではないかと思う。
次の記事