すべてのプロダクト
Search
ドキュメントセンター

Tablestore:Tablestore KNN ベクトルクエリの性能を最適化するにはどうすればよいですか?

最終更新日:Apr 15, 2025

Tablestore の k 近傍 (KNN) ベクトルクエリ機能を使用すると、大規模なデータセット内で、クエリ対象のベクトルと最も類似性の高いデータ項目を特定できます。KNN ベクトルクエリを使用してセマンティック検索を実行したときに、検索結果が期待どおりでない場合は、このトピックのトラブルシューティング手順に従って機能の性能を最適化できます。

KNN ベクトルクエリのスコアリング式

説明

Tablestore の KNN ベクトルクエリ機能を使用すると、ベクトルに基づいて近似最近傍検索を実行できます。この機能は、検索拡張生成 (RAG)、レコメンデーションシステム、類似性検出、自然言語処理 (NLP)、セマンティック検索など、さまざまなシナリオに適しています。詳細については、「概要」をご参照ください。

KNN ベクトルクエリ機能では、ベクトルに対してユークリッド距離、ドット積、コサインの距離測定アルゴリズムがサポートされています。ベクトル間の距離の計算に使用するスコアリング式は、距離測定アルゴリズムによって異なります。Tablestore は、距離測定アルゴリズムのスコアリング式を使用して、ベクトル間の類似性を測定します。次の表に、スコアリング式を示します。

メトリックタイプ

スコアリング式

euclidean

image

dot_product

image

cosine

image

トラブルシューティングと分析

1. ソート方法を確認する

KNN ベクトルクエリ機能を使用する場合、ソート方法が ScoreSort に設定されているかどうかを確認して、クエリ結果をスコアでソートします。デフォルトでは、クエリ結果はプライマリキーでソートされます。

2. KnnVectorQuery と BoolQuery の組み合わせを調整する

KnnVectorQueryBoolQuery の組み合わせを使用する場合は、検索インデックスのクエリタイプを KnnVectorQuery に設定することをお勧めします。BoolQuery のクエリ条件を KNN ベクトルクエリフィルタ (Filter) に含めると、スコアリングは影響を受けません。

重要

クエリタイプを BoolQuery に設定し、KnnVectorQueryBoolQuery のサブクエリ条件として指定すると、BoolQuery の他のクエリ条件がスコアリングに影響を与える可能性があります。詳細については、「付録 1: KNN ベクトルクエリとブールクエリの組み合わせ」をご参照ください。

次の Java コードサンプルは、KNN ベクトルクエリ機能の使用方法の例を示しています。

private static void knnVectorQuery(SyncClient client) {
    SearchQuery searchQuery = new SearchQuery();
    KnnVectorQuery query = new KnnVectorQuery();
    query.setFieldName("Col_Vector");
    query.setTopK(10); // 指定されたベクトルと最も類似性の高い上位 10 個のベクトルをテーブルから返します。
    query.setFloat32QueryVector(new float[]{0.1f, 0.2f, 0.3f, 0.4f});
    // 上位 10 個のベクトルのクエリ条件を指定します。Col_Keyword 列の値は hangzhou で、Col_Long 列の値は 4 未満です。
    query.setFilter(QueryBuilders.bool()
            .must(QueryBuilders.term("Col_Keyword", "hangzhou"))
            .must(QueryBuilders.range("Col_Long").lessThan(4))
    );
    searchQuery.setQuery(query);
    searchQuery.setLimit(10);
    // クエリ結果をスコアでソートします。
    searchQuery.setSort(new Sort(Collections.singletonList(new ScoreSort())));
    SearchRequest searchRequest = new SearchRequest("<テーブル名>", "<検索インデックス名>", searchQuery);
    SearchRequest.ColumnsToGet columnsToGet = new SearchRequest.ColumnsToGet();
    columnsToGet.setColumns(Arrays.asList("Col_Keyword", "Col_Long"));
    searchRequest.setColumnsToGet(columnsToGet);
    // Search 操作を呼び出します。
    SearchResponse resp = client.search(searchRequest);
    for (SearchHit hit : resp.getSearchHits()) {
        // スコアを表示します。
        System.out.println(hit.getScore());
        // データを表示します。
        System.out.println(hit.getRow());
    }
}

3. ベクトルの生成性能を確認する

Tablestore はベクトル間の類似性を計算し、ベクトルの生成性能が最適かどうかは考慮しません。データベース内のベクトルとクエリ対象のベクトルは、外部埋め込みモデルによって生成されます。専門分野では、生成されたベクトルが不正確な場合があります。ベクトル生成性能の低下のトラブルシューティングを行うには、次の手順を実行します。

  1. Tablestore 以外の方法またはツールを使用してスコアリングを行う。

    1. クエリ対象のベクトルに ベクトル a という名前を付け、Tablestore テーブルから返すベクトルに ベクトル b という名前を付けます。

      説明

      検索インデックス、セカンダリインデックス、または Wide Column モデルのデータテーブルからデータを読み取る操作を使用して、ベクトル b のデータを取得できます。

    2. 付録: KNN ベクトルクエリのスコアリング式のサンプルコード で説明されている MetricFunction.COSINE.compare(a, b) メソッドを使用して、ベクトル a とベクトル b の類似性を表す スコア a を計算します。

  2. Tablestore を使用してスコアリングを行う。

    Tablestore の KNN ベクトルクエリ機能を使用して ベクトル a をクエリし、応答の各行の スコア b を確認します。

  3. スコアを比較および分析する。

    Tablestore の KNN ベクトルクエリで ベクトル b を含む行が見つからない場合、応答の各行の スコア b は理論的には スコア a よりも高くなります。

    スコアは、KNN ベクトルクエリの性能低下が、埋め込みモデルのベクトル生成性能の低下によって引き起こされていることを示しています。KNN ベクトルクエリ応答には、スコアが予想スコアよりも高いベクトルデータが含まれています。スコアが予想スコアよりも低いベクトルデータは返されません。

  4. ソリューションを選択する。

    ほとんどの場合、前述の問題は専門分野で発生します。たとえば、一般的な埋め込みモデルを使用する場合、バイオテクノロジー業界や医療業界の用語ではベクトル生成が期待どおりに機能しません。特定の用語は、専門分野では類似したセマンティクスを持つ場合がありますが、一般的な埋め込みモデルでは大きく異なるセマンティクスを持つ場合があります。この場合は、次のソリューションの使用を検討してください。

    • 専門分野専用の埋め込みモデルを使用する。

      ModelScope は、さまざまな埋め込みモデルを提供しています。ビジネス要件に基づいて、公共サービス、e コマース、ヘルスケア、司法、金融など、専門分野の埋め込みモデルを選択できます。詳細については、「埋め込みモデルリスト」を参照してください。

    • 合法的な方法を使用して大量の専門コーパスを収集し、適切な埋め込みモデルをトレーニングする。

付録: KNN ベクトルクエリのスコアリング式のサンプルコード

次の Java コードサンプルは、距離測定アルゴリズムのスコアリング式の使用方法の例を示しています。

import java.util.concurrent.ThreadLocalRandom;

public class CompareVector {

    public static void main(String[] args) {
        // クエリ対象のベクトルを a として指定します。
        float[] a = randomVector(512);
        // 返される行に含まれると予想されるベクトルを b として指定します。
        float[] b = randomVector(512);
        // 検索インデックスで設定した類似性測定アルゴリズムを指定して、スコアを生成します。
        System.out.println(MetricFunction.COSINE.compare(a, b));
    }

    public static float[] randomVector(int dim) {
        float[] vec = new float[dim];
        for (int i = 0; i < dim; i++) {
            vec[i] = ThreadLocalRandom.current().nextFloat();
            if (ThreadLocalRandom.current().nextBoolean()) {
                vec[i] = -vec[i];
            }
        }
        return l2normalize(vec, true);
    }

    public static float[] l2normalize(float[] v, boolean throwOnZero) {
        double squareSum = 0.0f;
        int dim = v.length;
        for (float x : v) {
            squareSum += x * x;
        }
        if (squareSum == 0) {
            if (throwOnZero) {
                throw new IllegalArgumentException("長さゼロのベクトルを正規化します");
            } else {
                return v;
            }
        }
        double length = Math.sqrt(squareSum);
        for (int i = 0; i < dim; i++) {
            v[i] /= length;
        }
        return v;
    }

    public enum MetricFunction {
        /**
         * ユークリッド距離。
         */
        EUCLIDEAN {
            @Override
            public float compare(float[] v1, float[] v2) {
                return 1 / (1 + VectorUtil.squareDistance(v1, v2));
            }
        },

        /**
         * ドット積。
         */
        DOT_PRODUCT {
            @Override
            public float compare(float[] v1, float[] v2) {
                return (1 + VectorUtil.dotProduct(v1, v2)) / 2;
            }
        },

        /**
         * コサイン。
         */
        COSINE {
            @Override
            public float compare(float[] v1, float[] v2) {
                return (1 + VectorUtil.cosine(v1, v2)) / 2;
            }
        };

        public abstract float compare(float[] v1, float[] v2);
    }


    static final class VectorUtil {

        private static void checkParam(float[] a, float[] b) {
            if (a.length != b.length) {
                throw new IllegalArgumentException("ベクトルの次元が異なります: " + a.length + "!=" + b.length);
            }
        }

        public static float dotProduct(float[] a, float[] b) {
            checkParam(a, b);
            float res = 0f;
            for (int i = 0; i < a.length; i++) {
                res += b[i] * a[i];
            }
            return res;
        }

        public static float cosine(float[] a, float[] b) {
            checkParam(a, b);
            float sum = 0.0f;
            float norm1 = 0.0f;
            float norm2 = 0.0f;
            for (int i = 0; i < a.length; i++) {
                float elem1 = a[i];
                float elem2 = b[i];
                sum += elem1 * elem2;
                norm1 += elem1 * elem1;
                norm2 += elem2 * elem2;
            }
            return (float) (sum / Math.sqrt((double) norm1 * (double) norm2));
        }

        public static float squareDistance(float[] a, float[] b) {
            checkParam(a, b);
            float sum = 0.0f;
            for (int i = 0; i < a.length; i++) {
                float difference = a[i] - b[i];
                sum += difference * difference;
            }
            return sum;
        }
    }
}