TensorFlow 2.0 : ガイド : Keras :- 前処理層で作業する (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/14/2021
* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
人工知能研究開発支援 | 人工知能研修サービス | テレワーク & オンライン授業を支援 |
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。 |
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ |
Facebook: https://www.facebook.com/ClassCatJP/ |
ガイド : Keras :- 前処理層で作業する
Keras 前処理層
Keras 前処理層 API は開発者に Keras-native 入力処理パイプラインを構築することを可能にします。これらの入力処理パイプラインは Keras モデルと直接結び付けられ、そして Keras SavedModel の一部としてエクスポートされた、非-Keras ワークフローにおける独立した前処理コードとして利用できます。
Keras 前処理層で、真に end-to-end なモデル : raw 画像や raw 構造化データを入力として受け取るモデル ; 特徴正規化やそれら自身の上の特徴値インデキシングを処理するモデルを構築してエクスポートできます。
利用可能な前処理層
Core 前処理層
- TextVectorization 層: raw 文字列を Embedding 層や Dense 層で読めるエンコードされた表現に変えます。
- Normalization 層: 入力特徴の特徴単位の正規化を遂行します。
構造化データ前処理層
これらの層は構造化データ・エンコーディングと特徴エンジニアリングのためです。
- CategoryEncoding 層: 整数カテゴリカル特徴を one-hot, マルチ-hot あるいは TF-IDF dense 表現に変えます。
- Hashing 層: 「ハッシュトリック (= hashing trick)」としても知られる、カテゴリカル特徴ハッシングを遂行します。
- Discretization (離散化) 層: 連続数値特徴を整数カテゴリカル特徴に変えます。
- StringLookup 層: 文字列カテゴリカル値を整数インデックスに変えます。
- IntegerLookup 層: 整数カテゴリカル値を整数インデックスに変えます。
- CategoryCrossing 層: カテゴリカル特徴を共起 (= co-occurrence) 特徴に結びつけます。 E.g. 特徴値 “a” と “b” を持つ場合、それは組合せ特徴 “a と b は同時に存在する” を提供できます。
画像前処理層
これらの層は画像モデルの入力を標準化するためです。
- Resizing 層: 画像のバッチをターゲットサイズにリサイズする。
- Rescaling 層: 画像のバッチの値をリスケールしてオフセットする (e.g. [0, 255] 範囲の入力から [0, 1] 範囲の入力に進める)。
- CenterCrop 層: 画像のバッチである場合に中心クロップを返す。
画像データ増強層
これらの層は画像のバッチにランダムな増強変換を適用します。それらは訓練の間だけアクティブです。
- RandomCrop 層
- RandomFlip 層
- RandomTranslation 層
- RandomRotation 層
- RandomZoom 層
- RandomHeight 層
- RandomWidth 層
adapt() メソッド
幾つかの前処理層は訓練データのサンプルに基づいて計算されなければならない内部状態を持ちます。stateful な前処理層リストは :
- TextVectorization: 文字列トークンと整数インデックスの間のマッピングを保持します。
- Normalization: 特徴の平均と標準偏差を保持します。
- StringLookup と IntegerLookup: 入力値と出力インデックスの間のマッピングを保持します。
- CategoryEncoding: 入力値のインデックスを保持します。
- Discretization (離散化) : 値バケット境界についての情報を保持します。
重要なことは、これらの層は 非-訓練可能 であることです (訓練可能ではありません)。それらの状態は訓練の間に設定されません ; それは 訓練の前 に設定されなければなりません、”adaptation (適応)” と呼ばれるステップです。
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)
加えて、適応可能な (= adaptable) 層はコンストラクタ引数か重み割当てを通して状態を直接設定するためのオプションを常に公開しています。意図された状態値が層構築時に知られていたり、adapt() 呼び出しの外側で計算される場合、それらは層の内部計算に依拠することなく設定できます。例えば、TextVectorization, StringLookup や 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: 前処理されたデータのバッチを yield するデータセットを得るために、それを貴方の tf.data.Dataset に適用します、このようにです :
dataset = dataset.map( lambda x, y: (preprocessing_layer(x), y))
このオプションでは、前処理は CPU 上で非同期に発生し、そしてモデルに進む前にバッファリングされます。
これは TextVectorization 、そして総ての構造化データ前処理層のために最善なオプションです。それはまた CPU 上で訓練していて画像前処理層を使用する場合に良いオプションであり得ます。
推論時にモデルの内部で前処理を行なうメリット
オプション 2 で進める場合でさえ、前処理層を含む、推論-only end-to-end モデルを後でエクスポートすることを望むかもしれません。これを行なう主要なメリットはそれが モデルを可搬にして 訓練/サービングのねじれ (= skew) を減じる ことに役立つからです。
総てのデータ前処理がモデルの一部であるとき、他の人は各特徴がエンコードされて正規化されることをどのように想定されているかを知らなければならないことなく貴方のモデルをロードして利用できます。貴方の推論モデルは raw 画像や raw 構造化データを処理することができて、モデルのユーザに例えばテキストのために使用されるトークン化スキーム、カテゴリカル特徴のために使用されるインデキシング・スキーム、画像ピクセル値が [-1, +1] か [0, 1] に正規化されるかどうか、等々の詳細を知ることを要求しません。これは貴方がモデルを TensorFlow.js のような他のランタイムにエクスポートしている場合に特にパワフルです : JavaScript での前処理パイプラインを再実装しなくても構いません。
初期時に前処理層を tf.data パイプラインに置く場合、前処理をパッケージ化する推論モデルをエクスポートできます。単純に前処理層と訓練モデルを連鎖する新しいモデルをインスタンス化します :
inputs = keras.Input(shape=input_shape) x = preprocessing_layer(inputs) outputs = training_model(x) infernece_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)
サンプル image classification from scratch で実践の同様のセットアップを見ることができます。
数値特徴を正規化する
# 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 [==============================] - 6s 0us/step 1563/1563 [==============================] - 4s 2ms/step - loss: 2.1744 <tensorflow.python.keras.callbacks.History at 0x7f4497e53780>
one-hot エンコーディングを通して文字列カテゴリカル特徴をエンコードする
# 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 は out-of-vocabulary (語彙外の) 値 (adapt() の間に見られなかった値) のために予約されていることに注意してください。StringLookup の mask_token と oov_token コンストラクタ引数を使用してこれを configure できます。
サンプル structured data classification from scratch で実践で StringLookup と CategoryEncoding 層を見ることができます。
one-hot エンコーディングを通して整数カテゴリカル特徴をエンコードする
# 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 は out-of-vocabulary (語彙外の) 値 (adapt() の間に見られなかった値) のために予約されていることに注意してください。IntegerLookup の mask_value と oov_token コンストラクタ引数を使用してこれを configure できます。
サンプル structured data classification from scratch で実践で IntegerLookup と CategoryEncoding 層を見ることができます。
整数カテゴリカル特徴にハッシュトリックを適用する
データで各値が数回だけ現れるような、 (10e3 かそれ以上のオーダーで) 多くの異なる値を取れるカテゴリカル特徴を持つ場合、それは特徴値をインデックスして one-hot エンコードすることは実現困難で効果的ではありません。代わりに、「ハッシュトリック (= hashing trick)」を適用することは良い考えです : 値を固定サイズのベクトルにハッシュ化します。これは特徴空間のサイズを管理可能にし、明示的なインデキシングの必要性を取り除きます。
# 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)
テキストをトークン・インデックスのシークエンスとしてエンコードする
これは Embedding 層に渡されるテキストをどのように前処理するかです。
# 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']
サンプル text classification from scratch で Embedding モードと結び付けられた、TextVectorization を実践で見ることができます。
そのようなモデルを訓練するとき、ベスト・パフォーマンスのためには、入力パイプラインの一部として TextVectorization 層を使用するべきであることに注意してください (これは上のテキスト分類サンプルで行なったことです)。
テキストをマルチ-hot エンコーディングで 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.69113374]], 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([[-1.5733842]], shape=(1, 1), dtype=float32)
以上