Tablestore s'appuie sur sa fonctionnalité d'index de recherche pour proposer la recherche vectorielle. Cette fonctionnalité vous permet d'identifier les éléments de données les plus similaires au sein de vastes ensembles de données. Si les résultats de la recherche vectorielle pour la recherche sémantique ne sont pas satisfaisants, reportez-vous à cette rubrique pour optimiser les performances.
Formules de score pour la recherche vectorielle
La recherche vectorielle (KnnVectorQuery) de Tablestore utilise des vecteurs numériques pour exécuter des requêtes de plus proches voisins approximatifs. Cette approche convient aux scénarios tels que la génération augmentée par récupération (RAG), les systèmes de recommandation, la détection de similarité, le traitement du langage naturel et la recherche sémantique. Pour en savoir plus sur l'utilisation de la recherche vectorielle, consultez la rubrique Recherche vectorielle.
La recherche vectorielle prend en charge les mesures de distance suivantes : distance euclidienne (euclidean), similarité cosinus (cosine) et produit scalaire (dot_product). Chaque mesure de distance dispose d'une formule de score spécifique. Tablestore utilise ces formules pour évaluer la similarité entre les vecteurs. Le tableau ci-dessous décrit les formules de score.
|
MetricType |
Scoring formula |
|
Distance euclidienne (euclidean) |
|
|
Produit scalaire (dot_product) |
|
|
Similarité cosinus (cosine) |
|
Dépannage et analyse
1. Vérifier la méthode de tri
Lorsque vous utilisez la recherche vectorielle, vous pouvez trier les résultats par score à l'aide de ScoreSort. Par défaut, les résultats sont triés par clé primaire.
2. Ajuster l'utilisation de BoolQuery
Si vous combinez une requête KnnVectorQuery (recherche vectorielle) avec une requête BoolQuery (requête multi-conditions), vous pouvez définir le type de requête de l'index de recherche sur KnnVectorQuery et ajouter les conditions BoolQuery au paramètre Filter (filtre de recherche vectorielle). Cette méthode n'affecte pas le calcul du score.
Si vous définissez le type de requête sur BoolQuery et utilisez KnnVectorQuery comme sous-condition, les autres conditions de requête dans la BoolQuery peuvent affecter le calcul du score. Pour plus d'informations, consultez la section Utilisation avec BoolQuery.
Le code Java suivant fournit un exemple de recherche vectorielle.
private static void knnVectorQuery(SyncClient client) {
SearchQuery searchQuery = new SearchQuery();
KnnVectorQuery query = new KnnVectorQuery();
query.setFieldName("Col_Vector");
query.setTopK(10); // Return the top K nearest neighbors.
query.setFloat32QueryVector(new float[]{0.1f, 0.2f, 0.3f, 0.4f});
// The nearest neighbor vectors must meet the condition: 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);
// Sort 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);
// Access the Search interface.
SearchResponse resp = client.search(searchRequest);
for (SearchHit hit : resp.getSearchHits()) {
// Print the score.
System.out.println(hit.getScore());
// Print the data.
System.out.println(hit.getRow());
}
}
3. Vérifier la qualité de la génération des vecteurs
Tablestore calcule la similarité des données vectorielles, mais n'évalue pas la qualité de leur génération. Un modèle d'embedding externe génère à la fois les vecteurs stockés dans la base de données et les vecteurs de requête. Dans des scénarios hautement spécialisés, les vecteurs générés peuvent s'avérer inefficaces. Vous pouvez résoudre ce problème comme suit :
-
Calculez le score directement sans utiliser Tablestore.
-
Définissez le vecteur de requête comme
vector aet le vecteur que vous souhaitez récupérer depuis la table Tablestore commevector b.RemarqueVous pouvez obtenir les données pour
vector ben utilisant l'index de recherche, l'index secondaire ou les API de lecture de données de table wide. Calculez
score aà l'aide de la méthodeMetricFunction.COSINE.compare(a, b). Cette méthode est fournie dans le code de démonstration de la section Annexe : Code de démonstration des formules de score pour la recherche vectorielle.
-
-
Calculez le score en utilisant Tablestore.
Utilisez la fonctionnalité de recherche vectorielle de Tablestore pour interroger
vector a. Ensuite, vérifiezscore bpour chaque ligne dans les résultats retournés. -
Analyse comparative
Si les résultats de la recherche vectorielle de Tablestore n'incluent pas la ligne contenant
vector b, alorsscore bpour chaque ligne retournée est théoriquement supérieur àscore a.Ce résultat confirme que les résultats insatisfaisants de la recherche vectorielle sont causés par les faibles performances du modèle d'embedding. Les résultats de la recherche ne contiennent que des données vectorielles dont les scores sont supérieurs à votre résultat attendu. Par conséquent, le résultat attendu n'est pas retourné.
-
Solutions recommandées.
Ce problème survient généralement dans des domaines spécialisés. Par exemple, des termes spécifiques en biomédecine peuvent ne pas être bien traités par un modèle d'embedding généraliste. Des termes sémantiquement similaires dans un domaine spécialisé peuvent ne pas être considérés comme tels par le modèle. Dans ce cas, envisagez les solutions suivantes :
-
Trouvez un modèle d'embedding adapté à votre domaine spécifique.
ModelScope propose de nombreux modèles d'embedding prêts à l'emploi. Vous pouvez choisir des modèles pour des domaines spécialisés tels que les affaires gouvernementales, le commerce électronique, la santé, le droit et la finance. Pour plus d'informations, consultez la Liste des modèles d'embedding.
Collectez un grand volume de données de corpus professionnelles et utilisez-les pour entraîner un modèle d'embedding approprié.
-
Annexe : Code de démonstration des formules de score pour la recherche vectorielle
Le code Java suivant illustre les formules de score pour les mesures de distance prises en charge.
import java.util.concurrent.ThreadLocalRandom;
public class CompareVector {
public static void main(String[] args) {
// a is the query vector
float[] a = randomVector(512);
// b is the vector from the row in the index that you expect to be returned
float[] b = randomVector(512);
// Select the similarity measure algorithm that you set in your search index and output the 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;
}
}
}


