<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Python - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Python - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/jp/search-labs/blog/category/python-programming</link>
    </image>
    <link>https://www.elastic.co/jp/search-labs/blog/category/python-programming</link>
    <atom:link href="https://www.elastic.co/jp/search-labs/rss/category/python-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[jp]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 16:08:14 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Elasticsearch と SigLIP-2 による山頂のマルチモーダル探索 ]]></title>
    <description><![CDATA[SigLIP-2 埋め込みと Elasticsearch kNN ベクトル検索を使用して、テキストから画像、画像から画像へのマルチモーダル検索を実装する方法を学びます。プロジェクトの焦点: エベレスト トレッキングでアマ ダブラム山の山頂の写真を探す。]]></description>
    <content:encoded><![CDATA[<p>写真アルバムを意味で検索したいと思ったことはありませんか?「青いジャケットを着てベンチに座っている写真を見せてください」「エベレストの写真を見せてください」「日本酒と寿司」などのクエリを試してみてください。コーヒー（またはお好みの飲み物）を飲みながら、読み続けてください。このブログでは、マルチモーダル ハイブリッド検索アプリケーションの構築方法を紹介します。マルチモーダルとは、アプリが単語だけでなく、テキスト、画像、音声などさまざまな種類の入力を理解して検索できることを意味します。ハイブリッドとは、キーワード マッチング、kNN ベクトル検索、ジオフェンシングなどの技術を組み合わせて、より鮮明な結果を提供することを意味します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="エベレスト登山のさまざまな山頂の写真のライブラリ。" /><p>これを実現するために、Google の SigLIP-2 を使用して画像とテキストの両方のベクトル埋め込みを生成し、Elasticsearch ベクトル データベースに保存します。クエリ時に、検索入力、テキストまたは画像を埋め込みに変換し、高速 kNN ベクトル検索を実行して結果を取得します。この設定により、効率的なテキストから画像への検索、画像から画像への検索が可能になります。Streamlit UI は、テキストベースの検索を行ってアルバムから一致する写真を検索して表示するだけでなく、アップロードされた画像から山頂を識別し、フォトアルバムでその山の他の写真を表示できるフロントエンドを提供することで、このプロジェクトを実現します。また、検索精度を向上させるために実行した手順や、実用的なヒントやコツについても説明します。さらに詳しく調べるために、 <a href="https://github.com/navneet83/multimodal-mountain-peak-search">GitHub リポジトリ</a>と<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">Colab ノートブック</a>を提供しています。</p><h2>始まり</h2><p>このブログ投稿は、エベレストベースキャンプトレッキングで撮ったアマダブラム山の写真を全部見せてほしいと私に頼んだ10歳の子供からインスピレーションを受けたものです。写真アルバムを精査しながら、私は他のいくつかの山頂を特定するよう求められましたが、そのうちのいくつかは名前がわかりませんでした。</p><p>それで、これは楽しいコンピューター ビジョン プロジェクトになるかもしれないというアイデアが浮かびました。私たちが達成したかったこと:</p><ul><li><p>山頂の写真を名前で検索する</p></li><li><p>画像から山頂の名前を推測し、写真アルバムで似たような山頂を見つける</p></li><li><p>概念クエリを機能させる（<em>人</em>、<em>川</em>、<em>祈りの旗</em><em>など）</em></p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf82df9d7005fc3fe/6a17da78abe0f2e77bdfe8b9/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" alt="アマ・ダブラム山 " /><h2>ドリームチームを結成: SigLIP-2、Elasticsearch、Streamlit</h2><p>これを機能させるには、テキスト (「Ama Dablam」) と画像 (私のアルバムの写真) の両方を、意味のある比較が可能なベクトル、つまり同じベクトル空間に変換する必要があることがすぐに明らかになりました。これを実行すると、検索は単に「最も近いものを見つける」だけになります。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2、Elasticsearch、Streamlit のドリームチーム。" /><p>画像の埋め込みを生成するために、多言語<a href="https://huggingface.co/blog/vlms-2025">ビジョン言語エンコーダー</a>を使用します。これにより、山の写真と「Ama Dablam」などのフレーズが同じベクトル空間に配置されます。</p><p>最近 Google がリリースした<a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2</strong></a>はここによく当てはまります。タスク固有のトレーニング (<strong>ゼロショット</strong>設定) なしで埋め込みを生成でき、ラベルのない写真や異なる名前と言語を持つピークなど、私たちのユースケースに適しています。テキストと画像のマッチングがトレーニングされているため、クエリ言語やスペルが異なっていても、トレッキング中の山の写真と短いテキストプロンプトは埋め込みとして近いものになります。</p><p>SigLIP-2 は、品質と速度のバランスが優れており、複数の入力解像度をサポートし、CPU と GPU の両方で実行されます。SigLIP-2 は、オリジナルの CLIP などの以前のモデルと比較して、屋外での写真撮影に対してより堅牢になるように設計されています。私たちのテストでは、SigLIP-2 は一貫して信頼できる結果を生成しました。また、サポートも非常に充実しており、このプロジェクトに最適な選択肢となっています。</p><p>次に、埋め込みとパワー検索を保存するためのベクトル データベースが必要です。画像埋め込みに対するコサイン kNN 検索をサポートするだけでなく、単一のクエリでジオフェンスとテキスト フィルターを適用することもサポートする必要があります。Elasticsearch はここで最適です。ベクトル (dense_vector フィールドの HNSW kNN) を非常に適切に処理し、テキスト、ベクトル、地理クエリを組み合わせたハイブリッド検索をサポートし、フィルタリングと並べ替えをすぐに使用できます。また、水平方向にも拡張できるため、数枚の写真から数千枚の写真まで簡単に拡張できます。公式の<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">Elasticsearch Python クライアントは</a>、配管をシンプルに保ち、プロジェクトときれいに統合します。最後に、検索クエリを入力して結果を表示できる軽量のフロントエンドが必要です。簡単な Python ベースのデモには、Streamlit が最適です。ファイルのアップロード、レスポンシブな画像グリッド、並べ替えとジオフェンシングのためのドロップダウン メニューなど、必要な基本的な機能を提供します。簡単にクローンを作成してローカルで実行でき、Colab ノートブックでも動作します。</p><h2>実装</h2><h3>Elasticsearchのインデックス設計とインデックス戦略</h3><p>このプロジェクトでは、 <code>peaks_catalog</code>と<code>photos</code>の 2 つのインデックスを使用します。</p><h4>Peaks_catalogインデックス</h4><p>この索引は、エベレストベースキャンプトレッキング中に見える主要な山頂のコンパクトなカタログとして機能します。このインデックス内の各ドキュメントは、エベレスト山などの単一の山頂に対応しています。各山頂ドキュメントには、名前/エイリアス、オプションの緯度経度座標、および SigLIP-2 テキストプロンプト (+ オプションの参照画像) を組み合わせて構築された単一のプロトタイプ ベクトルが保存されます。</p><p><strong>インデックスマッピング:</strong></p><p>分野</p><p>タイプ</p><p>例</p><p>目的/注意事項</p><p>ベクトル/インデックス</p><p>id</p><p>キーワード</p><p>アマ・ダブラム</p><p>安定したスラッグ/ID</p><p>—</p><p>名前</p><p>テキスト + キーワードサブフィールド</p><p>["アマ・ダブラム"、"アマダブラム"]</p><p>エイリアス/多言語名; 正確なフィルターのためのnames.raw</p><p>—</p><p>ラトロン</p><p>ジオポイント</p><p>{"lat":27.8617,"lon":86.8614}</p><p>緯度/経度の組み合わせによるピーク GPS 座標 (オプション)</p><p>—</p><p>高度m</p><p>整数</p><p>6812</p><p>標高（オプション）</p><p>—</p><p>テキスト埋め込み</p><p>dense_vector</p><p>768</p><p>このピークのブレンドプロトタイプ（プロンプトとオプションで1～3枚の参照画像）</p><p>index:true、類似度:"cosine"、index_options: {type:"hnsw", m:16, ef_construction:128}</p><p>このインデックスは主に、画像から山頂を識別するなど、画像間の検索に使用されます。このインデックスは、テキストから画像への検索結果を強化するためにも使用されます。</p><p>要約すると、 <code>peaks_catalog</code>は「これは何の山ですか？」という質問を焦点を絞った最近傍問題に変換し、概念的理解を画像データの複雑さから効果的に分離します。</p><p><strong>peaks_catalog インデックスのインデックス戦略:</strong> EBC トレッキング中に見える最も目立つ山頂のリストを作成することから始めます。各山頂の地理的位置、名前、同義語、標高を<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">yaml ファイル</a>に保存します。次のステップは、各ピークの<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351">埋め込みを生成し</a>、それを<code>text_embed</code>フィールドに保存することです。堅牢な埋め込みを生成するために、次の手法を使用します。</p><ul><li><p>以下を使用してテキスト プロトタイプを作成します。</p><ul><li><p>山の名前</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">プロンプト アンサンブル</a>(複数の異なるプロンプトを使用して同じ質問に答える)、例:</p><ul><li><p>「ネパール、ヒマラヤ山脈の山頂{name}の自然写真」</p></li><li><p>「クンブ地域の{name}マーク的な山頂、高山の風景」</p></li><li><p>「 {name}山頂、雪、岩だらけの尾根」</p></li></ul></li><li><p>オプションの<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L333">反概念</a>（SigLIP-2 に一致しないものを指示する）: 「絵画、イラスト、ポスター、地図、ロゴ」の小さなベクトルを減算して、実際の写真に偏向させます。</p></li></ul></li><li><p>ピークの参照画像が提供されている場合は、オプションで<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">画像プロトタイプを作成します</a>。</p></li></ul><p>次に、<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">テキストと画像のプロトタイプをブレンドして</a>、最終的な埋め込みを生成します。最後に、ドキュメントはすべての必須フィールドで<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">インデックス化され</a>ます。</p>def l2norm(v: np.ndarray) -&gt; np.ndarray:
    return v / (np.linalg.norm(v) + 1e-12)
def compute_blended_peak_vec(
        emb: Siglip2,
        names: List[str],
        peak_id: str,
        peaks_images_root: str,
        alpha_text: float = 0.5,
        max_images: int = 3,
) -&gt; Tuple[np.ndarray, int, int, List[str]]:
    """
    Build blended vector for a single peak.

    Returns:
      vec           : np.ndarray (L2-normalized)
      found_count   : number of reference images discovered
      used_count    : number of references used (&lt;= max_images)
      used_filenames: list of filenames used (for logging)
    """
    # 1) TEXT vector
    tv = embed_text_blend(emb, names)

    # 2) IMAGE refs: prefer folder by id; fallback to slug of the primary name
    root = Path(peaks_images_root)
    candidates = [root / peak_id]
    if names:
        candidates.append(root / slugify(names[0]))

    all_refs: List[Path] = []
    for c in candidates:
        if c.exists() and c.is_dir():
            all_refs = list_ref_images(c)
            if all_refs:
                break

    found = len(all_refs)
    used_list = all_refs[:max_images] if (max_images and found &gt; max_images) else all_refs
    used = len(used_list)

    img_v = embed_image_mean(emb, used_list) if used_list else None

    # 3) Blend TEXT and IMAGE vectors, clamp alpha to [0,1]
    a = max(0.0, min(1.0, float(alpha_text)))
    vec = l2norm(tv if img_v is None else (a * tv + (1.0 - a) * img_v)).astype("float32")
    return vec, found, used, [p.name for p in used_list]<p><code>peaks_catalog</code>インデックスからのサンプル ドキュメント:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="Elasticsearch の peaks_catalog インデックスからのサンプル ドキュメント。" /><h4>写真インデックス</h4><p>このプライマリ インデックスには、アルバム内のすべての写真に関する詳細情報が保存されます。各ドキュメントは 1 枚の写真を表し、次の情報が含まれています。</p><ul><li><p>フォトアルバム内の写真への相対パス。これを使用して、一致する画像を表示したり、検索 UI に画像を読み込んだりできます。</p></li><li><p>写真のGPSと時間情報。</p></li><li><p>SigLIP-2 によって生成された画像エンコーディング用の密なベクトル。</p></li><li><p><code>predicted_peaks</code> ピーク名でフィルタリングできます。<strong>インデックスマッピング</strong></p></li></ul><p>分野</p><p>タイプ</p><p>例</p><p>目的/注意事項</p><p>ベクター / インデックス</p><p>パス</p><p>キーワード</p><p>データ/画像/IMG_1234.HEIC</p><p>UI でサムネイル/フル画像を開く方法</p><p>—</p><p>クリップ画像</p><p>dense_vector</p><p>768</p><p>SigLIP-2画像埋め込み</p><p>index:true、類似度:"cosine"、index_options: {type:"hnsw", m:16, ef_construction:128}</p><p>予測ピーク</p><p>キーワード</p><p>["ama-dablam","pumori"]</p><p>インデックス時の上位Kの推測（安価なUXフィルター/ファセット）</p><p>—</p><p>GPS</p><p>ジオポイント</p><p>{"lat":27.96,"lon":86.83}</p><p>地理フィルターを有効にする</p><p>—</p><p>ショット時間</p><p>date</p><p>2023年10月18日09:41:00Z</p><p>撮影時間: 並べ替え/フィルター</p><p>—</p><p><strong>写真インデックスのインデックス戦略:</strong>アルバム内の写真ごとに、次の操作を実行します。
<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">画像メタデータから</a>画像<code>shot_time</code>と<code>gps</code>情報を抽出します。</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">SigLIP-2 画像埋め込み</a>: 画像をモデルに渡し、ベクトルを L2 正規化します。埋め込みを<code>clip_image</code>フィールドに保存します。</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">ピークを予測し</a>、 <code>predicted_peaks</code>フィールドに保存します。これを行うには、まず前の手順で生成された写真の画像ベクトルを取得し、次に<code>peaks_catalog</code>インデックスの text_embed フィールドに対して簡単な kNN 検索を実行します。上位 3 ～ 4 つのピークを保持し、残りは無視します。</p></li><li><p>画像名とパスの<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">ハッシュ</a>を実行して<code>_id</code>フィールドを計算します。これにより、複数回実行した後に重複が発生しなくなります。</p></li></ul><p>写真のすべてのフィールドを決定したら、<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530">一括</a>インデックスを使用して写真ドキュメントを一括でインデックスします。</p>def bulk_index_photos(
        es: Elasticsearch,
        images_root: str,
        photos_index: str = "photos",
        peaks_index: str = "peaks_catalog",
        topk_predicted: int = 5,
        batch_size: int = 200,
        refresh: str = "false",
) -&gt; None:
    """Walk a folder of images, embed + enrich, and bulk index to Elasticsearch."""
    root = Path(images_root)
    if not root.exists():
        raise SystemExit(f"Images root not found: {images_root}")

    emb = Siglip2()
    batch: List[Dict[str, Any]] = []
    n_indexed = 0

    for p in iter_images(root):
        rel = relpath_within(root, p)
        _id = id_for_path(rel)

        # 1) Image embedding (and reuse it for predicted_peaks)
        try:
            with Image.open(p) as im:
                ivec = emb.image_vec(im.convert("RGB")).astype("float32")
        except (UnidentifiedImageError, OSError) as e:
            print(f"[skip] {rel} — cannot embed: {e}")
            continue

        # 2) Predict top-k peak names
        try:
            top_names = predict_peaks(es, ivec.tolist(), peaks_index=peaks_index, k=topk_predicted)
        except Exception as e:
            print(f"[warn] predict_peaks failed for {rel}: {e}")
            top_names = []

        # 3) EXIF enrichment (safe)
        gps = get_gps_decimal(str(p))
        shot = get_shot_time(str(p))

        # 4) Build doc and stage for bulk
        doc = {"path": rel, "clip_image": ivec.tolist(), "predicted_peaks": top_names}
        if gps:
            doc["gps"] = gps
        if shot:
            doc["shot_time"] = shot

        batch.append(
            {"_op_type": "index", "_index": photos_index, "_id": _id, "_source": doc}
        )

        # 5) Periodic flush
        if len(batch) &gt;= batch_size:
            helpers.bulk(es, batch, refresh=refresh)
            n_indexed += len(batch)
            print(f"[photos] indexed {n_indexed} (last: {rel})")
            batch.clear()

    # Final flush
    if batch:
        helpers.bulk(es, batch, refresh=refresh)
        n_indexed += len(batch)
        print(f"[photos] indexed {n_indexed} total.")

    print("[done] photos indexing")<p>写真インデックスからのサンプルドキュメント:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="Elasticsearch の写真インデックスからのサンプル ドキュメント。" /><p>要約すると、写真のインデックスは、アルバム内のすべての写真を格納する、高速でフィルタリング可能な kNN 対応のストアです。マッピングは意図的に最小限に抑えられており、すばやく取得し、きれいに表示し、結果を空間と時間で分割するのに十分な構造になっています。このインデックスは、両方の検索ユースケースに対応します。両方のインデックスを作成するための Python スクリプトは<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">ここに</a>あります。</p><p>以下の Kibana マップの視覚化では、写真アルバムのドキュメントが緑のドットで表示され、 <code>peaks_catalog</code>インデックスの山頂が赤い三角形で表示されています。緑のドットはエベレスト ベース キャンプのトレッキング トレイルとぴったり一致しています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="Kibana マップの視覚化では、写真アルバムのドキュメントが緑のドットで表示され、peaks_catalog インデックスの山頂が赤い三角形で表示されています。緑のドットは、エベレスト ベース キャンプのトレッキング トレイルとよく揃っています。" /><h2>検索ユースケース</h2><p><strong>名前による検索（テキストから画像へ）：</strong>この機能により、ユーザーはテキスト クエリを使用して山頂の写真（さらには「祈りの旗」のような抽象的な概念）を見つけることができます。これを実現するために、テキスト入力は SigLIP-2 を使用して<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">テキスト ベクトルに変換されます</a>。堅牢なテキストベクトル生成のために、 インデックスでテキスト埋め込みを作成するために使用したのと同じ戦略を採用しています。つまり、テキスト入力を小さな<code>peaks_catalog</code><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100"> プロンプトアンサンブル</a> と<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"> 組み合わせ</a><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> 、小さな 反概念ベクトル</a> を減算し、<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"> L2正規化</a> を適用して最終的なクエリベクトルを生成します。次に、 <code>photos.clip_image</code>フィールドで kNN<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">クエリを</a>実行し、コサイン類似度に基づいて一致する上位のピークを取得して、最も近い画像を見つけます。オプションとして、クエリの一部として地理および日付<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">フィルター</a>、および/または<code>photos.predicted_peaks</code>用語フィルターを適用することで、検索結果の関連性を高めることができます (以下のクエリ例を参照)。これにより、トレッキング中に実際には見えていない、似たような山頂を除外することができます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="Elasticsearch でのマルチモーダルな名前による検索 (テキストから画像への検索) の仕組み。" /><p><strong>ジオフィルターを使用した Elasticsearch クエリ:</strong></p>POST photos/_search
{
  "knn": {
    "field": "clip_image",
    "query_vector": [ ... ],
    "k": 60,
    "num_candidates": 2000
  },
  "query": {
    "bool": {
      "filter": [
        { "geo_bounding_box": { "gps": { "top_left": "...", "bottom_right": "..." } } }
      ]
    }
  },
  "_source": ["path","predicted_peaks","gps","shot_time"]
}

Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<p><strong>画像による検索 (画像間):</strong>この機能を使用すると、写真内の山を識別し、写真アルバム内で同じ山の他の画像を見つけることができます。画像がアップロードされると、SigLIP-2 画像エンコーダーによって処理され、<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">画像ベクトル</a>が生成されます。次に、 <code>peaks_catalog.text_embed</code>フィールドで<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">kNN 検索を</a>実行し、最も一致するピーク名を特定します。次に、これらの一致するピーク名から<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257">テキスト ベクトルが生成され</a>、写真インデックスに対して別の<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263">kNN 検索が</a>実行され、対応する写真が検索されます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Elasticsearch で画像によるマルチモーダル検索 (画像間検索) がどのように機能するかを説明します。" /><p><strong>Elasticsearchクエリ:</strong></p><p>ステップ1: 一致するピーク名を見つける</p>GET peaks_catalog/_search
{
 "knn": {
   "field": "text_embed",
   "query_vector": [...image-vector... ],
   "k": 3,
   "num_candidates": 500
 },
 "_source": [
   "id",
   "names",
   "latlon",
   "text_embed"
 ]
}


Response (first two documents):
{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 3,
     "relation": "eq"
   },
   "max_score": 0.58039916,
   "hits": [
     {
       "_index": "peaks_catalog",
       "_id": "pumori",
       "_score": 0.58039916,
       "_source": {
         "id": "pumori",
         "names": [
           "Pumori",
           "Pumo Ri"
         ],
         "latlon": {
           "lat": 28.01472,
           "lon": 86.82806
         },
         "text_embed": [
                  ... embeddings...
         ]
       }
     },
     {
       "_index": "peaks_catalog",
       "_id": "kyajo-ri",
       "_score": 0.57942784,
       "_source": {
         "id": "kyajo-ri",
         "names": [
           "Kyajo Ri",
           "Kyazo Ri"
         ],
         "latlon": {
           "lat": 27.909167,
           "lon": 86.673611
         },
         "text_embed": [
           ... embeddings...
         ]
       }
     }
   ]
 }
}<p>ステップ 2: <code>photos</code>インデックスで検索を実行し、一致する画像を見つけます (テキストから画像への検索ユース ケースに示されているのと同じクエリ)。</p>POST photos/_search
{
 "knn": {
   "field": "clip_image",
   "query_vector": [ ...image-vector... ],
   "k": 30,
   "num_candidates": 2000
 },
 "_source": [
   "path",
   "gps",
   "shot_time",
   "predicted_peaks",
   "clip_image"
 ],
 "query": {
   "bool": {
     "filter": [
       {
         "term": {
           "predicted_peaks": "Pumori"
         }
       }
     ]
   }
 }
}


Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<h2>流線型のUI</h2><p>すべてをまとめるために、両方の検索ユースケースを実行できるシンプルな Streamlit UI を作成しました。左側のレールには、チェックボックスとミニマップ/ジオフィルターが付いた、スクロール可能なピークのリスト（ <code>photos.predicted_peaks</code>から集約）が表示されます。上部には、<strong>名前による検索</strong>ボックスと<strong>写真アップロードからの識別</strong>ボタンがあります。中央のペインには、kNN スコア、予測ピーク バッジ、キャプチャ時間を表示するレスポンシブなサムネイル グリッドがあります。各画像には、フル解像度のプレビューを表示するための<strong>画像表示</strong>ボタンが含まれています。</p><p><strong>画像をアップロードして検索:</strong>ピークを予測し、写真アルバムから一致するピークを見つけます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="アマダブラム山の山頂をテキストから画像、画像から画像の両方でマルチモーダル検索できる、シンプルで合理化された UI。" /><p><strong>テキスト検索</strong>: アルバム内の一致するピークをテキストから検索します</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="山頂ライブラリでテキスト検索を使用してエベレスト山の山頂を検索する方法。" /><h2>まとめ</h2><p><em><strong>アマ・ダブラムの</strong></em> <em>写真</em><em> だけ見せてもらえませんか ？ というところから始まりました。</em>小規模で実用的な<strong>マルチモーダル検索</strong>システムになりました。私たちは生のトレッキング写真を撮影し、それを<strong>SigLIP-2 埋め込み</strong>に変換し、 <strong>Elasticsearch</strong>を使用してベクトルに対して高速<strong>kNN を</strong>実行し、さらに単純な地理/時間フィルターを使用して<em>意味</em>によって適切な画像を浮かび上がらせました。途中で、私たちは 2 つのインデックス、つまり混合プロトタイプの小さな<code>peaks_catalog</code> (識別用) と、画像ベクトルと EXIF のスケーラブルな<code>photos</code>インデックス (検索用) で関心を分離しました。実用的かつ再現性があり、拡張も簡単です。</p><p>調整したい場合は、いくつかの設定を試すことができます。</p><ul><li><p><strong>クエリ時間の設定:</strong> <code>k</code> (返す近隣の数) と<code>num_candidates</code> (最終スコアリングの前に検索する範囲)。これらの設定については、<a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">こちらの</a>ブログで説明されています。</p></li><li><p><strong>インデックス時間の設定:</strong> <code>m</code> (グラフの接続性) および<code>ef_construction</code> (ビルド時間の精度とメモリ)。クエリの場合は、 <code>ef_search</code>も試してください。値が大きいほど、通常は、レイテンシを多少トレードオフして、リコール率が向上します。これらの設定の詳細については、<a href="https://www.elastic.co/search-labs/blog/hnsw-graph">このブログ</a>を参照してください。</p></li></ul><p>今後、<strong>マルチモーダル</strong>および<strong>多言語</strong>検索用のネイティブモデル/リランカーがElasticエコシステムにまもなく導入される予定です。これにより、画像/テキスト検索とハイブリッドランキングがさらに強化されるはずです<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Completes-Acquisition-of-Jina-AI-a-Leader-in-Frontier-Models-for-Multimodal-and-Multilingual-Search/default.aspx?utm_source=chatgpt.com">。ir.elastic.co+1</a></p><p>これを自分で試してみたい場合は:</p><ul><li><p><strong>GitHub リポジトリ:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search"><em>https://github.com/navneet83/multimodal-mountain-peak-search</em></a></p></li><li><p><strong>Colab クイックスタート:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb</a></p></li></ul><p>これで私たちの旅は終わり、帰る時間になりました。これが役に立つことを願っています。これを壊した場合（または改善した場合）、何を変更したかをお聞かせください。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce2fff1569d2a8b/6a17da894b055dd1f24320a2/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" alt="" />]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[ハイブリッド検索]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccb66279debb05f9/6a17da8b63baffe228741b15/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ユーザー行動データに基づく判断リストを使用して Elasticsearch で LTR モデルをトレーニングする]]></title>
    <description><![CDATA[UBI データを使用して判断リストを作成し、Elasticsearch での Learning to Rank (LTR) モデルのトレーニングを自動化する方法を学びます。]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr"><em><strong>ランク付け学習</strong></em></a>モデルを使用する際の大きな課題は、モデルをトレーニングするための高品質の<a href="https://www.elastic.co/search-labs/blog/judgment-lists"><em><strong>判断リスト</strong></em></a>を作成することです。従来、このプロセスでは、クエリとドキュメントの関連性を<em><strong>手動で</strong></em>評価し、それぞれにグレードを割り当てていました。これは、拡張性が低く、維持が困難な、時間のかかるプロセスです (数百のエントリを含むリストを手動で更新する必要があることを想像してください)。</p><p>さて、検索アプリケーションでの実際のユーザーインタラクションを使用してこのトレーニングデータを作成できたらどうなるでしょうか?<a href="https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-insights"><em><strong>UBI</strong></em></a>データを使用すると、まさにそれが実現できます。検索、クリック、その他のインタラクションをキャプチャして使用し、判断リストを生成できる自動システムを作成します。このプロセスは、手動による操作よりもはるかに簡単に拡張および繰り返すことができ、より良い結果が得られる傾向があります。このブログでは、Elasticsearch に保存されている UBI データをクエリして意味のある信号を計算し、 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction"><em><strong>LTR</strong></em></a>モデルのトレーニング データセットを生成する方法について説明します。</p><p><em><strong>完全な実験は</strong></em><a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git"><em><strong> ここで</strong></em></a><em><strong> ご覧いただけます 。</strong></em></p><h2>UBIデータがLTRモデルのトレーニングに役立つ理由</h2><p>UBI データには、手動による注釈に比べていくつかの利点があります。</p><ul><li><p><strong>量:</strong> UBI データは実際のやり取りから得られるため、手動で生成できるよりもはるかに多くのデータを収集できます。もちろん、このデータを生成するのに十分なトラフィックがあることを前提としています。</p></li><li><p><strong>実際のユーザーの意図:</strong>従来、手動の判断リストは、利用可能なデータの専門家による評価から作成されます。一方、UBI データは実際のユーザー行動を反映しています。これは、何が関連しているべきかという理論的な仮定ではなく、ユーザーが実際にコンテンツとやりとりして価値を見出す方法に基づいているため、検索システムの精度を向上させる、より優れたトレーニング データを生成できることを意味します。</p></li><li><p><strong>継続的な更新:</strong>判断リストは時間の経過とともに更新する必要があります。UBI データから作成すれば、最新のデータが得られ、判断リストが更新されます。</p></li><li><p><strong>コスト効率:</strong>判断リストを手動で作成するオーバーヘッドがないため、プロセスを何度でも効率的に繰り返すことができます。</p></li><li><p><strong>自然なクエリ分布</strong>: UBI データは実際のユーザー クエリを表し、より深い変化を促すことができます。たとえば、ユーザーはシステム内で検索する際に自然言語を使用しているでしょうか?もしそうなら、セマンティック検索またはハイブリッド検索アプローチを実装する必要があるかもしれません。</p></li></ul><p>ただし、いくつかの警告も伴います。</p><ul><li><p><strong>バイアスの増幅:</strong>人気のあるコンテンツは、露出が増えるため、クリックされる可能性が高くなります。そのため、人気のあるアイテムが強調され、より良い選択肢が埋もれてしまう可能性があります。</p></li><li><p><strong>カバレッジが不完全:</strong>新しいコンテンツにはインタラクションがないため、結果の上位に表示されることは難しい可能性があります。まれなクエリでは、意味のあるトレーニング データを作成するのに十分なデータ ポイントが不足している場合もあります。</p></li><li><p><strong>季節的な変動:</strong>ユーザーの行動が時間の経過とともに劇的に変化することが予想される場合、履歴データからは、どのような結果が適切であるかについてあまり情報が得られない可能性があります。</p></li><li><p><strong>タスクの曖昧さ:</strong>クリックしても、ユーザーが探していたものが見つかるとは限らない。</p></li></ul><h2>成績計算</h2><h3>LTRトレーニングのグレード</h3><p>LTR モデルをトレーニングするには、ドキュメントがクエリにどの程度関連しているかを数値で表現する必要があります。私たちの実装では、この数値は 0.0 から 5.0+ までの連続したスコアであり、スコアが高いほど関連性が高くなります。</p><p>この評価システムがどのように機能するかを示すために、手動で作成された次の例を考えてみましょう。</p><p>クエリ</p><p>文書の内容</p><p>学年</p><p>説明</p><p>「最高のピザレシピ」</p><p>「本格的なイタリアンピザ生地のレシピ（写真付きステップバイステップ）」</p><p>4.0</p><p>関連性が高く、まさにユーザーが探しているもの</p><p>「最高のピザレシピ」</p><p>「イタリアのピザの歴史」</p><p>1.0</p><p>ピザに関する内容ですが、レシピではありません</p><p>「最高のピザレシピ」</p><p>「初心者向け15分でできる簡単ピザレシピ」</p><p>3.0</p><p>関連性があり、良い結果ですが、「最高」のレシピとは言えないかもしれません。</p><p>「最高のピザレシピ」</p><p>「車のメンテナンスガイド」</p><p>0.0</p><p>全く関係ありません。クエリとは全く関係ありません。</p><p>ここからわかるように、グレードは、ドキュメントが「最高のピザのレシピ」というサンプルクエリにどれだけ関連しているかを数値で表したものです。これらのスコアを使用して、LTR モデルはどのドキュメントを結果の上位に表示する必要があるかを学習できます。</p><p>成績の計算方法は、トレーニング データセットの中核です。これを行うには<a href="https://www.elastic.co/search-labs/blog/judgment-lists">複数のアプローチ</a>があり、それぞれに長所と短所があります。たとえば、関連性がある場合は 1、関連性がない場合は 0 というバイナリ スコアを割り当てたり、クエリごとに結果のドキュメントのクリック数をカウントしたりすることもできます。</p><p>このブログ投稿では、<em><strong>ユーザーの行動を入力として考慮し、グレード番号を出力として計算する、</strong></em>異なるアプローチを使用します。また、ドキュメントの関連性に関係なく、検索結果の上位の方がクリックされる傾向があるという事実から生じる可能性のあるバイアスも修正します。</p><h2>成績の計算 - COECアルゴリズム</h2><p>COEC ( <a href="https://www.wsdm-conference.org/2010/proceedings/docs/p351.pdf">Clicks over Expected Clicks</a> ) アルゴリズムは、ユーザーのクリックから判断グレードを計算する手法です。前述したように、ユーザーは、ドキュメントがクエリに最も関連していない場合でも、上位に表示された結果をクリックする傾向があります。これは、<a href="https://eugeneyan.com/writing/position-bias/">ポジション バイアス</a>と呼ばれます。COEC アルゴリズムを使用する際の基本的な考え方は、すべてのクリックが同等に重要であるわけではないということです。つまり、位置 10 のドキュメントをクリックすると、位置 1 のドキュメントをクリックするよりも、そのドキュメントがクエリとの関連性がはるかに高いことを示します。COEC アルゴリズムに関する研究論文 (上記リンク) を引用します。</p><p><em>「検索結果や広告のクリック率（CTR）は、検索結果の順位によって大幅に低下することがよく知られています。」</em></p><p>ポジションバイアスの詳細については、<a href="https://www.researchgate.net/publication/200110550_An_experimental_comparison_of_click_position-bias_models">こちらを</a>ご覧ください。</p><p>COEC アルゴリズムを使用してこれに対処するには、次の手順に従います。</p><p><strong>1. 位置のベースラインを確立する:</strong>検索位置ごとに 1 から 10 までのクリック率 (CTR) を計算します。つまり、通常、ユーザーの何パーセントが位置 1、位置 2 などをクリックするかを決定します。このステップでは、ユーザーの自然な位置の偏りを捉えます。CTR は次のように計算します。</p><p>どこ：</p><p>p = 位置。1から10まで
Cp = すべてのクエリにおける位置pでの合計クリック数（任意のドキュメント）
 Ip = 総表示回数: すべてのクエリで、任意のドキュメントが位置 p に表示された回数</p><p>ここでは、上位の順位の方がクリック数が多くなると予想されます。</p><p><strong>2.</strong><strong>予想クリック数（EC）を計算する</strong>:</p><p>この指標は、ドキュメントが表示された位置とその位置のCTRに基づいて、ドキュメントが「受け取るべき」クリック数を決定します。ECは次の方法で計算します。</p><p>どこ：</p><p>Qd = 文書dが出現したすべてのクエリ
pos(d,q) = クエリqの結果における文書dの位置</p><p>3.<strong>実際のクリック数をカウントする:</strong>ドキュメントが表示されたすべてのクエリでドキュメントが受け取った実際の合計クリック数をカウントします。以降、 <strong>A(d) と呼びます。</strong></p><p>4. <strong>COECスコアを計算します。</strong>これは、実際のクリック数（A(d)）と予想クリック数（EC(d)）の比率です。</p><p>このメトリックは、次のように位置バイアスを正規化します。</p><ul><li><p>スコア 1.0 は、ドキュメントが表示された位置に応じて、期待どおりに実行されたことを意味します。</p></li><li><p>スコアが 1.0 を超える場合、ドキュメントの位置を見ると予想よりもパフォーマンスが優れていることを意味します。したがって、このドキュメントはクエリに対してより関連性があります。</p></li><li><p>スコアが 1.0 未満の場合、ドキュメントの位置から判断すると、予想よりもパフォーマンスが悪かったことを意味します。したがって、このドキュメントはクエリとの関連性が低くなります。</p></li></ul><p><em><strong>最終結果は、検索システムとの実際のやりとりから抽出された位置ベースの期待を考慮して、ユーザーが探しているものを捉えたグレード番号になります。</strong></em></p><h2>技術的な実装</h2><p>LTR モデルをトレーニングするための判断リストを作成するスクリプトを作成します。</p><p>このスクリプトの入力は、Elastic でインデックス化された UBI データ (クエリとイベント) です。</p><p>出力は、COEC アルゴリズムを使用してこれらの UBI ドキュメントから生成された CSV ファイル内の判断リストです。この判断リストを<a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction">Eland</a>で使用すると、関連する特徴を抽出し、LTR モデルをトレーニングできます。</p><h3>クイックスタート</h3><p>このブログのサンプル データから判断リストを生成するには、次の手順に従います。</p><p>1. リポジトリをクローンします。</p>git clone https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git  
cd elastic-ltr-judgement_list-blog<p>2. 必要なライブラリをインストールする</p><p>このスクリプトには、次のライブラリが必要です。</p><ul><li><p><em>pandas</em> : 判定リストを保存する</p></li><li><p><em>elasticsearch</em> : ElasticデプロイメントからUBIデータを取得する</p></li></ul><p>Python 3.11も必要です</p>pip install -r requirements.txt<p>3. <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/.env-example">.envファイル</a>でElasticデプロイメントの環境変数を更新します。</p><ul><li><p>ES_ホスト</p></li><li><p>API_キー</p></li></ul><p>環境変数を追加するには、次を使用します。</p>source .env<p>4. ubi_queries、ubi_events インデックスを作成し、サンプル データをアップロードします。setup.py ファイルを実行します。</p>python setup.py<p>5. Python スクリプトを実行します。</p>python judgement_list-generator.py<p>これらの手順に従うと、次のような judgement_list.csv という新しいファイルが表示されます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94317eda8f7af194/6a170aa46f7f04542f914821/2531090131ac9fe3e4e1d79de9d156fc47a7825a-782x531.png" alt="" /><p>このスクリプトは、以下に示す<strong>calculate_relevance_grade()</strong>関数を使用して、前に説明した COEC アルゴリズムを適用して成績を計算します。</p><h2>データアーキテクチャ</h2><h3>Ubiクエリ</h3><p>UBI クエリ インデックスには、検索システムで実行されたクエリに関する情報が含まれています。これはサンプルドキュメントです:</p>{
          "client_id": "client_002",
          "query": "italian pasta recipes",
          "query_attributes": {
            "search_type": "recipe",
            "category": "food",
            "cuisine": "italian"
          },
          "query_id": "q002",
          "query_response_id": "qr002",
          "query_response_object_ids": [
            "doc_011",
            "doc_012",
            "doc_013",
            "doc_014",
            "doc_015",
            "doc_016",
            "doc_017",
            "doc_018",
            "doc_019",
            "doc_020"
          ],
          "timestamp": "2024-08-14T11:15:00Z",
          "user_query": "italian pasta recipes"
        }<p>ここでは、ユーザー（client_id）、クエリの結果（query_response_object_ids）、クエリ自体（timestamp、user_query）のデータを見ることができます。</p><h3>Ubiクリックイベント</h3><p>ubi_events インデックスには、ユーザーが結果内のドキュメントをクリックするたびに収集されたデータが含まれています。これはサンプルドキュメントです:</p>{
          "action_name": "click",
          "application": "recipe_search",
          "client_id": "client_001",
          "event_attributes": {
            "object": {
              "description": "Authentic Italian Pizza Dough Recipe with Step-by-Step Photos",
              "device": "desktop",
              "object_id": "doc_001",
              "position": {
                "ordinal": 1,
                "page_depth": 1
              },
              "user": {
                "city": "New York",
                "country": "USA",
                "ip": "192.168.1.100",
                "location": {
                  "lat": 40.7128,
                  "lon": -74.006
                },
                "region": "NY"
              }
            }
          },
          "message": "User clicked on document doc_001",
          "message_type": "click",
          "query_id": "q001",
          "timestamp": "2024-08-14T10:31:00Z",
          "user_query": "best pizza recipe"
        }<h2>判定リスト生成スクリプト</h2><h3>スクリプトの一般的な概要</h3><p>このスクリプトは、Elasticsearch に保存されているクエリとクリック イベントからの UBI データを使用して、判断リストの生成を自動化します。次のタスクを実行します:</p><ul><li><p>Elasticsearch で UBI データを取得して処理します。</p></li><li><p>UBI イベントをそのクエリと関連付けます。</p></li><li><p>各位置の CTR を計算します。</p></li><li><p>各ドキュメントの予想クリック数 (EC) を計算します。</p></li><li><p>各ドキュメントの実際のクリック数をカウントします。</p></li><li><p>各クエリとドキュメントのペアの COEC スコアを計算します。</p></li><li><p>判定リストを生成し、CSVファイルに書き込みます。</p></li></ul><p>それぞれの機能を見ていきましょう。</p><h3>connect_to_elasticsearch()</h3>def connect_to_elasticsearch(host, api_key):
    """Create and return Elasticsearch client"""
    try:
        es = Elasticsearch(
            hosts=[host],
            api_key=api_key,
            request_timeout=60
        )
        # Test the connection
        if es.ping():
            print(f"✓ Successfully connected to Elasticsearch at {host}")
            return es
        else:
            print("✗ Failed to connect to Elasticsearch")
            return None
    except Exception as e:
        print(f"✗ Error connecting to Elasticsearch: {e}")
        return None<p>この関数は、ホストと API キーを使用して Elasticsearch クライアント オブジェクトを返します。</p><h3>fetch_ubi_data()</h3>def fetch_ubi_data(es_client: Elasticsearch, queries_index: str, events_index: str,
                   size: int = 10000) -&gt; Tuple[List[Dict], List[Dict]]:
    """
    Fetch UBI queries and events data from Elasticsearch indices.

    Args:
        es_client: Elasticsearch client
        queries_index: Name of the UBI queries index
        events_index: Name of the UBI events index
        size: Maximum number of documents to fetch

    Returns:
        Tuple of (queries_data, events_data)
    """
    logger.info(f"Fetching data from {queries_index} and {events_index}")

    # Fetch queries with error handling
    try:
        queries_response = es_client.search(
            index=queries_index,
            body={
                "query": {"match_all": {}},
                "size": size
            }
        )
        queries_data = [hit['_source'] for hit in queries_response['hits']['hits']]
        logger.info(f"Fetched {len(queries_data)} queries")

    except Exception as e:
        logger.error(f"Error fetching queries from {queries_index}: {e}")
        raise

    # Fetch events (only click events for now) with error handling
    try:
        events_response = es_client.search(
            index=events_index,
            body={
                "query": {
                    "term": {"message_type.keyword": "CLICK_THROUGH"}
                },
                "size": size
            }
        )
        events_data = [hit['_source'] for hit in events_response['hits']['hits']]
        logger.info(f"Fetched {len(events_data)} click events")

    except Exception as e:
        logger.error(f"Error fetching events from {events_index}: {e}")
        raise

    logger.info(f"Data fetch completed successfully - Queries: {len(queries_data)}, Events: {len(events_data)}")

    return queries_data, events_data<p>この関数はデータ抽出レイヤーであり、Elasticsearch に接続して match_all クエリを使用して UBI クエリを取得し、UBI イベントをフィルタリングして 'CLICK_THROUGH' イベントのみを取得します。</p><h3>プロセスubi_data()</h3>def process_ubi_data(queries_data: List[Dict], events_data: List[Dict]) -&gt; pd.DataFrame:
    """
    Process UBI data and generate judgment list.

    Args:
        queries_data: List of query documents from UBI queries index
        events_data: List of event documents from UBI events index

    Returns:
        DataFrame with judgment list (qid, docid, grade, keywords)
    """
    logger.info("Processing UBI data to generate judgment list")

    # Group events by query_id
    clicks_by_query = {}
    for event in events_data:
        query_id = event['query_id']
        if query_id not in clicks_by_query:
            clicks_by_query[query_id] = {}

        # Extract clicked document info
        object_id = event['event_attributes']['object']['object_id']
        position = event['event_attributes']['object']['position']['ordinal']

        clicks_by_query[query_id][object_id] = {
            'position': position,
            'timestamp': event['timestamp']
        }

    judgment_list = []

    # Process each query
    for query in queries_data:
        query_id = query['query_id']
        user_query = query['user_query']
        document_ids = query['query_response_object_ids']

        # Get clicks for this query
        query_clicks = clicks_by_query.get(query_id, {})

        # Generate judgment for each document shown
        for doc_id in document_ids:
            grade = calculate_relevance_grade(doc_id, query_clicks, document_ids, queries_data, events_data)

            judgment_list.append({
                'qid': query_id,
                'docid': doc_id,
                'grade': grade,
                'query': user_query
            })

    df = pd.DataFrame(judgment_list)
    logger.info(f"Generated {len(df)} judgment entries for {df['qid'].nunique()} unique queries")

    return df<p>この関数は判定リストの生成を処理します。UBI イベントとクエリを関連付けることで、UBI データの処理を開始します。次に、ドキュメントとクエリのペアごとに calculate_relevance_grade() 関数を呼び出して、判断リストのエントリを取得します。最後に、結果のリストを pandas データフレームとして返します。</p><h3>関連性グレードを計算する()</h3>def calculate_relevance_grade(document_id: str, clicks_data: Dict,
                              query_response_ids: List[str], all_queries_data: List[Dict] = None,
                              all_events_data: List[Dict] = None) -&gt; float:
    """
    Calculate COEC (Click Over Expected Clicks) relevance score for a document.

    Args:
        document_id: ID of the document
        clicks_data: Dictionary of clicked documents with their positions for current query
        query_response_ids: List of document IDs shown in search results (ordered by position)
        all_queries_data: All queries data for calculating position CTR averages
        all_events_data: All events data for calculating position CTR averages

    Returns:
        COEC relevance score (continuous value, typically 0.0 to 5.0+)
    """

    # If no global data provided, fall back to simple position-based grading
    if all_queries_data is None or all_events_data is None:
        logger.warning("No global data provided, falling back to position-based grading")
        # Simple fallback logic
        if document_id in clicks_data:
            position = clicks_data[document_id]['position']
            if position &gt; 3:
                return 4.0
            elif position &gt;= 1 and position &lt;= 3:
                return 3.0
        if document_id in query_response_ids:
            position = query_response_ids.index(document_id) + 1
            if position &lt;= 5:
                return 2.0
            elif position &gt;= 6 and position &lt;= 10:
                return 1.0
        return 0.0

    # Calculate rank-aggregated click-through rates
    position_ctr_averages = {}
    position_impression_counts = {}
    position_click_counts = {}

    # Initialize counters
    for pos in range(1, 11):  # Positions 1-10
        position_impression_counts[pos] = 0
        position_click_counts[pos] = 0

    # Count impressions (every document shown contributes)
    for query in all_queries_data:
        for i, doc_id in enumerate(query['query_response_object_ids'][:10]):  # Top 10 positions
            position = i + 1
            position_impression_counts[position] += 1

    # Count clicks by position
    for event in all_events_data:
        if event.get('action_name') == 'click':
            position = event['event_attributes']['object']['position']['ordinal']
            if position &lt;= 10:
                position_click_counts[position] += 1

    # Calculate average CTR per position
    for pos in range(1, 11):
        if position_impression_counts[pos] &gt; 0:
            position_ctr_averages[pos] = position_click_counts[pos] / position_impression_counts[pos]
        else:
            position_ctr_averages[pos] = 0.0

    # Calculate expected clicks for this specific document
    expected_clicks = 0.0

    # Count how many times this document appeared at each position for any query
    for query in all_queries_data:
        if document_id in query['query_response_object_ids']:
            position = query['query_response_object_ids'].index(document_id) + 1
            if position &lt;= 10:
                expected_clicks += position_ctr_averages[position]

    # Count total actual clicks for this document across all queries
    actual_clicks = 0
    for event in all_events_data:
        if (event.get('action_name') == 'click' and
                event['event_attributes']['object']['object_id'] == document_id):
            actual_clicks += 1

    # Calculate COEC score
    if expected_clicks &gt; 0:
        coec_score = actual_clicks / expected_clicks
    else:
        coec_score = 0.0

    logger.debug(
        f"Document {document_id}: {actual_clicks} clicks / {expected_clicks:.3f} expected = {coec_score:.3f} COEC")

    return coec_score<p>これは COEC アルゴリズムを実装する関数です。各位置の CTR を計算し、次にドキュメントとクエリのペアの実際のクリック数を比較し、最後にそれぞれの実際の COEC スコアを計算します。</p><h3>判断統計を生成する()</h3>def generate_judgment_statistics(df: pd.DataFrame) -&gt; Dict:
    """Generate statistics about the judgment list."""
    stats = {
        'total_judgments': len(df),
        'unique_queries': df['qid'].nunique(),
        'unique_documents': df['docid'].nunique(),
        'grade_distribution': df['grade'].value_counts().to_dict(),
        'avg_judgments_per_query': len(df) / df['qid'].nunique() if df['qid'].nunique() &gt; 0 else 0,
        'queries_with_clicks': len(df[df['grade'] &gt; 1]['qid'].unique()),
        'click_through_rate': len(df[df['grade'] &gt; 1]) / len(df) if len(df) &gt; 0 else 0
    }
    return stats<p>判定リストから、合計クエリ数、合計ユニークドキュメント数、グレード分布などの有用な統計情報を生成します。これは純粋に情報提供であり、結果の判断リストは変更されません。</p><h2>結果と影響</h2><p>クイック スタート セクションの指示に従うと、320 エントリの判定リストを含む CSV ファイルが生成されます (リポジトリで<a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/judgment_list.csv">サンプル出力を</a>確認できます)。これらのフィールド:</p><ul><li><p>qid: クエリの一意のID</p></li><li><p>docid: 結果のドキュメントの一意の識別子</p></li><li><p>グレード: クエリとドキュメントのペアの計算されたグレード</p></li><li><p>クエリ: ユーザークエリ</p></li></ul><p> 「イタリア料理のレシピ」というクエリの結果を見てみましょう。</p><p>クイド</p><p>ドシド</p><p>学年</p><p>クエリ</p><p>q1-イタリア料理レシピ</p><p>パスタの基本レシピ</p><p>0.0</p><p>イタリアのレシピ</p><p>q1-イタリア料理レシピ</p><p>レシピ_ピザ_マルゲリータ</p><p>3.333333</p><p>イタリアのレシピ</p><p>q1-イタリア料理レシピ</p><p>レシピ_リゾット_ガイド</p><p>10.0</p><p>イタリアのレシピ</p><p>q1-イタリア料理レシピ</p><p>レシピ_フレンチ_クロワッサン</p><p>0.0</p><p>イタリアのレシピ</p><p>q1-イタリア料理レシピ</p><p>レシピ_スペイン_パエリア</p><p>0.0</p><p>イタリアのレシピ</p><p>q1-イタリア料理レシピ</p><p>ギリシャ風ムサカのレシピ</p><p>1.875</p><p>イタリアのレシピ</p><p>結果から、「イタリアのレシピ」というクエリに対して次のことがわかります。</p><ul><li><p>リゾットのレシピは間違いなくクエリに対する最高の結果であり、予想よりも10倍多くのクリックを獲得しています。</p></li><li><p>ピザ マルゲリータも素晴らしい出来栄えです。</p></li><li><p>ギリシャのムサカも（意外にも）良い結果であり、結果上の順位が示唆するよりも良い成績を残しています。これは、イタリア料理のレシピを探していた数人のユーザーが、代わりにこのレシピに興味を持ったことを意味します。おそらくこれらのユーザーは地中海料理全般に興味があるのでしょう。結局のところ、このことからわかるのは、これは上で説明した他の 2 つの「より良い」一致の下に表示される良い結果になる可能性があるということです。</p></li></ul><h2>まとめ</h2><p>UBI データを使用すると、LTR モデルのトレーニングを自動化し、独自のユーザーから高品質の判断リストを作成できます。UBI データは、検索システムがどのように使用されているかを反映する大きなデータセットを提供します。COEC アルゴリズムを使用して成績を生成することで、固有の偏りを考慮しながら、同時にユーザーがより良い結果と考えるものを反映します。ここで概説した方法は、実際のユースケースに適用でき、実際の使用傾向に合わせて進化する、より優れた検索エクスペリエンスを提供できます。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/training-learning-to-rank-models-elasticsearch-ubi-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/training-learning-to-rank-models-elasticsearch-ubi-data</guid>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[Elastic Cloud Hosted]]></category>
    <dc:creator><![CDATA[Alexander Dávila]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt037eb2f4d380fe65/6a170aa67d8d67397170e6e6/762bf09c28829d626d42c2cfadc719e1dd618d1b-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ES|QL を使用した Elasticsearch 地理空間検索]]></title>
    <description><![CDATA[Elasticsearch クエリ言語 (ES|QL) での地理空間検索。Elasticsearch には強力な地理空間検索機能があり、これが ES|QL にも導入され、使いやすさと OGC への親しみやすさが大幅に向上しました。]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch は長年にわたって強力な<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/geospatial-analysis.html">地理空間検索および分析機能を</a>提供してきましたが、その API は一般的な GIS ユーザーが使い慣れたものとはまったく異なっていました。過去 1 年間で、SQL と同じくらい、あるいは SQL よりも簡単なパイプ クエリ言語<a href="https://www.elastic.co/search-labs/blog/esql-piped-query-language-goes-ga">である ES|QL クエリ言語を追加しました</a>。これは、Elastic が得意とする検索、セキュリティ、可観測性のユースケースに特に適しています。また、ES|QL 内での地理空間検索と分析のサポートも追加されており、特に SQL または<a href="https://en.wikipedia.org/wiki/Geographic_information_system">GIS</a>コミュニティ出身のユーザーにとって、はるかに使いやすくなります。</p><p>Elasticsearch 8.12 および 8.13 では、ES|QL に地理空間タイプの基本サポートが導入されました。これは、8.14 で地理空間検索機能が追加されたことにより大幅に強化されました。<a href="https://en.wikipedia.org/wiki/Open_Geospatial_Consortium">さらに重要なことは、このサポートは、PostGIS などの他の空間データベースで使用されている Open</a> Geospatial Consortium (OGC) の<a href="https://en.wikipedia.org/wiki/Simple_Features"> Simple Feature Access</a> 標準に厳密に準拠するように設計されているため、これらの標準に精通している GIS 専門家にとって非常に使いやすくなっていることです。</p><p>このブログでは、ES|QL を使用して地理空間検索を実行する方法と、それを SQL およびクエリ DSL と同等の機能と比較する方法を紹介します。また、ES|QL を使用して空間結合を実行する方法と、結果を Kibana Maps で視覚化する方法も紹介します。ここで説明する機能はすべて「テクニカル プレビュー」段階であるため、改善方法について皆様からのフィードバックをお待ちしています。</p><h2>地理空間データの検索</h2><p>クエリの例から始めましょう:</p>FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
      city_boundary,
      "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
  )
| KEEP abbrev, airport, region, city, city_location
<p>これは、三亜鳳凰国際空港 (SYX) の周囲の長方形の検索ポリゴンと交差する都市境界ポリゴンの検索を実行します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3897df6bed5d6061/6a17d7c2abe0f29eccdfe861/e48bac8f246c8842f2ea97ddd54910045262aeb1-1440x808.png" alt="ESQL 地理空間検索" /><p>空港、都市、都市境界のサンプル データセットでは、この検索により交差するポリゴンが検索され、一致するドキュメントから必要なフィールドが返されます。</p><p>略語</p><p>空港</p><p>地域</p><p>市</p><p>都市の場所</p><p>シックス</p><p>三亜フェニックス国際空港</p><p>天外区</p><p>三亜</p><p>点(109.5036 18.2533)</p><p>簡単でした！次に、同じクエリの従来の Elasticsearch クエリ DSL と比較してみましょう。</p>GET /airport_city_boundaries/_search
{
  "_source": ["abbrev", "airport", "region", "city", "city_location"],
  "query": {
    "geo_shape": {
      "city_boundary": {
        "shape": {
          "type": "polygon",
          "coordinates" : [[
            [109.4, 18.1],
            [109.6, 18.1],
            [109.6, 18.3],
            [109.4, 18.3],
            [109.4, 18.1]
          ]]
        }
      }
    }
  }
}
<p>どちらのクエリも意図はかなり明確ですが、ES|QL クエリは SQL によく似ています。PostGIS での同じクエリは次のようになります。</p>SELECT abbrev, airport, region, city, city_location
FROM airport_city_boundaries
WHERE ST_INTERSECTS(
    city_boundary,
    'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
);
<p>ES|QL の例を振り返ってみましょう。とても似ていますよね？</p>FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
      city_boundary,
      "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
  )
| KEEP abbrev, airport, region, city, city_location
<p>Elasticsearch API の既存のユーザーにとって、ES|QL の方がはるかに使いやすいことがわかりました。既存の SQL ユーザー、特に Spatial SQL ユーザーにとって、ES|QL は使い慣れたものと非常によく似ていると感じられるものと期待しています。</p><h4>なぜSQLではないのですか?</h4><p>Elasticsearch SQL についてはどうですか?しばらく前から存在しており、いくつかの地理空間機能を備えています。ただし、Elasticsearch SQL は元のクエリ API の上にラッパーとして記述されたため、元の API にトランスパイルできるクエリのみがサポートされていました。ES|QL にはこの制限はありません。完全に新しいスタックであるため、SQL では不可能だった多くの最適化が可能になります。私たちのベンチマークでは、ES|QL は、特に集計において、<a href="https://elasticsearch-benchmarks.elastic.co/#tracks/esql/nightly/default/6M">クエリ API よりも非常に高速である</a>ことが示されています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18bda964c8b24e36/6a17d7c3e3179155d22d568a/b8b6c2b2e45850d832805ed1e71e522f4955f53c-1440x813.png" alt="多角形交差ベンチマーク" /><h2>SQLとの違い</h2><p>前の例から、ES|QL は SQL と多少似ていることは明らかですが、いくつか重要な違いもあります。たとえば、ES|QL はパイプ クエリ言語であり、FROM などのソース コマンドで開始し、後続のすべてのコマンドをパイプ | 文字で連結します。これにより、各コマンドがデータ テーブルを受け取って、そのテーブルに対して何らかのアクション ( <code>WHERE</code>によるフィルタリング、 <code>EVAL</code>による列の追加、 <code>STATS</code>による集計の実行など) を実行する方法が非常に簡単に理解できるようになります。最終的な出力列を定義するために<code>SELECT</code>から始めるのではなく、1 つ以上の<code>KEEP</code>コマンドがあり、最後のコマンドで最終的な出力結果を指定できます。この構造により、クエリに関する推論が簡素化されます。</p><p>上記の例の<code>WHERE</code>コマンドに注目すると、PostGIS の例と非常によく似ていることがわかります。</p><p><em>ES|QL</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
)
<p><em>ポストGIS</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
)
<p>文字列引用文字の違いは別として、最も大きな違いは、文字列を空間型に型キャストする方法にあります。PostGIS では<code>::geometry</code>サフィックスを使用し、ES|QL では<code>::geo_shape</code>サフィックスを使用します。これは、ES|QL が Elasticsearch 内で実行され、型キャスト演算子<code>::</code>を使用して文字列を<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-limitations.html#_supported_types">サポートされているいずれかの ES|QL 型</a>(この場合は<code>geo_shape</code> ) に変換できるためです。さらに、Elasticsearch の<code>geo_shape</code>および<code>geo_point</code>タイプは、WGS84 と呼ばれる空間座標系を意味し、通常は SRID 番号 4326 を使用して参照されます。PostGIS ではこれを明示的にする必要があるため、WKT 文字列に<code>SRID=4326;</code>プレフィックスを使用します。そのプレフィックスが削除されると、SRID は 0 に設定され、特定の座標系に関連付けられていない Elasticsearch タイプ<code>cartesian_point</code>および<code>cartesian_shape</code>に似たものになります。</p><p>ES|QL と PostGIS はどちらも型変換関数の構文も提供しています。</p><p><em>ES|QL</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    TO_GEOSHAPE("POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))")
)
<p><em>ポストGIS</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    ST_SetSRID(
      ST_GeomFromText('POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'),
      4326
    )
)
<h2>OGC関数</h2><p>Elasticsearch 8.14 では、次の 4 つの OGC 空間検索関数が導入されています。</p><p>ES|QL</p><p>ポストGIS</p><p>説明</p><p>ST_INTERSECTS</p><p>ST_交差</p><p>2 つのジオメトリが交差する場合は true を返し、そうでない場合は false を返します。</p><p>ST_DISJOINT</p><p>ST_分離</p><p>2 つのジオメトリが交差しない場合は true を返し、そうでない場合は false を返します。ST_INTERSECTS の逆。</p><p>ST_CONTAINS</p><p>ST_Contains</p><p>1 つのジオメトリに別のジオメトリが含まれている場合は true を返し、含まれていない場合は false を返します。</p><p>ST_WITHIN</p><p>ST_以内</p><p>1 つのジオメトリが別のジオメトリ内にある場合は true を返し、そうでない場合は false を返します。ST_CONTAINS の逆。</p><p>これらの関数は PostGIS の対応する関数と同様に動作し、同じように使用されます。たとえば、 <code>ST_INTERSECTS</code> 2 つのジオメトリが交差する場合は true を返し、そうでない場合は false を返します。上記の表のドキュメント リンクに従うと、すべての ES|QL の例が<code>FROM</code>句の後の<code>WHERE</code>句内にあるのに対し、すべての PostGIS の例はリテラル ジオメトリを使用していることに気付くでしょう。実際、どちらのプラットフォームでも、意味のあるクエリのどの部分でも関数の使用がサポートされています。</p><p><code>ST_INTERSECTS</code>の PostGIS ドキュメントの最初の例は次のとおりです。</p>SELECT ST_Intersects(
    'POINT(0 0)'::geometry,
    'LINESTRING ( 2 0, 0 2 )'::geometry
);
<p>ES|QL でこれに相当するものは次のとおりです。</p>ROW ST_INTERSECTS(
    "POINT(0 0)"::geo_point,
    "LINESTRING ( 2 0, 0 2 )"::geo_shape
)
<p>PostGIS の例では SRID を指定していないことに注意してください。これは、PostGIS で<code>geometry</code>タイプを使用する場合、すべての計算が平面座標系で実行されるため、両方のジオメトリが同じ SRID を持つ場合、SRID が何であるかは問題にならないためです。Elasticsearch でも、これはほとんどの関数に当てはまりますが、 <code>geo_shape</code>と<code>geo_point</code>球面計算が使用されるという例外があります。これについては、空間距離検索に関する次のブログで説明します。</p><h2>ES|QLの汎用性</h2><p>上記では、 <code>WHERE</code>句と<code>ROW</code>コマンドで空間関数を使用する例を見てきました。他にどこで意味を成すのでしょうか?非常に便利な場所の 1 つは、 <code>EVAL</code>コマンドです。このコマンドを使用すると、式を評価して結果を返すことができます。たとえば、国名ごとにグループ化されたすべての空港の重心が、国の境界内にあるかどうかを判断します。</p>FROM airports
| EVAL in_uk = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL in_iceland = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| EVAL within_uk = ST_WITHIN(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL within_iceland = ST_WITHIN(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| STATS centroid = ST_CENTROID_AGG(location), count=COUNT() BY in_uk, in_iceland, within_uk, within_iceland
| SORT count ASC
<p>結果は予想通りで、英国の空港の重心は英国の境界内にあり、アイスランドの境界内にはなく、その逆も同様です。</p><p>重心</p><p>カウント</p><p>英国</p><p>アイスランド</p><p>英国内</p><p>アイスランド内</p><p>ポイント (-21.94663446396589364.13187285885215)</p><p>1</p><p>間違い</p><p>true</p><p>間違い</p><p>true</p><p>ポイント (-2.597342072712148 54.33551226578214)</p><p>17</p><p>true</p><p>間違い</p><p>true</p><p>間違い</p><p>ポイント (0.04453958108176276 23.74658354606057)</p><p>873</p><p>間違い</p><p>間違い</p><p>間違い</p><p>間違い</p><p>実際、これらの関数は、そのシグネチャが意味を成すクエリのどの部分でも使用できます。これらはすべて、リテラル空間オブジェクトまたは空間型のフィールドのいずれかである 2 つの引数を取り、ブール値を返します。重要な考慮事項の 1 つは、ジオメトリの座標参照システム (CRS) が一致している必要があることです。一致していない場合はエラーが返されます。つまり、同じ関数呼び出しで<code>geo_shape</code>型と<code>cartesian_shape</code>型を混在させることはできません。ただし、 <code>geo_point</code>タイプは<code>geo_shape</code>タイプの特殊なケースであり、両方とも同じ座標参照系を共有しているため、 <code>geo_point</code>タイプと<code>geo_shape</code>タイプを混在させることができます。上記で定義された各関数のドキュメントには、サポートされている型の組み合わせがリストされています。</p><p>さらに、どちらの引数も、空間リテラルまたはフィールドを任意の順序で指定できます。2 つのフィールド、2 つのリテラル、フィールドとリテラル、またはリテラルとフィールドを指定することもできます。唯一の要件は、タイプに互換性があることです。たとえば、次のクエリは同じインデックス内の 2 つのフィールドを比較します。</p>FROM airport_city_boundaries
| EVAL in_city = ST_INTERSECTS(city_location, city_boundary)
| STATS count=COUNT(*) BY in_city
| SORT count ASC
| EVAL cardinality = CASE(count &lt; 10, "very few", count &lt; 100, "few", "many")
| KEEP cardinality, count, in_city
<p>このクエリは基本的に、都市の場所が都市の境界内にあるかどうかを尋ねます。これは通常当てはまるはずですが、常に例外があります。</p><p>基数</p><p>カウント</p><p>市内</p><p>少し</p><p>29</p><p>間違い</p><p>多くの</p><p>740</p><p>true</p><p>さらに興味深い質問は、空港の場所が、その空港がサービスを提供する都市の境界内にあるかどうかです。ただし、空港の位置は、都市の境界を含むインデックスとは異なるインデックスに存在します。これには、これら 2 つの個別のインデックスからデータを効果的にクエリして相関させる方法が必要です。</p><h2>空間結合</h2><p>ES|QL は<code>JOIN</code>コマンドをサポートしていませんが、SQL の「左結合」と同様に動作する<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-commands.html#esql-enrich"><code>ENRICH</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-commands.html#esql-enrich">コマンド</a>を使用して、特殊な結合を実現できます。このコマンドは SQL の「左結合」に似た動作をし、2 つのデータセット間の空間関係に基づいて、1 つのインデックスの結果を別のインデックスのデータで強化することができます。</p><p>たとえば、空港の場所を含む都市の境界を見つけることで、空港のテーブルからの結果に、空港がサービスを提供する都市に関する追加情報を付加し、結果に対していくつかの統計を実行してみましょう。</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| MV_EXPAND city_boundary
| EVAL boundary_wkt_length = LENGTH(TO_STRING(city_boundary))
| STATS centroid = ST_CENTROID_AGG(location), count = COUNT(city_location), min_wkt = MIN(boundary_wkt_length), max_wkt = MAX(boundary_wkt_length) BY region
| SORT count DESC
| LIMIT 5
<p>これは、空港が最も多い上位 5 つの地域と、一致する地域を持つすべての空港の重心、およびそれらの地域内の都市境界の WKT 表現の長さの範囲を返します。</p><p>重心</p><p>カウント</p><p>最小wkt</p><p>最大wkt</p><p>地域</p><p>ポイント (-32.5609347096071932.598117914802714)</p><p>90</p><p>207</p><p>207</p><p>ヌル</p><p>ポイント (-73.9451533276587740.70366442203522）</p><p>9</p><p>438</p><p>438</p><p>ニューヨーク市</p><p>ポイント (-83.1039831787347842.300230911932886)</p><p>9</p><p>473</p><p>473</p><p>デトロイト</p><p>ポイント (-156.302024586126220.176383580081165)</p><p>5</p><p>307</p><p>803</p><p>ハワイ</p><p>ポイント (-73.8890273217111845.57078813901171)</p><p>4</p><p>837</p><p>837</p><p>モントリオール</p><p>それで、ここで実際に何が起こったのでしょうか?<code>JOIN</code>はどこで発生したのでしょうか?クエリの核心は<code>ENRICH</code>コマンドにあります。</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
<p>このコマンドは、Elasticsearch に、 <code>airports</code>インデックスから取得された結果を強化し、元のインデックスの<code>city_location</code>フィールドと、前のいくつかの例で使用した<code>airport_city_boundaries</code>インデックスの<code>city_boundary</code>フィールドの間で<code>intersects</code>結合を実行するように指示します。しかし、この情報の一部はこのクエリでは明確に表示されません。表示されるのはエンリッチポリシーの名前<code>city_boundaries</code>であり、不足している情報はそのポリシー定義内にカプセル化されています。</p>{
  "geo_match": {
    "indices": "airport_city_boundaries",
    "match_field": "city_boundary",
    "enrich_fields": ["city", "airport", "region", "city_boundary"]
  }
}
<p>ここでは、 <code>geo_match</code>クエリ ( <code>intersects</code>がデフォルト) が実行され、照合するフィールドが<code>city_boundary</code>であり、 <code>enrich_fields</code>が元のドキュメントに追加するフィールドであることがわかります。これらのフィールドの 1 つである<code>region</code>は、実際には<code>STATS</code>コマンドのグループ化キーとして使用されていましたが、これはこの「左結合」機能がなければ実行できませんでした。エンリッチ ポリシーの詳細については、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-setup.html">エンリッチのドキュメント</a>を参照してください。これらのドキュメントを読んでいくと、取り込みパイプラインを構成することによって、インデックス作成時にデータを拡充するための拡充インデックスの使用について説明されていることに気付くでしょう。ES|QL では、 <code>ENRICH</code>コマンドがクエリ時に機能するため、これは必要ありません。必要なデータとエンリッチポリシーを使用してエンリッチインデックスを準備し、ES|QL クエリで<code>ENRICH</code>コマンドを使用するだけで十分です。</p><p>また、最も頻繁に見つかった地域は<code>null</code>であったことにも気付くでしょう。これは何を意味するのでしょうか?このコマンドを SQL の「左結合」に例えたことを思い出してください。つまり、空港に一致する都市境界が見つからない場合でも、その空港は返されますが、 <code>airport_city_boundaries</code>インデックスのフィールドには<code>null</code>値が含まれます。一致する<code>city_boundary</code>が見つからなかった空港が 89 か所あり、 <code>region</code>フィールドが<code>null</code>である一致する空港が 1 か所あることがわかりました。これにより、結果に<code>region</code>が含まれない空港が 90 件見つかりました。もう 1 つの興味深い点は、 <code>MV_EXPAND</code>コマンドの必要性です。これが必要なのは、 <code>ENRICH</code>コマンドが入力行ごとに複数の結果を返す場合があり、 <code>MV_EXPAND</code>これらの結果を結果ごとに 1 つずつ複数の行に分割するのに役立つためです。これにより、「ハワイ」が異なる<code>min_wkt</code>と<code>max_wkt</code>結果を表示する理由も明らかになります。同じ名前でありながら境界が異なる複数の地域が存在したためです。</p><h2>Kibanaマップ</h2><p>Kibana は、マップ アプリケーションに Spatial ES|QL のサポートを追加しました。つまり、ES|QL を使用して Elasticsearch で地理空間データを検索し、その結果をマップ上に視覚化できるようになりました。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91922eb4ef43d290/6a16f7161949f737bfe7a7b5/bd78470bd8a4bc60f0db7006bd804b8fe87e2fea-1440x683.png" alt="Kibana レイヤー ES|QL" /><p>レイヤー追加メニューに、「ES|QL」という新しいレイヤー オプションがあります。これまで説明したすべての地理空間機能と同様に、これは「技術プレビュー」段階です。このオプションを選択すると、ES|QL クエリの結果に基づいてマップにレイヤーを追加できます。たとえば、世界中のすべての空港を表示するレイヤーをマップに追加できます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5185ef7461d7e81d/6a16f718839dfa1559dcfcb1/1dd28d3d0509f92d26b0bb5320a2925f7a54c5d9-1440x736.png" alt="キバナ ES|QL - 空港" /><p>または、 <code>airport_city_boundaries</code>インデックスからポリゴンを表示するレイヤーを追加することもできます。さらに良い方法として、各地域に空港がいくつあるかという統計を生成する、上記の複雑な<code>ENRICH</code>クエリはどうでしょうか。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt51ee3543bb811d98/6a16f71a8b73cbbe63189df1/679a0a401faa613c7cedddd07c64f614ac2b7144-1440x727.png" alt="Kibana ES|QL - 地域統計" /><h2>新しいエクスペリエンス</h2><p>上記の 2 つの例では、さらに別の空間関数<code>ST_CENTROID_AGG</code>が組み込まれていることに気付いたかもしれません。これは、 <code>STATS</code>コマンドで使用される集計関数であり、ES|QL に追加する予定の多くの空間分析機能の最初のものです。もっと詳しく紹介できるようになったら、ブログで紹介します!</p><p>その前に、私たちが取り組んできた特に興味深い機能、つまり Elasticsearch で最もよく使用される空間検索機能の 1 つである空間距離検索を実行する機能について詳しく説明したいと思います。距離検索の構文がどのようになるか想像できますか?おそらく OGC 関数に似ていますか?詳細については、このシリーズの次のブログをお読みください。</p><p>ネタバレ注意: Elasticsearch 8.15 がリリースされました。ES|QL による空間距離検索が含まれています。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one</guid>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Craig Taverner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd05627be20e89dfb/6a17d7c6414c640256944fdb/de886289dcb56494920875303b622b030b9b810f-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 12 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[検索関連性の評価パート1－BEIRベンチマーク]]></title>
    <description><![CDATA[検索評価プロセスを改善するためのヒントやテクニックとともに、BEIRベンチマークをよりよく理解した上で検索システムを評価する方法を学びましょう。]]></description>
    <content:encoded><![CDATA[<p>これは、BEIRベンチマークをより深く理解するという観点から、独自の検索システムを評価する方法について検討するブログ記事シリーズの最初の投稿です。BEIRをよりよく理解するために、検索評価プロセスを改善するための具体的なヒントやテクニックを紹介します。また、評価の信頼性を下げる一般的な落とし穴も紹介します。最後に、LLMは検索エンジニアに強力な新しいツールを提供することを指摘し、検索の評価にLLMをどのように使用できるかを例を挙げて示します。</p><h2>検索関連性評価におけるBEIRベンチマークの理解</h2><p>あらゆるシステムを改善するには、それがどの程度うまく機能しているかを測定できる必要があります。検索の観点では、<a href="https://arxiv.org/abs/2104.08663">BEIR</a>（または<a href="https://huggingface.co/spaces/mteb/leaderboard">MTEB</a>リーダーボードの検索セクションに相当するもの）が情報検索コミュニティの「聖杯」と考えられており、それ自体は驚くことではありません。これは、さまざまなタスクにわたる多様なデータセットを備えた、非常によく構造化されたベンチマークです。具体的には、以下の領域が対象となります。</p><ul><li><p>引数取得（ArguAna、Touche2020）</p></li><li><p>オープンドメインQA（HotpotQA、Natural Questions、FiQA）</p></li><li><p>パッセージ検索（MSMARCO）</p></li><li><p>重複質問取得（Quora、CQADupstack）</p></li><li><p>ファクトチェック（FEVER、Climate-FEVER、Scifact）</p></li><li><p>バイオメディカル情報検索（TREC-COVID、NFCorpus、BioASQ）</p></li><li><p>エンティティ検索（DBPedia）</p></li><li><p>引用予測（SCIDOCS）</p></li></ul><p>これは、システムが返す上位結果の中で、各タスク例で最も関連性の高いドキュメントをシステムがどの程度一致させたかに関する単一の統計であるnDCG@10を提供します。人間が操作する検索システムでは、上位結果の関連性が非常に重要です。しかし、検索の評価には多くのニュアンスがあり、単一の要約統計では見逃してしまうものがあります。</p><h2>BEIRデータセットの構造</h2><p>各ベンチマークには3つのアーティファクトがあります。</p><ul><li><p>取得すべきコーパスや文書</p></li><li><p>クエリ</p></li><li><p>クエリの関連性判断（別名 <code>qrels</code>）</p></li></ul><p>関連性判定は、ゼロ以上のスコアとして提供されます。0以外のスコアは、ドキュメントがクエリに多少関連していることを示します。</p><p>データセット</p><p>コーパスのサイズ</p><p>テストセット内のクエリ数</p><p>肯定的にラベル付けされた#qrels</p><p>ゼロに等しい#qrels</p><p>コーパス内の#duplicates</p><p>Arguana</p><p>8,674</p><p>1,406</p><p>1,406</p><p>0</p><p>96</p><p>Climate-FEVER</p><p>5,416,593</p><p>1,535</p><p>4,681</p><p>0</p><p>0</p><p>DBPedia</p><p>4,635,922</p><p>400</p><p>15,286</p><p>28,229</p><p>0</p><p>FEVER</p><p>5,416,568</p><p>6,666</p><p>7,937</p><p>0</p><p>0</p><p>FiQA-2018</p><p>57,638</p><p>648</p><p>1,706</p><p>0</p><p>0</p><p>HotpotQA</p><p>5,233,329</p><p>7,405</p><p>14,810</p><p>0</p><p>0</p><p>Natural Questions</p><p>2,681,468</p><p>3,452</p><p>4,021</p><p>0</p><p>16,781</p><p>NFCorpus</p><p>3,633</p><p>323</p><p>12,334</p><p>0</p><p>80</p><p>Quora</p><p>522,931</p><p>10,000</p><p>15,675</p><p>0</p><p>1,092</p><p>SCIDOCS</p><p>25,657</p><p>1,000</p><p>4,928</p><p>25,000</p><p>2</p><p>Scifact</p><p>5,183</p><p>300</p><p>339</p><p>0</p><p>0</p><p>Touche2020</p><p>382,545</p><p>49</p><p>932</p><p>1,982</p><p>5,357</p><p>TREC-COVID</p><p>171,332</p><p>50</p><p>24,763</p><p>41,663</p><p>0</p><p>MSMARCO</p><p>8,841,823</p><p>6,980</p><p>7,437</p><p>0</p><p>324</p><p>CQADupstack (sum)</p><p>457,199</p><p>13,145</p><p>23,703</p><p>0</p><p>0</p><p><strong>表1</strong>：データセットの統計。数値はデータセットのテスト部分（ <code>MSMARCO</code>の場合は<code>dev</code> ）で計算されました。</p><p><strong>表1</strong>は、<code>BEIR</code>ベンチマークを構成するデータセットの統計を示しています。これには、コーパス内のドキュメント数、テストデータセット内のクエリ数、<code>qrels</code>ファイル内の正/負（クエリ、ドキュメント）ペアの数などが含まれます。データをざっと見てみると、すぐに次のことが推測できます。</p><ul><li><p>ほとんどのデータセットには、<code>qrels</code>ファイル内に負の関係、つまりゼロスコアが含まれていません。これは、ドキュメントが与えられたクエリに対して明確に無関係であることを示します。</p></li><li><p>クエリごとの平均ドキュメント関係数（<code>#qrels</code>/<code>#queries</code>）は、<code>ArguAna</code>の場合1.0から<code>TREC-COVID</code>の493.5まで変化しますが、大部分のケースでは値が<code>&lt;</code>5です。</p></li><li><p>一部のデータセットでは、コーパス内に重複したドキュメントが存在するため、ドキュメントがクエリに関連していると見なされても、その重複は関連していないなど、誤った評価につながる場合があります。例えば、<code>ArguAna</code>では、クエリに関連するとマークされたドキュメントが1つしかない、重複したドキュメントペアを96件確認しました。重複も含め初期qrelsリストを「拡張」することで、 <code>nDCG@10</code>スコアが平均で約1%相対的に増加することが観測されました。</p></li></ul>{
  "_id": "test-economy-epiasghbf-pro02b",
  "title": "economic policy international africa society gender house believes feminisation",
  "text": "Again employment needs to be contextualised with …",
  "metadata": {}
}
{
  "_id": "test-society-epiasghbf-pro02b",
  "title": "economic policy international africa society gender house believes feminisation",
  "text": "Again employment needs to be contextualised with …",
  "metadata": {}
}
<p><strong>ArguAnaの重複ペアの例。qrelsファイルでは、（反論として）最初のものだけがクエリ「test-economy-epiasghbf-pro02a」に関連しているようです。</strong></p><p>MTEBリーダーボード上のモデルを比較する場合、平均的な検索品質に焦点を当てたくなることがあります。これはモデル全体の品質を示す良い指標ですが、必ずしもモデルのパフォーマンスを正確に示すわけではありません。結果はデータセットごとに報告されるため、異なるデータセットが検索タスクとどれほど密接に関連しているかを理解し、最も関連性の高いものだけを使用してモデルを再スコアリングする価値があります。さらに深く掘り下げたい場合は、さまざまなデータセットコーパスとのトピックの重複を追加で確認することもできます。品質基準をトピック別に階層化することで、トピックごとの長所と短所をより細かく評価できます。</p><p>ここで重要な注意点は、ドキュメントが<code>qrels</code>ファイル内でマークされていない場合、デフォルトではクエリとは無関係であると見なされることです。この領域をさらに掘り下げ、「評価者が（クエリ、ドキュメント）ペアに対して、グラウンドトゥルース情報がない場合、どれくらいの頻度で提示されるか？」という問いに光を当てるための証拠を収集します。これが重要な理由は、浅いマークアップしか利用できない（したがって、すべての関連文書がそのようなラベル付けされているわけではない）場合、1つの情報検索システムが、異なる関連（しかしマークされていない）ドキュメントを単に「選択」して表面化させるため、別のシステムよりも劣っていると判断される可能性があるためです。これは高品質の評価セットを作成する際によくある時限爆弾で、特に大規模なデータセットの場合に顕著です。手動でのラベル付けは、通常、現在のシステムによって返される上位の結果に重点を置くため、盲点にある関連ドキュメントを見逃す可能性があります。したがって、通常は、より広範囲な浅いマークアップよりも、より少ないクエリのより完全なマークアップにより多くのリソースを集中させることが好ましいです。</p><h2>検索関連性評価におけるBEIRベンチマークの活用</h2><p>分析を開始するために、次のシナリオを実装します（<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/evaluating-search-relevance-part-1/retrieve-and-rerank.ipynb">ノートブック</a>を参照）。</p><ol><li><p>まず、各データセットのコーパスをElasticsearchインデックスにロードします。</p></li><li><p>テストセットの各クエリについて、BM25の上位100件の文書を取得します。</p></li><li><p>取得したドキュメントを、さまざまなSOTA再ランク付けモデルを使用して再ランク付けします。</p></li><li><p>最後に、ステップ2（取得後）とステップ3（リランキング後）の上位10件のドキュメントの「判定率」をレポートします。つまり、<code>qrels</code>ファイルにスコアがある上位ドキュメント10件の平均割合を算出します。</p></li></ol><p>使用したモデルのリランキングのリストは次のとおりです。</p><ul><li><p><a href="https://docs.cohere.com/reference/rerank">Cohereの</a><code>rerank-english-v2.0</code>と <code>rerank-english-v3.0</code></p></li><li><p><a href="https://huggingface.co/BAAI/bge-reranker-base">BGEベース</a></p></li><li><p><a href="https://huggingface.co/mixedbread-ai/mxbai-rerank-xsmall-v1">mxbai-rerank-xsmall-v1</a></p></li><li><p><a href="https://huggingface.co/cross-encoder/ms-marco-MiniLM-L-6-v2">MiniLM-L-6-v2</a></p></li></ul><p></p><p>検索</p><p>リランキング</p><p></p><p></p><p></p><p></p><p>データセット</p><p>BM25 (%)</p><p>Cohere Rerank v2 (%)</p><p>Cohereリランク v3(%)</p><p>BGEベース (%)</p><p>mxbai-rerank-xsmall-v1 (%)</p><p>MiniLM-L-6-v2 (%)</p><p>Arguana</p><p>7.54</p><p>4.87</p><p>7.87</p><p>4.52</p><p>4.53</p><p>6.84</p><p>Climate-FEVER</p><p>5.75</p><p>6.24</p><p>8.15</p><p>9.36</p><p>7.79</p><p>7.58</p><p>DBPedia</p><p>61.18</p><p>60.78</p><p>64.15</p><p>63.9</p><p>63.5</p><p>67.62</p><p>FEVER</p><p>8.89</p><p>9.97</p><p>10.08</p><p>10.19</p><p>9.88</p><p>9.88</p><p>FiQa-2018</p><p>7.02</p><p>11.02</p><p>10.77</p><p>8.43</p><p>9.1</p><p>9.44</p><p>HotpotQA</p><p>12.59</p><p>14.5</p><p>14.76</p><p>15.1</p><p>14.02</p><p>14.42</p><p>Natural Questions</p><p>5.94</p><p>8.84</p><p>8.71</p><p>8.37</p><p>8.14</p><p>8.34</p><p>NFCorpus</p><p>31.67</p><p>32.9</p><p>33.91</p><p>30.63</p><p>32.77</p><p>32.45</p><p>Quora</p><p>12.2</p><p>10.46</p><p>13.04</p><p>11.26</p><p>12.58</p><p>12.78</p><p>SCIDOCS</p><p>8.62</p><p>9.41</p><p>9.71</p><p>8.04</p><p>8.79</p><p>8.52</p><p>Scifact</p><p>9.07</p><p>9.57</p><p>9.77</p><p>9.3</p><p>9.1</p><p>9.17</p><p>Touche2020</p><p>38.78</p><p>30.41</p><p>32.24</p><p>33.06</p><p>37.96</p><p>33.67</p><p>TREC-COVID</p><p>92.4</p><p>98.4</p><p>98.2</p><p>93.8</p><p>99.6</p><p>97.4</p><p>MSMARCO</p><p>3.97</p><p>6.00</p><p>6.03</p><p>6.07</p><p>5.47</p><p>6.11</p><p>CQADupstack (avg.)</p><p>5.47</p><p>6.32</p><p>6.87</p><p>5.89</p><p>6.22</p><p>6.16</p><p><strong>表 2</strong>：上位10件の取得/リランキングされたドキュメントに基づいて計算された（データセット、リランカー）ペアごとの判定率</p><p><strong>表</strong>2から、<code>TREC-COVID</code> （カバレッジ90%超）、<code>DBPedia</code> （～65% ）、<code>Touche2020</code> 、<code>nfcorpus</code> （～35% ）を除き、大半のデータセットの検索後またはリランキング付け後のラベリング率は5%から10%を少し超える程度であることがわかります。これは、マークされていないドキュメントがすべて関連性があるという意味ではありませんが、それらのサブセット（特に上位に配置されたもの）が正である可能性があります。</p><p>汎用命令調整言語モデルの登場により、関連性の判断を自動化できる可能性のある強力な新しいツールが生まれました。これらの方法は通常、検索のためにオンラインで使用するには計算コストがかかりすぎますが、ここではオフライン評価に焦点を当てます。以下では、これらを使用して、BEIRデータセットの一部が浅いマークアップに悩まされているという証拠を調査します。</p><p>この仮説をさらに調査するために、MSMARCOに焦点を当て、100件のクエリのサブセットと、（Cohere v2で）リランキングされた上位5つのドキュメントのうち、現在関連性があるとマークされていないものを選択することにしました。私たちは 2 つの異なる評価方法を採用しました。まず、慎重に調整されたプロンプト（これについては後の投稿で詳説します）を使用して、最近リリースされた<a href="https://huggingface.co/microsoft/Phi-3-mini-4k-instruct">Phi-3-mini-4k</a>モデルを準備し、クエリに対するドキュメントの関連性（または関連性の欠如）を予測しました。並行して、これらのケースは手動でラベル付けされ、LLMの出力と人間の判断との一致率も評価されました。全体として、以下の2つの結論が導き出されます。</p><ul><li><p>LLMの応答と人間の判断の間の一致率は約80％で、その方向性において十分な出発点であるように思われます。</p></li><li><p>57.6％のケース（人間の判断に基づく）で、返されたドキュメントが実際にクエリに関連していることが判明しました。言い換えれば、100件のクエリに対して107件のドキュメントに関連性があると判断されましたが、少なくとも0.576 x 5 x 100 = 288の追加のドキュメントが実際には関連しています！</p></li></ul><p>以下は、<code>MSMARCO</code>/<code>dev</code> データセットから抽出された、クエリ、注釈付きの正のドキュメント（<code>qrels</code>から）、不完全なマークアップによる偽陰性のドキュメントを含む例です。</p><p>例1：</p>{
  "query":
    {
        "_id": 155234,
        "text": "do bigger tires affect gas mileage"
    },
  "positive_doc":
    {
        "_id": 502713,
        "text": "Tire Width versus Gas Mileage. Tire width is one of the only tire size factors that can influence gas mileage in a positive way. For example, a narrow tire will have less wind resistance, rolling resistance, and weight; thus increasing gas mileage.",
    },
    "negative_doc":
    {
        "_id": 7073658,
        "text": "Tire Size and Width Influences Gas Mileage. There are two things to consider when thinking about tires and their effect on gas mileage; one is wind resistance, and the other is rolling resistance. When a car is driving at higher speeds, it experiences higher wind resistance; this means lower fuel economy."
    }
}
<p>例2：</p>{
  "query":
    {
        "_id": 300674,
        "text": "how many years did william bradford serve as governor of plymouth colony?"
    },
  "positive_doc":
    {
        "_id": 7067032,
        "text": "http://en.wikipedia.org/wiki/William_Bradford_(Plymouth_Colony_governor) William Bradford (c.1590 \u00e2\u0080\u0093 1657) was an English Separatist leader in Leiden, Holland and in Plymouth Colony was a signatory to the Mayflower Compact. He served as Plymouth Colony Governor five times covering about thirty years between 1621 and 1657."
    },
    "negative_doc":
    {
        "_id": 2495763,
        "text": "William Bradford was the governor of Plymouth Colony for 30 years. The colony was founded by people called Puritans. They were some of the first people from England to settle in what is now the United States. Bradford helped make Plymouth the first lasting colony in New England."
    }
}
<p>このような特定のクエリを手動で評価することは、nDCG@10のような定量的尺度を補完する検索品質を理解する上で一般的に役立つ手法です。検索に変更を加えるときに常に実行する代表的なクエリのセットがあれば、統計には表示されない、パフォーマンスの変化に関する重要な定性情報が得られます。例えば、検索で返される誤った結果についてより深い洞察を得ることができます。取得した結果の中で明らかな誤りを特定したり、ドメイン固有の用語の誤解釈などの関連する誤りのクラスを特定したりすることができます。</p><p>私たちの結果は、 <code>MSMARCO</code>評価に関する関連研究と一致しています。例えば、<a href="https://arxiv.org/pdf/2109.00062">Arabzadehら</a>は同様の手順を踏んでおり、クラウドソーシングされたワーカーに選好判定を行わせています。特に、彼らは、多くの場合、リランキングモジュールによって返されたドキュメントがMSMARCO <code>qrels</code>ファイル内のドキュメントよりも選好されるということを示しています。もう一つの証拠は、<a href="https://arxiv.org/pdf/2010.08191">RocketQA</a>リランカーの著者らによるもので、リランキングされたドキュメントの70％以上が手動検査後に関連性があると判断されたと報告しています。</p><p>更新 - 9月9日：データセットの慎重な再評価の結果、関連ドキュメントの事例が15件追加で特定され、合計数は273件から288件に増加しました。</p><h2>主なポイントと次のステップ</h2><ul><li><p>ベンチマークやモデルの比較にとって、より優れたグラウンドトゥルースの追求は極めて重要であるため、終わりがありません。LLMは、慎重に使用し、適切な指示に従って調整すれば、いくつかの評価領域で役立つ可能性があります。</p></li><li><p>より一般的には、ベンチマークが完璧になることは決してないことを考えると、純粋なスコアの比較から、統計的に有意な差異を捕捉するより堅牢な手法に切り替えることが望ましいかもしれません。<a href="https://arxiv.org/pdf/2109.00062">Arabzadehら</a>の研究はこのことを示す良い例であり、彼らは調査結果に基づいて、さまざまな実行間での有意な差（または有意でない差）を示す95%信頼区間を構築しています。付属の<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/evaluating-search-relevance-part-1/retrieve-and-rerank.ipynb">ノートブック</a>では、<a href="https://en.wikipedia.org/wiki/Bootstrapping_(statistics)">ブートストラッピング</a>を使用した信頼区間の実装を提供しています。</p></li><li><p>エンドユーザーの視点からは、ベンチマークの結果を読む際にタスクの整合性を考えることが有用です。例えば、RAGパイプラインを構築し、最も一般的なユースケースがさまざまなソースから複数の情報を集めることであると知っているAIエンジニアにとっては、BEIRベンチマーク全体のグローバル平均ではなく、HotpotQAのようなマルチホップQAデータセットで検索モデルのパフォーマンスを評価する方が有意義です。</p></li></ul><p>次の<a href="https://www.elastic.co/search-labs/blog/evaluating-search-relevance-part-2">ブログ記事</a>では、LLMとしてのPhi-3の使用と、関連性を予測するようにチューニングする過程をさらに詳しく説明します。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/evaluating-search-relevance-part-1</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/evaluating-search-relevance-part-1</guid>
    <category><![CDATA[MLの調査]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Thanos Papaoikonomou,Thomas Veasey]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d9912c8d4187096/6a1704f5b0367d30e672bc17/54a6e5197f5721b36fc65f27387d29803ed35589-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 16 Jul 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[AI盗作：Elasticsearchによる盗作検出]]></title>
    <description><![CDATA[ここでは、NLP モデルと Vector Search のユースケースに焦点を当て、Elasticsearch を使用して AI の盗用をチェックする方法を説明します。]]></description>
    <content:encoded><![CDATA[<p>盗作には、<strong>直接的な</strong>盗用（コンテンツの一部または全体をコピーする）と<strong>言い換えれば言い換え</strong>（著者の作品のいくつかの単語やフレーズを変更して言い換える）があります。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6e139760e56ec98/6a171147dc55de0ad2e00edf/5d7073187fda829438aeec8d3a1194a5bea2ba57-1440x347.png" alt="" /><p>インスピレーションと言い換えには違いがあります。コンテンツを読んでインスピレーションを得て、たとえ同じような結論に達したとしても、自分の言葉でそのアイデアを探求することは可能です。</p><p>盗作は長い間議論されてきたテーマですが、コンテンツの制作と公開が加速するにつれて、盗作は関連性を保ち、継続的な課題となっています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2af1678cb04506b/6a171149cf4f251c2ab2d257/a0b9a98d729db09dae0a79315c001e6763c12704-1400x1016.png" alt="" /><p>この課題は、盗作チェックが頻繁に行われる書籍、学術研究、司法文書に限定されません。それは新聞やソーシャルメディアにも及ぶ可能性があります。</p><p>情報が豊富で出版物に簡単にアクセスできるようになった今、スケーラブルなレベルで盗作を効果的にチェックするにはどうすればよいでしょうか?</p><p>大学、政府機関、企業はさまざまなツールを使用していますが、単純な<a href="https://www.elastic.co/search-labs/lexical-and-semantic-search-with-elasticsearch">語彙検索で</a>直接的な盗用を効果的に検出できる一方で、<strong>言い換えられたコンテンツを特定することが主な課題です。</strong></p><h2>生成AIによる盗作検出</h2><p>生成 AI によって新たな課題が生まれます。AI によって生成されたコンテンツは、コピーされた場合に盗作とみなされますか?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a1ed1b6fa3f1a56/6a17114b4a531bc40736aa69/9345b28d6d27c37469bc38e823c41780b4eabfe5-1440x875.png" alt="" /><p>たとえば、 <a href="https://openai.com/">OpenAI の</a><a href="https://openai.com/policies/terms-of-use">利用規約</a>では、OpenAI はユーザー向けに API によって生成されたコンテンツに対する著作権を主張しないことが規定されています。この場合、生成 AI を使用する個人は、生成されたコンテンツを引用なしで自由に使用できます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc2e08ab5b808e38/6a17114dab7f086cbadb9f93/e1f415f69a247666f02ddc81468944920c874cd7-968x814.png" alt="" /><p>しかし、効率性を向上させるために生成 AI を使用することが受け入れられるかどうかは、依然として議論の余地があります。</p><p>OpenAIは盗作検出に貢献しようと<a href="https://huggingface.co/roberta-base-openai-detector">検出モデル</a>を開発したが、後にその精度が十分に高くないことを認めた。</p><p><em>「これは単独の検出では精度が十分ではなく、より効果を上げるにはメタデータベースのアプローチ、人間の判断、一般の教育と組み合わせる必要があると考えています。」</em></p><p>課題は依然として残っていますが、利用できるツールが増えたことにより、言い換えられたコンテンツや AI コンテンツの場合でも盗作を検出するための選択肢が増えています。</p><h2>Elasticsearchで盗作を検出する</h2><p>これを認識して、このブログでは、メタデータ検索を超えて、自然言語処理 (NLP) モデルとベクトル検索、盗作検出を使用したもう 1 つのユース ケースを検討します。</p><p>これは<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/plagiarism-detection-with-elasticsearch/plagiarism_detection_es.ipynb"> Python の例</a> で実証されており、NLP 関連の記事を含む<a href="https://www.sbert.net/"> SentenceTransformers</a> の<a href="https://sbert.net/datasets/emnlp2016-2018.json"> データセット を活用しています。</a>以前に Elasticsearch にインポートされた<a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2">テキスト埋め込みモデル</a>で生成された「要約」埋め込みを考慮して「意味的テキスト類似性」を実行することにより、要約の盗用をチェックします。さらに、AI によって生成されたコンテンツ (AI 盗作) を識別するために、OpenAI によって開発された<a href="https://huggingface.co/roberta-base-openai-detector">NLP モデル</a>も Elasticsearch にインポートされました。</p><p>次の図はデータ フローを示しています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0464f88d3ed12070/6a17114fab7f084905db9f97/1ad89c98a2f42a497548ca3947749bad54ec1172-1440x880.png" alt="" /><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-processor.html">推論プロセッサ</a> を使用した<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html"> 取り込みパイプラインの</a> 実行中に、「abstract」段落は 768 次元のベクトル「abstract_vector.predicted_value」にマッピングされます。</p><p>マッピング：</p>"abstract_vector.predicted_value": { # Inference results field
"type": "dense_vector", 
"dims": 768, # model embedding_size
"index": "true", 
"similarity": "dot_product" # When indexing vectors for approximate kNN search, you need to specify the similarity function for comparing the vectors.
<p>ベクトル表現間の類似性は、「類似性」<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params">パラメータ</a>を使用して定義されるベクトル類似性メトリックを使用して測定されます。</p><p><a href="https://en.wikipedia.org/wiki/Cosine_similarity">コサイン</a>はデフォルトの類似度メトリックであり、「(1 + cosine(クエリ、ベクトル)) / 2」として計算されます。元のベクトルを保持する必要があり、事前に正規化できない場合を除き、コサイン類似度を実行する最も効率的な方法は、すべてのベクトルを単位長さに正規化することです。これにより、検索中に余分なベクトルの長さの計算を実行することを回避でき、代わりに 'dot_product' を使用できます。</p><p>この同じパイプラインでは、<a href="https://huggingface.co/roberta-base-openai-detector">テキスト分類モデル</a>を含む別の推論プロセッサが、コンテンツがおそらく人間によって書かれた「本物」か、おそらく AI によって書かれた「偽物」かを検出し、各ドキュメントに「openai-detector.predicted_value」を追加します。</p><p>取り込みパイプライン:</p>client.ingest.put_pipeline( 
    id="plagiarism-checker-pipeline",
    processors = [
    {
      "inference": { #for ml models - to infer against the data that is being ingested in the pipeline
        "model_id": "roberta-base-openai-detector", #text classification model id
        "target_field": "openai-detector", # Target field for the inference results
        "field_map": { #Maps the document field names to the known field names of the model.
        "abstract": "text_field" # Field matching our configured trained model input. 
        }
      }
    },
    {
      "inference": {
        "model_id": "sentence-transformers__all-mpnet-base-v2", #text embedding model id
        "target_field": "abstract_vector", # Target field for the inference results
        "field_map": {
        "abstract": "text_field" # Field matching our configured trained model input. Typically for NLP models, the field name is text_field.
        }
      }
    }
    
  ]
)
<p>クエリ時に、同じテキスト埋め込みモデルを使用して、「query_vector_builder」オブジェクト内のクエリ「model_text」のベクトル表現も生成されます。</p><p>k 最近傍 (kNN) 検索は、類似度メトリックによって測定されたクエリ ベクトルに最も近い k ベクトルを見つけます。</p><p>各ドキュメントの _score は類似性から導き出され、スコアが大きいほどランキングが高くなります。これは、ドキュメントが意味的に類似していることを意味します。結果として、3 つの可能性を出力します。スコアが 0.9 を超える場合は「高い類似性」を考慮し、スコアが 0.7 未満の場合は「低い類似性」、それ以外の場合は「中程度の類似性」を考慮します。ユースケースに応じて、_score のどのレベルが盗作とみなされるかを決定するために、さまざまなしきい値を柔軟に設定できます。</p><p>さらに、テキスト分類が実行され、テキスト クエリ内の AI によって生成された要素もチェックされます。</p><p>クエリ:</p>from elasticsearch import Elasticsearch
from elasticsearch.client import MlClient

#duplicated text - direct plagiarism test

model_text = 'Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at http://hucvl.github.io/recipeqa.'

response = client.search(index='plagiarism-checker', size=1,
    knn={
        "field": "abstract_vector.predicted_value",
        "k": 9,
        "num_candidates": 974,
        "query_vector_builder": { #The 'all-mpnet-base-v2' model is also employed to generate the vector representation of the query in a 'query_vector_builder' object.
            "text_embedding": {
                "model_id": "sentence-transformers__all-mpnet-base-v2",
                "model_text": model_text
            }
        }
    }
)

for hit in response['hits']['hits']:
    score = hit['_score']
    title = hit['_source']['title']
    abstract = hit['_source']['abstract']
    openai = hit['_source']['openai-detector']['predicted_value']
    url = hit['_source']['url']

    if score &gt; 0.9:
        print(f"\nHigh similarity detected! This might be plagiarism.")
        print(f"\nMost similar document: '{title}'\n\nAbstract: {abstract}\n\nurl: {url}\n\nScore:{score}\n\n")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

    elif score &lt; 0.7:
        print(f"\nLow similarity detected. This might not be plagiarism.")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

    else:
        print(f"\nModerate similarity detected.")
        print(f"\nMost similar document: '{title}'\n\nAbstract: {abstract}\n\nurl: {url}\n\nScore:{score}\n\n")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

ml_client = MlClient(client)

model_id = 'roberta-base-openai-detector' #open ai text classification model

document = [
    {
        "text_field": model_text
    }
]

ml_response = ml_client.infer_trained_model(model_id=model_id, docs=document)

predicted_value = ml_response['inference_results'][0]['predicted_value']

if predicted_value == 'Fake':
    print("\nNote: The text query you entered may have been generated by AI.\n")
<p>アウトプット：</p>High similarity detected! This might be plagiarism.

Most similar document: 'RecipeQA: A Challenge Dataset for Multimodal Comprehension of Cooking Recipes'

Abstract: Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at[ http://hucvl.github.io/recipeqa](http://hucvl.github.io/recipeqa).

url:[http://aclweb.org/anthology/D18-1166](http://aclweb.org/anthology/D18-1166)

Score:1.0
<p>この例では、データセットの「抽象」値の 1 つをテキスト クエリ「model_text」として使用した後、盗用が特定されました。類似度スコアは 1.0 であり、類似度が高いこと、<strong>つまり直接的な盗用であること</strong>を示しています。ベクトル化されたクエリとドキュメントは、予想どおり AI 生成コンテンツとして認識されませんでした。</p><p>クエリ:</p>#similar text - paraphrase plagiarism test 

model_text = 'Comprehending and deducing information from culinary instructions represents a promising avenue for research aimed at empowering artificial intelligence to decipher step-by-step text. In this study, we present CuisineInquiry, a database for the multifaceted understanding of cooking guidelines. It encompasses a substantial number of informative recipes featuring various elements such as headings, explanations, and a matched assortment of visuals. Utilizing an extensive set of automatically crafted question-answer pairings, we formulate a series of tasks focusing on understanding and logic that necessitate a combined interpretation of visuals and written content. This involves capturing the sequential progression of events and extracting meaning from procedural expertise. Our initial findings suggest that CuisineInquiry is poised to function as a demanding experimental platform.'
<p>アウトプット：</p>High similarity detected! This might be plagiarism.

Most similar document: 'RecipeQA: A Challenge Dataset for Multimodal Comprehension of Cooking Recipes'

Abstract: Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at[ http://hucvl.github.io/recipeqa](http://hucvl.github.io/recipeqa).

url:[http://aclweb.org/anthology/D18-1166](http://aclweb.org/anthology/D18-1166)

Score:0.9302529

Note: The text query you entered may have been generated by AI.
<p>テキストクエリ「model_text」を、類似した単語の繰り返しを最小限に抑えながら同じメッセージを伝える AI 生成テキストで更新すると、検出された類似度は依然として高かったものの、スコアは 1.0 ではなく 0.9302529 になりました<strong>(言い換え盗用)</strong> 。AIによって生成されたこのクエリが検出されることも予想されました。</p><p>最後に、テキスト クエリ「model_text」を、これらのドキュメントの要約ではない Elasticsearch に関するテキストと見なすと、検出された類似度は 0.68991005 となり、考慮されたしきい値によると類似度が低いことが示されました。</p><p>クエリ:</p>#different text - not a plagiarism

model_text = 'Elasticsearch provides near real-time search and analytics for all types of data.'
<p>アウトプット：</p>Low similarity detected. This might not be plagiarism.
<p>AI によって生成されたテキスト クエリでは、言い換えや直接コピーされたコンテンツの場合と同様に、盗用が正確に識別されましたが、盗用検出の状況を把握するには、さまざまな側面を認識する必要があります。</p><p>AI 生成コンテンツの検出という観点から、私たちは価値ある貢献を果たすモデルを検討しました。ただし、単独の検出には固有の限界があり、精度を高めるには他の方法を組み込む必要があることを認識することが重要です。</p><p>テキスト埋め込みモデルの選択によってもたらされる変動性も、もうひとつの考慮事項です。異なるデータセットでトレーニングされたさまざまなモデルでは、さまざまなレベルの類似性が得られ、生成されたテキスト埋め込みの重要性が強調されます。</p><p>最後に、これらの例では、ドキュメントの要約を使用しました。ただし、盗作検出には大きな文書が関係することが多く、テキストの長さの問題に対処することが不可欠です。テキストがモデルのトークン制限を超えることはよくあり、埋め込みを構築する前にチャンクに分割する必要があります。これを処理する<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.11/knn-search.html#nested-knn-search">実用的なアプローチ</a>としては、dense_vector を使用したネストされた構造を利用することが挙げられます。</p><h2>まとめ</h2><p>このブログでは、特に言い換えられたコンテンツや AI によって生成されたコンテンツにおける盗作の検出の課題と、この目的のために意味的なテキストの類似性とテキスト分類をどのように使用できるかについて説明しました。</p><p>これらの方法を組み合わせることで、AI によって生成されたコンテンツ、直接的な盗用と言い換えられた盗用を正常に識別した盗用検出の例を示しました。</p><p>主な目的は検出を簡素化するフィルタリング システムを確立することですが、検証には人間による評価が依然として不可欠です。</p><p>意味的テキスト類似性と NLP についてさらに詳しく知りたい場合は、次のリンクもご覧ください。</p><ul><li><p><a href="https://www.elastic.co/what-is/semantic-search">セマンティック検索とは？</a></p></li><li><p><a href="https://www.elastic.co/what-is/natural-language-processing">自然言語処理 (NLP) とは何ですか?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch">Elasticsearch による語彙検索と意味検索</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/chunking-via-ingest-pipelines">大規模なドキュメントをインジェストパイプラインでチャンク化し、ネストされたベクトルと組み合わせることで、簡単にパッセージを検索できます。</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-plagiarism-checker-with-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-plagiarism-checker-with-elasticsearch</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68a5bc2434a9b03b/6a1711510e2e49a09641a22a/83e05cd4f81799fbb7b7950ed87600e825ec81e9-1024x1024.png" length="0" type="image/png"/>
    <pubDate>Tue, 19 Dec 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch による語彙検索と意味検索]]></title>
    <description><![CDATA[このブログでは、語彙検索と意味検索に焦点を当て、Elasticsearch を使用して情報を取得するためのさまざまなアプローチについて説明します。]]></description>
    <content:encoded><![CDATA[<p>検索は、検索クエリまたは複合クエリに基づいて最も関連性の高い情報を検索するプロセスであり、関連する検索結果はこれらのクエリに最も一致するドキュメントです。検索にはさまざまな課題と方法がありますが、最終的な目標は、<strong>質問に対する最適な回答を見つけることです</strong>。</p><p>この目標を考慮して、このブログ投稿では、Elasticsearch を使用して情報を取得するためのさまざまなアプローチを検討し、特にテキスト検索（<strong>語彙検索とセマンティック検索）に焦点を当てます。</strong></p><h2>要件</h2><p>これを実現するために、eコマース製品情報をシミュレートするために生成された<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/products-ecommerce.json">データセット</a>でのさまざまな検索シナリオを示す Python の例を提供します。</p><p>このデータセットには 2,500 を超える製品が含まれており、それぞれに説明が付いています。これらの製品は 76 の異なる製品カテゴリに分類されており、各カテゴリには以下に示すようにさまざまな数の製品が含まれています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34415151c00ced2b/6a17d8710b0bed6c9bdd342c/4104466050f3024b6bcaf382da2a702650f62227-1440x708.png" alt="" /><p><em>ツリーマップの視覚化 - カテゴリ.キーワードの上位 22 個の値 (製品カテゴリ)</em></p><p>セットアップには次のものが必要です:</p><ul><li><p>Python 3.6以降</p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/index.html">Elastic Pythonクライアント</a></p></li><li><p>Elastic 8.8 以降のデプロイメント、8GB メモリの機械学習ノード</p></li><li><p><a href="https://www.elastic.co/guide/en/machine-learning/8.9/ml-nlp-elser.html">Elastic Learned Sparse EncodeR</a>モデルは、Elastic にプリロードされており、デプロイメントにインストールされて起動します。</p></li></ul><p>Elastic Cloud を使用します。<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">無料トライアルを</a>ご利用いただけます。</p><p>このブログ記事で提供されている検索クエリに加えて、 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">Python ノートブックで</a>は次のプロセスをガイドします。</p><ul><li><p>Pythonクライアントを使用してElasticデプロイメントへの接続を確立する</p></li><li><p>Elasticsearchクラスターにテキスト埋め込みモデルをロードする</p></li><li><p>特徴ベクトルと密ベクトルのインデックスを作成するためのマッピングを使用してインデックスを作成します。</p></li><li><p>テキスト埋め込みとテキスト拡張のための推論プロセッサを備えた取り込みパイプラインを作成する</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e95406ece8f08d9/6a17d8731d1b83d32e93e2f4/54a9a490a3b0cf1b9c2228bee8eddd3f566bd435-1418x1102.png" alt="" /><h2>語彙検索 - スパース検索</h2><p>テキスト クエリに基づいて Elasticsearch がドキュメントの関連性をランク付けする従来の方法では、<strong> 語彙検索用のスパース</strong> モデルである<a href="https://en.wikipedia.org/wiki/Okapi_BM25"> BM25 モデルの</a> Lucene 実装が使用されます。この方法は、正確な用語の一致を探すという、テキスト検索の従来のアプローチに従います。</p><p>この検索を可能にするために、Elasticsearch はテキスト分析を実行して<strong>テキスト フィールド</strong>データを検索可能な形式に変換します。</p><p><strong>テキスト分析</strong>は、検索に関連するトークンを抽出するプロセスを管理する一連のルールである<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html">アナライザー</a>によって実行されます。アナライザーには、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html">トークナイザー</a>が 1 つだけ必要です。トークナイザーは文字のストリームを受け取り、それを個々のトークン (通常は個々の単語) に分割します。以下に例を示します。</p><h3>語彙検索のための文字列トークン化</h3>#Performs text analysis on a string and returns the resulting tokens.

# Define the text to be analyzed
text = "Comfortable furniture for a large balcony"

# Define the analyze request
request_body = {
  "analyzer": "standard",
  "text": text
}

# Perform the analyze request
response = client.indices.analyze(analyzer=request_body["analyzer"], text=request_body["text"])

# Extract and display the analyzed tokens
tokens = [token["token"] for token in response["tokens"]]
print("Analyzed Tokens:", tokens)
<p>出力</p>Analyzed Tokens: ['comfortable', 'furniture', 'for', 'a', 'large', 'balcony']
<p>この例では、デフォルトのアナライザーである<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html">標準</a>アナライザーを使用しています。これは、英語の文法に基づいたトークン化を提供するため、ほとんどのユースケースに適しています。トークン化により、個々の用語での一致が可能になりますが、各トークンは文字どおりに一致します。</p><p>検索エクスペリエンスをカスタマイズしたい場合は、別の<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html">組み込みアナライザーを</a>選択できます。たとえば、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-analyzer.html">ストップ アナライザーを</a>使用するようにコードを更新すると、ストップワードの削除がサポートされ、文字以外の文字でテキストがトークンに分割されます。</p>...
# Define the analyze request
request_body = {
  "analyzer": "stop",
  "text": text
}
...
<p>出力</p>Analyzed Tokens: ['comfortable', 'furniture', 'large', 'balcony']
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html">組み込みアナライザーがニーズを満たさない場合は、ゼロ個以上の 文字フィルター</a> 、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html"> トークナイザー</a> 、およびゼロ個以上の <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html">トークン</a> フィルター の適切な組み合わせを使用する<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-custom-analyzer.html"> カスタム アナライザー</a> を作成できます。</p>"analyzer":  {

  "my_analyzer": {

    "type": "custom", #For custom analyzers, use a type of custom or omit the type parameter.

    "tokenizer": "standard", #Built-in or customized tokenizer

    "filter": ["lowercase", "synonym"] #Built-in or customized token filters
  }
}
<p>トークナイザーとトークン フィルターを組み合わせた上記の例では、テキストは、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html"> </a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-tokenfilter.html#:~:text=Elasticsearch%20will%20use%20the%20token,applied%20to%20the%20synonym%20entries.">同義語トークン フィルター によって処理される前に、</a> 小文字フィルター によって小文字化されます。</p><h2>語彙マッチング</h2><p><a href="https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables">BM25 は</a>、用語の頻度と重要度に基づいて、特定の検索クエリに対するドキュメントの関連性を測定します。</p><p>以下のコードは、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html"> 一致</a> クエリを実行し、<em> 「ecommerce-search」</em> インデックスの<em> 「description」</em> <strong>フィールド値と検索クエリ</strong><em><strong> 「 Comfortable furniture for a large balcony 」</strong></em><strong> を考慮して最大</strong> 2 つのドキュメントを検索します。</p><p>このクエリに一致すると見なされるドキュメントの基準を絞り込むと、精度が向上します。ただし、より具体的な結果を得るには、変動に対する許容度が低くなるというデメリットがあります。</p># BM25

response = client.search(size=2,
index="ecommerce-search",
query= {
  "match": {
    "description" : {  
      "query": "Comfortable furniture for a large balcony",
      "analyzer": "stop"
    }
  }
}
)

hits = response['hits']['hits']

if not hits:
  print("No matches found")

else:
  for hit in hits:
    score = hit['_score']
    product = hit['_source']['product']
    category = hit['_source']['category']
    description = hit['_source']['description']
    print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>出力</p>Score: 15.607948
Product: Barbie Dreamhouse
Category: Toys
Description: is a classic Barbie playset with multiple rooms, furniture, a large balcony, a pool, and accessories. It allows kids to create their dream Barbie world.

Score: 9.137739
Product: Comfortable Rocking Chair
Category: Indoor Furniture
Description: enjoy relaxing moments with this comfortable rocking chair. Its smooth motion and cushioned seat make it an ideal piece of furniture for unwinding.
<p>出力を分析すると、最も関連性の高い結果は「<em> おもちゃ</em> 」カテゴリの「バービードリームハウス 」製品であり、その説明には「<em> 家具</em> 」、「<em> 大型」</em> 、「バルコニー 」という用語が含まれているため関連性が高く、説明に検索クエリに一致する用語が 3 つ含まれている唯一の製品であり、説明に「バルコニー」 という用語が含まれているのもこの製品のみです。</p><p>2 番目に関連性の高い製品は、「 屋内用家具」に分類される「快適なロッキングチェア 」で、その説明には「<em> 快適な</em> 」および「<em> 家具</em> 」という用語が含まれています。データセット内のこの検索クエリの少なくとも 2 つの用語に一致する製品は 3 つだけであり、この製品はそのうちの 1 つです。</p><p><em>「快適」という</em>言葉は 105 製品の説明に登場し、 <em>「家具」という</em>言葉は、<em>おもちゃ</em>、<em>屋内用家具、屋外用家具、および「犬と猫の用品とおもちゃ」という 4 つのカテゴリーの 4 製品の説明に登場しています。</em></p><p>ご覧のとおり、クエリを考慮すると最も関連性の高い製品はおもちゃであり、2 番目に関連性の高い製品は屋内用家具です。これらのドキュメントが一致する理由を知るためにスコア計算に関する詳細な情報が必要な場合は、 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html"><em>explain</em></a> __query パラメータを true に設定できます。</p><p>両方の結果が最も関連性の高いものであるにもかかわらず、このデータセット内のドキュメントの数と用語の出現の両方を考慮すると、クエリ「<em>大きなバルコニー用の快適な家具</em>」の背後にある意図は、おもちゃや室内用家具などを除いた、実際の大きなバルコニー用の家具を検索することです。</p><p>語彙検索は比較的<strong>単純かつ高速</strong>ですが、ユーザーの意図やクエリを必ずしも知らずにすべての用語と同義語を知ることが常に可能であるとは限らないため、限界があります。自然言語の使用においてよく見られる現象は<strong>語彙の不一致</strong>です。<a href="https://dl.acm.org/doi/abs/10.1145/32206.32212">調査</a>によると、平均して<strong>80% の確率で</strong>、異なる人々 (同じ分野の専門家) が同じものを異なる名前で呼ぶことがわかっています。</p><p>これらの制限により、意味的知識を組み込んだ他のスコアリング モデルを探すことになります。自然言語のような連続的な入力トークンの処理に優れたトランスフォーマーベースのモデルは、ドキュメントとクエリの両方の数学的表現を考慮して、検索の根本的な意味を捉えます。これにより、テキストの高密度でコンテキストを認識したベクトル表現が可能になり、関連するコンテンツを見つけるための洗練された方法である<strong>セマンティック検索</strong>が強化されます。</p><h2>セマンティック検索 - 高密度検索</h2><p>このコンテキストでは、データを意味のあるベクトル値に変換した後、 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">k 最近傍 (kNN)</a>検索アルゴリズムを使用して、データセット内でクエリ ベクトルに最も類似したベクトル表現を検索します。Elasticsearch は、kNN 検索に、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#exact-knn">正確なブルート フォース kNN</a>と<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#approximate-knn">近似 kNN</a> (ANN とも呼ばれる) の 2 つの方法をサポートしています。</p><p>ブルートフォース kNN は正確な結果を保証しますが、大規模なデータセットでは適切に拡張できません。近似 kNN は、パフォーマンスを向上させるために精度をある程度犠牲にして、近似最近傍を効率的に見つけます。</p><p>Lucene の kNN 検索と高密度ベクトル インデックスのサポートにより、Elasticsearch は階層的ナビゲート可能スモール ワールド (HNSW) アルゴリズムを活用し、さまざまな<a href="http://ann-benchmarks.com/">ann ベンチマーク データセット</a>にわたって強力な検索パフォーマンスを発揮します。以下のサンプルコードを使用して、Python で近似 kNN 検索を実行できます。</p><h3>近似kNNによるセマンティック検索</h3># KNN - approximate kNN

response = client.search(index='ecommerce-search', size=2,
knn={
  "field": "description_vector.predicted_value",
  "k": 50, # Number of nearest neighbors to return as top hits.
#The optimal value of k is dependent on the data. It can vary in different scenarios.

  "num_candidates": 500, # Number of nearest neighbor candidates to consider per shard.

#Increasing num_candidates tends to improve the accuracy of the final k results.

  "query_vector_builder": { # Object indicating how to build a query_vector. kNN search enables you to perform semantic search by using a previously deployed text embedding model, the steps for this process are demonstrated in the Python notebook.
    "text_embedding": { 
      "model_id": "sentence-transformers__all-mpnet-base-v2", # Text embedding model id
      "model_text": "Comfortable furniture for a large balcony" # Query
    }
  }
}
)

for hit in response['hits']['hits']:
        
  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>このコード ブロックは、Elasticsearch の kNN を使用して、製品データセットの「<em> description</em><em> 」フィールドの埋め込みを考慮した「 大きなバルコニー用の快適な家具</em> 」のベクトル化されたクエリ (query_vector_build) に類似した説明を持つ最大 2 つの製品を返します。</p><p>製品の埋め込みは、以前はパイプラインに取り込まれたデータに対して推論するための<em>「</em> <a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2"><em>all-mpnet-base-v2</em></a> <em>」</em>テキスト埋め込みモデルを含む推論プロセッサを使用して、取り込みパイプラインで生成されていました。</p><p>このモデルは、 <em>「</em> <a href="https://github.com/UKPLab/sentence-transformers/blob/master/docs/package_reference/sentence_transformer/evaluation.md"><em>sentence_transformers.evaluation</em></a> <em>」</em>を使用した事前学習済みモデルの評価に基づいて選択されました。トレーニング中にさまざまなクラスを使用してモデルを評価します。「all-mpnet-base-v2」モデルは<a href="https://www.sbert.net/docs/pretrained_models.html">、Sentence-Transformers ランキング</a>で最高の平均パフォーマンスを示し、 <a href="https://huggingface.co/spaces/mteb/leaderboard">Massive Text Embedding Benchmark (MTEB)</a>リーダーボードでも好位置を獲得しました。このモデルは<a href="https://huggingface.co/microsoft/mpnet-base">、Microsoft/mpnet ベースの</a>モデルを事前トレーニングし、10 億の文のペアのデータセットで微調整されており、文を 768 次元の密なベクトル空間にマッピングします。</p><p>あるいは、特にドメイン固有のデータに合わせて微調整されたモデルなど、使用できる他のモデルも多数あります。</p><p>出力</p>Score: 0.79207325
Product: Patio Sofa Set with Ottoman
Category: Outdoor Furniture
Description: is a versatile and comfortable patio sofa set, including a sofa, ottoman, and coffee table, great for outdoor lounging.

Score: 0.7836937
Product: Patio Sofa Set with Canopy
Category: Outdoor Furniture
Description: is a luxurious and comfortable patio sofa set with a canopy, providing shade and style for outdoor lounging.
<p><em>出力は、選択したモデル、</em><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example"><em> フィルター</em></a><em> 、および</em><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#tune-approximate-knn-for-speed-accuracy"><em> おおよその kNN チューニング</em></a><em> によって異なる場合があります 。</em></p><p>kNN 検索結果は両方とも「 <em>Outdoor Furniture</em> 」カテゴリにありますが、クエリの一部として「 <em>outdoor</em> 」という単語が明示的に言及されておらず、コンテキストにおけるセマンティクスの理解の重要性が強調されています。</p><p>高密度ベクトル検索にはいくつかの利点があります。</p><ul><li><p>セマンティック検索の有効化</p></li><li><p>非常に大規模なデータセットを処理できるスケーラビリティ</p></li><li><p>幅広いデータタイプを処理できる柔軟性</p></li></ul><p>しかし、<strong>高密度ベクトル探索には独自の課題もあります</strong>。</p><ul><li><p>ユースケースに適した埋め込みモデルの選択</p></li><li><p>モデルが選択されると、ドメイン固有のデータセットでのパフォーマンスを最適化するためにモデルを微調整する必要がある場合があり、このプロセスにはドメイン専門家の関与が求められる。</p></li><li><p>さらに、高次元ベクトルのインデックス作成は計算コストが高くなる可能性がある。</p></li></ul><h2>セマンティック検索 - 学習されたスパース検索</h2><p>別のアプローチとして、セマンティック検索を実行する別の方法である学習済みスパース検索を検討してみましょう。</p><p>スパースモデルとして、数十年にわたる最適化の恩恵を受けている Elasticsearch の Lucene ベースの転置インデックスを活用します。ただし、このアプローチは、BM25 などの語彙スコアリング関数を使用して同義語を単純に追加するだけにとどまりません。代わりに、より深い言語スケールの知識を使用して学習した関連性を組み込み、関連性を最適化します。</p><p><a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Elastic Learned Sparse Encoder は</a>、検索クエリを拡張して元のクエリには存在しない関連用語を含めることにより、以下の例に示すように、<strong>スパース ベクトル埋め込みを改善します</strong>。</p><h3>Elastic Learned Sparse Encoder によるスパースベクトル検索</h3># Elastic Learned Sparse Encoder

response = client.search(index='ecommerce-search', size=2,
query={
  "text_expansion": {
    "ml.tokens": {
      "model_id":"elser_model",
      "model_text":"Comfortable furniture for a large balcony"                
    }
  }
}
)

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>出力</p>Score: 14.405318
Product: Garden Lounge Set with Side Table
Category: Garden Furniture
Description: is a comfortable and stylish garden lounge set, including a sofa, chairs, and a side table for outdoor relaxation.

Score: 14.281318
Product: Rattan Patio Conversation Set
Category: Outdoor Furniture
Description: is a stylish and comfortable outdoor furniture set, including a sofa, two chairs, and a coffee table, all made of durable rattan material.
<p>この場合の結果には、「<em> 屋外用家具</em> 」に非常に類似した製品を提供する「<em> ガーデン家具</em> 」カテゴリが含まれます。</p><p>「ml.tokens」を分析すると、学習済みスパース検索によって生成されたトークンを含む「rank_features」フィールドを見ると、生成されたさまざまなトークンの中に、「<em>リラックス</em>」（快適）、「<em>ソファ</em>」（家具）、「<em>屋外</em>」（バルコニー）など、検索クエリの一部ではないものの、意味的には関連している用語があることがわかります。</p><p>以下の画像は、用語拡張ありとなしの両方で、クエリとともにこれらの用語の一部を強調表示しています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e869985347b19a7/6a17d875e31791dc2c2d56a1/dd86607fce6137d843a3ec002390eaa988b432f9-1440x502.png" alt="" /><p>ご覧のとおり、このモデルはコンテキスト認識型の検索を提供し、より解釈しやすい結果を提供しながら語彙の不一致の問題を軽減するのに役立ちます。ドメイン固有の再トレーニングが適用されない場合でも、高密度ベクトル モデルよりも優れたパフォーマンスを発揮します。</p><h2>ハイブリッド検索: 語彙検索と意味検索を組み合わせた関連性の高い結果</h2><p>検索に関しては、普遍的な解決策はありません。これらの検索方法にはそれぞれ長所がありますが、課題もあります。ユースケースに応じて、最適なオプションは変わる場合があります。多くの場合、複数の検索方法間で最良の結果が補完的になります。したがって、関連性を高めるために、それぞれの方法の長所を組み合わせることを検討します。</p><p><strong>ハイブリッド検索を</strong>実装する方法は複数あります。線形結合、各スコアへの重み付け、重みの指定が不要な逆ランク融合 (RRF) などです。</p><h3>Elasticsearch: 語彙検索と意味検索の両方の長所を活用</h3># BM25 + Elastic Learned Sparse Encoder (Linear Combination)

response = client.search(index='ecommerce-search', size=2,

query= {
  "bool": {
    "should": [
    {
      "match": {
        "description" : {  
          "query": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
    },                   
    {
      "text_expansion": {
        "ml.tokens": {
          "model_id": "elser_model",
          "model_text": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
     }
    ]
  }
}
)

# The boost value is 1 for the text expansion and match query. This means that the relevance score of the results of these queries are not boosted. You can specify a boost value to give a weight to each score in the sum. The scores will be calculated as: score = boost value * match_score + boost value * text_expansion_score

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>このコードでは、「<em>大きなバルコニー用のダイニング テーブルと快適な椅子</em>」という値を持つ 2 つのクエリを使用してハイブリッド検索を実行しました。検索語として「<em>家具</em>」を使用する代わりに、探しているものを指定しており、両方の検索で同じフィールド値「説明」を考慮しています。ランキングは、BM25 スコアと ELSER スコアに等しい重み付けをした線形結合によって決定されます。</p><p>出力</p>Score: 31.628141
Product: Garden Dining Set with Swivel Rockers
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel rockers for easy movement.

Score: 31.334227
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.
<p>以下のコードでは、クエリに同じ値を使用しますが、逆ランク融合法を使用して BM25 (クエリ パラメータ) と kNN (knn パラメータ) のスコアを結合し、ドキュメントを結合してランク付けします。</p># BM25 + KNN (RRF)

response = client.search(index='ecommerce-search', size=2,
query={
  "bool": {
    "should": [
    {
      "match": {
        "description": {
        "query": "A dining table and comfortable chairs for a large balcony"
        }
      }
    }
    ]
  }
},
knn={
  "field": "description_vector.predicted_value",
  "k": 50,
  "num_candidates": 500,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": "sentence-transformers__all-mpnet-base-v2",
      "model_text": "A dining table and comfortable chairs for a large balcony"
    }
  }
},
rank={
  "rrf": { # Reciprocal rank fusion
    "window_size": 50, # This value determines the size of the individual result sets per query.
    "rank_constant": 20 # This value determines how much influence documents in individual result sets per query have over the final ranked result set.
  }
}
)

for hit in response['hits']['hits']:
        
  rank = hit['_rank']
  category = hit['_source']['category']
  product = hit['_source']['product']
  description = hit['_source']['description']
  print(f"\nRank: {rank}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p><em>RRF 機能はテクニカル プレビュー段階です。GA の前に構文が変更される可能性があります。</em></p><p>出力</p>Rank: 1
Product: Patio Dining Set with Bench
Category: Outdoor Furniture
Description: is a spacious and functional patio dining set, including a dining table, chairs, and a bench for additional seating.

Rank: 2
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.
<p>ここでは、異なるフィールドと値を使用することもできます。これらの例のいくつかは、 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">Python ノートブック</a>で利用できます。</p><p>ご覧のとおり、Elasticsearch を使用すると、従来の語彙検索とベクトル検索（疎かでも密かでも）の両方の利点を活用でき、目標を達成し<strong>て質問に対する最適な回答を見つけることができます。</strong></p><p>ここで説明したアプローチについてさらに学習したい場合は、次のブログが役立ちます。</p><ul><li><p><a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-hybrid">Elastic Stackでの情報検索の改善：ハイブリッド検索</a></p></li><li><p><a href="https://www.elastic.co/blog/vector-search-elasticsearch-rationale">Elasticsearchのベクトル検索：設計の背後にある理論的根拠</a></p></li><li><p><a href="https://www.elastic.co/blog/lexical-ai-powered-search-elastic-vector-database">Elasticのベクターデータベースで語彙検索とAIを活用した検索を最大限に活用する方法</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-sparse-encoder-ai-model">Elastic Learned Sparse Encoder のご紹介: セマンティック検索のための Elastic の AI モデル</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-information-retrieval-elasticsearch-ai-model">Elastic Stackでの情報検索の改善: 新しい検索モデルElastic Learned Sparse Encoderのご紹介</a></p></li></ul><p>Elasticsearch は、ベクター検索を構築するために必要なすべてのツールとともに、ベクター データベースを提供します。</p><ul><li><p>Elasticsearch<a href="https://www.elastic.co/elasticsearch/vector-database">ベクターデータベース</a></p></li><li><p>Elasticによる<a href="https://www.elastic.co/enterprise-search/vector-search">ベクトル検索のユース</a>ケース</p></li></ul><h2>まとめ</h2><p>このブログ記事では、Elasticsearch を使用して情報を取得するためのさまざまなアプローチについて検討し、特にテキスト、語彙、意味の検索に焦点を当てました。これを実証するために、eコマース製品情報を含むデータセットを使用してさまざまな検索シナリオを紹介する Python の例を示しました。</p><p>BM25 を使用した従来の語彙検索をレビューし、語彙の不一致などの利点と課題について議論しました。この問題を克服するために、意味的知識を取り入れることの重要性を強調しました。さらに、セマンティック検索を可能にする高密度ベクトル検索について説明し、高次元ベクトルのインデックス作成時の計算コストなど、この検索方法に関連する課題についても説明しました。</p><p>一方、スパースベクトルは圧縮率が非常に高いことを説明しました。そこで、元のクエリには存在しない関連用語を含めるように検索クエリを拡張する Elastic の Learned Sparse Encoder について説明しました。</p><p>検索に関しては、万能の解決策は存在しません。それぞれの検索方法には長所と課題があります。そこで、ハイブリッド検索の概念についても議論しました。</p><p>ご覧のとおり、Elasticsearch を使用すると、従来の語彙検索とベクトル検索の両方の長所を活用できます。</p><p>始める準備はできましたか?利用可能な<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">Python ノートブック</a>を確認し、 <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">Elastic Cloud の無料トライアル</a>を開始してください。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[クエリー言語]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbd7e961f594005e7/6a17d80f033c8d981f6bb009/d240bfef29e9d432069059b312dd044eb76eec6c-1440x840.png" length="0" type="image/png"/>
    <pubDate>Tue, 03 Oct 2023 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>