לשמור את התאריך! קלט / פלט של Google חוזר 18-20 במאי הירשם עכשיו
דף זה תורגם על ידי Cloud Translation API.
Switch to English

הגשה יעילה

צפה ב- TensorFlow.org הפעל בגוגל קולאב צפה במקור ב- GitHub הורד מחברת

מודלים לאחזור נבנים לרוב על מנת להעלות קומץ מועמדים מובילים מתוך מיליונים ואף מאות מיליוני מועמדים. כדי להיות מסוגל להגיב להקשר ולהתנהגות של המשתמש, הם צריכים להיות מסוגלים לעשות זאת בזמן, תוך מספר שניות.

חיפוש השכן הקרוב ביותר (ANN) הוא הטכנולוגיה המאפשרת זאת. במדריך זה נראה כיצד להשתמש ב- ScaNN - חבילת אחזור השכנים הקרובה ביותר - בכדי להגדיל בצורה חלקה את אחזור ה- TFRS למיליוני פריטים.

מה זה ScaNN?

ScaNN היא ספרייה של Google Research שמבצעת חיפוש דמיון וקטורי צפוף בקנה מידה גדול. בהינתן מסד נתונים של טבלאות מועמדים, ScaNN מוסיף לאינדקס את ההטבעות באופן שמאפשר חיפוש מהיר בזמן ההסקה. ScaNN משתמש בטכניקות דחיסה וקטוריות מתקדמות ביותר ובאלגוריתמים המיושמים בקפידה כדי להשיג את הפיזור הטוב ביותר של דיוק מהירות. זה יכול לעלות בצורה משמעותית על החיפוש בכוח הגס תוך להקריב מעט מבחינת הדיוק.

בניית מודל המופעל על ידי ScaNN

כדי לנסות את ScaNN ב- TFRS, נבנה מודל אחזור פשוט של MovieLens, בדיוק כפי שעשינו במדריך האחזור הבסיסי . אם עקבת אחר אותה הדרכה, חלק זה יהיה מוכר וניתן לדלג עליו בבטחה.

כדי להתחיל, התקן את ערכות הנתונים TFRS ו- TensorFlow:

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

עלינו להתקין גם scann : זו תלות אופציונלית של TFRS, ולכן יש להתקין אותה בנפרד.

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

לפני שנוכל לבנות מודל, עלינו להגדיר את אוצר המילים של המשתמש והסרט:

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

אנו נקבע גם את ערכות ההדרכה והמבחנים:

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.experimental.preprocessing.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.experimental.preprocessing.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 [==============================] - 2s 157ms/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.9680 - regularization_loss: 0.0000e+00 - total_loss: 69808.9680
Epoch 2/3
10/10 [==============================] - 2s 156ms/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.8828 - regularization_loss: 0.0000e+00 - total_loss: 67485.8828
Epoch 3/3
10/10 [==============================] - 2s 157ms/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
<tensorflow.python.keras.callbacks.History at 0x7f5943ff9cc0>

ולהעריך את זה.

model.evaluate(test.batch(8192), return_dict=True)
3/3 [==============================] - 2s 197ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0012 - factorized_top_k/top_5_categorical_accuracy: 0.0094 - factorized_top_k/top_10_categorical_accuracy: 0.0223 - factorized_top_k/top_50_categorical_accuracy: 0.1266 - factorized_top_k/top_100_categorical_accuracy: 0.2363 - loss: 49466.8779 - regularization_loss: 0.0000e+00 - total_loss: 49466.8779
{'factorized_top_k/top_1_categorical_accuracy': 0.0012000000569969416,
 'factorized_top_k/top_5_categorical_accuracy': 0.009399999864399433,
 'factorized_top_k/top_10_categorical_accuracy': 0.022299999371170998,
 'factorized_top_k/top_50_categorical_accuracy': 0.12655000388622284,
 'factorized_top_k/top_100_categorical_accuracy': 0.23634999990463257,
 'loss': 28242.833984375,
 'regularization_loss': 0,
 'total_loss': 28242.833984375}

חיזוי משוער

הדרך הפשוטה ביותר לאחזר מועמדים מובילים בתגובה לשאילתה היא לעשות זאת באמצעות כוח אכזרי: חישוב ציוני סרטים של משתמשים לכל הסרטים האפשריים, מיון, ובחר כמה המלצות מובילות.

ב- TFRS זה נעשה באמצעות שכבת BruteForce :

brute_force = tfrs.layers.factorized_top_k.BruteForce(model.user_model)
brute_force.index(movies.batch(128).map(model.movie_model), movies)
<tensorflow_recommenders.layers.factorized_top_k.BruteForce at 0x7f5943ff9ba8>

לאחר שנוצר ואוכלס במועמדים (באמצעות שיטת 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)
849 µs ± 6.8 µ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(lots_of_movies_embeddings, lots_of_movies)
<tensorflow_recommenders.layers.factorized_top_k.BruteForce at 0x7f59447700f0>

ההמלצות עדיין זהות

_, 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)
25.5 ms ± 212 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

ככל שמספר המועמדים גדל, משך הזמן הדרוש גדל באופן ליניארי: עם 10 מיליון מועמדים המשרתים המועמדים המובילים ייקחו 250 אלפיות השנייה. ברור שזה איטי מדי לשירות חי.

כאן נכנסים מנגנונים מקורבים.

השימוש ב- ScaNN ב- TFRS מתבצע באמצעות שכבת tfrs.layers.factorized_top_k.ScaNN . הוא פועל באותו ממשק כמו שאר שכבות k העליונות:

scann = tfrs.layers.factorized_top_k.ScaNN(num_reordering_candidates=1000)
scann.index(lots_of_movies_embeddings, lots_of_movies)
<tensorflow_recommenders.layers.factorized_top_k.ScaNN at 0x7f57d9511780>

ההמלצות זהות (בערך!)

_, 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)
3.31 ms ± 32.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

במקרה זה, אנו יכולים לאחזר את שלושת הסרטים המובילים מתוך סט של ~ מיליון בערך 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 21min 21s, sys: 1min 44s, total: 23min 6s
Wall time: 48.3 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 13.3 s, sys: 2.83 s, total: 16.2 s
Wall time: 1.6 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.16

זה מרמז כי על בסיס נתונים מלאכותי זה, יש מעט אובדן מהקירוב. באופן כללי, כל השיטות המשוערות מציגות פשרות עם דיוק מהירות. כדי להבין זאת לעומק יותר תוכלו לבדוק את מדדי ה- ANN של אריק ברנהרדסון .

פריסת המודל המשוער

הדגם מבוסס ScaNN משולב במלואו בדגמי TensorFlow, וההגשה שלו קלה כמו הגשת כל דגם TensorFlow אחר.

אנו יכולים לשמור אותו כאובייקט SavedModel

# 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(lots_of_movies_embeddings, lots_of_movies)

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

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

  loaded = tf.keras.models.load_model(path)
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.
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/tmp91frzv_0/model/assets
INFO:tensorflow:Assets written to: /tmp/tmp91frzv_0/model/assets
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.

ואז טען אותו והגיש, וקבל בדיוק את אותן התוצאות בחזרה:

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

ניתן להגיש את המודל המתקבל בכל שירות פיתון שבו מותקנת TensorFlow ו- ScaNN.

ניתן להגיש אותו גם באמצעות גרסה מותאמת אישית של TensorFlow Serving, הזמינה כמיכל Docker ב- Docker Hub . אתה יכול גם לבנות את התמונה בעצמך מה- Dockerfile .

כוונון ScaNN

עכשיו בואו נבדוק את כוונון שכבת ה- ScaNN שלנו בכדי להגיע לפיזור ביצועים / דיוק טובים יותר. על מנת לעשות זאת ביעילות, ראשית עלינו למדוד את ביצועי הבסיס ואת הדיוק שלנו.

מלמעלה, יש לנו כבר מדידה של זמן ההשהיה של המודל שלנו לעיבוד שאילתה אחת (לא קבוצה) (אם כי שים לב שכמות נכונה של חביון זה היא ממרכיבים שאינם ScaNN של המודל).

כעת עלינו לחקור את הדיוק של ScaNN, אותו אנו מודדים באמצעות זיכרון. משמעות זיכרון @ k של x% היא שאם אנו משתמשים בכוח הברוט כדי לאחזר את השכנים האמיתיים העליונים, ונשווה את התוצאות הללו לשימוש ב- 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 המשתנה 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)
3.35 ms ± 14.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

בואו נראה אם ​​נוכל לעשות טוב יותר!

לשם כך אנו זקוקים למודל של השפעה על כפתורי הכוונון של ScaNN על הביצועים. המודל הנוכחי שלנו משתמש באלגוריתם העץ-AH של ScaNN. אלגוריתם זה מחלק את בסיס הנתונים של ההטבעות ("העץ") ואז מבקיע את המחיצות המבטיחות ביותר מבין המחיצות באמצעות AH, המהווה שגרת חישוב מרחקים משוערת במיוחד.

פרמטרי ברירת המחדל לשכבת ScaNN Keras של TensorFlow ממליצים מגדירה num_leaves=100 ו- num_leaves_to_search=10 . פירוש הדבר שמסד הנתונים שלנו מחולק ל -100 קבוצות משנה נפרדות, ו -10 הבטיחות מבין המחיצות הללו נקלעות באמצעות AH. פירוש הדבר ש -10 / 100 = 10% ממערך הנתונים מחפשים באמצעות AH.

אם יש לנו, נניח, num_leaves=1000 ו- num_leaves_to_search=100 , היינו גם מחפשים 10% ממסד הנתונים עם AH. עם זאת, בהשוואה להגדרה הקודמת, 10% num_leaves יכילו מועמדים איכותיים יותר, מכיוון num_leaves גבוה יותר מאפשר לנו לקבל החלטות 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(lots_of_movies_embeddings, lots_of_movies)

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

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

עם זאת, בתור פשרה, החביון שלנו גדל גם הוא. הסיבה לכך היא ששלב החלוקה התייקר; scann בוחר את 10 מתוך 100 המחיצות scann2 ואילו scann2 בוחר את 100 הראשונים מתוך 1000 המחיצות. האחרון יכול להיות יקר יותר מכיוון שהוא כולל הסתכלות על מחיצות פי 10.

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

באופן כללי, כוונון חיפוש ScaNN נועד לבחור את הפשרות המתאימות. בדרך כלל כל שינוי פרמטר בודד לא יהפוך את החיפוש למהיר ומדויק יותר; המטרה שלנו היא לכוון את הפרמטרים להתאמה אופטימלית בין שתי המטרות הסותרות הללו.

במקרה שלנו, scann2 שיפר משמעותית את הזיכרון על פני scann במחיר כלשהו 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(lots_of_movies_embeddings, lots_of_movies)

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

scann3 מספק כ- 3% רווח scann מוחלט על פני scann תוך שהוא מספק חביון נמוך יותר:

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

ניתן לכוונן את הכפתורים הללו כדי לבצע אופטימיזציה לנקודות שונות לאורך גבול הפרטו. האלגוריתמים של ScaNN יכולים להשיג ביצועים חדישים במגוון רחב של יעדי זיכרון.

לקריאה נוספת

ScaNN משתמש בטכניקות כימות וקטוריות מתקדמות וביישום אופטימלי ביותר להשגת תוצאותיה. לתחום כימות הווקטור יש היסטוריה עשירה עם מגוון גישות. טכניקת הכימות הנוכחית של ScaNN מפורטת במאמר זה , שפורסם ב- ICML 2020. המאמר פורסם גם יחד עם מאמר בבלוג זה הנותן סקירה ברמה גבוהה של הטכניקה שלנו.

טכניקות כימות קשורות רבות מוזכרות בהפניות של מאמר ICML 2020 שלנו, ומחקרים אחרים הקשורים ל- ScaNN מופיעים בכתובת http://sanjivk.com/