MLコミュニティデーは11月9日です! TensorFlow、JAXからの更新のために私たちに参加し、より多くの詳細をご覧ください

副次的機能の使用:機能の前処理

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示 ノートブックをダウンロード

ディープラーニングフレームワークを使用してレコメンダーモデルを構築することの大きな利点の1つは、豊富で柔軟な特徴表現を自由に構築できることです。

生の機能は通常、モデルですぐに使用できるわけではないため、これを行うための最初のステップは機能を準備することです。

例えば:

  • ユーザーIDとアイテムIDは、文字列(タイトル、ユーザー名)または大きくて連続していない整数(データベースID)の場合があります。
  • アイテムの説明は生のテキストである可能性があります。
  • 相互作用のタイムスタンプは、生のUnixタイムスタンプである可能性があります。

モデルの構築に役立つようにするには、これらを適切に変換する必要があります。

  • ユーザーIDとアイテムIDは、埋め込みベクトルに変換する必要があります。これは、モデルが目的をより適切に予測できるように、トレーニング中に調整される高次元の数値表現です。
  • 生のテキストはトークン化され(個々の単語などの小さな部分に分割され)、埋め込みに翻訳される必要があります。
  • 数値の特徴は、それらの値が0付近の小さな間隔に収まるように正規化する必要があります。

幸い、TensorFlowを使用することで、個別の前処理ステップではなく、このような前処理をモデルの一部にすることができます。これは便利なだけでなく、トレーニング中とサービング中の前処理がまったく同じであることを保証します。これにより、非常に高度な前処理を含むモデルを安全かつ簡単にデプロイできます。

このチュートリアルでは、推薦人に注力しようとしていると前処理、我々は上で行う必要がありMovieLensデータセット。あなたが推薦システムの焦点なく、より大きなチュートリアルに興味があるなら、フルで見ていKeras前処理ガイド

MovieLensデータセット

まず、MovieLensデータセットから使用できる機能を見てみましょう。

pip install -q --upgrade tensorflow-datasets
import pprint

import tensorflow_datasets as tfds

ratings = tfds.load("movielens/100k-ratings", split="train")

for x in ratings.take(1).as_numpy_iterator():
  pprint.pprint(x)
2021-10-02 11:59:46.956587: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected
{'bucketized_user_age': 45.0,
 'movie_genres': array([7]),
 'movie_id': b'357',
 'movie_title': b"One Flew Over the Cuckoo's Nest (1975)",
 'raw_user_age': 46.0,
 'timestamp': 879024327,
 'user_gender': True,
 'user_id': b'138',
 'user_occupation_label': 4,
 'user_occupation_text': b'doctor',
 'user_rating': 4.0,
 'user_zip_code': b'53211'}
2021-10-02 11:59:47.327679: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] 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.

ここにはいくつかの重要な機能があります。

  • 映画のタイトルは、映画の識別子として役立ちます。
  • ユーザーIDはユーザー識別子として役立ちます。
  • タイムスタンプを使用すると、時間の影響をモデル化できます。

最初の2つはカテゴリ機能です。タイムスタンプは継続的な機能です。

カテゴリ機能を埋め込みに変換する

カテゴリの特徴は連続量を発現しない特徴である、むしろ固定値のセットのうちの1つをとります。

ほとんどの深層学習モデルは、これらの特徴を高次元のベクトルに変換することで表現します。モデルのトレーニング中に、そのベクトルの値は、モデルがその目的をより適切に予測できるように調整されます。

たとえば、どのユーザーがどの映画を視聴するかを予測することが目標であるとします。そのために、各ユーザーと各映画を埋め込みベクトルで表します。最初は、これらの埋め込みはランダムな値を取りますが、トレーニング中に、ユーザーの埋め込みとユーザーが視聴する映画がより近くなるように調整します。

生のカテゴリ機能を取得して埋め込みに変換することは、通常、2段階のプロセスです。

  1. まず、生の値(「スターウォーズ」)を整数(たとえば15)にマッピングするマッピング(「語彙」と呼ばれる)を構築することにより、生の値を連続する整数の範囲に変換する必要があります。
  2. 次に、これらの整数を取得して埋め込みに変換する必要があります。

語彙の定義

最初のステップは、語彙を定義することです。これは、Keras前処理レイヤーを使用して簡単に行うことができます。

import numpy as np
import tensorflow as tf

movie_title_lookup = tf.keras.layers.StringLookup()

レイヤー自体にはまだ語彙がありませんが、データを使用して構築できます。

movie_title_lookup.adapt(ratings.map(lambda x: x["movie_title"]))

print(f"Vocabulary: {movie_title_lookup.get_vocabulary()[:3]}")
Vocabulary: ['[UNK]', 'Star Wars (1977)', 'Contact (1997)']

これができたら、レイヤーを使用して生のトークンを埋め込みIDに変換できます。

movie_title_lookup(["Star Wars (1977)", "One Flew Over the Cuckoo's Nest (1975)"])
<tf.Tensor: shape=(2,), dtype=int64, numpy=array([ 1, 58])>

レイヤーのボキャブラリーには、1つ(または複数!)の不明な(または「ボキャブラリー外」、OOV)トークンが含まれていることに注意してください。これは本当に便利です。つまり、語彙に含まれていないカテゴリ値をレイヤーが処理できるということです。実際には、これは、モデルが語彙の構築中に見られなかった機能を使用しても、学習を続け、推奨を行うことができることを意味します。

機能ハッシュの使用

実際には、 StringLookup層は、私たちは、複数のOOV指標を設定することができます。これを行うと、ボキャブラリーにない生の値は、決定論的にOOVインデックスの1つにハッシュされます。このようなインデックスが多ければ多いほど、2つの異なる生の特徴値が同じOOVインデックスにハッシュされる可能性は低くなります。したがって、そのようなインデックスが十分にある場合、モデルは、トークンリストを維持する必要がないという欠点なしに、明示的な語彙を持つモデルと同様にトレーニングできるはずです。

これを論理的に極端なものにし、語彙をまったく使用せずに、機能ハッシュに完全に依存することができます。これが実装されtf.keras.layers.Hashing層。

# We set up a large number of bins to reduce the chance of hash collisions.
num_hashing_bins = 200_000

movie_title_hashing = tf.keras.layers.Hashing(
    num_bins=num_hashing_bins
)

語彙を作成しなくても、以前と同じようにルックアップを実行できます。

movie_title_hashing(["Star Wars (1977)", "One Flew Over the Cuckoo's Nest (1975)"])
<tf.Tensor: shape=(2,), dtype=int64, numpy=array([101016,  96565])>

埋め込みの定義

今、私たちは、整数のIDを持っていることを、我々は使用することができますEmbedding埋め込みにそれらを回すためにレイヤーを。

埋め込みレイヤーには2つのディメンションがあります。最初のディメンションは、埋め込むことができる個別のカテゴリの数を示します。 2つ目は、それぞれを表すベクトルがどれだけ大きくなるかを示しています。

映画のタイトルの埋め込みレイヤーを作成するときは、最初の値をタイトルの語彙のサイズ(またはハッシュビンの数)に設定します。 2つ目は私たち次第です。サイズが大きいほど、モデルの容量は大きくなりますが、フィットとサービスの速度は遅くなります。

movie_title_embedding = tf.keras.layers.Embedding(
    # Let's use the explicit vocabulary lookup.
    input_dim=movie_title_lookup.vocab_size(),
    output_dim=32
)
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.

2つを1つのレイヤーにまとめて、生のテキストを取り込み、埋め込みを生成することができます。

movie_title_model = tf.keras.Sequential([movie_title_lookup, movie_title_embedding])

そのように、映画のタイトルの埋め込みを直接取得できます。

movie_title_model(["Star Wars (1977)"])
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ['Star Wars (1977)']
Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ['Star Wars (1977)']
Consider rewriting this model with the Functional API.
<tf.Tensor: shape=(1, 32), dtype=float32, numpy=
array([[-0.00255408,  0.00941082,  0.02599109, -0.02758816, -0.03652344,
        -0.03852248, -0.03309812, -0.04343383,  0.03444691, -0.02454401,
         0.00619583, -0.01912323, -0.03988413,  0.03595274,  0.00727529,
         0.04844356,  0.04739804,  0.02836904,  0.01647964, -0.02924066,
        -0.00425701,  0.01747661,  0.0114414 ,  0.04916174,  0.02185034,
        -0.00399858,  0.03934855,  0.03666003,  0.01980535, -0.03694187,
        -0.02149243, -0.03765338]], dtype=float32)>

ユーザーの埋め込みでも同じことができます。

user_id_lookup = tf.keras.layers.StringLookup()
user_id_lookup.adapt(ratings.map(lambda x: x["user_id"]))

user_id_embedding = tf.keras.layers.Embedding(user_id_lookup.vocab_size(), 32)

user_id_model = tf.keras.Sequential([user_id_lookup, user_id_embedding])
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.

連続特徴の正規化

連続機能にも正規化が必要です。例えば、 timestamp機能が深いモデルで直接使用することがあまりにも大きいです。

for x in ratings.take(3).as_numpy_iterator():
  print(f"Timestamp: {x['timestamp']}.")
Timestamp: 879024327.
Timestamp: 875654590.
Timestamp: 882075110.

使用する前に処理する必要があります。これを行う方法はたくさんありますが、離散化と標準化は2つの一般的な方法です。

標準化

標準の再スケールは、特徴の平均値を減算し、その標準偏差で割ることによって、それらの範囲を正規化しています。これは一般的な前処理変換です。

これは、簡単に使用して達成することができるtf.keras.layers.Normalization層を:

timestamp_normalization = tf.keras.layers.Normalization(
    axis=None
)
timestamp_normalization.adapt(ratings.map(lambda x: x["timestamp"]).batch(1024))

for x in ratings.take(3).as_numpy_iterator():
  print(f"Normalized timestamp: {timestamp_normalization(x['timestamp'])}.")
Normalized timestamp: [-0.84293723].
Normalized timestamp: [-1.4735204].
Normalized timestamp: [-0.27203268].

離散化

もう1つの一般的な変換は、連続特徴をいくつかのカテゴリ特徴に変換することです。これは、機能の効果が非連続的であると疑う理由がある場合に意味があります。

これを行うには、最初に、離散化に使用するバケットの境界を確立する必要があります。最も簡単な方法は、特徴の最小値と最大値を特定し、結果の間隔を均等に分割することです。

max_timestamp = ratings.map(lambda x: x["timestamp"]).reduce(
    tf.cast(0, tf.int64), tf.maximum).numpy().max()
min_timestamp = ratings.map(lambda x: x["timestamp"]).reduce(
    np.int64(1e9), tf.minimum).numpy().min()

timestamp_buckets = np.linspace(
    min_timestamp, max_timestamp, num=1000)

print(f"Buckets: {timestamp_buckets[:3]}")
Buckets: [8.74724710e+08 8.74743291e+08 8.74761871e+08]

バケットの境界が与えられると、タイムスタンプを埋め込みに変換できます。

timestamp_embedding_model = tf.keras.Sequential([
  tf.keras.layers.Discretization(timestamp_buckets.tolist()),
  tf.keras.layers.Embedding(len(timestamp_buckets) + 1, 32)
])

for timestamp in ratings.take(1).map(lambda x: x["timestamp"]).batch(1).as_numpy_iterator():
  print(f"Timestamp embedding: {timestamp_embedding_model(timestamp)}.")
Timestamp embedding: [[-0.02532113 -0.00415025  0.00458465  0.02080876  0.03103903 -0.03746337
   0.04010465 -0.01709593 -0.00246077 -0.01220842  0.02456966 -0.04816503
   0.04552222  0.03535838  0.00769508  0.04328252  0.00869263  0.01110227
   0.02754457 -0.02659499 -0.01055292 -0.03035731  0.00463334 -0.02848787
  -0.03416766  0.02538678 -0.03446608 -0.0384447  -0.03032914 -0.02391632
   0.02637175 -0.01158618]].

テキスト機能の処理

モデルにテキスト機能を追加することもできます。通常、製品の説明などは自由形式のテキストであり、特にコールドスタートまたはロングテールのシナリオでは、モデルがそれらに含まれる情報を使用してより適切な推奨を行うことを学習できることを期待できます。

MovieLensデータセットは豊富なテキスト機能を提供しませんが、映画のタイトルを使用することはできます。これは、タイトルが非常に似ている映画が同じシリーズに属する可能性が高いという事実を把握するのに役立つ場合があります。

テキストに適用する必要のある最初の変換は、トークン化(構成単語または単語部分への分割)、語彙学習、埋め込みです。

Keras tf.keras.layers.TextVectorization層が私たちのために最初の2つのステップを行うことができます。

title_text = tf.keras.layers.TextVectorization()
title_text.adapt(ratings.map(lambda x: x["movie_title"]))

それを試してみましょう:

for row in ratings.batch(1).map(lambda x: x["movie_title"]).take(1):
  print(title_text(row))
tf.Tensor([[ 32 266 162   2 267 265  53]], shape=(1, 7), dtype=int64)

各タイトルは、トークン化した各ピースに1つずつ、一連​​のトークンに翻訳されます。

学習した語彙をチェックして、レイヤーが正しいトークン化を使用していることを確認できます。

title_text.get_vocabulary()[40:45]
['first', '1998', '1977', '1971', 'monty']

これは正しいように見えます。レイヤーはタイトルを個々の単語にトークン化しています。

処理を終了するには、テキストを埋め込む必要があります。各タイトルには複数の単語が含まれているため、各タイトルに複数の埋め込みがあります。ドンストリームモデルで使用する場合、これらは通常、単一の埋め込みに圧縮されます。ここではRNNやTransformersのようなモデルが役立ちますが、すべての単語の埋め込みを平均化することは良い出発点です。

すべてを一緒に入れて

これらのコンポーネントを配置すると、すべての前処理を一緒に行うモデルを構築できます。

ユーザーモデル

完全なユーザーモデルは次のようになります。

class UserModel(tf.keras.Model):

  def __init__(self):
    super().__init__()

    self.user_embedding = tf.keras.Sequential([
        user_id_lookup,
        tf.keras.layers.Embedding(user_id_lookup.vocab_size(), 32),
    ])
    self.timestamp_embedding = tf.keras.Sequential([
      tf.keras.layers.Discretization(timestamp_buckets.tolist()),
      tf.keras.layers.Embedding(len(timestamp_buckets) + 2, 32)
    ])
    self.normalized_timestamp = tf.keras.layers.Normalization(
        axis=None
    )

  def call(self, inputs):

    # Take the input dictionary, pass it through each input layer,
    # and concatenate the result.
    return tf.concat([
        self.user_embedding(inputs["user_id"]),
        self.timestamp_embedding(inputs["timestamp"]),
        tf.reshape(self.normalized_timestamp(inputs["timestamp"]), (-1, 1))
    ], axis=1)

それを試してみましょう:

user_model = UserModel()

user_model.normalized_timestamp.adapt(
    ratings.map(lambda x: x["timestamp"]).batch(128))

for row in ratings.batch(1).take(1):
  print(f"Computed representations: {user_model(row)[0, :3]}")
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
Computed representations: [-0.04705765 -0.04739009 -0.04212048]

映画モデル

映画モデルについても同じことができます。

class MovieModel(tf.keras.Model):

  def __init__(self):
    super().__init__()

    max_tokens = 10_000

    self.title_embedding = tf.keras.Sequential([
      movie_title_lookup,
      tf.keras.layers.Embedding(movie_title_lookup.vocab_size(), 32)
    ])
    self.title_text_embedding = tf.keras.Sequential([
      tf.keras.layers.TextVectorization(max_tokens=max_tokens),
      tf.keras.layers.Embedding(max_tokens, 32, mask_zero=True),
      # We average the embedding of individual words to get one embedding vector
      # per title.
      tf.keras.layers.GlobalAveragePooling1D(),
    ])

  def call(self, inputs):
    return tf.concat([
        self.title_embedding(inputs["movie_title"]),
        self.title_text_embedding(inputs["movie_title"]),
    ], axis=1)

それを試してみましょう:

movie_model = MovieModel()

movie_model.title_text_embedding.layers[0].adapt(
    ratings.map(lambda x: x["movie_title"]))

for row in ratings.batch(1).take(1):
  print(f"Computed representations: {movie_model(row)[0, :3]}")
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
Computed representations: [-0.01670959  0.02128791  0.04631067]

次のステップ

上記の2つのモデルを使用して、レコメンダーモデルで豊富な機能を表現するための最初のステップを実行しました。これをさらに進めて、これらを使用して効果的なディープリコメンダーモデルを構築する方法を調べるには、ディープレコメンダーチュートリアルをご覧ください。