효율적인 서빙

TensorFlow.org에서 보기 Google Colab에서 실행 GitHub에서 소스 보기 노트북 다운로드

검색 모델은 종종 수백만 또는 후보자의 수백만의 수백에서 최고 후보의 소수를 표면에 내장되어 있습니다. 사용자의 상황과 행동에 반응할 수 있으려면 몇 밀리초 만에 즉석에서 이를 수행할 수 있어야 합니다.

이를 가능하게 하는 기술이 ANN(Approximate Nearest Neighbor Search)입니다. 이 튜토리얼에서는 최신 인접 이웃 검색 패키지인 ScaNN을 사용하여 TFRS 검색을 수백만 개의 항목으로 원활하게 확장하는 방법을 보여줍니다.

ScanNN이란 무엇입니까?

ScanNN은 고밀도 벡터 유사성 검색을 대규모로 수행하는 Google Research의 라이브러리입니다. 후보 임베딩 데이터베이스가 주어지면 ScanN은 추론 시간에 빠르게 검색할 수 있는 방식으로 이러한 임베딩을 인덱싱합니다. ScanNN은 최고의 속도-정확도 트레이드오프를 달성하기 위해 최첨단 벡터 압축 기술과 신중하게 구현된 알고리즘을 사용합니다. 정확도 측면에서 약간 희생하면서 무차별 대입 검색을 크게 능가할 수 있습니다.

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)

모델 정의

그냥 같이 기본 검색 자습서, 우리는 단순한 2 타워 모델을 구축 할 수 있습니다.

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}

대략적인 예측

쿼리에 대한 응답으로 최고 후보자를 검색하는 가장 간단한 방법은 무차별 대입을 통해 검색하는 것입니다. 가능한 모든 영화에 대한 사용자 영화 점수를 계산하고 정렬하고 몇 가지 최고 추천 항목을 선택합니다.

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

그러나 그들은 훨씬 더 오래 걸립니다. 100만 개의 영화 후보 세트를 사용하면 무차별 대입 예측이 상당히 느려집니다.

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

후보자 수가 늘어남에 따라 필요한 시간은 선형적으로 증가합니다. 천만 명의 후보자가 있을 때 상위 후보자에게 서비스를 제공하는 데 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 검색 메커니즘(예: ScanNN)을 사용할 때 검색 속도는 종종 정확성을 희생시킵니다. 이 절충안을 이해하려면 ScanNN을 사용할 때 모델의 평가 메트릭을 측정하고 기준선과 비교하는 것이 중요합니다.

다행히도 TFRS는 이를 쉽게 만듭니다. 검색 작업의 메트릭을 ScanNN을 사용하는 메트릭으로 재정의하고 모델을 다시 컴파일하고 평가를 실행하기만 하면 됩니다.

비교를 위해 먼저 기준 결과를 실행해 보겠습니다. 원본 영화 세트가 아닌 확대된 후보 세트를 사용하고 있는지 확인하기 위해 여전히 메트릭을 재정의해야 합니다.

# 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 기반 평가는 훨씬 더 빠릅니다. 10배 이상 빠릅니다! 이 이점은 더 큰 데이터 세트의 경우 훨씬 더 커질 것이므로 큰 데이터 세트의 경우 항상 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)']

결과 모델은 TensorFlow 및 ScanNN이 설치된 모든 Python 서비스에서 제공될 수 있습니다.

또한에 도커 컨테이너로 사용할 수 TensorFlow 서빙의 사용자 정의 버전을 사용하여 제공 할 수 있습니다 도커 허브 . 당신은 또한에서 이미지를 직접 구축 할 수 있습니다 Dockerfile을 .

ScanN 튜닝

이제 더 나은 성능/정확도를 얻기 위해 ScanN 계층을 조정하는 방법을 살펴보겠습니다. 이를 효과적으로 수행하려면 먼저 기본 성능과 정확도를 측정해야 합니다.

위에서 이미 단일(비일괄 처리) 쿼리를 처리하기 위한 모델의 대기 시간을 측정했습니다(이 대기 시간의 상당 부분은 모델의 비 ScanNN 구성 요소에서 발생한다는 점에 유의).

이제 리콜을 통해 측정한 ScanNN의 정확도를 조사해야 합니다. x%의 리콜@k는 무차별 대입을 사용하여 진정한 상위 k 이웃을 검색하고 이 결과를 ScanNN을 사용하여 상위 k 이웃도 검색하는 것과 비교하는 경우 ScanNN 결과의 x%가 진정한 무차별 대입 결과에 있음을 의미합니다. 현재 ScanN 검색기의 리콜을 계산해 보겠습니다.

먼저 무차별 대입, 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)

우리의 변수 titles_ground_truth 이제 무차별 검색에 의해 반환 된 톱 10 영화 권장 사항을 포함하고 있습니다. 이제 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)

다음으로 리콜을 계산하는 함수를 정의합니다. 각 쿼리에 대해 무차별 대입 결과와 ScanNN 결과의 교차점에 있는 결과 수를 계산하고 이를 무차별 대입 결과 수로 나눕니다. 모든 쿼리에 대한 이 수량의 평균이 리콜입니다.

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 구성을 사용하여 기준선 리콜@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의 tree-AH 알고리즘을 사용합니다. 이 알고리즘은 임베딩 데이터베이스("트리")를 분할한 다음 고도로 최적화된 근사 거리 계산 루틴인 AH를 사용하여 이러한 분할 중 가장 유망한 점수를 매깁니다.

TensorFlow 추천인 'ScaNN Keras 층 세트의 기본 파라미터 num_leaves=100num_leaves_to_search=10 . 즉, 데이터베이스가 100개의 분리된 하위 집합으로 분할되고 이러한 분할 영역 중 가장 유망한 10개는 AH로 점수가 매겨집니다. 이는 데이터 세트의 10/100=10%가 AH로 검색되고 있음을 의미합니다.

우리가있는 경우, 말, num_leaves=1000num_leaves_to_search=100 , 우리는 또한 AH와 데이터베이스의 10 %를 검색하는 것입니다. 높은 있기 때문에, 이전 설정에 비해, 우리는 검색 것 10 %, 높은 품질의 후보자를 포함 num_leaves 데이터 세트의 일부 가치가 검색 무엇인지에 대해 세분화 된 결정을 내릴 수있게 해준다.

그런 다음 놀랄 일을 없다 그와 함께 num_leaves=1000num_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 가기 1,000 파티션 (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의 현재 양자화 기법에 자세히 설명되어 본 논문 용지도 함께 출시 된 ICML 2020 년에 출판 된 이 블로그 글 우리 기술의 높은 수준의 개요를 제공합니다.

많은 관련 양자화 기술이 우리의 ICML 2020 논문의 참고 문헌에 언급, 기타 ScaNN 관련 연구가 나열됩니다 http://sanjivk.com/