前処理レイヤーを使用する

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

Keras 前処理レイヤー

Keras 前処理レイヤー API を使用すると、開発者は Keras ネイティブの入力処理パイプラインを構築できます。これらの入力処理パイプラインは、Keras 以外のワークフローで独立した前処理コードとして使用し、Keras モデルと直接組み合わせて、Keras SavedModel の一部としてエクスポートできます。

Keras 前処理レイヤーを使用すると、真にエンドツーエンドのモデル(生の画像または生の構造化データを入力として受け入れるモデルや特徴の正規化または特徴値のインデックス作成を独自に処理するモデル)を構築およびエクスポートできます。

利用可能な前処理レイヤー

主な前処理レイヤー

  • TextVectorizationレイヤー:生の文字列を、EmbeddedレイヤーまたはDenseレイヤーで読み取ることができるエンコードされた表現に変換します。
  • Normalizationレイヤー:入力する特徴の特徴ごとの正規化を実行します。

構造化されたデータ前処理レイヤー

これらのレイヤーは、構造化データのエンコードとフィーチャエンジニアリング用です。

  • CategoryEncodingレイヤー:整数のカテゴリカルフィーチャをワンホット、マルチホット、または TF-IDF の密な表現に変換します。
  • Hashingレイヤー:「ハッシュトリック」とも呼ばれるカテゴリカルフィーチャハッシュを実行します。
  • Discretizationレイヤー:連続する数値特徴を整数のカテゴリカルフィーチャに変換します。
  • StringLookupレイヤー:文字列のカテゴリ値を整数のインデックスに変換します。
  • IntegerLookupレイヤー:整数のカテゴリ値を整数のインデックスに変換します。
  • CategoryCrossingレイヤー:カテゴリカルフィーチャを共起フィーチャに結合します。 例えば、 特徴値「a」と「b」がある場合、「a と b が同時に存在する」という組み合わせの特徴を提供できます。

画像前処理レイヤー

これらのレイヤーは、画像モデルの入力を標準化するためのものです。

  • Resizingレイヤー:画像のバッチをターゲットサイズにサイズ変更します。
  • Rescalingレイヤー:画像のバッチの値を再スケーリングしてオフセットします(たとえば、[0, 255]範囲の入力から[0, 1]範囲の入力に移動します。
  • CenterCropレイヤー:画像のバッチの場合、中央のトリミングを返します。

画像データ増強レイヤー

これらのレイヤーは、ランダムな増強変換を画像のバッチに適用します。これらはトレーニング中にのみアクティブになります。

  • RandomCropレイヤー
  • RandomFlipレイヤー
  • RandomTranslationレイヤー
  • RandomRotationレイヤー
  • RandomZoomレイヤー
  • RandomHeightレイヤー
  • RandomWidthレイヤー

adapt()メソッド

一部の前処理レイヤーには、トレーニングデータのサンプルに基づいて計算する必要がある内部状態があります。ステートフル前処理レイヤーのリストは次のとおりです。

  • TextVectorization: 文字列トークンと整数インデックス間のマッピングを保持します
  • Normalization: 特徴の平均と標準偏差を保持します
  • StringLookupIntegerLookup: 入力値と出力インデックスの間のマッピングを保持します。
  • CategoryEncoding: 入力値のインデックスを保持します。
  • Discretization: 値バケットの境界に関する情報を保持します。

重要な点は、これらのレイヤーはトレーニング不可なことです。 それらの状態はトレーニング中に設定されません。トレーニングの前に、「適応」と呼ばれるステップで設定する必要があります。

adapt()メソッドを使用して、トレーニングデータに前処理レイヤーを公開することにより、前処理レイヤーの状態を設定します。

import numpy as np
import tensorflow as tf
from tensorflow.keras.layers.experimental import preprocessing

data = np.array([[0.1, 0.2, 0.3], [0.8, 0.9, 1.0], [1.5, 1.6, 1.7],])
layer = preprocessing.Normalization()
layer.adapt(data)
normalized_data = layer(data)

print("Features mean: %.2f" % (normalized_data.numpy().mean()))
print("Features std: %.2f" % (normalized_data.numpy().std()))
Features mean: 0.00
Features std: 1.00

adapt()メソッドは、Numpy 配列またはtf.data.Datasetオブジェクトのいずれかを取ります。StringLookupおよびTextVectorizationの場合、文字列のリストを渡すこともできます。

data = [
    "ξεῖν᾽, ἦ τοι μὲν ὄνειροι ἀμήχανοι ἀκριτόμυθοι",
    "γίγνοντ᾽, οὐδέ τι πάντα τελείεται ἀνθρώποισι.",
    "δοιαὶ γάρ τε πύλαι ἀμενηνῶν εἰσὶν ὀνείρων:",
    "αἱ μὲν γὰρ κεράεσσι τετεύχαται, αἱ δ᾽ ἐλέφαντι:",
    "τῶν οἳ μέν κ᾽ ἔλθωσι διὰ πριστοῦ ἐλέφαντος,",
    "οἵ ῥ᾽ ἐλεφαίρονται, ἔπε᾽ ἀκράαντα φέροντες:",
    "οἱ δὲ διὰ ξεστῶν κεράων ἔλθωσι θύραζε,",
    "οἵ ῥ᾽ ἔτυμα κραίνουσι, βροτῶν ὅτε κέν τις ἴδηται.",
]
layer = preprocessing.TextVectorization()
layer.adapt(data)
vectorized_text = layer(data)
print(vectorized_text)
tf.Tensor(
[[37 12 25  5  9 20 21  0  0]
 [51 34 27 33 29 18  0  0  0]
 [49 52 30 31 19 46 10  0  0]
 [ 7  5 50 43 28  7 47 17  0]
 [24 35 39 40  3  6 32 16  0]
 [ 4  2 15 14 22 23  0  0  0]
 [36 48  6 38 42  3 45  0  0]
 [ 4  2 13 41 53  8 44 26 11]], shape=(8, 9), dtype=int64)

さらに、適応可能なレイヤーは、コンストラクタ引数または重みの割り当てを介して状態を直接設定するオプションを常に公開します。意図した状態値がレイヤー構築時にわかっている場合、またはadapt()呼び出しの外で計算される場合は、レイヤーの内部計算に依存せずに設定できます。例えば、TextVectorizationStringLookup、または、IntegerLookupレイヤーの外部語彙ファイルがすでに存在する場合、レイヤーのコンストラクタ引数で語彙ファイルへのパスを渡すことにより、それらをルックアップテーブルに直接読み込めます。

以下の例では、事前に計算された語彙を使用してStringLookupレイヤーをインスタンス化します。

vocab = ["a", "b", "c", "d"]
data = tf.constant([["a", "c", "d"], ["d", "z", "b"]])
layer = preprocessing.StringLookup(vocabulary=vocab)
vectorized_data = layer(data)
print(vectorized_data)
tf.Tensor(
[[2 4 5]
 [5 1 3]], shape=(2, 3), dtype=int64)

モデルの前またはモデル内のデータの前処理

前処理レイヤーを使用する方法は 2 つあります。

オプション 1: 次のように、それらをモデルの一部にします。

inputs = keras.Input(shape=input_shape)
x = preprocessing_layer(inputs)
outputs = rest_of_the_model(x)
model = keras.Model(inputs, outputs)

このオプションを使用すると、モデルの残りの実行と同期してデバイス上で前処理が行われるため、GPU アクセラレーションの恩恵を受けることができます。GPU でトレーニングしている場合、これはNormalizationレイヤー、およびすべての画像前処理レイヤーとデータ増強レイヤーに最適なオプションです。

オプション 2: これをtf.data.Datasetに適用して、次のように前処理されたデータのバッチを生成するデータセットを取得します。

dataset = dataset.map(
  lambda x, y: (preprocessing_layer(x), y))

このオプションを使用すると、前処理は CPU で非同期に行われ、モデルに入れる前にバッファリングされます。

これは、TextVectorizationおよびすべての構造化データ前処理レイヤーに最適なオプションです。CPU でトレーニングしていて、画像前処理レイヤーを使用している場合にも、これは良いオプションです。

推論時にモデル内で前処理を行うことの利点

オプション 2 を選択した場合でも、後で前処理レイヤーを含む推論のみのエンドツーエンドモデルをエクスポートしたい場合があります。これを行う主な利点は、モデルを移植可能にすることと、トレーニング/サービングスキューを減らせることです。

すべてのデータ前処理がモデルの一部である場合、他のユーザーは、各特徴がどのようにエンコードおよび正規化されるかを知らなくても、モデルを読み込んで使用できます。推論モデルは生の画像または生の構造化データを処理できるようになり、モデルのユーザーは画像のピクセル値が[-1, +1]、または、[0, 1]に正規化されていても、テキストに使用されるトークン化スキーム、カテゴリカルフィーチャに使用されるインデックススキームの詳細を認識する必要はありません。これは、モデルを TensorFlow.js などの別のランタイムにエクスポートする場合に特に有用です。JavaScript で前処理パイプラインを再実装する必要はありません。

最初に前処理レイヤーをtf.dataパイプラインに配置した場合、前処理をパッケージ化する推論モデルをエクスポートできます。前処理レイヤーとトレーニングモデルをチェーンする新しいモデルをインスタンス化するだけです。

inputs = keras.Input(shape=input_shape)
x = preprocessing_layer(inputs)
outputs = training_model(x)
inference_model = keras.Model(inputs, outputs)

クイックレシピ

画像データ増強(デバイス上)

画像データ増強レイヤーはトレーニング中にのみアクティブになることに注意してください(Dropoutレイヤーと同様)。

from tensorflow import keras
from tensorflow.keras import layers

# Create a data augmentation stage with horizontal flipping, rotations, zooms
data_augmentation = keras.Sequential(
    [
        preprocessing.RandomFlip("horizontal"),
        preprocessing.RandomRotation(0.1),
        preprocessing.RandomZoom(0.1),
    ]
)

# Create a model that includes the augmentation stage
input_shape = (32, 32, 3)
classes = 10
inputs = keras.Input(shape=input_shape)
# Augment images
x = data_augmentation(inputs)
# Rescale image values to [0, 1]
x = preprocessing.Rescaling(1.0 / 255)(x)
# Add the rest of the model
outputs = keras.applications.ResNet50(
    weights=None, input_shape=input_shape, classes=classes
)(x)
model = keras.Model(inputs, outputs)

画像分類を最初から行うの例で同様の設定が実際に行われていることを確認できます。

数値的特徴の正規化

# Load some data
(x_train, y_train), _ = keras.datasets.cifar10.load_data()
x_train = x_train.reshape((len(x_train), -1))
input_shape = x_train.shape[1:]
classes = 10

# Create a Normalization layer and set its internal state using the training data
normalizer = preprocessing.Normalization()
normalizer.adapt(x_train)

# Create a model that include the normalization layer
inputs = keras.Input(shape=input_shape)
x = normalizer(inputs)
outputs = layers.Dense(classes, activation="softmax")(x)
model = keras.Model(inputs, outputs)

# Train the model
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
model.fit(x_train, y_train)
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 4s 0us/step
1563/1563 [==============================] - 3s 1ms/step - loss: 2.1758
<tensorflow.python.keras.callbacks.History at 0x7fbc178e1cc0>

ワンホットエンコーディングによる文字列カテゴリカルフィーチャのエンコーディング

# Define some toy data
data = tf.constant(["a", "b", "c", "b", "c", "a"])

# Use StringLookup to build an index of the feature values
indexer = preprocessing.StringLookup()
indexer.adapt(data)

# Use CategoryEncoding to encode the integer indices to a one-hot vector
encoder = preprocessing.CategoryEncoding(output_mode="binary")
encoder.adapt(indexer(data))

# Convert new test data (which includes unknown feature values)
test_data = tf.constant(["a", "b", "c", "d", "e", ""])
encoded_data = encoder(indexer(test_data))
print(encoded_data)
tf.Tensor(
[[0. 0. 0. 0. 1.]
 [0. 0. 0. 1. 0.]
 [0. 0. 1. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [1. 0. 0. 0. 0.]], shape=(6, 5), dtype=float32)

インデックス 0 は、欠落している値(空の文字列""として指定する必要があります)のために予約されていることに注意してください。インデックス 1 は、語彙外の値(adapt()の際に表示されなかった値)のために予約されています。これは、mask_tokenおよびoov_tokenコンストラクタ引数を使用して構成できます。

StringLookupCategoryEncodingレイヤーの動作を確認するには構造化データ分類を最初から行うの例をご覧ください。

ワンホットエンコーディングによる整数カテゴリカルフィーチャのエンコーディング

# Define some toy data
data = tf.constant([10, 20, 20, 10, 30, 0])

# Use IntegerLookup to build an index of the feature values
indexer = preprocessing.IntegerLookup()
indexer.adapt(data)

# Use CategoryEncoding to encode the integer indices to a one-hot vector
encoder = preprocessing.CategoryEncoding(output_mode="binary")
encoder.adapt(indexer(data))

# Convert new test data (which includes unknown feature values)
test_data = tf.constant([10, 10, 20, 50, 60, 0])
encoded_data = encoder(indexer(test_data))
print(encoded_data)
tf.Tensor(
[[0. 0. 0. 1. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 1. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [1. 0. 0. 0. 0.]], shape=(6, 5), dtype=float32)

インデックス 0 は欠落した値(0 として指定する必要があります)のために予約されており、インデックス 1 は語彙外の値(adapt()の際に表示されなかった値)のために予約されていることに注意してください。これは、mask_value および oov_valueコンストラクタ引数を使用して構成できます。

IntegerLookupレイヤーとCategoryEncodingレイヤーの動作を確認するには、構造化データの分類を最初から行うの例をご覧ください。

整数のカテゴリカルフィーチャにハッシュトリックを適用する

多くの異なる値(10 の 3 乗以上の桁)をとることができるカテゴリカルフィーチャがあり、各値がデータに数回しか表示されない場合、特徴値にインデックスを付けてワンホットエンコードすることは非現実的で効果的ではありません。このような場合は、代わりに、固定サイズのベクトルに値をハッシュする「ハッシュトリック」を適用することをお勧めします。これにより、特徴スペースのサイズが管理しやすくなり、明示的なインデックス作成が不要になります。

# Sample data: 10,000 random integers with values between 0 and 100,000
data = np.random.randint(0, 100000, size=(10000, 1))

# Use the Hashing layer to hash the values to the range [0, 64]
hasher = preprocessing.Hashing(num_bins=64, salt=1337)

# Use the CategoryEncoding layer to one-hot encode the hashed values
encoder = preprocessing.CategoryEncoding(max_tokens=64, output_mode="binary")
encoded_data = encoder(hasher(data))
print(encoded_data.shape)
(10000, 64)

トークンインデックスのシーケンスとしてテキストをエンコードする

以下は、Embeddedレイヤーに渡されるテキストを前処理する方法です。

# Define some text data to adapt the layer
data = tf.constant(
    [
        "The Brain is wider than the Sky",
        "For put them side by side",
        "The one the other will contain",
        "With ease and You beside",
    ]
)
# Instantiate TextVectorization with "int" output_mode
text_vectorizer = preprocessing.TextVectorization(output_mode="int")
# Index the vocabulary via `adapt()`
text_vectorizer.adapt(data)

# You can retrieve the vocabulary we indexed via get_vocabulary()
vocab = text_vectorizer.get_vocabulary()
print("Vocabulary:", vocab)

# Create an Embedding + LSTM model
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
x = layers.Embedding(input_dim=len(vocab), output_dim=64)(x)
outputs = layers.LSTM(1)(x)
model = keras.Model(inputs, outputs)

# Call the model on test data (which includes unknown tokens)
test_data = tf.constant(["The Brain is deeper than the sea"])
test_output = model(test_data)
Vocabulary: ['', '[UNK]', 'the', 'side', 'you', 'with', 'will', 'wider', 'them', 'than', 'sky', 'put', 'other', 'one', 'is', 'for', 'ease', 'contain', 'by', 'brain', 'beside', 'and']

テキスト分類を最初から行うの例では、Embeddedモードと組み合わされてTextVectorizationレイヤーが動作する方法を確認できます。

このようなモデルをトレーニングする場合、最高のパフォーマンスを得るには、入力パイプラインの一部としてTextVectorizationレイヤーを使用する必要があることに注意してください(上記のテキスト分類の例で示すように)。

マルチホットエンコーディングを使用した ngram の密な行列としてのテキストのエンコーディング

これは、Denseレイヤーに渡されるテキストを前処理する方法です。

# Define some text data to adapt the layer
data = tf.constant(
    [
        "The Brain is wider than the Sky",
        "For put them side by side",
        "The one the other will contain",
        "With ease and You beside",
    ]
)
# Instantiate TextVectorization with "binary" output_mode (multi-hot)
# and ngrams=2 (index all bigrams)
text_vectorizer = preprocessing.TextVectorization(output_mode="binary", ngrams=2)
# Index the bigrams via `adapt()`
text_vectorizer.adapt(data)

print(
    "Encoded text:\n",
    text_vectorizer(["The Brain is deeper than the sea"]).numpy(),
    "\n",
)

# Create a Dense model
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

# Call the model on test data (which includes unknown tokens)
test_data = tf.constant(["The Brain is deeper than the sea"])
test_output = model(test_data)

print("Model output:", test_output)
Encoded text:
 [[1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 1. 1. 0. 0. 0. 0. 0.

  0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 1. 1. 0. 0. 0.]] 

Model output: tf.Tensor([[0.2965997]], shape=(1, 1), dtype=float32)

TF-IDF 重み付けを使用した ngramの 密な行列としてのテキストのエンコード

これは、テキストをDenseレイヤーに渡す前に前処理する別の方法です。

# Define some text data to adapt the layer
data = tf.constant(
    [
        "The Brain is wider than the Sky",
        "For put them side by side",
        "The one the other will contain",
        "With ease and You beside",
    ]
)
# Instantiate TextVectorization with "tf-idf" output_mode
# (multi-hot with TF-IDF weighting) and ngrams=2 (index all bigrams)
text_vectorizer = preprocessing.TextVectorization(output_mode="tf-idf", ngrams=2)
# Index the bigrams and learn the TF-IDF weights via `adapt()`
text_vectorizer.adapt(data)

print(
    "Encoded text:\n",
    text_vectorizer(["The Brain is deeper than the sea"]).numpy(),
    "\n",
)

# Create a Dense model
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

# Call the model on test data (which includes unknown tokens)
test_data = tf.constant(["The Brain is deeper than the sea"])
test_output = model(test_data)
print("Model output:", test_output)
Encoded text:
 [[8.04719   1.6945957 0.        0.        0.        0.        0.

  0.        0.        0.        0.        0.        0.        0.
  0.        0.        1.0986123 1.0986123 1.0986123 0.        0.
  0.        0.        0.        0.        0.        0.        0.
  1.0986123 0.        0.        0.        0.        0.        0.
  0.        1.0986123 1.0986123 0.        0.        0.       ]] 

Model output: tf.Tensor([[-0.80648196]], shape=(1, 1), dtype=float32)