خدمة فعالة

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب تحميل دفتر

نماذج استرجاع غالبا ما تكون مبنية على السطح حفنة من رأس المرشحين للخروج من الملايين أو مئات بل الملايين من المرشحين. لتكون قادرًا على الرد على سياق المستخدم وسلوكه ، يجب أن يكون قادرًا على القيام بذلك على الفور ، في غضون أجزاء من الثانية.

البحث التقريبي عن أقرب الجيران (ANN) هو التقنية التي تجعل ذلك ممكنًا. في هذا البرنامج التعليمي ، سنعرض كيفية استخدام ScaNN - حزمة استرداد أقرب جيران - لتوسيع نطاق استرداد TFRS بسهولة لملايين العناصر.

ما هو ScaNN؟

ScaNN هي مكتبة من Google Research تقوم بإجراء بحث كثيف عن التشابه المتجه على نطاق واسع. بالنظر إلى قاعدة بيانات لحفلات الزفاف المرشحة ، تقوم ScaNN بفهرسة هذه الزخارف بطريقة تسمح بالبحث عنها بسرعة في وقت الاستدلال. تستخدم ScaNN أحدث تقنيات ضغط المتجهات والخوارزميات المطبقة بعناية لتحقيق أفضل مقايضة السرعة والدقة. يمكن أن يتفوق بشكل كبير على البحث بالقوة الغاشمة مع التضحية بالقليل من حيث الدقة.

بناء نموذج يعمل بالطاقة ScaNN

لمحاولة الخروج ScaNN في معدلات الخصوبة الكلية، سنقوم ببناء بسيطة MovieLens نموذج استرجاع، تماما كما فعلنا في استرجاع الأساسي التعليمي. إذا كنت قد اتبعت هذا البرنامج التعليمي ، فسيكون هذا القسم مألوفًا ويمكن تخطيه بأمان.

للبدء ، قم بتثبيت مجموعات بيانات TFRS و TensorFlow:

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

نحن بحاجة أيضا إلى تثبيت scann : انها التبعية اختياري من معدلات الخصوبة الكلية، وهكذا الاحتياجات ليتم تثبيتها بشكل منفصل.

pip install -q scann

قم بإعداد كافة عمليات الاستيراد الضرورية.

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

وتحميل البيانات:

# 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

قبل أن نتمكن من بناء نموذج ، نحتاج إلى إعداد مفردات المستخدم والأفلام:

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.

سنقوم أيضًا بإعداد مجموعات التدريب والاختبار:

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)

تعريف النموذج

كما هو الحال في استرجاع الأساسي البرنامج التعليمي، وبناء نموذج برج اثنين بسيط.

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)

التركيب والتقييم

نموذج TFRS هو مجرد نموذج Keras. يمكننا تجميعها:

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

قدر ذلك:

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>

وتقييمه.

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}

التنبؤ التقريبي

الطريقة الأكثر مباشرة لاسترداد أفضل المرشحين استجابةً لاستعلام ما هي القيام بذلك عن طريق القوة الغاشمة: حساب نتائج أفلام المستخدم لجميع الأفلام الممكنة وفرزها واختيار اثنين من أفضل التوصيات.

في معدلات الخصوبة الكلية، ويتم ذلك عن طريق BruteForce طبقة:

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>

وبمجرد إنشاء ويسكنها مع المرشحين (عبر index طريقة)، يمكننا أن نسميها للحصول على توقعات من:

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

في مجموعة بيانات صغيرة تقل عن 1000 فيلم ، يكون هذا سريعًا جدًا:

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

ولكن ماذا يحدث إذا كان لدينا المزيد من المرشحين - الملايين بدلاً من الآلاف؟

يمكننا محاكاة ذلك عن طريق فهرسة جميع أفلامنا عدة مرات:

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

يمكننا أن نبني BruteForce مؤشر على هذه البينات أكبر:

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>

التوصيات لا تزال هي نفسها

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

لكنهم يأخذون وقتا أطول. مع مجموعة مرشحة من مليون فيلم ، يصبح التنبؤ بالقوة الغاشمة بطيئًا للغاية:

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

مع زيادة عدد المرشحين ، يزداد مقدار الوقت المطلوب خطيًا: مع وجود 10 ملايين مرشح ، سيستغرق تقديم أفضل المرشحين 250 مللي ثانية. من الواضح أن هذا بطيء جدًا بالنسبة للخدمة الحية.

هذا هو المكان الذي تأتي فيه الآليات التقريبية.

باستخدام ScaNN في معدلات الخصوبة الكلية ويتم إنجاز عبر tfrs.layers.factorized_top_k.ScaNN طبقة. إنها تتبع نفس الواجهة مثل طبقات k العليا الأخرى:

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>

التوصيات (تقريبًا!) هي نفسها

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

لكنها أسرع بكثير في الحساب:

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

في هذه الحالة ، يمكننا استرداد أفضل 3 أفلام من مجموعة تبلغ حوالي 1 مليون في حوالي 2 مللي ثانية: 15 مرة أسرع من حساب أفضل المرشحين عبر القوة الغاشمة. تزداد ميزة الطرق التقريبية بشكل أكبر لمجموعات البيانات الأكبر حجمًا.

تقدير التقريب

عند استخدام آليات استرجاع أعلى K تقريبية (مثل ScaNN) ، غالبًا ما تأتي سرعة الاسترجاع على حساب الدقة. لفهم هذه المقايضة ، من المهم قياس مقاييس تقييم النموذج عند استخدام ScaNN ، ومقارنتها بخط الأساس.

لحسن الحظ ، فإن نظام TFRS يجعل هذا الأمر سهلاً. نحن ببساطة نتجاوز المقاييس في مهمة الاسترجاع بالمقاييس باستخدام ScaNN ، ونعيد تجميع النموذج ، وندير التقييم.

لإجراء المقارنة ، دعنا أولاً نجري نتائج خط الأساس. ما زلنا بحاجة إلى تجاوز مقاييسنا للتأكد من أنها تستخدم المجموعة المرشحة الموسعة بدلاً من المجموعة الأصلية من الأفلام:

# 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

يمكننا فعل الشيء نفسه باستخدام 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

التقييم المعتمد على ScaNN أسرع بكثير: إنه أسرع بعشر مرات! ستزداد هذه الميزة بشكل أكبر بالنسبة لمجموعات البيانات الأكبر ، وبالتالي بالنسبة لمجموعات البيانات الكبيرة ، قد يكون من الحكمة إجراء تقييم يستند إلى ScaNN دائمًا لتحسين سرعة تطوير النموذج.

لكن ماذا عن النتائج؟ لحسن الحظ ، النتائج متشابهة تقريبًا في هذه الحالة:

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

يشير هذا إلى أنه في قاعدة البيانات الاصطناعية هذه ، هناك خسارة قليلة من التقريب. بشكل عام ، تعرض جميع الطرق التقريبية مقايضات دقة السرعة. لفهم هذا على نحو أكثر عمقا يمكنك التحقق من إريك Bernhardsson ل معايير ANN .

نشر النموذج التقريبي

و ScaNN تم دمج نموذج المستندة بالكامل إلى نماذج TensorFlow، ويقضي ذلك سهلا كما هو خدمة أي نموذج TensorFlow الآخرين.

يمكننا إنقاذ بأنها SavedModel الكائن

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

ثم قم بتحميله وتقديمه ، للحصول على نفس النتائج بالضبط:

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

يمكن تقديم النموذج الناتج في أي خدمة Python مثبت عليها TensorFlow و ScaNN.

ويمكن أيضا أن تتحقق باستخدام نسخة مخصصة من TensorFlow خدمة، متوفر على شكل حاوية عامل الميناء على عامل الميناء المحور . يمكنك أيضا بناء نفسك صورة من Dockerfile .

ضبط ScaNN

الآن دعونا ننظر في ضبط طبقة ScaNN الخاصة بنا للحصول على أداء أفضل / مقايضة دقة. من أجل القيام بذلك بشكل فعال ، نحتاج أولاً إلى قياس أداء خط الأساس لدينا ودقته.

من الأعلى ، لدينا بالفعل قياس زمن انتقال نموذجنا لمعالجة استعلام واحد (غير مجمّع) (على الرغم من ملاحظة أن قدرًا معقولاً من وقت الاستجابة هذا يأتي من مكونات غير تابعة للنموذج ScaNN).

نحتاج الآن إلى التحقق من دقة ScaNN ، والتي نقيسها من خلال الاسترجاع. يعني الاستدعاء @ k من x٪ أنه إذا استخدمنا القوة الغاشمة لاسترداد أعلى جيران k الحقيقيين ، وقارننا هذه النتائج باستخدام ScaNN لاسترداد أيضًا أعلى k جيران ، فإن x٪ من نتائج ScaNN تكون في نتائج القوة الغاشمة الحقيقية. دعنا نحسب استدعاء باحث ScaNN الحالي.

أولاً ، نحتاج إلى توليد القوة الغاشمة ، الحقيقة الأساسية أعلى 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)

لدينا متغير titles_ground_truth الآن يحتوي على توصيات الفيلم أعلى 10 إرجاعها بواسطة استرجاع القوة الغاشمة. الآن يمكننا حساب نفس التوصيات عند استخدام 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)

بعد ذلك ، نحدد وظيفتنا التي تحسب الاسترجاع. لكل استعلام ، فإنه يحسب عدد النتائج الموجودة في تقاطع القوة الغاشمة ونتائج ScaNN ويقسم ذلك على عدد نتائج القوة الغاشمة. متوسط ​​هذه الكمية في جميع الاستعلامات هو استرجاعنا.

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

هذا يعطينا استدعاء خط الأساس @ 10 مع تكوين ScaNN الحالي:

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

يمكننا أيضًا قياس وقت الاستجابة الأساسي:

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

دعونا نرى ما إذا كان بإمكاننا أن نفعل ما هو أفضل!

للقيام بذلك ، نحتاج إلى نموذج لكيفية تأثير مقابض ضبط ScaNN على الأداء. يستخدم نموذجنا الحالي خوارزمية شجرة ScaNN- AH. تقسم هذه الخوارزمية قاعدة بيانات الزخارف ("الشجرة") ثم تسجل أكثر الأقسام الواعدة باستخدام AH ، وهو روتين محسّن للغاية لحساب المسافة التقريبية.

المعلمات الافتراضية لTensorFlow Recommenders "ScaNN Keras مجموعات طبقة num_leaves=100 و num_leaves_to_search=10 . هذا يعني أن قاعدة البيانات الخاصة بنا مقسمة إلى 100 مجموعة فرعية منفصلة ، ويتم تسجيل أكثر 10 أقسام واعدة من هذه الأقسام باستخدام AH. هذا يعني أن 10/100 = 10٪ من مجموعة البيانات يتم البحث عنها باستخدام AH.

إذا كان لدينا مثلا، num_leaves=1000 و num_leaves_to_search=100 ، وسنكون أيضا البحث 10٪ من قاعدة البيانات مع ه. ومع ذلك، في المقارنة إلى الإعداد السابق، فإن 10٪ فإننا بحث يحتوي على المرشحين عالية الجودة، وذلك لأن أعلى num_leaves يسمح لنا لاتخاذ قرارات الحبيبات الدقيقة حول ما أجزاء من ورقة العمل هي تستحق البحث.

فإنه ليس من المستغرب بعد ذلك أنه مع num_leaves=1000 و num_leaves_to_search=100 نحصل على استدعاء أعلى بكثير:

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

ومع ذلك ، كمقايضة ، زاد زمن انتقالنا أيضًا. هذا لأن خطوة التقسيم أصبحت أكثر تكلفة ؛ scann يختار أفضل 10 من 100 أقسام حين scann2 يختار أعلى 100 من 1000 أقسام. يمكن أن يكون الأخير أكثر تكلفة لأنه يتضمن النظر إلى 10 أضعاف عدد الأقسام.

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

بشكل عام ، يتعلق ضبط بحث ScaNN باختيار المقايضات الصحيحة. عمومًا ، لن يؤدي كل تغيير في معلمة فردية إلى جعل البحث أسرع وأكثر دقة ؛ هدفنا هو ضبط المعلمات للمقايضة المثلى بين هذين الهدفين المتعارضين.

في حالتنا، scann2 تحسنت بشكل ملحوظ استدعاء أكثر من scann في بعض التكاليف في الكمون. هل يمكننا الاتصال ببعض المقابض الأخرى لتقليل زمن الوصول ، مع الحفاظ على معظم ميزة الاسترجاع لدينا؟

دعنا نحاول البحث عن 70/1000 = 7٪ من مجموعة البيانات باستخدام AH ، وفقط إنقاذ 400 مرشح نهائي:

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 يسلم عن مكاسب تذكر المطلق 3٪ خلال scann بينما كان يلقى أيضا الكمون أقل:

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

يمكن تعديل هذه المقابض بشكل أكبر لتحسين النقاط المختلفة على طول حدود أداء باريتو. يمكن لخوارزميات ScaNN تحقيق أداء متطور على نطاق واسع من أهداف الاسترجاع.

قراءة متعمقة

تستخدم ScaNN تقنيات تكميم ناقلات متقدمة وتنفيذ مُحسَّن للغاية لتحقيق نتائجها. مجال تكميم المتجهات له تاريخ غني مع مجموعة متنوعة من الأساليب. وترد تفاصيل تقنية تكميم الحالية ScaNN في هذه الورقة ، التي نشرت في ICML عام 2020. وأطلق سراح الورقة أيضا جنبا إلى جنب مع هذه المادة بلوق الذي يعطي نظرة عامة على مستوى عال من التقنية لدينا.

وذكر العديد من التقنيات تكميم ذات الصلة في المراجع من ورقتنا ICML عام 2020، ويتم سرد البحوث الأخرى ذات الصلة ScaNN في http://sanjivk.com/