Tablestore は、検索インデックス機能を使用してベクトル検索を提供します。この機能を使用して、大規模なデータセットで最も類似したデータ項目を見つけることができます。セマンティック検索のベクトル検索結果に満足できない場合は、このトピックを参照してパフォーマンスを最適化してください。
ベクトル検索のスコアリング式
Tablestore のベクトル検索 (KnnVectorQuery) は、数値ベクトルを使用して近似近傍検索を実行します。これは、検索拡張生成 (RAG)、推薦システム、類似性検出、自然言語処理、セマンティック検索などのシナリオに適しています。詳細については、「ベクトル検索」をご参照ください。
ベクトル検索は、ユークリッド距離 (euclidean)、コサイン類似度 (cosine)、およびドット積 (dot_product) の距離尺度をサポートしています。各距離尺度には異なるスコアリング式があります。Tablestore はこれらのスコアリング式を使用して、ベクトル間の類似性を評価します。次の表に、スコアリング式を示します。
|
MetricType |
スコアリング式 |
|
ユークリッド距離 (euclidean) |
|
|
ドット積 (dot_product) |
|
|
コサイン類似度 (cosine) |
|
トラブルシューティングと分析
1. 並べ替え方法の確認
ベクトル検索を使用する場合、ScoreSort を使用して結果をスコアで並べ替えることができます。デフォルトでは、結果はプライマリキーによって並べ替えられます。
2. BoolQuery の使用方法の調整
KnnVectorQuery (ベクトル検索) と BoolQuery (複数条件クエリ) を組み合わせる場合、多次元インデックスのクエリタイプを KnnVectorQuery に設定し、BoolQuery の条件を Filter (ベクトル検索フィルター) パラメーターに追加できます。この方法では、スコア計算に影響はありません。
クエリタイプを BoolQuery に設定し、KnnVectorQuery をサブ条件として使用すると、BoolQuery 内の他のクエリ条件がスコア計算に影響を与える可能性があります。詳細については、「BoolQuery との使用」をご参照ください。
次の Java コードは、ベクトル検索のサンプルです。
private static void knnVectorQuery(SyncClient client) {
SearchQuery searchQuery = new SearchQuery();
KnnVectorQuery query = new KnnVectorQuery();
query.setFieldName("Col_Vector");
query.setTopK(10); // 上位 K 個の最近傍を返します。
query.setFloat32QueryVector(new float[]{0.1f, 0.2f, 0.3f, 0.4f});
// 最近傍ベクトルは、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("<TABLE_NAME>", "<SEARCH_INDEX_NAME>", 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 はベクトルデータの類似度を計算しますが、ベクトル生成の品質は評価しません。外部の埋め込みモデルによって、データベース内のベクトルとクエリベクトルの両方が生成されます。高度に専門化されたシナリオでは、生成されたベクトルが有効でない場合があります。この問題は、次のようにトラブルシューティングできます。
-
Tablestore を使用せずに直接スコアを計算
-
クエリベクトルを
vector aと定義し、Tablestore テーブルから取得したいベクトルをvector bと定義します。説明vector bのデータは、多次元インデックス、セカンダリインデックス、またはワイドテーブルのデータ読み取り API を使用して取得できます。 -
score aはMetricFunction.COSINE.compare(a, b)メソッドを使用して計算できます。このメソッドは、「付録: ベクトル検索のスコアリング式のデモコード」のデモコードで提供されています。
-
-
Tablestore を使用してスコアを計算
Tablestore のベクトル検索機能を使用して
vector aをクエリし、返された結果の各行でscore bを確認することができます。 -
比較分析
Tablestore のベクトル検索結果に
vector bを含む行が含まれない場合、返された各行のscore bは、理論上score aよりも高くなります。この結果から、ベクトル検索の結果が期待どおりでない原因が、埋め込みモデルのパフォーマンスの低さにあることが確認できます。検索結果には、期待する結果よりもスコアが高いベクトルデータのみが含まれているため、期待する結果は返されません。
-
推奨される解決策
この問題は通常、専門分野で発生します。たとえば、生物医学の特定の用語は、汎用の埋め込みモデルではうまく機能しない場合があります。専門分野で意味的に類似している用語が、モデルによって意味的に類似していると見なされない場合があります。この場合、次の解決策を検討できます:
-
特定のドメイン向けの埋め込みモデルを見つけます。
ModelScope は、すぐに使用できる多くの埋め込みモデルを提供しています。行政、E コマース、ヘルスケア、法律、金融などの専門ドメイン向けのモデルを選択できます。詳細については、「Embedding Model List」をご参照ください。
-
大量の専門的なコーパスデータを収集し、このデータを使用して適切な埋め込みモデルをトレーニングします。
-
付録: ベクトル検索のスコアリング式のデモコード
次の 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("normalize a zero-length vector");
} 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("vector dimensions differ: " + 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;
}
}
}


