Service efficace

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier

Modèles de récupération sont souvent construits à la surface une poignée de meilleurs candidats de millions ou des centaines voire de millions de candidats. Pour pouvoir réagir au contexte et au comportement de l'utilisateur, il doit pouvoir le faire à la volée, en quelques millisecondes.

La recherche approximative du voisin le plus proche (ANN) est la technologie qui rend cela possible. Dans ce didacticiel, nous montrerons comment utiliser ScaNN - un package de récupération du plus proche voisin à la pointe de la technologie - pour adapter de manière transparente la récupération TFRS à des millions d'éléments.

Qu'est-ce que ScaNN ?

ScaNN est une bibliothèque de Google Research qui effectue une recherche de similarité vectorielle dense à grande échelle. Étant donné une base de données de plongements candidats, ScaNN indexe ces plongements d'une manière qui permet de les rechercher rapidement au moment de l'inférence. ScaNN utilise des techniques de compression vectorielle de pointe et des algorithmes soigneusement mis en œuvre pour obtenir le meilleur compromis vitesse-précision. Il peut largement surpasser la recherche par force brute tout en sacrifiant peu en termes de précision.

Construire un modèle basé sur ScaNN

Pour essayer scann en TFRS, nous allons construire un modèle simple recherche MovieLens, comme nous l' avons fait dans la recherche de base tutoriel. Si vous avez suivi ce didacticiel, cette section vous sera familière et peut être ignorée en toute sécurité.

Pour commencer, installez les ensembles de données TFRS et TensorFlow :

pip install -q tensorflow-recommenders
pip install -q --upgrade tensorflow-datasets

Nous avons également besoin d'installer scann : il est une dépendance facultative de TFRS, et ainsi doit être installé séparément.

pip install -q scann

Configurez toutes les importations nécessaires.

from typing import Dict, Text

import os
import pprint
import tempfile

import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
import tensorflow_recommenders as tfrs

Et charge les données :

# Load the MovieLens 100K data.
ratings = tfds.load(
    "movielens/100k-ratings",
    split="train"
)

# Get the ratings data.
ratings = (ratings
           # Retain only the fields we need.
           .map(lambda x: {"user_id": x["user_id"], "movie_title": x["movie_title"]})
           # Cache for efficiency.
           .cache(tempfile.NamedTemporaryFile().name)
)

# Get the movies data.
movies = tfds.load("movielens/100k-movies", split="train")
movies = (movies
          # Retain only the fields we need.
          .map(lambda x: x["movie_title"])
          # Cache for efficiency.
          .cache(tempfile.NamedTemporaryFile().name))
2021-10-02 11:53:59.413405: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected

Avant de pouvoir créer un modèle, nous devons configurer les vocabulaires utilisateur et vidéo :

user_ids = ratings.map(lambda x: x["user_id"])

unique_movie_titles = np.unique(np.concatenate(list(movies.batch(1000))))
unique_user_ids = np.unique(np.concatenate(list(user_ids.batch(1000))))
2021-10-02 11:54:00.296290: W tensorflow/core/kernels/data/cache_dataset_ops.cc:233] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.
2021-10-02 11:54:04.003150: W tensorflow/core/kernels/data/cache_dataset_ops.cc:233] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

Nous mettrons également en place les ensembles d'entraînement et de test :

tf.random.set_seed(42)
shuffled = ratings.shuffle(100_000, seed=42, reshuffle_each_iteration=False)

train = shuffled.take(80_000)
test = shuffled.skip(80_000).take(20_000)

Définition du modèle

Tout comme dans la recherche de base tutoriel, nous construisons un modèle simple à deux tours.

class MovielensModel(tfrs.Model):

  def __init__(self):
    super().__init__()

    embedding_dimension = 32

    # Set up a model for representing movies.
    self.movie_model = tf.keras.Sequential([
      tf.keras.layers.StringLookup(
        vocabulary=unique_movie_titles, mask_token=None),
      # We add an additional embedding to account for unknown tokens.
      tf.keras.layers.Embedding(len(unique_movie_titles) + 1, embedding_dimension)
    ])

    # Set up a model for representing users.
    self.user_model = tf.keras.Sequential([
      tf.keras.layers.StringLookup(
        vocabulary=unique_user_ids, mask_token=None),
        # We add an additional embedding to account for unknown tokens.
      tf.keras.layers.Embedding(len(unique_user_ids) + 1, embedding_dimension)
    ])

    # Set up a task to optimize the model and compute metrics.
    self.task = tfrs.tasks.Retrieval(
      metrics=tfrs.metrics.FactorizedTopK(
        candidates=movies.batch(128).cache().map(self.movie_model)
      )
    )

  def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor:
    # We pick out the user features and pass them into the user model.
    user_embeddings = self.user_model(features["user_id"])
    # And pick out the movie features and pass them into the movie model,
    # getting embeddings back.
    positive_movie_embeddings = self.movie_model(features["movie_title"])

    # The task computes the loss and the metrics.

    return self.task(user_embeddings, positive_movie_embeddings, compute_metrics=not training)

Ajustement et évaluation

Un modèle TFRS n'est qu'un modèle Keras. On peut le compiler :

model = MovielensModel()
model.compile(optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.1))

Estimez-le :

model.fit(train.batch(8192), epochs=3)
Epoch 1/3
10/10 [==============================] - 3s 223ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_5_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_10_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_50_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_100_categorical_accuracy: 0.0000e+00 - loss: 69808.9716 - regularization_loss: 0.0000e+00 - total_loss: 69808.9716
Epoch 2/3
10/10 [==============================] - 3s 222ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_5_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_10_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_50_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_100_categorical_accuracy: 0.0000e+00 - loss: 67485.8842 - regularization_loss: 0.0000e+00 - total_loss: 67485.8842
Epoch 3/3
10/10 [==============================] - 3s 220ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_5_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_10_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_50_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_100_categorical_accuracy: 0.0000e+00 - loss: 66311.9581 - regularization_loss: 0.0000e+00 - total_loss: 66311.9581
<keras.callbacks.History at 0x7fc02423c150>

Et l'évaluer.

model.evaluate(test.batch(8192), return_dict=True)
3/3 [==============================] - 2s 246ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0011 - factorized_top_k/top_5_categorical_accuracy: 0.0095 - factorized_top_k/top_10_categorical_accuracy: 0.0222 - factorized_top_k/top_50_categorical_accuracy: 0.1261 - factorized_top_k/top_100_categorical_accuracy: 0.2363 - loss: 49466.8789 - regularization_loss: 0.0000e+00 - total_loss: 49466.8789
{'factorized_top_k/top_1_categorical_accuracy': 0.0010999999940395355,
 'factorized_top_k/top_5_categorical_accuracy': 0.009549999609589577,
 'factorized_top_k/top_10_categorical_accuracy': 0.022199999541044235,
 'factorized_top_k/top_50_categorical_accuracy': 0.1261499971151352,
 'factorized_top_k/top_100_categorical_accuracy': 0.23634999990463257,
 'loss': 28242.8359375,
 'regularization_loss': 0,
 'total_loss': 28242.8359375}

Prédiction approximative

Le moyen le plus simple de récupérer les meilleurs candidats en réponse à une requête est de le faire par force brute : calculez les scores des films utilisateur pour tous les films possibles, triez-les et choisissez quelques-unes des meilleures recommandations.

En TFRS, cela se fait par la BruteForce couche:

brute_force = tfrs.layers.factorized_top_k.BruteForce(model.user_model)
brute_force.index_from_dataset(
    movies.batch(128).map(lambda title: (title, model.movie_model(title)))
)
<tensorflow_recommenders.layers.factorized_top_k.BruteForce at 0x7fbfc1d4fe10>

Une fois créé et peuplé de candidats (via l' index méthode), nous pouvons appeler pour obtenir des prédictions sur:

# Get predictions for user 42.
_, titles = brute_force(np.array(["42"]), k=3)

print(f"Top recommendations: {titles[0]}")
Top recommendations: [b'Homeward Bound: The Incredible Journey (1993)'
 b"Kid in King Arthur's Court, A (1995)" b'Rudy (1993)']

Sur un petit ensemble de données de moins de 1000 films, c'est très rapide :

%timeit _, titles = brute_force(np.array(["42"]), k=3)
983 µs ± 5.44 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Mais que se passe-t-il si nous avons plus de candidats - des millions au lieu de milliers ?

Nous pouvons simuler cela en indexant tous nos films plusieurs fois :

# Construct a dataset of movies that's 1,000 times larger. We 
# do this by adding several million dummy movie titles to the dataset.
lots_of_movies = tf.data.Dataset.concatenate(
    movies.batch(4096),
    movies.batch(4096).repeat(1_000).map(lambda x: tf.zeros_like(x))
)

# We also add lots of dummy embeddings by randomly perturbing
# the estimated embeddings for real movies.
lots_of_movies_embeddings = tf.data.Dataset.concatenate(
    movies.batch(4096).map(model.movie_model),
    movies.batch(4096).repeat(1_000)
      .map(lambda x: model.movie_model(x))
      .map(lambda x: x * tf.random.uniform(tf.shape(x)))
)

Nous pouvons construire un BruteForce index sur cet ensemble de données plus vaste:

brute_force_lots = tfrs.layers.factorized_top_k.BruteForce()
brute_force_lots.index_from_dataset(
    tf.data.Dataset.zip((lots_of_movies, lots_of_movies_embeddings))
)
<tensorflow_recommenders.layers.factorized_top_k.BruteForce at 0x7fbfc1d80610>

Les recommandations sont toujours les mêmes

_, titles = brute_force_lots(model.user_model(np.array(["42"])), k=3)

print(f"Top recommendations: {titles[0]}")
Top recommendations: [b'Homeward Bound: The Incredible Journey (1993)'
 b"Kid in King Arthur's Court, A (1995)" b'Rudy (1993)']

Mais ils prennent beaucoup plus de temps. Avec un ensemble candidat de 1 million de films, la prédiction par force brute devient assez lente :

%timeit _, titles = brute_force_lots(model.user_model(np.array(["42"])), k=3)
33 ms ± 245 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

À mesure que le nombre de candidats augmente, le temps nécessaire augmente de manière linéaire : avec 10 millions de candidats, servir les meilleurs candidats prendrait 250 millisecondes. C'est clairement trop lent pour un service en direct.

C'est là qu'interviennent les mécanismes approximatifs.

L' utilisation scann dans TFRS est réalisée par l' intermédiaire de la tfrs.layers.factorized_top_k.ScaNN couche. Il suit la même interface que les autres couches k supérieures :

scann = tfrs.layers.factorized_top_k.ScaNN(num_reordering_candidates=100)
scann.index_from_dataset(
    tf.data.Dataset.zip((lots_of_movies, lots_of_movies_embeddings))
)
<tensorflow_recommenders.layers.factorized_top_k.ScaNN at 0x7fbfc2571990>

Les recommandations sont (à peu près !) les mêmes

_, titles = scann(model.user_model(np.array(["42"])), k=3)

print(f"Top recommendations: {titles[0]}")
Top recommendations: [b'Homeward Bound: The Incredible Journey (1993)'
 b"Kid in King Arthur's Court, A (1995)" b'Rudy (1993)']

Mais ils sont beaucoup, beaucoup plus rapides à calculer :

%timeit _, titles = scann(model.user_model(np.array(["42"])), k=3)
4.35 ms ± 34.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Dans ce cas, nous pouvons récupérer les 3 meilleurs films sur un ensemble d'environ 1 million en environ 2 millisecondes : 15 fois plus rapidement qu'en calculant les meilleurs candidats par force brute. L'avantage des méthodes approximatives est encore plus grand pour les ensembles de données plus volumineux.

Évaluation de l'approximation

Lorsque vous utilisez des mécanismes de récupération approximatifs du top K (tels que ScaNN), la vitesse de récupération se fait souvent au détriment de la précision. Pour comprendre ce compromis, il est important de mesurer les métriques d'évaluation du modèle lors de l'utilisation de ScaNN et de les comparer avec la ligne de base.

Heureusement, TFRS rend cela facile. Nous remplaçons simplement les métriques de la tâche de récupération par des métriques à l'aide de ScaNN, recompilons le modèle et exécutons l'évaluation.

Pour faire la comparaison, exécutons d'abord les résultats de référence. Nous devons toujours remplacer nos métriques pour nous assurer qu'elles utilisent l'ensemble de candidats élargi plutôt que l'ensemble de films d'origine :

# Override the existing streaming candidate source.
model.task.factorized_metrics = tfrs.metrics.FactorizedTopK(
    candidates=lots_of_movies_embeddings
)
# Need to recompile the model for the changes to take effect.
model.compile()

%time baseline_result = model.evaluate(test.batch(8192), return_dict=True, verbose=False)
CPU times: user 22min 5s, sys: 2min 7s, total: 24min 12s
Wall time: 51.9 s

Nous pouvons faire la même chose en utilisant ScaNN :

model.task.factorized_metrics = tfrs.metrics.FactorizedTopK(
    candidates=scann
)
model.compile()

# We can use a much bigger batch size here because ScaNN evaluation
# is more memory efficient.
%time scann_result = model.evaluate(test.batch(8192), return_dict=True, verbose=False)
CPU times: user 10.5 s, sys: 3.26 s, total: 13.7 s
Wall time: 1.85 s

L'évaluation basée sur ScaNN est beaucoup, beaucoup plus rapide : c'est plus de dix fois plus rapide ! Cet avantage va encore augmenter pour les ensembles de données plus volumineux. Par conséquent, pour les ensembles de données volumineux, il peut être prudent de toujours exécuter une évaluation basée sur ScaNN pour améliorer la vitesse de développement du modèle.

Mais qu'en est-il des résultats ? Heureusement, dans ce cas, les résultats sont presque les mêmes :

print(f"Brute force top-100 accuracy: {baseline_result['factorized_top_k/top_100_categorical_accuracy']:.2f}")
print(f"ScaNN top-100 accuracy:       {scann_result['factorized_top_k/top_100_categorical_accuracy']:.2f}")
Brute force top-100 accuracy: 0.15
ScaNN top-100 accuracy:       0.27

Cela suggère que sur cette base de données artificielle, il y a peu de perte de l'approximation. En général, toutes les méthodes approximatives présentent des compromis vitesse-précision. Pour comprendre plus en profondeur , vous pouvez consulter Erik Bernhardsson benchmarks ANN .

Déploiement du modèle approximatif

Le ScaNN modèle à base est entièrement intégré dans les modèles tensorflow et au service , il est aussi facile que de servir tout autre modèle tensorflow.

Nous pouvons l' enregistrer comme un SavedModel objet

lots_of_movies_embeddings
<ConcatenateDataset shapes: (None, 32), types: tf.float32>
# We re-index the ScaNN layer to include the user embeddings in the same model.
# This way we can give the saved model raw features and get valid predictions
# back.
scann = tfrs.layers.factorized_top_k.ScaNN(model.user_model, num_reordering_candidates=1000)
scann.index_from_dataset(
    tf.data.Dataset.zip((lots_of_movies, lots_of_movies_embeddings))
)

# Need to call it to set the shapes.
_ = scann(np.array(["42"]))

with tempfile.TemporaryDirectory() as tmp:
  path = os.path.join(tmp, "model")
  tf.saved_model.save(
      scann,
      path,
      options=tf.saved_model.SaveOptions(namespace_whitelist=["Scann"])
  )

  loaded = tf.saved_model.load(path)
2021-10-02 11:55:53.875291: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
WARNING:absl:Found untraced functions such as query_with_exclusions while saving (showing 1 of 1). These functions will not be directly callable after loading.
INFO:tensorflow:Assets written to: /tmp/tmpm0piq8hx/model/assets
INFO:tensorflow:Assets written to: /tmp/tmpm0piq8hx/model/assets

puis chargez-le et servez, en obtenant exactement les mêmes résultats :

_, titles = loaded(tf.constant(["42"]))

print(f"Top recommendations: {titles[0][:3]}")
Top recommendations: [b'Homeward Bound: The Incredible Journey (1993)'
 b"Kid in King Arthur's Court, A (1995)" b'Rudy (1993)']

Le modèle résultant peut être servi dans n'importe quel service Python sur lequel TensorFlow et ScaNN sont installés.

Il peut également être servi en utilisant une version personnalisée de tensorflow Serving, disponible en tant que conteneur Docker sur Docker Hub . Vous pouvez également créer vous - même l'image de la Dockerfile .

Réglage de ScanNN

Voyons maintenant comment régler notre couche ScaNN pour obtenir un meilleur compromis performances/précision. Pour le faire efficacement, nous devons d'abord mesurer nos performances et notre précision de base.

D'en haut, nous avons déjà une mesure de la latence de notre modèle pour traiter une requête unique (non par lots) (bien que notez qu'une bonne partie de cette latence provient de composants non-ScaNN du modèle).

Nous devons maintenant étudier la précision de ScaNN, que nous mesurons par rappel. Un rappel@k de x% signifie que si nous utilisons la force brute pour récupérer les vrais premiers k voisins et que nous comparons ces résultats à l'utilisation de ScaNN pour récupérer également les k premiers voisins, x% des résultats de ScaNN sont dans les vrais résultats de force brute. Calculons le rappel pour le chercheur ScaNN actuel.

Tout d'abord, nous devons générer la force brute, vérité terrain top-k :

# Process queries in groups of 1000; processing them all at once with brute force
# may lead to out-of-memory errors, because processing a batch of q queries against
# a size-n dataset takes O(nq) space with brute force.
titles_ground_truth = tf.concat([
  brute_force_lots(queries, k=10)[1] for queries in
  test.batch(1000).map(lambda x: model.user_model(x["user_id"]))
], axis=0)

Notre variable titles_ground_truth contient maintenant les recommandations de films top-10 renvoyés par la récupération de la force brute. Maintenant, nous pouvons calculer les mêmes recommandations lors de l'utilisation de ScaNN :

# Get all user_id's as a 1d tensor of strings
test_flat = np.concatenate(list(test.map(lambda x: x["user_id"]).batch(1000).as_numpy_iterator()), axis=0)

# ScaNN is much more memory efficient and has no problem processing the whole
# batch of 20000 queries at once.
_, titles = scann(test_flat, k=10)

Ensuite, nous définissons notre fonction qui calcule le rappel. Pour chaque requête, il compte le nombre de résultats à l'intersection des résultats de force brute et de ScaNN et le divise par le nombre de résultats de force brute. La moyenne de cette quantité sur toutes les requêtes est notre rappel.

def compute_recall(ground_truth, approx_results):
  return np.mean([
      len(np.intersect1d(truth, approx)) / len(truth)
      for truth, approx in zip(ground_truth, approx_results)
  ])

Cela nous donne le rappel de base @ 10 avec la configuration actuelle de ScaNN :

print(f"Recall: {compute_recall(titles_ground_truth, titles):.3f}")
Recall: 0.931

Nous pouvons également mesurer la latence de base :

%timeit -n 1000 scann(np.array(["42"]), k=10)
4.67 ms ± 25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Voyons si nous pouvons faire mieux !

Pour ce faire, nous avons besoin d'un modèle de la façon dont les boutons de réglage de ScaNN affectent les performances. Notre modèle actuel utilise l'algorithme tree-AH de ScaNN. Cet algorithme partitionne la base de données des plongements (l'« arbre »), puis note la plus prometteuse de ces partitions en utilisant AH, qui est une routine de calcul de distance approximative hautement optimisée.

Les paramètres par défaut pour la couche scann Keras de tensorflow recommandeurs ensembles num_leaves=100 et num_leaves_to_search=10 . Cela signifie que notre base de données est partitionnée en 100 sous-ensembles disjoints, et les 10 plus prometteuses de ces partitions sont notées avec AH. Cela signifie que 10/100=10% de l'ensemble de données est recherché avec AH.

Si nous avons, disons, num_leaves=1000 et num_leaves_to_search=100 , nous serions également être à la recherche de 10% de la base de données avec AH. Cependant, par rapport au réglage précédent, les 10% que nous chercherions contiendra des candidats de meilleure qualité, car une plus grande num_leaves nous permet de prendre des décisions plus fines de ce que les parties de l'ensemble de données valent la recherche.

Il est donc pas surprenant que , avec num_leaves=1000 et num_leaves_to_search=100 nous obtenons un rappel significativement plus élevé:

scann2 = tfrs.layers.factorized_top_k.ScaNN(
    model.user_model, 
    num_leaves=1000,
    num_leaves_to_search=100,
    num_reordering_candidates=1000)
scann2.index_from_dataset(
    tf.data.Dataset.zip((lots_of_movies, lots_of_movies_embeddings))
)

_, titles2 = scann2(test_flat, k=10)

print(f"Recall: {compute_recall(titles_ground_truth, titles2):.3f}")
Recall: 0.966

Cependant, en contrepartie, notre latence a également augmenté. C'est parce que l'étape de partitionnement est devenue plus chère ; scann prend le dessus 10 de 100 partitions tout en scann2 prend le dessus 100 de 1000 partitions. Cette dernière peut être plus chère car elle implique de regarder 10 fois plus de partitions.

%timeit -n 1000 scann2(np.array(["42"]), k=10)
4.86 ms ± 21.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

En général, le réglage de la recherche ScaNN consiste à choisir les bons compromis. Chaque changement de paramètre individuel ne rendra généralement pas la recherche à la fois plus rapide et plus précise ; notre objectif est de régler les paramètres pour un compromis optimal entre ces deux objectifs contradictoires.

Dans notre cas, scann2 considérablement amélioré le rappel sur scann à un coût de la latence. Pouvons-nous rappeler d'autres boutons pour réduire la latence, tout en préservant la plupart de notre avantage de rappel ?

Essayons de rechercher 70/1000 = 7 % de l'ensemble de données avec AH, et de ne renoter que les 400 derniers candidats :

scann3 = tfrs.layers.factorized_top_k.ScaNN(
    model.user_model,
    num_leaves=1000,
    num_leaves_to_search=70,
    num_reordering_candidates=400)
scann3.index_from_dataset(
    tf.data.Dataset.zip((lots_of_movies, lots_of_movies_embeddings))
)

_, titles3 = scann3(test_flat, k=10)
print(f"Recall: {compute_recall(titles_ground_truth, titles3):.3f}")
Recall: 0.957

scann3 fournit environ un gain de rappel absolu 3% par rapport à scann tout en offrant une latence plus faible:

%timeit -n 1000 scann3(np.array(["42"]), k=10)
4.58 ms ± 37.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Ces boutons peuvent être ajustés davantage pour optimiser différents points le long de la frontière de Pareto précision-performance. Les algorithmes de ScaNN peuvent atteindre des performances de pointe sur un large éventail de cibles de rappel.

Lectures complémentaires

ScaNN utilise des techniques avancées de quantification vectorielle et une implémentation hautement optimisée pour atteindre ses résultats. Le domaine de la quantification vectorielle a une histoire riche avec une variété d'approches. Technique de quantification actuelle scann est détaillée dans ce document , publié à ICML 2020. Le document a également été publié en même temps que cet article de blog qui donne un aperçu de haut niveau de notre technique.

De nombreuses techniques de quantification connexes sont mentionnées dans les références de notre document ICML 2020, et d' autres recherches sur scann est référencé à http://sanjivk.com/