Closed6

ベクトルデータベース「Weaviate」を試す 7:キーワード検索&ハイブリッド検索

kun432kun432

前回の続き

https://zenn.dev/kun432/scraps/b7da2669f38a40

Weaviateは、ベクトル検索だけでなく、BM25を使ったキーワード検索、さらにそれをベクトル検索と組み合わせたハイブリッド検索に対応している。

ただし、日本語の場合、以下の記事にもある通り、トークナイザーが日本語に非対応だったため、動作しななかった(もしかするとこれまででも動作していたのかもしれないけど、おそらく期待した通りにはならなかったのではないかと思われる)

https://weaviate.io/blog/weaviate-non-english-languages

Weaviateを英語以外の言語で使用する際の現在の制限事項

ご覧いただいたように、Weaviate ベクターデータベースは、英語以外の言語でもセマンティック検索やジェネレーティブ検索のアプリケーションに使用することができます。しかし、現在のところ、英語以外の言語に対する公式なサポート/ポリシーがないため、いくつかの制限があります。

この制限は主にWeaviateのキーワードベース検索機能BM25に影響します。つまり、英語以外の言語、特に Unicode エンコーディングを必要とする言語では、BM25 検索はエラーを返します。

この制限はWeaviateのハイブリッド検索機能にも影響します。ハイブリッド検索クエリを実行してもエラーは発生しませんが、alpha != 0 のハイブリッド検索クエリはすべて、純粋なセマンティック検索と同じ結果を返します。alpha = 0 のハイブリッド検索(純粋なキーワードベース検索)は、空の結果リストを返します。

ただし、上記の記事には以下ともある。

お分かりのように、Weaviateは現在、英語以外の言語を完全にサポートしていません。しかし、将来的には他の言語もサポートしたいと考えています。現在、日本語と中国語のトークナイザーの実装を試しています。英語以外の言語サポートの追加に貢献することに興味がある方は、Slackでご連絡ください。

この変更は以下のPRで行われており、v1.24.0にはマージされている。

https://github.com/weaviate/weaviate/pull/4028

ということですこし試してみる。

kun432kun432
kun432kun432

DockerでWeaviateを起動

以下の際に作成したdocker-compose.ymlを使ってweaviateを起動する。なお、gseによるトークン化を有効にするために環境変数を追加している

https://zenn.dev/kun432/scraps/c07f2502b19f12

docker-compose.yml
---
version: '3.4'
services:
  weaviate:
    command:
    - --host
    - 0.0.0.0
    - --port
    - '8080'
    - --scheme
(weaviate-docker) kun432@rtx4090:/data/repository/weaviate-docker$ tail docker-compose.yml
    restart: on-failure:0
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
      DEFAULT_VECTORIZER_MODULE: 'text2vec-openai'
      ENABLE_MODULES: 'text2vec-openai,ref2vec-centroid,generative-openai,generative-aws,reranker-cohere'
      CLUSTER_HOSTNAME: 'node1'
      USE_GSE: 'true'     # enable gse for tokenization

必要な環境変数を.envに設定しておく。こちらは後でJupyterLabから読み込む。

.env
OPENAI_API_KEY=XXXXXXXXXX
COHERE_API_KEY=XXXXXXXXXX
AWS_ACCESS_KEY_ID=XXXXXXXXXX
AWS_SECRET_ACCESS_KEY=XXXXXXXXXX

起動

$ docker compose up -d

事前準備

ここからはjupyter-labで。

まずパッケージインストール。前回同様Weaviateに登録するドキュメントの整形にLlamaIndexを使っている。

!pip install weaviate-client llama-index llama-index-readers-file python-dotenv

環境変数を読み込む

from dotenv import load_dotenv

load_dotenv(verbose=True)

ドキュメントをLlamaIndexで読み込む。

from pathlib import Path
import requests
import re

def replace_heading(match):
    level = len(match.group(1))
    return '#' * level + ' ' + match.group(2).strip()

# Wikipediaからのデータ読み込み
wiki_titles = ["オグリキャップ"]
for title in wiki_titles:
    response = requests.get(
        "https://ja.wikipedia.org/w/api.php",
        params={
            "action": "query",
            "format": "json",
            "titles": title,
            "prop": "extracts",
            # 'exintro': True,
            "explaintext": True,
        },
    ).json()
    page = next(iter(response["query"]["pages"].values()))
    wiki_text = f"# {title}\n\n## 概要\n\n"
    wiki_text += page["extract"]

    wiki_text = re.sub(r"(=+)([^=]+)\1", replace_heading, wiki_text)
    wiki_text = re.sub(r"\t+", "", wiki_text)
    wiki_text = re.sub(r"\n{3,}", "\n\n", wiki_text)
    data_path = Path("data")
    if not data_path.exists():
        Path.mkdir(data_path)

    with open(data_path / f"{title}.md", "w") as fp:
        fp.write(wiki_text)
from pathlib import Path
import glob
import os

from llama_index.core.node_parser import MarkdownNodeParser
from llama_index.readers.file import FlatReader
from llama_index.core.schema import MetadataMode

files = glob.glob('data/*.md')

docs = []
for f in files:
    doc = FlatReader().load_data(Path(f))
    docs.extend(doc)

parser = MarkdownNodeParser()
nodes = parser.get_nodes_from_documents(docs)

nodes_for_delete = []
sections_for_delete = ["競走成績", "外部リンク", "参考文献", "関連作品"]

for idx, n in enumerate(nodes):
    # メタデータからセクション情報を取り出す。
    metadatas = []
    header_keys = []
    for m in n.metadata:
        if m.startswith("Header"):
            metadatas.append(n.metadata[m])
            header_keys.append(m)
    if len(metadatas) > 0:
        # セクション情報を新たなメタデータに設定
        n.metadata["section"] = metadata_str = " > ".join(metadatas)
        # 古いセクション情報を削除
        for k in header_keys:
            if k.startswith("Header"):
               del n.metadata[k]

    # コンテンツ整形
    contents = n.get_content().split("\n")
    if len(contents) == 1:
        # コンテンツが1つだけ≒セクションタイトルのみの場合は削除対象
        nodes_for_delete.append(idx)
    elif contents[0] in sections_for_delete:
        # 任意のセクションを削除対象
        nodes_for_delete.append(idx)
    else:
        # コンテンツの冒頭にあるセクションタイトル部分、及びそれに続く改行を削除
        content_for_delete = []
        for c_idx, c in enumerate(contents):
            if c in (metadatas):
                content_for_delete.append(c_idx)
            elif c in ["", "\n", None]:
                content_for_delete.append(c_idx)
            else:
                break
        contents = [item for i, item in enumerate(contents) if i not in content_for_delete]

    # 整形したコンテンツでノードを書き換え
    n.set_content("\n".join(contents))

base_nodes = [item for i, item in enumerate(nodes) if i not in nodes_for_delete]

LlamaIndexで作成したドキュメントのノードを、Weaviateに登録するためのオブジェクトに変換する。

import re
import weaviate.classes as wvc

def text_split(text, max_length=400):
    chunks = re.split(r'([。!?])', text)
    temp_chunk = ""
    final_chunks = []

    for chunk in chunks:
        if len(temp_chunk + chunk) <= max_length:
            temp_chunk += chunk
        else:
            final_chunks.append(temp_chunk)
            temp_chunk = chunk

    if temp_chunk:
        final_chunks.append(temp_chunk)

    return final_chunks

wvc_objs = []
for n in base_nodes:
    content = n.get_content().replace("\n", " ")
    chunks = text_split(content, 400)
    if len(chunks) == 1:
        idx = len(wvc_objs) + 1
        wvc_objs.append(wvc.data.DataObject(
            properties={
                "chunk_id": id,
                "chapter_title": n.metadata["section"],
                "chunk": chunks[0],
            }
        ))
    else:
        for chunk_idx, chunk in enumerate(chunks, start=1):
            id = len(wvc_objs) + 1
            wvc_objs.append(wvc.data.DataObject(
                properties={
                    "chunk_id": id,
                    "chapter_title": n.metadata["section"] + f"({chunk_idx})",
                    "chunk": chunk,
                }
            ))

こういうオブジェクトになる。

print(len(wvc_objs))
print(wvc_objs[0])
107
DataObject(properties={'chunk_id': 1, 'chapter_title': 'オグリキャップ > 概要(1)', 'chunk': 'オグリキャップ(欧字名:Oguri Cap、1985年3月27日 - 2010年7月3日)は、日本の競走馬、種牡馬。 1987年5月に岐阜県の地方競馬・笠松競馬場でデビュー。8連勝、重賞5勝を含む12戦10勝を記録した後、1988年1月に中央競馬へ移籍し、重賞12勝(うちGI4勝)を記録した。1988年度のJRA賞最優秀4歳牡馬、1989年度のJRA賞特別賞、1990年度のJRA賞最優秀5歳以上牡馬および年度代表馬。1991年、JRA顕彰馬に選出。愛称は「オグリ」「芦毛の怪物」など多数。 中央競馬時代はスーパークリーク、イナリワンの二頭とともに「平成三強」と総称され、自身と騎手である武豊の活躍を中心として起こった第二次競馬ブーム期において、第一次競馬ブームの立役者とされるハイセイコーに比肩するとも評される高い人気を得た。'}, uuid=None, vector=None, references=None)

オブジェクトのインデックス、チャプタータイトル、本文というデータになっている。

キーワード検索

まずはベクトル検索は行わずに、キーワード検索のみでやってみる。

クライアント初期化。キーワード検索のみなので、各種モジュール用のAPIキー等の設定はここで定義していない。

import weaviate
import weaviate.classes as wvc
import os

client = weaviate.connect_to_local()

コレクション定義。まずは今回登録オブジェクトのうち、テキスト本文だけをキーワード検索の対象とする。

  • index_searchable=Trueでキーワード検索が有効になる。デフォルトは有効なので実際には指定する必要はないが、今回は明示的に設定している。
  • キーワード検索はプロパティのデータ型がTEXT/TEXT_ARRAYの場合のみ。数値などは検索できない。
  • ちなみにindex_filterableはroaling bitmap indexというやつらしく、高速な事前フィルタに使えるらしい。
  • 日本語で使えるトークナイザーとしてまずgseを使ってみる。
import weaviate.classes as wvc

oguricap = client.collections.create(
    name="OguriCap",
    properties=[
        wvc.config.Property(
            name="chunk_id",
            data_type=wvc.config.DataType.INT,
        ),
        wvc.config.Property(
            name="chapter_title",
            data_type=wvc.config.DataType.TEXT,
            index_searchable=False,
            index_filterable=False,
        ),
        wvc.config.Property(
            name="chunk",
            data_type=wvc.config.DataType.TEXT,
            index_searchable=True,
            index_filterable=True,
            tokenization=wvc.config.Tokenization.GSE
        ),
    ]
)

データ登録

oguricap.data.insert_many(wvc_objs)
response = oguricap.aggregate.over_all(total_count=True)
print(response.total_count)

107件登録

107

ではキーワード検索してみる。キーワード検索の場合は、distancecertaintyではなく、scoreexplain_scoreを使うらしい、ここに書いてある。

response = oguricap.query.bm25(
    limit=5,
    query="武豊",
    query_properties=["chunk"],
    return_metadata=wvc.query.MetadataQuery(score=True, explain_score=True)
)

for p in response.objects:
    print(f"===== {p.properties['chunk_id']} =====")
    print(p.metadata.score)
    print(p.metadata.explain_score)
    print(p.properties["chapter_title"])
    print(p.properties["chunk"])
    print()
===== 101 =====
3.0115909576416016
, BM25F_武豊_frequency:3, BM25F_武豊_propLength:155
オグリキャップ > 騎手(4)
 南井克巳 1988年の京都4歳特別に、河内洋の代役として騎乗した。翌1989年には前述のように岡部が近藤からの騎乗依頼を断った後で瀬戸口から騎乗依頼を受け、主戦騎手を務めた。1990年はバンブービギンに騎乗することを決断し、自ら降板を申し出た。1989年の第34回有馬記念における騎乗について野平祐二と岡部は南井の騎乗ミスを指摘した。南井は有馬記念の騎乗について、オグリキャップの調子が悪くいつもの末脚を発揮することが難しいため、好位置の楽な競馬で気力を取り戻すことを期待したと説明している。 武豊 1990年にアメリカ遠征が決定した際、武豊が鞍上を務めることが決まったことで陣営は第40回安田記念にも騎乗を依頼し、同レースで騎乗することとなった。

===== 48 =====
2.349754810333252
, BM25F_武豊_frequency:2, BM25F_武豊_propLength:228
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 1990年後半の不振と復活 > 第35回有馬記念のレース内容(2)
またライターの関口隆哉も、「レース展開、出走馬たちのレベル、当日の状態など、すべてのファクターがオグリキャップ有利に働いた」とし、瀬戸慎一郎は「2着、3着に入った馬が、幾度となくジリ脚に泣いたメジロライアンとホワイトストーンであっただけに、相手関係にもかなり恵まれたといわなければならない」と述べ、また武豊が「調子は7、8分でも力が違います」と骨っぽい相手がいなかったことを匂わせるような発言をしていたといい、ペースが落ち着いて楽に追走できたことと「直線で素早く抜け出せる爆発的な瞬発力を持った馬」が出走していなかったことが大きかったと述べている。 武豊は1993年に同レースを振り返った際には「別に謙遜してるわけじゃなく、強い馬が走りやすいように走らせただけなんですよ。だから、勝っても驚きはしなかった。

===== 89 =====
2.1181771755218506
, BM25F_武豊_frequency:1, BM25F_武豊_propLength:122
オグリキャップ > 人気 > 第二次競馬ブームとの関係
オグリキャップの人気は、ほぼ同時期にデビューした騎手の武豊の人気、JRAのCMによるイメージ戦略、およびバブル景気との相乗効果によって競馬ブームを巻き起こしたとされる。このブームは「第二次競馬ブーム」と呼ばれ、競馬の世界を通り越した社会現象と評されている。 第二次競馬ブームとオグリキャップの関係について、大川慶次郎は「競馬ブームを最終的に構築したのはオグリキャップだ」と評している。ライターの瀬戸慎一郎は、第二次競馬ブームの主役がオグリキャップであったのはいうまでもない、と述べている。

===== 51 =====
2.017594337463379
, BM25F_武豊_frequency:1, BM25F_武豊_propLength:140
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 引退式(2)
オグリキャップは安藤勝己が騎乗して競馬場のコースを2周走行し、また岐阜県がオグリキャップを、笠松町が小栗と鷲見を表彰した。東京競馬場での引退式では7万6000人のファンが集まり、武豊が騎乗して第35回有馬記念でのゼッケン「8番」を付けてコースを走行した。当日は東京競馬記者クラブ賞を受賞した瀬戸口勉、辻本光雄、池江敏郎の表彰式が行われた。各場での引退式においては、安西美穂子が作詞したバラード調の曲『オグリキャップの歌』(歌:YUKARI、作曲:青木美恵子)がBGMとして使用された。

===== 97 =====
1.9556831121444702
, BM25F_武豊_frequency:1, BM25F_武豊_propLength:152
オグリキャップ > 人気 > 第35回有馬記念優勝後の人気(2)
武豊は1998年に受けたインタビューにおいて、同レースが「奇蹟」などと言われていることについて、「こんな言い方は失礼かもしれないけど、オグリよりも、あの時(翌1991年の有馬記念)のダイユウサクの脚のほうが『奇蹟』でしょう」と述べている。 第35回有馬記念はNHKとフジテレビが生放送し、ビデオリサーチの発表によると視聴率はそれぞれ11.7%と9.6%だった。前年の有馬記念はNHKが16.2%、フジテレビが10.4%を記録していたが、2局合わせた番組占拠率は50.3%を記録した。

scoreがキーワード検索での合致度のスコアで、BM25FというBM25のバリエーションに基づいているものらしい。explain_scoreはこれを検索キーワードの要素に分解したもの。

もう一つ別の例

response = oguricap.query.bm25(
    limit=5,
    query="有馬記念 勝利",
    query_properties=["chunk"],
    return_metadata=wvc.query.MetadataQuery(score=True, explain_score=True)
)

for p in response.objects:
    print(f"===== {p.properties['chunk_id']} =====")
    print(p.metadata.score)
    print(p.metadata.explain_score)
    print(p.properties["chapter_title"])
    print(p.properties["chunk"])
    print()
===== 67 =====
3.7314274311065674
, BM25F_有馬_propLength:197, BM25F_勝利_frequency:2, BM25F_勝利_propLength:197, BM25F_記念_frequency:2, BM25F_記念_propLength:197, BM25F_有馬_frequency:2
オグリキャップ > 特徴・評価 > 知能・精神面に関する特徴・評価(1)
ダンシングキャップ産駒の多くは気性が荒いことで知られていたが、オグリキャップは現3歳時に調教のために騎乗した河内洋と岡部幸雄が共に古馬のように落ち着いていると評するなど、落ち着いた性格の持ち主であった。オグリキャップの落ち着きは競馬場でも発揮され、パドックで観客の歓声を浴びても動じることがなく、ゲートでは落ち着き過ぎてスタートが遅れることがあるほどであった。岡部幸雄は1988年の有馬記念のレース後に「素晴らしい精神力だね。この馬は耳を立てて走るんだ。レースを楽しんでいるのかもしれない」と語り、1990年の有馬記念でスローペースの中で忍耐強く折り合いを保ち続けて勝利したことについて、「類稀なる精神力が生んだ勝利だ」と評したが、オグリキャップと対戦した競走馬の関係者からもオグリキャップの精神面を評価する声が多く挙がっている。

===== 79 =====
3.1181640625
, BM25F_有馬_frequency:2, BM25F_有馬_propLength:177, BM25F_勝利_frequency:1, BM25F_勝利_propLength:177, BM25F_記念_frequency:2, BM25F_記念_propLength:177
オグリキャップ > 特徴・評価 > 総合的な評価(2)
自身が騎乗した4歳時のジャパンカップまではGIで勝利を挙げられなかったものの、次走の第33回有馬記念以降から長く厳しいレースをしながら最後まで丈夫に走り続けたことについて、「本当に野武士のような馬だったね」と述べている。 第33回有馬記念で騎乗した岡部幸雄は、オグリキャップとシンボリルドルフとを比較し、力を出す必要のない時に手を抜いて走ることができるかどうかの点で「オグリキャップは他の馬よりはそれができるけれど、ルドルフと比べるとまじめ過ぎる感じ」という評価を下し、また2000mから2200mがベスト距離のシンボリルドルフがオグリキャップのベスト距離である1600mで戦った場合についても、調教を通して短距離のペースに慣れさせることで勝つだろうと述べた。

===== 49 =====
3.0284743309020996
, BM25F_有馬_propLength:191, BM25F_勝利_frequency:1, BM25F_勝利_propLength:191, BM25F_記念_frequency:2, BM25F_記念_propLength:191, BM25F_有馬_frequency:2
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 1990年後半の不振と復活 > 第35回有馬記念のレース内容(3)
奇跡でも何でもないと思う」と語り、「あの馬の絶対能力がズバ抜けていた、というだけのことでしょうね。もしオグリがピークのデキだったら、ブッチぎって勝っていたでしょうね」と述べ、レース直後に受けたインタビューでは「強い馬は強いんです」というコメントを残している。 岡部幸雄は「極端なスローペースが良かった」としつつ、「スローに耐えて折り合うのは大変」「ある意味で有馬記念は過酷なペースだった」とし、「ピタッと折り合える忍耐強さを最も備えていたのがオグリキャップだった」、「想像以上に過酷な超スローペースで折り合いを保ち続けたオグリ。類稀なる精神力が生んだ勝利だ」と評している。なお、野平祐二はレース前の段階で有馬記念がゆったりした流れになれば本質的にマイラーであるオグリキャップの雪辱は可能と予測していた。

===== 8 =====
2.9635629653930664
, BM25F_勝利_frequency:3, BM25F_勝利_propLength:196
オグリキャップ > 競走馬時代 > 笠松競馬時代 > 競走内容(2)
ハナ差で勝利した7戦目のジュニアクラウン以外の全てのレースで2着を2馬身以上引き離して勝利し、4歳初戦のゴールドジュニアを勝利して「笠松には凄い大物がいるらしい」という評判が立つようになった。 前述のようにオグリキャップはデビュー戦と4戦目の2度にわたってマーチトウショウに敗れている。敗れたのはいずれもダート800mのレースで、短距離戦では大きな不利に繋がるとされる出遅れをした。一方オグリキャップに勝ったレースでマーチトウショウに騎乗していた原隆男によると、オグリキャップがエンジンのかかりが遅い馬であったのに対し、マーチトウショウは「一瞬の脚が武器のような馬で、短い距離が合っていた」という。マーチトウショウに敗れた2戦はいずれも追い込んだが届かずといった内容で距離不足が理由だったため、「オグリは特急、他の馬は鈍行。出遅れがちょうどいいハンデ」と言われた。

===== 65 =====
2.6571433544158936
, BM25F_勝利_frequency:2, BM25F_勝利_propLength:174
オグリキャップ > 成績 > 種牡馬成績 > 子孫(2)
2017年にラインミーティア(父メイショウボーラー、母の母の父がオグリキャップ)がアイビスサマーダッシュを勝利し、オグリキャップの血を引く馬としては初めて重賞を制覇した。しかし、34頭のうち産駒が繁殖牝馬となった馬もほとんどおらず、オグリキャップの血を引く繁殖牝馬は急激に減少している。 2020年10月にホワイトシスネ(母キョウワスピカ、母父オグリキャップ)がホッカイドウ競馬を登録抹消となり一旦はオグリキャップの孫世代の現役競走馬がいなくなった。その後、2021年4月にミンナノアイドルの産駒であるミンナノヒーロー(父ゴールドアリュール、母父オグリキャップ)が岩手競馬でデビュー。2021年5月に初勝利し、その後通算3勝目を挙げたが、同年7月20日の盛岡競馬第8競走で故障し、左第1指関節開放脱臼のため予後不良となり死亡した。

複数キーワードの場合でも、それぞれがトークンとして認識されているように見える。日本語キーワード検索は動作している模様。

では次にトリグラムで。一旦コレクション削除。

client.collections.delete(name="OguriCap")

再度コレクション定義、今回はトークナイザーにトリグラムを指定して、オブジェクト登録。

https://weaviate.io/developers/weaviate/concepts/prefiltering#prefiltering-with-roaring-bitmaps

import weaviate.classes as wvc

oguricap = client.collections.create(
    name="OguriCap",
    properties=[
        wvc.config.Property(
            name="chunk_id",
            data_type=wvc.config.DataType.INT,
        ),
        wvc.config.Property(
            name="chapter_title",
            data_type=wvc.config.DataType.TEXT,
            index_searchable=False,
            index_filterable=False,
        ),
        wvc.config.Property(
            name="chunk",
            data_type=wvc.config.DataType.TEXT,
            index_searchable=True,
            index_filterable=True,
            tokenization=wvc.config.Tokenization.TRIGRAM
        ),
    ]
)

oguricap.data.insert_many(wvc_objs)

検索してみる。

response = oguricap.query.bm25(
    limit=5,
    query="有馬記念",
    query_properties=["chunk"],
    return_metadata=wvc.query.MetadataQuery(score=True, explain_score=True)
)

for p in response.objects:
    print(f"===== {p.properties['chunk_id']} =====")
    print(p.metadata.score)
    print(p.metadata.explain_score)
    print(p.properties["chapter_title"])
    print(p.properties["chunk"])
    print()

3文字のトークンで検索されているのがわかる。

===== 20 =====
1.6917977333068848
, BM25F_馬記念_frequency:4, BM25F_馬記念_propLength:269, BM25F_有馬記_frequency:4, BM25F_有馬記_propLength:269
オグリキャップ > 競走馬時代 > 中央競馬時代 > 4歳(1988年) > 競走内容(7)
第4コーナーから進路を確保しつつ前方への進出を開始したがペイザバトラーとタマモクロスを抜けず3着に敗れた。レース後、次走の有馬記念で挽回を果たしたいと考えた佐橋は、瀬戸口を通じてこの時点で有馬記念での騎乗馬が決まっていなかった岡部幸雄を鞍上に希望し、瀬戸口を通じて騎乗依頼が出されたものの、岡部は「西(栗東)の馬はよくわからないから」と婉曲に断った。しかし瀬戸口が「一回だけ」という条件付きで依頼し、これを岡部が承知したことで有馬記念での騎乗が決まった。  有馬記念までの間は美浦トレーニングセンターで調整を行うこととなった。オグリキャップはタマモクロスに次ぐファン投票2位で出走が決まり、当日の単勝オッズもタマモクロスに次ぐ2番人気に支持された。

===== 97 =====
1.6762537956237793
, BM25F_有馬記_frequency:3, BM25F_有馬記_propLength:191, BM25F_馬記念_frequency:3, BM25F_馬記念_propLength:191
オグリキャップ > 人気 > 第35回有馬記念優勝後の人気(2)
武豊は1998年に受けたインタビューにおいて、同レースが「奇蹟」などと言われていることについて、「こんな言い方は失礼かもしれないけど、オグリよりも、あの時(翌1991年の有馬記念)のダイユウサクの脚のほうが『奇蹟』でしょう」と述べている。 第35回有馬記念はNHKとフジテレビが生放送し、ビデオリサーチの発表によると視聴率はそれぞれ11.7%と9.6%だった。前年の有馬記念はNHKが16.2%、フジテレビが10.4%を記録していたが、2局合わせた番組占拠率は50.3%を記録した。

===== 102 =====
1.6680089235305786
, BM25F_有馬記_frequency:4, BM25F_有馬記_propLength:290, BM25F_馬記念_frequency:4, BM25F_馬記念_propLength:290
オグリキャップ > 騎手(5)
この起用は武がオグリキャップと共に第二次競馬ブームの象徴的存在であったことに加え、前年の有馬記念の開催3日前にテレビ番組「森田一義アワー 笑っていいとも!」に出演した際、「オグリキャップは何を考えているかわからないところがあって、嫌いです」と発言していたことで話題となり、激しく反発するファンも現れた。武は第31回宝塚記念でスーパークリークに騎乗することを選択し、さらにオグリキャップの海外遠征が中止となったことでコンビ解消となったが、当初第35回有馬記念で騎乗予定だったスーパークリークが故障により引退したため、再びオグリキャップに騎乗した。武は平成三強全てに騎乗した経験を持つが、三頭の中で好みのタイプを挙げるとすればスーパークリークだと答えている。武は第35回有馬記念について、「有馬記念を勝ったとたん、アンチが一気に減った。

===== 44 =====
1.582543969154358
, BM25F_馬記念_propLength:257, BM25F_有馬記_frequency:3, BM25F_有馬記_propLength:257, BM25F_馬記念_frequency:3
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 1990年後半の不振と復活 > 体調(4)
また小説家の高橋源一郎によると、有馬記念2日前の21日にサンケイスポーツ主催で催された「有馬記念前夜祭」に出席した際、打ち上げの席で野平祐二にオグリキャップの具合について訊ねたところ、野平は「私には悪いようには見えません。皆さんは色々おっしゃっていますが、私の見た限りでは、そんなに悪い状態には思えないのですよ」と答えたという。中央競馬時代のオグリキャップの診察を担当していた獣医師の吉村秀之は、オグリキャップが中央競馬へ移籍した当初の時点で既に備えていたが大敗を続けた時期にはなくなっていたスポーツ心臓を有馬記念の前に取り戻したことから体調の上昇を察知し、家族に対し「今度は勝つ」と予言していた。

===== 85 =====
1.5420763492584229
, BM25F_馬記念_frequency:2, BM25F_馬記念_propLength:164, BM25F_有馬記_frequency:2, BM25F_有馬記_propLength:164
オグリキャップ > 特徴・評価 > 投票における評価(3)
また、日本馬主協会連合会が史誌『日本馬主協会連合会40年史』(2001年)の中で、登録馬主を対象に行ったアンケートでは、「一番印象に残る競走馬」の部門で第1位を獲得、「一番印象に残っているレース」の部門でも、ラストランの第35回有馬記念が第38回有馬記念(トウカイテイオー優勝)と同率での第1位(504票中19票)に選ばれた。「一番の名馬」部門では第5位(第1位はシンザン)、「一番好きな競走馬」部門では第9位(第1位はハイセイコー)だった。

Click to add a cell.

ただトリグラムなので、分かち書きが行われているわけではないし、3文字未満だと検索結果は0件になる。

esponse = oguricap.query.bm25(
    limit=5,
    query="武豊",
    query_properties=["chunk"],
    return_metadata=wvc.query.MetadataQuery(score=True, explain_score=True)
)

print(len(response.objects))
0

複数キーワードの場合も同じ。1つのキーワードが3文字以上である必要がある。

response = oguricap.query.bm25(
    limit=5,
    query="武豊 有馬",
    query_properties=["chunk"],
    return_metadata=wvc.query.MetadataQuery(score=True, explain_score=True)
)

print(len(response.objects))
0
kun432kun432

ハイブリッド検索

ではベクトル検索も有効にして、今度はハイブリッド検索を試してみる。

クライアント初期化時にOpenAI APIキーを有効化しておく。

import weaviate
import weaviate.classes as wvc
import os

client = weaviate.connect_to_local(
    headers={
        "X-OpenAI-Api-Key": os.environ['OPENAI_API_KEY']
    }
)

一旦コレクションを削除

client.collections.delete(name="OguriCap")

ベクトル化を有効にしてコレクション作成+オブジェクト登録。キーワード検索の方はgseで。

import weaviate.classes as wvc

oguricap = client.collections.create(
    name="OguriCap",
    vectorizer_config=wvc.config.Configure.Vectorizer.text2vec_openai(
        model="text-embedding-3-small",   # モデルはtext-embedding-3-smallを使用
        vectorize_collection_name=False,  # コレクション名はベクトル化煮含めない
    ),
    properties=[
        wvc.config.Property(
            name="chunk_id",
            data_type=wvc.config.DataType.INT,  # INT型はベクトル化されない
            skip_vectorization=True
        ),
        wvc.config.Property(
            name="chapter_title",
            data_type=wvc.config.DataType.TEXT,
            skip_vectorization=True,     # ベクトル化無効
            index_searchable=False,
            index_filterable=False,
        ),
        wvc.config.Property(
            name="chunk",
            data_type=wvc.config.DataType.TEXT,
            skip_vectorization=False,      # ベクトル化を有効
            vectorize_property_name=False, # プロパティ名をベクトル化に含めない
            index_searchable=True,
            index_filterable=False,
            tokenization=wvc.config.Tokenization.GSE
        ),
    ]
)

oguricap.data.insert_many(wvc_objs)
response = oguricap.aggregate.over_all(total_count=True)
print(response.total_count)

ではまずキーワード検索

response = oguricap.query.bm25(
    limit=10,
    query="武豊 有馬記念",
    query_properties=["chunk"],
    return_metadata=wvc.query.MetadataQuery(distance=True, certainty=True, score=True, explain_score=True)
)

for p in response.objects:
    print(f"===== {p.properties['chunk_id']} =====")
    print(p.metadata.distance)
    print(p.metadata.certainty)
    print(p.metadata.score)
    print(p.metadata.explain_score)
    print(p.properties["chapter_title"])
    print(f"{p.properties['chunk'][:80]}...")
    print()
===== 101 =====
None
None
4.345559120178223
, BM25F_武豊_propLength:155, BM25F_有馬_frequency:2, BM25F_有馬_propLength:155, BM25F_記念_frequency:3, BM25F_記念_propLength:155, BM25F_武豊_frequency:3
オグリキャップ > 騎手(4)
 南井克巳 1988年の京都4歳特別に、河内洋の代役として騎乗した。翌1989年には前述のように岡部が近藤からの騎乗依頼を断った後で瀬戸口から騎乗依頼を受け、主...

===== 97 =====
None
None
3.3929693698883057
, BM25F_武豊_propLength:152, BM25F_有馬_frequency:3, BM25F_有馬_propLength:152, BM25F_記念_frequency:3, BM25F_記念_propLength:152, BM25F_武豊_frequency:1
オグリキャップ > 人気 > 第35回有馬記念優勝後の人気(2)
武豊は1998年に受けたインタビューにおいて、同レースが「奇蹟」などと言われていることについて、「こんな言い方は失礼かもしれないけど、オグリよりも、あの時(翌1...

===== 38 =====
None
None
3.2065792083740234
, BM25F_武豊_frequency:1, BM25F_武豊_propLength:165, BM25F_有馬_frequency:2, BM25F_有馬_propLength:165, BM25F_記念_frequency:3, BM25F_記念_propLength:165
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 競走内容(4)
レースでは最後方から追走し、第3コーナーから前方への進出を開始したが直線で伸びを欠き、11着に敗れた(レースに関する詳細については第10回ジャパンカップを参照)...

===== 51 =====
None
None
2.9767026901245117
, BM25F_有馬_propLength:140, BM25F_記念_frequency:1, BM25F_記念_propLength:140, BM25F_武豊_frequency:1, BM25F_武豊_propLength:140, BM25F_有馬_frequency:1
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 引退式(2)
オグリキャップは安藤勝己が騎乗して競馬場のコースを2周走行し、また岐阜県がオグリキャップを、笠松町が小栗と鷲見を表彰した。東京競馬場での引退式では7万6000人...

===== 42 =====
None
None
2.587602138519287
, BM25F_有馬_propLength:197, BM25F_記念_frequency:1, BM25F_記念_propLength:197, BM25F_武豊_frequency:1, BM25F_武豊_propLength:197, BM25F_有馬_frequency:1
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 1990年後半の不振と復活 > 体調(2)
  池江によると、オグリキャップはテレビ取材のカメラに一日中追いかけられた事で「それまではカイバを食べている鼻先にカメラを近づけられても全然気にしていなかったの...

===== 36 =====
None
None
2.5106043815612793
, BM25F_記念_frequency:4, BM25F_記念_propLength:174, BM25F_武豊_frequency:1, BM25F_武豊_propLength:174
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 競走内容(2)
当初初戦には大阪杯が予定されていたが、故障は見当たらないものの調子は思わしくなく、安田記念に変更された。この競走では武豊が初めて騎乗した。レースでは2、3番手を...

===== 45 =====
None
None
2.4850239753723145
, BM25F_武豊_frequency:1, BM25F_武豊_propLength:215, BM25F_有馬_frequency:1, BM25F_有馬_propLength:215, BM25F_記念_frequency:1, BM25F_記念_propLength:215
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 1990年後半の不振と復活 > 騎手との相性(1)
ライターの渡瀬夏彦は天皇賞(秋)とジャパンカップで騎乗した増沢末夫について、スタート直後から馬に気合を入れる増沢の騎乗スタイルと、岡部幸雄が「真面目すぎるくらい...

===== 48 =====
None
None
2.349754810333252
, BM25F_武豊_frequency:2, BM25F_武豊_propLength:228
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 1990年後半の不振と復活 > 第35回有馬記念のレース内容(2)
またライターの関口隆哉も、「レース展開、出走馬たちのレベル、当日の状態など、すべてのファクターがオグリキャップ有利に働いた」とし、瀬戸慎一郎は「2着、3着に入っ...

===== 81 =====
None
None
2.183497428894043
, BM25F_武豊_frequency:1, BM25F_武豊_propLength:184, BM25F_記念_frequency:1, BM25F_記念_propLength:184
オグリキャップ > 特徴・評価 > 総合的な評価(4)
 武豊は自身が騎乗するまでのオグリキャップに対して「にくいほど強い存在でしたし、あこがれの存在でもありました」と述べ、初めてコンビを組んだ安田記念は「自分でも乗...

===== 89 =====
None
None
2.1181771755218506
, BM25F_武豊_frequency:1, BM25F_武豊_propLength:122
オグリキャップ > 人気 > 第二次競馬ブームとの関係
オグリキャップの人気は、ほぼ同時期にデビューした騎手の武豊の人気、JRAのCMによるイメージ戦略、およびバブル景気との相乗効果によって競馬ブームを巻き起こしたと...

次にベクトル検索。ベクトル検索の場合、クエリは自然言語で行うべきだと思うけど、一旦はキーワード検索と同じもので。

response = oguricap.query.near_text(
    limit=10,
    query="武豊 有馬記念",
    return_metadata=wvc.query.MetadataQuery(distance=True, certainty=True, score=True, explain_score=True)
)

for p in response.objects:
    print(f"===== {p.properties['chunk_id']} =====")
    print(p.metadata.distance)
    print(p.metadata.certainty)
    print(p.metadata.score)
    print(p.metadata.explain_score)
    print(p.properties["chapter_title"])
    print(f"{p.properties['chunk'][:80]}...")
    print()
===== 97 =====
0.45138221979141235
0.7743089199066162
0.0

オグリキャップ > 人気 > 第35回有馬記念優勝後の人気(2)
武豊は1998年に受けたインタビューにおいて、同レースが「奇蹟」などと言われていることについて、「こんな言い方は失礼かもしれないけど、オグリよりも、あの時(翌1...

===== 36 =====
0.4725378751754761
0.763731062412262
0.0

オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 競走内容(2)
当初初戦には大阪杯が予定されていたが、故障は見当たらないものの調子は思わしくなく、安田記念に変更された。この競走では武豊が初めて騎乗した。レースでは2、3番手を...

===== 102 =====
0.4733424186706543
0.7633287906646729
0.0

オグリキャップ > 騎手(5)
この起用は武がオグリキャップと共に第二次競馬ブームの象徴的存在であったことに加え、前年の有馬記念の開催3日前にテレビ番組「森田一義アワー 笑っていいとも!」に出...

===== 101 =====
0.4880242347717285
0.7559878826141357
0.0

オグリキャップ > 騎手(4)
 南井克巳 1988年の京都4歳特別に、河内洋の代役として騎乗した。翌1989年には前述のように岡部が近藤からの騎乗依頼を断った後で瀬戸口から騎乗依頼を受け、主...

===== 104 =====
0.4950408935546875
0.7524795532226562
0.0

オグリキャップ > 騎手(7)
増さんに乗ってもらうのも分かっていたけど、豊くんで負ければ納得いくんじゃないかと思って…」と語り、もし鞍上が武で決まらなかった場合は内心オグリキャップを有馬記念...

===== 81 =====
0.5107378959655762
0.7446310520172119
0.0

オグリキャップ > 特徴・評価 > 総合的な評価(4)
 武豊は自身が騎乗するまでのオグリキャップに対して「にくいほど強い存在でしたし、あこがれの存在でもありました」と述べ、初めてコンビを組んだ安田記念は「自分でも乗...

===== 48 =====
0.531639814376831
0.7341800928115845
0.0

オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 1990年後半の不振と復活 > 第35回有馬記念のレース内容(2)
またライターの関口隆哉も、「レース展開、出走馬たちのレベル、当日の状態など、すべてのファクターがオグリキャップ有利に働いた」とし、瀬戸慎一郎は「2着、3着に入っ...

===== 45 =====
0.5324968695640564
0.7337515354156494
0.0

オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 1990年後半の不振と復活 > 騎手との相性(1)
ライターの渡瀬夏彦は天皇賞(秋)とジャパンカップで騎乗した増沢末夫について、スタート直後から馬に気合を入れる増沢の騎乗スタイルと、岡部幸雄が「真面目すぎるくらい...

===== 20 =====
0.5360528230667114
0.7319735884666443
0.0

オグリキャップ > 競走馬時代 > 中央競馬時代 > 4歳(1988年) > 競走内容(7)
第4コーナーから進路を確保しつつ前方への進出を開始したがペイザバトラーとタマモクロスを抜けず3着に敗れた。レース後、次走の有馬記念で挽回を果たしたいと考えた佐橋...

===== 38 =====
0.552097737789154
0.7239511013031006
0.0

オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 競走内容(4)
レースでは最後方から追走し、第3コーナーから前方への進出を開始したが直線で伸びを欠き、11着に敗れた(レースに関する詳細については第10回ジャパンカップを参照)...

ではハイブリッド。alphaで割合を決める。0に近いほどキーワード検索を有効に、1に近いほどベクトル検索を有効にする感じ。デフォルトは0.75でベクトル検索寄りになっているらしい。

response = oguricap.query.hybrid(
    limit=10,
    query="武豊 有馬記念",
    alpha=0.75,
    query_properties=["chunk"],
    return_metadata=wvc.query.MetadataQuery(distance=True, certainty=True, score=True, explain_score=True)
)

for p in response.objects:
    print(f"===== {p.properties['chunk_id']} =====")
    print(p.metadata.distance)
    print(p.metadata.certainty)
    print(p.metadata.score)
    print(p.metadata.explain_score)
    print(p.properties["chapter_title"])
    print(f"{p.properties['chunk'][:80]}...")
    print()

ハイブリッドの場合、explain_scoreにキーワード検索とベクトル検索での元々のスコアが出力されており、これがハイブリッドによって正規化されているのがわかる。

===== 97 =====
None
None
0.9402898550033569

Hybrid (Result Set vector) Document ea6790a0-5a03-4767-b47a-2672ae398967: original score 0.5486178, normalized score: 0.75 - 
Hybrid (Result Set keyword) Document ea6790a0-5a03-4767-b47a-2672ae398967: original score 3.3929694, normalized score: 0.19028986
オグリキャップ > 人気 > 第35回有馬記念優勝後の人気(2)
武豊は1998年に受けたインタビューにおいて、同レースが「奇蹟」などと言われていることについて、「こんな言い方は失礼かもしれないけど、オグリよりも、あの時(翌1...

===== 101 =====
None
None
0.9091730117797852

Hybrid (Result Set vector) Document 29626e4b-9546-4dfc-a910-b49341fc67ed: original score 0.51197577, normalized score: 0.659173 - 
Hybrid (Result Set keyword) Document 29626e4b-9546-4dfc-a910-b49341fc67ed: original score 4.345559, normalized score: 0.25
オグリキャップ > 騎手(4)
 南井克巳 1988年の京都4歳特別に、河内洋の代役として騎乗した。翌1989年には前述のように岡部が近藤からの騎乗依頼を断った後で瀬戸口から騎乗依頼を受け、主...

===== 36 =====
None
None
0.8300701379776001

Hybrid (Result Set vector) Document a169c1b6-846a-4b7b-ac32-88bb4a5b0423: original score 0.52646506, normalized score: 0.69508857 - 
Hybrid (Result Set keyword) Document a169c1b6-846a-4b7b-ac32-88bb4a5b0423: original score 2.5106044, normalized score: 0.13498156
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 競走内容(2)
当初初戦には大阪杯が予定されていたが、故障は見当たらないものの調子は思わしくなく、安田記念に変更された。この競走では武豊が初めて騎乗した。レースでは2、3番手を...

===== 102 =====
None
None
0.7673690915107727

Hybrid (Result Set vector) Document f1cbe541-4af0-4d10-a229-cf57af121b83: original score 0.5266576, normalized score: 0.69556576 - 
Hybrid (Result Set keyword) Document f1cbe541-4af0-4d10-a229-cf57af121b83: original score 1.5026866, normalized score: 0.071803354
オグリキャップ > 騎手(5)
この起用は武がオグリキャップと共に第二次競馬ブームの象徴的存在であったことに加え、前年の有馬記念の開催3日前にテレビ番組「森田一義アワー 笑っていいとも!」に出...

===== 81 =====
None
None
0.7171149253845215

Hybrid (Result Set vector) Document 7e1ec6d3-d8e6-4153-9e68-7b916bc68461: original score 0.4891677, normalized score: 0.60263705 - 
Hybrid (Result Set keyword) Document 7e1ec6d3-d8e6-4153-9e68-7b916bc68461: original score 2.1834974, normalized score: 0.114477865
オグリキャップ > 特徴・評価 > 総合的な評価(4)
 武豊は自身が騎乗するまでのオグリキャップに対して「にくいほど強い存在でしたし、あこがれの存在でもありました」と述べ、初めてコンビを組んだ安田記念は「自分でも乗...

===== 104 =====
None
None
0.688194215297699

Hybrid (Result Set vector) Document d6c3adb9-5fea-4e10-a483-e32604123ade: original score 0.5049591, normalized score: 0.6417803 - 
Hybrid (Result Set keyword) Document d6c3adb9-5fea-4e10-a483-e32604123ade: original score 1.0976343, normalized score: 0.046413902
オグリキャップ > 騎手(7)
増さんに乗ってもらうのも分かっていたけど、豊くんで負ければ納得いくんじゃないかと思って…」と語り、もし鞍上が武で決まらなかった場合は内心オグリキャップを有馬記念...

===== 45 =====
None
None
0.682313859462738

Hybrid (Result Set vector) Document 703d5147-0f75-4aee-8bc9-c550131b5956: original score 0.46750313, normalized score: 0.5489357 - 
Hybrid (Result Set keyword) Document 703d5147-0f75-4aee-8bc9-c550131b5956: original score 2.485024, normalized score: 0.13337813
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 1990年後半の不振と復活 > 騎手との相性(1)
ライターの渡瀬夏彦は天皇賞(秋)とジャパンカップで騎乗した増沢末夫について、スタート直後から馬に気合を入れる増沢の騎乗スタイルと、岡部幸雄が「真面目すぎるくらい...

===== 38 =====
None
None
0.6789563298225403

Hybrid (Result Set vector) Document 7e5d8476-799c-4cde-8fdc-c35321a236c3: original score 0.44790226, normalized score: 0.50034976 - 
Hybrid (Result Set keyword) Document 7e5d8476-799c-4cde-8fdc-c35321a236c3: original score 3.2065792, normalized score: 0.17860658
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 競走内容(4)
レースでは最後方から追走し、第3コーナーから前方への進出を開始したが直線で伸びを欠き、11着に敗れた(レースに関する詳細については第10回ジャパンカップを参照)...

===== 48 =====
None
None
0.6759593486785889

Hybrid (Result Set vector) Document 3f996b3a-a271-48c1-af2c-2808528477a9: original score 0.4683602, normalized score: 0.55106014 - 
Hybrid (Result Set keyword) Document 3f996b3a-a271-48c1-af2c-2808528477a9: original score 2.3497548, normalized score: 0.12489919
オグリキャップ > 競走馬時代 > 近藤俊典への売却 > 6歳(1990年) > 1990年後半の不振と復活 > 第35回有馬記念のレース内容(2)
またライターの関口隆哉も、「レース展開、出走馬たちのレベル、当日の状態など、すべてのファクターがオグリキャップ有利に働いた」とし、瀬戸慎一郎は「2着、3着に入っ...

===== 20 =====
None
None
0.6120530366897583

Hybrid (Result Set vector) Document ca3de206-ebea-4958-a7e7-b2747ecaf7f1: original score 0.46391946, normalized score: 0.54005265 - 
Hybrid (Result Set keyword) Document ca3de206-ebea-4958-a7e7-b2747ecaf7f1: original score 1.50583, normalized score: 0.072000384
オグリキャップ > 競走馬時代 > 中央競馬時代 > 4歳(1988年) > 競走内容(7)
第4コーナーから進路を確保しつつ前方への進出を開始したがペイザバトラーとタマモクロスを抜けず3着に敗れた。レース後、次走の有馬記念で挽回を果たしたいと考えた佐橋...

各検索ごとのランクの違いをまとめてみる。

ランキング キーワード検索 ベクトル検索 ハイブリッド(alpha=0.75)
1位 101 97 97
2位 97 36 101
3位 38 102 36
4位 51 101 102
5位 42 104 81
6位 36 81 104
7位 45 48 45
8位 48 45 38
9位 81 20 48
10位 89 38 20

それぞれでランキング結果が異なっているのがわかる。

alphaによってどの程度違いが出るかを可視化してみる。

!pip install pandas matplotlib seaborn
query = "武豊 有馬記念"
limit = 10

all_results = {}

# keyword search
response = oguricap.query.bm25(
    limit=limit,
    query=query,
    query_properties=["chunk"],
    return_metadata=wvc.query.MetadataQuery(distance=True, certainty=True, score=True, explain_score=True)
)
all_results["KEYWORD"] = [p.properties['chunk_id'] for p in response.objects]

# hybrid search with alpha 0.0-1.0
for i in range(0, 11):
    alpha = i/10
    response = oguricap.query.hybrid(
        limit=limit,
        alpha=alpha,
        query=query,
        query_properties=["chunk"],
        return_metadata=wvc.query.MetadataQuery(distance=True, certainty=True, score=True, explain_score=True)
    )
    all_results[f"HYBRID_{alpha}"] = [p.properties['chunk_id'] for p in response.objects]

# vector search
response = oguricap.query.near_text(
    limit=limit,
    query=query,
    return_metadata=wvc.query.MetadataQuery(distance=True, certainty=True, score=True, explain_score=True)
)
all_results["VECTOR"] = [p.properties['chunk_id'] for p in response.objects]
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np

df = pd.DataFrame(all_results)
cmap = cm.coolwarm
cmap.set_bad('lightgray')
fig, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(df, annot=True, fmt='.0f', cmap=cmap, cbar=False, linewidths=0.5, ax=ax, mask=df.isnull())

plt.show()

なんとなくクロスな感じになってるのがわかるだろうか。

なお、ハイブリッドのアルゴリズムは、ranked fusion(デフォルト)とrelative score fusionの2つがある。ranked fusionはキーワード検索とベクトル検索の順位をベースにしたもの、relative score fusionはスコアをベースにしたもの。relativeScoreFusionのほうが精度はいいらしい。
fusion_typeで切り替えれる。

response = oguricap.query.hybrid(
    limit=10,
    alpha=0.75,
    query="武豊 有馬記念",
    query_properties=["chunk"],
    fusion_type=wvc.query.HybridFusion.RELATIVE_SCORE,  # デフォルトはRANKED
    return_metadata=wvc.query.MetadataQuery(distance=True, certainty=True, score=True, explain_score=True)
)

なお、今回の場合はどちらにしても順位は変わらなかった。

詳細は以下参照。

https://weaviate.io/blog/hybrid-search-fusion-algorithms

kun432kun432

日本語のトークナイザー自体が期待したとおりに動けば、いちいち自分でdense/sparseベクトルを自分で作らなくても、簡単にハイブリッドができるというのは、日本語対応という点については他のベクトルベンダーにはない強みだと思うし、とても期待ができると思う。

あえて、欲をいうならば、

  • トークナイザーにsudachi使いたい
  • sudachiの辞書を自分でカスタマイズしたい

あたりかな。

こうなってくると、対抗はベクトルDBベンダーというよりも、ElasticSearch、OpenSearch、Meilisearchあたりのベクトル検索に対応している全文検索ベンダーになってきそうな気はする。

このスクラップは2024/03/11にクローズされました