দক্ষ পরিবেশন

TensorFlow.org এ দেখুন Google Colab-এ চালান GitHub-এ উৎস দেখুন নোটবুক ডাউনলোড করুন

আহরণ মডেল প্রায়ই লক্ষ লক্ষ বা প্রার্থী কোটি কোটি এমনকি শত শত আউট শীর্ষ প্রার্থীর একটি থাবা পৃষ্ঠ আগে থেকেই রয়েছে। ব্যবহারকারীর প্রসঙ্গ এবং আচরণে প্রতিক্রিয়া জানাতে সক্ষম হওয়ার জন্য, তাদের মিলিসেকেন্ডের মধ্যে উড়ে গিয়ে এটি করতে সক্ষম হতে হবে।

আনুমানিক নিকটবর্তী প্রতিবেশী অনুসন্ধান (ANN) হল সেই প্রযুক্তি যা এটি সম্ভব করে। এই টিউটোরিয়ালে, আমরা দেখাব কিভাবে ScaNN ব্যবহার করতে হয় - একটি অত্যাধুনিক নিকটতম প্রতিবেশী পুনরুদ্ধার প্যাকেজ - লক্ষ লক্ষ আইটেমে TFRS পুনরুদ্ধারকে নির্বিঘ্নে স্কেল করতে।

ScaNN কি?

ScaNN হল গুগল রিসার্চের একটি লাইব্রেরি যা বৃহৎ পরিসরে ঘন ভেক্টর সাদৃশ্য অনুসন্ধান করে। প্রার্থী এমবেডিংয়ের একটি ডাটাবেস দেওয়া, ScaNN এই এমবেডিংগুলিকে এমনভাবে সূচী করে যা অনুমান সময়ে দ্রুত অনুসন্ধান করার অনুমতি দেয়। ScaNN সর্বোত্তম গতি-নির্ভুলতা ট্রেডঅফ অর্জন করতে অত্যাধুনিক ভেক্টর কম্প্রেশন কৌশল এবং সাবধানে প্রয়োগ করা অ্যালগরিদম ব্যবহার করে। এটি নির্ভুলতার দিক থেকে সামান্য বলিদানের সময় ব্রুট ফোর্স অনুসন্ধানকে ব্যাপকভাবে ছাড়িয়ে যেতে পারে।

একটি ScaNN-চালিত মডেল তৈরি করা

TFRS মধ্যে ScaNN চেষ্টা করে দেখতে, আমরা একটি সহজ 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))
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)

ফিটিং এবং মূল্যায়ন

একটি টিএফআরএস মডেল কেবল একটি কেরাস মডেল। আমরা এটি কম্পাইল করতে পারি:

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}

আনুমানিক ভবিষ্যদ্বাণী

একটি প্রশ্নের উত্তরে শীর্ষ প্রার্থীদের পুনরুদ্ধার করার সবচেয়ে সহজ উপায় হল ব্রুট ফোর্স এর মাধ্যমে এটি করা: সমস্ত সম্ভাব্য সিনেমার জন্য ব্যবহারকারী-মুভির স্কোর গণনা করুন, সেগুলিকে সাজান এবং কয়েকটি শীর্ষ সুপারিশ বেছে নিন।

TFRS, এই মাধ্যমে সম্পন্ন হয় 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)']

কিন্তু তারা অনেক বেশি সময় নেয়। 1 মিলিয়ন সিনেমার প্রার্থী সেট সহ, ব্রুট ফোর্স ভবিষ্যদ্বাণী বেশ ধীর হয়ে যায়:

%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 মিলিসেকেন্ড সময় লাগবে। এটি একটি লাইভ পরিষেবার জন্য স্পষ্টতই খুব ধীর।

এখানেই আনুমানিক মেকানিজম আসে।

TFRS মধ্যে 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)

এই ক্ষেত্রে, আমরা প্রায় 2 মিলিসেকেন্ডে ~1 মিলিয়ন সেটের মধ্যে শীর্ষ 3টি মুভি পুনরুদ্ধার করতে পারি: ব্রুট ফোর্স এর মাধ্যমে সেরা প্রার্থীদের গণনা করার চেয়ে 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 benchmarks

আনুমানিক মডেল স্থাপন করা হচ্ছে

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

টেনসরফ্লো এবং স্ক্যানএন ইনস্টল করা আছে এমন যেকোন পাইথন পরিষেবাতে ফলাফলের মডেলটি পরিবেশন করা যেতে পারে।

এছাড়া একটি Docker ধারক হিসাবে উপলব্ধ TensorFlow ভজনা একটি কাস্টমাইজড সংস্করণ ব্যবহার কাজ করা যেতে পারে Docker হাব । এছাড়াও আপনি থেকে ইমেজ নিজেকে নির্মাণ করতে পারেন Dockerfile

টিউনিং স্ক্যান

এখন আরও ভালো পারফরম্যান্স/নির্ভুলতা ট্রেডঅফ পেতে আমাদের স্ক্যানএন লেয়ার টিউন করার দিকে নজর দেওয়া যাক। এটি কার্যকরভাবে করার জন্য, আমাদের প্রথমে আমাদের বেসলাইন কর্মক্ষমতা এবং নির্ভুলতা পরিমাপ করতে হবে।

উপরে থেকে, আমাদের কাছে ইতিমধ্যেই একটি একক (নন-ব্যাচড) ক্যোয়ারী প্রক্রিয়া করার জন্য আমাদের মডেলের বিলম্বের একটি পরিমাপ আছে (যদিও নোট করুন যে এই বিলম্বের একটি ন্যায্য পরিমাণ মডেলের নন-স্ক্যানএন উপাদান থেকে)।

এখন আমাদের ScaNN এর নির্ভুলতা তদন্ত করতে হবে, যা আমরা রিকলের মাধ্যমে পরিমাপ করি। x% এর একটি recall@k এর মানে হল যে যদি আমরা সত্যিকারের শীর্ষ k প্রতিবেশীদের পুনরুদ্ধার করতে ব্রুট ফোর্স ব্যবহার করি এবং উপরের k প্রতিবেশীদের পুনরুদ্ধার করতে ScaNN ব্যবহার করার সাথে সেই ফলাফলগুলির তুলনা করি, ScaNN-এর x% ফলাফল সত্যিকারের ব্রুট ফোর্স ফলাফলে। চলুন বর্তমান ScaNN অনুসন্ধানকারীর জন্য প্রত্যাহার গণনা করা যাক।

প্রথমত, আমাদের ব্রুট ফোর্স তৈরি করতে হবে, গ্রাউন্ড ট্রুথ টপ-কে:

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

এটি আমাদের বর্তমান ScaNN কনফিগারেশনের সাথে বেসলাইন recall@10 দেয়:

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 ব্যবহার করে এই পার্টিশনগুলির মধ্যে সবচেয়ে প্রতিশ্রুতিশীল স্কোর করে, যা একটি অত্যন্ত অপ্টিমাইজ করা আনুমানিক দূরত্ব গণনা রুটিন।

TensorFlow প্রস্তাবকারী '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 শীর্ষ 100 10 পার্টিশন পছন্দ যখন scann2 শীর্ষ 1000 পার্টিশন 100 পছন্দ। পরেরটি আরও ব্যয়বহুল হতে পারে কারণ এতে 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 লেটেন্সি মধ্যে কিছু খরচে। আমরা কি লেটেন্সি কমাতে অন্য কিছু নব ডায়াল করতে পারি, আমাদের বেশিরভাগ প্রত্যাহার সুবিধা সংরক্ষণ করে?

আসুন AH দিয়ে ডেটাসেটের 70/1000=7% অনুসন্ধান করার চেষ্টা করি, এবং শুধুমাত্র চূড়ান্ত 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 বর্তমান quantization কৌশল বিস্তারিত হয় এই কাগজ , ICML 2020. প্রকাশিত কাগজ এছাড়াও সহ মুক্তি এই ব্লগ নিবন্ধ যা আমাদের কৌশল একটি উচ্চ পর্যায়ের ওভারভিউ দেয়।

অনেক সংশ্লিষ্ট quantization কৌশল আমাদের ICML 2020 কাগজ রেফারেন্স উল্লেখ করা হয়, এবং অন্যান্য ScaNN সংক্রান্ত গবেষণা তালিকাভুক্ত করা হয় http://sanjivk.com/