You can use the k-nearest neighbor (KNN) vector query feature of Tablestore to identify data items that have the highest similarity to the vector that you want to query in a large-scale dataset. When you use KNN vector query to perform a semantic search and the search results do not meet your expectation, you can follow the troubleshooting instructions in this topic to optimize the performance of the feature.
Scoring formulas for KNN vector query
You can use the KNN vector query feature of Tablestore to perform approximate nearest neighbor searches based on vectors. The feature is suitable for various scenarios, such as retrieval-augmented generation (RAG), recommendation systems, similarity detection, natural language processing (NLP), and semantic search. For more information, see Overview.
The KNN vector query feature supports the following distance measurement algorithms for vectors: euclidean, dot_product, and cosine. The scoring formula used to calculate the distance between vectors varies based on the distance measurement algorithm. Tablestore uses the scoring formulas of distance measurement algorithms to measure the similarity between vectors. The following table describes the scoring formulas.
Metric type | Scoring formula |
euclidean | |
dot_product | |
cosine |
Troubleshooting and analysis
1. Check the sorting method
Check whether the sorting method is set to ScoreSort
to sort the query results by score when you use the KNN vector query feature. By default, the query results are sorted by primary key.
2. Adjust the combination of KnnVectorQuery and BoolQuery
When you use the combination of KnnVectorQuery
and BoolQuery
, we recommend that you set the query type of the search index to KnnVectorQuery
. If you include the query conditions of BoolQuery
in the KNN vector query filter (Filter
), the scoring is not affected.
If you set the query type to BoolQuery
and specify KnnVectorQuery
as a subquery condition of BoolQuery
, other query conditions of BoolQuery
may affect the scoring. For more information, see Appendix 1: Combination of KNN vector query and Boolean query.
The following sample Java code provides an example on how to use the KNN vector query feature:
private static void knnVectorQuery(SyncClient client) {
SearchQuery searchQuery = new SearchQuery();
KnnVectorQuery query = new KnnVectorQuery();
query.setFieldName("Col_Vector");
query.setTopK(10); // Return the top 10 vectors in the table that have the highest similarity to the specified vector.
query.setFloat32QueryVector(new float[]{0.1f, 0.2f, 0.3f, 0.4f});
// Specify the query conditions for the top 10 vectors: the value of the Col_Keyword column is hangzhou and the value of the Col_Long column is less than 4.
query.setFilter(QueryBuilders.bool()
.must(QueryBuilders.term("Col_Keyword", "hangzhou"))
.must(QueryBuilders.range("Col_Long").lessThan(4))
);
searchQuery.setQuery(query);
searchQuery.setLimit(10);
// Sort the query results by score.
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);
// Call the Search operation.
SearchResponse resp = client.search(searchRequest);
for (SearchHit hit : resp.getSearchHits()) {
// Display the scores.
System.out.println(hit.getScore());
// Display the data.
System.out.println(hit.getRow());
}
}
3. Check the vector generation performance
Tablestore calculates the similarity between vectors and does not consider whether the vector generation performance is optimal. The vectors in databases and the vectors that you want to query are generated by external Embedding models. In specialized fields, the generated vectors may be inaccurate. To troubleshoot poor vector generation performance, perform the following steps:
Use methods or tools other than Tablestore for scoring.
Name the vector that you want to query as
Vector a
and the vector that you want to return from a Tablestore table asVector b
.NoteYou can use a search index or a secondary index or the operation for reading data from a data table in the Wide Column model to obtain the data of
Vector b
.Calculate
Score a
, which represents the similarity between Vector a and Vector b, by using theMetricFunction.COSINE.compare(a, b)
method described in Appendix: Sample code for the scoring formulas of KNN vector query.
Use Tablestore for scoring.
Use the KNN vector query feature of Tablestore to query
Vector a
and checkScore b
of each row in the response.Compare and analyze the scores.
If no rows that contain
Vector b
are found in the KNN vector query of Tablestore,Score b
of each row in the response is theoretically higher thanScore a
.The scores indicate that the poor KNN vector query performance is caused by the poor vector generation performance of the Embedding model. The KNN vector query response includes vector data whose score is higher than the expected score. The vector data whose score is less than the expected score is not returned.
Select a solution.
In most cases, the preceding issue occurs in specialized fields. For example, vector generation does not work as expected for terms in biotechnology and medical industries when general Embedding models are used. Specific terms may have similar semantics in specialized fields but significantly different semantics in general Embedding models. In this case, consider using the following solutions:
Use Embedding models dedicated for specialized fields.
ModelScope provides various Embedding models. You can select an Embedding model for a specialized field, such as public service, e-commerce, healthcare, justice, or finance, based on your business requirements. For more information, see Embedding model list.
Collect a large amount of specialized corpus by using legal methods to train a suitable Embedding model.
Appendix: Sample code for the scoring formulas of KNN vector query
The following sample Java code provides an example on how to use the scoring formulas of distance measurement algorithms:
import java.util.concurrent.ThreadLocalRandom;
public class CompareVector {
public static void main(String[] args) {
// Specify a as the vector that you want to query.
float[] a = randomVector(512);
// Specify b as the vector that you expect the returned row to include.
float[] b = randomVector(512);
// Specify the similarity measurement algorithm that you configured in the search index to generate a score.
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 distance.
*/
EUCLIDEAN {
@Override
public float compare(float[] v1, float[] v2) {
return 1 / (1 + VectorUtil.squareDistance(v1, v2));
}
},
/**
* Dot product.
*/
DOT_PRODUCT {
@Override
public float compare(float[] v1, float[] v2) {
return (1 + VectorUtil.dotProduct(v1, v2)) / 2;
}
},
/**
* Cosine.
*/
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;
}
}
}