Penyajian yang efisien

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub Unduh buku catatan

Model pengambilan sering dibangun ke permukaan beberapa kandidat dari jutaan atau bahkan ratusan juta calon. Agar dapat bereaksi terhadap konteks dan perilaku pengguna, mereka harus dapat melakukannya dengan cepat, dalam hitungan milidetik.

Approximate Nearest Neighbor Search (ANN) adalah teknologi yang memungkinkan hal ini. Dalam tutorial ini, kami akan menunjukkan cara menggunakan ScanN - paket pengambilan tetangga terdekat yang canggih - untuk menskalakan pengambilan TFRS dengan mulus ke jutaan item.

Apa itu ScanN?

ScanNN adalah perpustakaan dari Google Research yang melakukan pencarian kesamaan vektor padat dalam skala besar. Diberikan database kandidat embeddings, ScanN mengindeks embeddings ini dengan cara yang memungkinkan mereka untuk dicari dengan cepat pada waktu inferensi. ScanNN menggunakan teknik kompresi vektor tercanggih dan algoritma yang diterapkan dengan hati-hati untuk mencapai tradeoff akurasi kecepatan terbaik. Itu bisa sangat mengungguli pencarian brute force sambil mengorbankan sedikit dalam hal akurasi.

Membangun model bertenaga ScanN

Untuk mencoba ScaNN di TFRS, kami akan membangun MovieLens sederhana Model pengambilan, seperti yang kita lakukan di pengambilan dasar tutorial. Jika Anda telah mengikuti tutorial itu, bagian ini akan familier dan dapat dilewati dengan aman.

Untuk memulai, instal Kumpulan Data TFRS dan TensorFlow:

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

Kami juga perlu menginstal scann : ini merupakan ketergantungan opsional TFRS, sehingga kebutuhan untuk diinstal secara terpisah.

pip install -q scann

Siapkan semua impor yang diperlukan.

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

Dan memuat data:

# 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

Sebelum kita dapat membangun model, kita perlu menyiapkan kosa kata pengguna dan film:

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.

Kami juga akan menyiapkan set pelatihan dan pengujian:

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)

Definisi model

Sama seperti di pengambilan dasar tutorial, kami membangun model dua tower sederhana.

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)

Pemasangan dan evaluasi

Model TFRS hanyalah model Keras. Kita dapat mengkompilasinya:

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

Perkirakan itu:

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>

Dan mengevaluasinya.

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}

Perkiraan perkiraan

Cara paling mudah untuk mengambil kandidat teratas sebagai tanggapan atas kueri adalah dengan melakukannya melalui kekerasan: hitung skor film pengguna untuk semua kemungkinan film, urutkan, dan pilih beberapa rekomendasi teratas.

Dalam TFRS, ini dicapai melalui BruteForce lapisan:

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>

Setelah dibuat dan diisi dengan calon (melalui index metode), kita bisa menyebutnya untuk mendapatkan prediksi keluar:

# 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)']

Pada kumpulan data kecil di bawah 1000 film, ini sangat cepat:

%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)

Tapi apa yang terjadi jika kita memiliki lebih banyak kandidat - jutaan, bukan ribuan?

Kami dapat mensimulasikan ini dengan mengindeks semua film kami beberapa kali:

# 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)))
)

Kita bisa membangun BruteForce indeks pada dataset yang lebih besar ini:

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>

Rekomendasinya masih sama

_, 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)']

Tapi mereka membutuhkan waktu lebih lama. Dengan kandidat set 1 juta film, prediksi brute force menjadi sangat lambat:

%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)

Seiring bertambahnya jumlah kandidat, jumlah waktu yang dibutuhkan bertambah secara linier: dengan 10 juta kandidat, melayani kandidat teratas akan memakan waktu 250 milidetik. Ini jelas terlalu lambat untuk layanan langsung.

Di sinilah mekanisme perkiraan masuk.

Menggunakan ScaNN di TFRS dicapai melalui tfrs.layers.factorized_top_k.ScaNN lapisan. Ini mengikuti antarmuka yang sama dengan lapisan k teratas lainnya:

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>

Rekomendasinya (kurang lebih!) sama

_, 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)']

Tetapi mereka jauh, jauh lebih cepat untuk dihitung:

%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)

Dalam hal ini, kami dapat mengambil 3 film teratas dari kumpulan ~1 juta dalam sekitar 2 milidetik: 15 kali lebih cepat daripada dengan menghitung kandidat terbaik melalui kekerasan. Keuntungan dari metode perkiraan tumbuh lebih besar untuk kumpulan data yang lebih besar.

Mengevaluasi aproksimasi

Saat menggunakan perkiraan mekanisme pengambilan K teratas (seperti ScanN), kecepatan pengambilan sering kali mengorbankan akurasi. Untuk memahami trade-off ini, penting untuk mengukur metrik evaluasi model saat menggunakan ScanNN, dan membandingkannya dengan baseline.

Untungnya, TFRS membuat ini mudah. Kami hanya mengganti metrik pada tugas pengambilan dengan metrik menggunakan ScanNN, mengkompilasi ulang model, dan menjalankan evaluasi.

Untuk membuat perbandingan, pertama-tama kita jalankan hasil baseline. Kami masih perlu mengganti metrik kami untuk memastikan metrik tersebut menggunakan kumpulan kandidat yang diperbesar daripada kumpulan film asli:

# 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

Kita dapat melakukan hal yang sama menggunakan ScanNN:

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

Evaluasi berbasis ScanNN jauh lebih cepat: lebih dari sepuluh kali lebih cepat! Keuntungan ini akan tumbuh lebih besar untuk kumpulan data yang lebih besar, sehingga untuk kumpulan data yang besar mungkin lebih bijaksana untuk selalu menjalankan evaluasi berbasis ScanN untuk meningkatkan kecepatan pengembangan model.

Tapi bagaimana dengan hasilnya? Untungnya, dalam hal ini hasilnya hampir sama:

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

Hal ini menunjukkan bahwa pada datase buatan ini terdapat sedikit kerugian dari aproksimasi. Secara umum, semua metode perkiraan menunjukkan pengorbanan kecepatan-akurasi. Untuk memahami hal ini secara lebih mendalam Anda dapat memeriksa Erik Bernhardsson ini ANN benchmark .

Menyebarkan model perkiraan

The ScaNN Model berbasis sepenuhnya terintegrasi ke dalam model TensorFlow, dan menyajikannya semudah melayani model TensorFlow lainnya.

Kita bisa menyimpannya sebagai SavedModel objek

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

lalu muat dan sajikan, dapatkan kembali hasil yang persis sama:

_, 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)']

Model yang dihasilkan dapat disajikan dalam layanan Python apa pun yang telah menginstal TensorFlow dan ScanN.

Hal ini juga dapat dilayani menggunakan versi disesuaikan TensorFlow Melayani, tersedia sebagai wadah Docker pada Docker Hub . Anda juga dapat membangun diri gambar dari Dockerfile .

Menyetel PemindaianNN

Sekarang mari kita lihat penyetelan lapisan ScanN kita untuk mendapatkan kinerja/akurasi yang lebih baik. Untuk melakukan ini secara efektif, pertama-tama kita perlu mengukur kinerja dan akurasi dasar kita.

Dari atas, kami telah memiliki pengukuran latensi model kami untuk memproses kueri tunggal (non-batch) (walaupun perhatikan bahwa cukup banyak latensi ini berasal dari komponen model non-ScaNN).

Sekarang kita perlu menyelidiki akurasi ScanNN, yang kita ukur melalui recall. Recall@k dari x% berarti bahwa jika kita menggunakan brute force untuk mengambil k tetangga teratas yang sebenarnya, dan membandingkan hasil tersebut dengan menggunakan ScaNN untuk juga mengambil k tetangga teratas, x% dari hasil ScanN adalah hasil brute force yang sebenarnya. Mari kita hitung penarikan untuk pencari ScanN saat ini.

Pertama, kita perlu menghasilkan brute force, ground truth 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)

Variabel kita titles_ground_truth sekarang berisi top-10 rekomendasi film dikembalikan oleh pengambilan brute-force. Sekarang kita dapat menghitung rekomendasi yang sama saat menggunakan ScanNN:

# 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)

Selanjutnya, kita mendefinisikan fungsi kita yang menghitung recall. Untuk setiap kueri, ia menghitung berapa banyak hasil yang berada di persimpangan antara hasil brute force dan ScanNN dan membaginya dengan jumlah hasil brute force. Rata-rata jumlah ini untuk semua kueri adalah ingatan kami.

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)
  ])

Ini memberi kita baseline recall@10 dengan konfigurasi ScanN saat ini:

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

Kami juga dapat mengukur latensi dasar:

%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)

Mari kita lihat apakah kita bisa berbuat lebih baik!

Untuk melakukan ini, kita memerlukan model bagaimana tombol tuning ScanN mempengaruhi kinerja. Model kami saat ini menggunakan algoritma tree-AH ScanN. Algoritme ini mempartisi basis data embeddings ("pohon") dan kemudian menilai yang paling menjanjikan dari partisi ini menggunakan AH, yang merupakan perkiraan rutin perhitungan jarak yang sangat optimal.

Parameter default untuk TensorFlow Pemberi saran ScaNN Keras lapisan set num_leaves=100 dan num_leaves_to_search=10 . Ini berarti database kami dipartisi menjadi 100 subset yang terpisah, dan 10 partisi yang paling menjanjikan diberi skor dengan AH. Ini berarti 10/100=10% dari dataset sedang dicari dengan AH.

Jika kita memiliki, katakanlah, num_leaves=1000 dan num_leaves_to_search=100 , kita akan juga menjadi mencari 10% dari database dengan AH. Namun, dibandingkan dengan pengaturan sebelumnya, 10% kita akan mencari akan berisi kandidat berkualitas tinggi, karena lebih tinggi num_leaves memungkinkan kita untuk membuat keputusan yang lebih halus-grained tentang apa bagian dari dataset yang layak pencarian.

Tidaklah mengherankan kemudian bahwa dengan num_leaves=1000 dan num_leaves_to_search=100 kita mendapatkan recall secara signifikan lebih tinggi:

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

Namun, sebagai tradeoff, latensi kami juga meningkat. Ini karena langkah partisi menjadi lebih mahal; scann mengambil bagian atas 10 dari 100 partisi sementara scann2 mengambil atas 100 1000 partisi. Yang terakhir bisa lebih mahal karena melibatkan melihat partisi 10 kali lebih banyak.

%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)

Secara umum, menyetel pencarian ScanNN adalah tentang memilih pengorbanan yang tepat. Setiap perubahan parameter individu umumnya tidak akan membuat pencarian lebih cepat dan lebih akurat; tujuan kami adalah menyetel parameter untuk secara optimal menukar antara dua tujuan yang saling bertentangan ini.

Dalam kasus kami, scann2 meningkat secara signifikan mengingat lebih scann di beberapa biaya dalam latency. Bisakah kita memutar kembali beberapa kenop lain untuk mengurangi latensi, sambil mempertahankan sebagian besar keuntungan penarikan kita?

Mari kita coba mencari 70/1000=7% dari dataset dengan AH, dan hanya menskor ulang 400 kandidat terakhir:

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 memberikan sekitar keuntungan recall mutlak 3% lebih scann sementara juga memberikan latency rendah:

%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)

Kenop ini dapat disesuaikan lebih lanjut untuk mengoptimalkan titik yang berbeda di sepanjang batas pareto kinerja akurasi. Algoritme ScanN dapat mencapai kinerja mutakhir pada berbagai target penarikan.

Bacaan lebih lanjut

ScanNN menggunakan teknik kuantisasi vektor canggih dan implementasi yang sangat optimal untuk mencapai hasilnya. Bidang kuantisasi vektor memiliki sejarah yang kaya dengan berbagai pendekatan. Teknik kuantisasi ScaNN saat rinci dalam makalah ini , diterbitkan di ICML 2020. Makalah ini juga dirilis bersama dengan artikel blog ini yang memberikan gambaran tingkat tinggi dari teknik kami.

Banyak teknik kuantisasi terkait yang disebutkan dalam referensi kertas ICML 2020 kami, dan penelitian terkait ScaNN lainnya terdaftar di http://sanjivk.com/