Lavorare con i livelli di pre-elaborazione

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza la fonte su GitHub Scarica taccuino

Pre-elaborazione Keras

L'API dei livelli di pre-elaborazione Keras consente agli sviluppatori di creare pipeline di elaborazione dell'input native di Keras. Queste pipeline di elaborazione dell'input possono essere utilizzate come codice di pre-elaborazione indipendente in flussi di lavoro non Keras, combinate direttamente con i modelli Keras ed esportate come parte di un Keras SavedModel.

Con i livelli di pre-elaborazione di Keras, puoi creare ed esportare modelli che sono veramente end-to-end: modelli che accettano immagini grezze o dati strutturati grezzi come input; modelli che gestiscono autonomamente la normalizzazione delle caratteristiche o l'indicizzazione del valore delle caratteristiche.

Pre-elaborazione disponibile

Pre-elaborazione del testo

Pre-elaborazione delle caratteristiche numeriche

Pre-elaborazione delle caratteristiche categoriali

Pre-elaborazione delle immagini

Questi livelli servono per standardizzare gli input di un modello di immagine.

Incremento dei dati di immagine

Questi livelli applicano trasformazioni di aumento casuali a un batch di immagini. Sono attivi solo durante l'allenamento.

L' adapt() metodo

Alcuni livelli di pre-elaborazione hanno uno stato interno che può essere calcolato in base a un campione dei dati di addestramento. L'elenco dei livelli di preelaborazione con stato è:

  • TextVectorization : tiene una mappatura tra gettoni stringa e indici interi
  • StringLookup e IntegerLookup : tenere una mappatura tra i valori di ingresso e indici interi.
  • Normalization : tiene la media e deviazione standard delle caratteristiche.
  • Discretization : contiene le informazioni sui limiti di secchio di valore.

Fondamentalmente, questi strati sono non addestrabili. Il loro stato non viene impostato durante l'allenamento; deve essere impostato prima dell'allenamento, avviene mediante l'inizializzazione da una costante precalcolate, o da "adattare" loro su dati.

È possibile impostare lo stato di uno strato di pre-elaborazione esponendolo alla dati di allenamento, tramite l' adapt() Metodo:

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

data = np.array([[0.1, 0.2, 0.3], [0.8, 0.9, 1.0], [1.5, 1.6, 1.7],])
layer = layers.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

L' adapt() metodo richiede sia una matrice di Numpy o un tf.data.Dataset oggetto. Nel caso di StringLookup e TextVectorization , si può anche passare una lista di stringhe:

data = [
    "ξεῖν᾽, ἦ τοι μὲν ὄνειροι ἀμήχανοι ἀκριτόμυθοι",
    "γίγνοντ᾽, οὐδέ τι πάντα τελείεται ἀνθρώποισι.",
    "δοιαὶ γάρ τε πύλαι ἀμενηνῶν εἰσὶν ὀνείρων:",
    "αἱ μὲν γὰρ κεράεσσι τετεύχαται, αἱ δ᾽ ἐλέφαντι:",
    "τῶν οἳ μέν κ᾽ ἔλθωσι διὰ πριστοῦ ἐλέφαντος,",
    "οἵ ῥ᾽ ἐλεφαίρονται, ἔπε᾽ ἀκράαντα φέροντες:",
    "οἱ δὲ διὰ ξεστῶν κεράων ἔλθωσι θύραζε,",
    "οἵ ῥ᾽ ἔτυμα κραίνουσι, βροτῶν ὅτε κέν τις ἴδηται.",
]
layer = layers.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)

Inoltre, i livelli adattabili espongono sempre un'opzione per impostare direttamente lo stato tramite gli argomenti del costruttore o l'assegnazione del peso. Se i valori di stato destinati sono noti al momento costruzione a strati, o sono calcolati all'esterno del adapt() chiamata, possono essere impostati senza affidarsi calcolo interno del livello. Ad esempio, se i file di vocabolario esterno per gli TextVectorization , StringLookup o IntegerLookup già esistono strati, questi possono essere caricati direttamente nelle tabelle di ricerca passando un percorso del file di vocabolario argomenti del costruttore del livello.

Ecco un esempio in cui istanziamo uno StringLookup layer con il vocabolario precomputed:

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

Pre-elaborazione dei dati prima del modello o all'interno del modello

Esistono due modi per utilizzare i livelli di preelaborazione:

Opzione 1: Renderli parte del modello, in questo modo:

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

Con questa opzione, la pre-elaborazione avverrà sul dispositivo, in sincronia con il resto dell'esecuzione del modello, il che significa che trarrà vantaggio dall'accelerazione GPU. Se ti alleni su GPU, questa è l'opzione migliore per la Normalization livello e per tutti image pre-elaborazione e gli strati di dati di potenziamento.

Opzione 2: si applica al tuo tf.data.Dataset , in modo da ottenere un insieme di dati che produce lotti di dati preprocessati, come questo:

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

Con questa opzione, la tua pre-elaborazione avverrà sulla CPU, in modo asincrono, e verrà memorizzata nel buffer prima di entrare nel modello. Inoltre, se si chiama dataset.prefetch(tf.data.AUTOTUNE) sul set di dati, la pre-elaborazione avverrà in modo efficiente in parallelo con la formazione:

dataset = dataset.map(lambda x, y: (preprocessing_layer(x), y))
dataset = dataset.prefetch(tf.data.AUTOTUNE)
model.fit(dataset, ...)

Questa è l'opzione migliore per TextVectorization , e tutti i dati strutturati pre-elaborazione strati. Può anche essere una buona opzione se ti stai allenando sulla CPU e utilizzi i livelli di pre-elaborazione delle immagini.

Quando si esegue il TPU, si dovrebbe sempre porre pre-elaborazione livelli nella tf.data pipeline (con l'eccezione di Normalization e Rescaling , che corrono bene su TPU e sono comunemente utilizzati come il primo strato è un modello di immagine).

Vantaggi di eseguire la preelaborazione all'interno del modello al momento dell'inferenza

Anche se scegli l'opzione 2, in seguito potresti voler esportare un modello end-to-end di sola inferenza che includerà i livelli di pre-elaborazione. Il vantaggio principale di questa operazione è che rende il vostro modello di portatile e aiuta a ridurre la formazione / porzione skew .

Quando tutta la pre-elaborazione dei dati fa parte del modello, altre persone possono caricare e utilizzare il modello senza dover essere consapevoli di come ciascuna caratteristica dovrebbe essere codificata e normalizzata. Il tuo modello di inferenza sarà in grado di elaborare immagini grezze o dati strutturati grezzi e non richiederà agli utenti del modello di conoscere i dettagli, ad esempio lo schema di tokenizzazione utilizzato per il testo, lo schema di indicizzazione utilizzato per le caratteristiche categoriche, se i valori dei pixel dell'immagine sono normalizzati a [-1, +1] o [0, 1] , ecc Questo è particolarmente potente se si esporta il modello in un'altra fase di esecuzione, come ad esempio TensorFlow.js: non sarà necessario reimplementare vostro pre-elaborazione pipeline in JavaScript.

Se inizialmente metti i livelli pre-elaborazione nel vostro tf.data conduttura, è possibile esportare un modello di inferenza che i pacchetti del pre-elaborazione. Crea semplicemente un'istanza di un nuovo modello che concatena i tuoi livelli di pre-elaborazione e il tuo modello di addestramento:

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

Ricette veloci

Incremento dei dati di immagine

Si noti che i dati di immagine di aumento strati sono attivi solo durante l'allenamento (analogamente alla Dropout strato).

from tensorflow import keras
from tensorflow.keras import layers

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

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

# Create a tf.data pipeline of augmented images (and their labels)
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.batch(16).map(lambda x, y: (data_augmentation(x), y))


# Create a model and train it on the augmented image data
inputs = keras.Input(shape=input_shape)
x = layers.Rescaling(1.0 / 255)(inputs)  # Rescale inputs
outputs = keras.applications.ResNet50(  # Add the rest of the model
    weights=None, input_shape=input_shape, classes=classes
)(x)
model = keras.Model(inputs, outputs)
model.compile(optimizer="rmsprop", loss="sparse_categorical_crossentropy")
model.fit(train_dataset, steps_per_epoch=5)
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 6s 0us/step
170508288/170498071 [==============================] - 6s 0us/step
5/5 [==============================] - 11s 44ms/step - loss: 8.8927
<keras.callbacks.History at 0x7f1c0c3f16d0>

Si può vedere una configurazione simile in azione nell'esempio classificazione di immagini da zero .

Normalizzazione delle caratteristiche numeriche

# 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 = layers.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)
1563/1563 [==============================] - 3s 2ms/step - loss: 2.1304
<keras.callbacks.History at 0x7f1bc43f40d0>

Codifica delle funzionalità categoriali delle stringhe tramite la codifica 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 and encode output.
lookup = layers.StringLookup(output_mode="one_hot")
lookup.adapt(data)

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

Si noti che, qui, indice 0 è riservato per out-of-vocabolario valori (i valori che non sono stati visti durante adapt() ).

Si può vedere la StringLookup in azione nella classificazione di dati strutturati da zero esempio.

Codifica di caratteristiche categoriche intere tramite codifica 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 and encode output.
lookup = layers.IntegerLookup(output_mode="one_hot")
lookup.adapt(data)

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

Si noti che l'indice 0 è riservato per i valori mancanti (che si dovrebbe specificare come il valore 0), e l'indice 1 è riservato per out-of-vocabolario valori (i valori che non sono stati visti durante adapt() ). È possibile configurare questo utilizzando il mask_token e oov_token argomenti del costruttore di IntegerLookup .

Si può vedere IntegerLookup in azione nell'esempio classificazione dei dati strutturati da zero .

Applicare il trucco dell'hashing a una caratteristica categorica intera

Se si dispone di una caratteristica categoriale che può assumere molti valori diversi (nell'ordine di 10e3 o superiore), in cui ogni valore appare solo poche volte nei dati, diventa poco pratico e inefficace indicizzare e codificare a caldo i valori della caratteristica. Invece, può essere una buona idea applicare il "trucco dell'hashing": eseguire l'hashing dei valori su un vettore di dimensione fissa. Ciò mantiene gestibili le dimensioni dello spazio delle funzionalità ed elimina la necessità di un'indicizzazione esplicita.

# 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 = layers.Hashing(num_bins=64, salt=1337)

# Use the CategoryEncoding layer to multi-hot encode the hashed values
encoder = layers.CategoryEncoding(num_tokens=64, output_mode="multi_hot")
encoded_data = encoder(hasher(data))
print(encoded_data.shape)
(10000, 64)

Codifica del testo come sequenza di indici token

Questo è come si dovrebbe preprocessare testo da passare a un Embedding layer.

# Define some text data to adapt the layer
adapt_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",
    ]
)

# Create a TextVectorization layer
text_vectorizer = layers.TextVectorization(output_mode="int")
# Index the vocabulary via `adapt()`
text_vectorizer.adapt(adapt_data)

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

# Create a simple model
inputs = keras.Input(shape=(None,), dtype="int64")
x = layers.Embedding(input_dim=text_vectorizer.vocabulary_size(), output_dim=16)(inputs)
x = layers.GRU(8)(x)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

# Create a labeled dataset (which includes unknown tokens)
train_dataset = tf.data.Dataset.from_tensor_slices(
    (["The Brain is deeper than the sea", "for if they are held Blue to Blue"], [1, 0])
)

# Preprocess the string inputs, turning them into int sequences
train_dataset = train_dataset.batch(2).map(lambda x, y: (text_vectorizer(x), y))
# Train the model on the int sequences
print("\nTraining model...")
model.compile(optimizer="rmsprop", loss="mse")
model.fit(train_dataset)

# For inference, you can export a model that accepts strings as input
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = model(x)
end_to_end_model = keras.Model(inputs, outputs)

# Call the end-to-end model on test data (which includes unknown tokens)
print("\nCalling end-to-end model on test string...")
test_data = tf.constant(["The one the other will absorb"])
test_output = end_to_end_model(test_data)
print("Model output:", test_output)
Encoded text:
 [[ 2 19 14  1  9  2  1]]

Training model...
1/1 [==============================] - 3s 3s/step - loss: 0.4776

Calling end-to-end model on test string...
Model output: tf.Tensor([[0.04233753]], shape=(1, 1), dtype=float32)

Potete vedere il TextVectorization strato in azione, combinato con un Embedding modalità, nell'esempio di classificazione di testi da zero .

Si noti che quando la formazione di un tale modello, per le migliori prestazioni, si dovrebbe sempre utilizzare il TextVectorization livello come parte della pipeline di ingresso.

Codifica del testo come una matrice densa di ngram con codifica multi-hot

Questo è come si dovrebbe preprocessare testo da passare a un Dense strato di.

# Define some text data to adapt the layer
adapt_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 "multi_hot" output_mode
# and ngrams=2 (index all bigrams)
text_vectorizer = layers.TextVectorization(output_mode="multi_hot", ngrams=2)
# Index the bigrams via `adapt()`
text_vectorizer.adapt(adapt_data)

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

# Create a simple model
inputs = keras.Input(shape=(text_vectorizer.vocabulary_size(),))
outputs = layers.Dense(1)(inputs)
model = keras.Model(inputs, outputs)

# Create a labeled dataset (which includes unknown tokens)
train_dataset = tf.data.Dataset.from_tensor_slices(
    (["The Brain is deeper than the sea", "for if they are held Blue to Blue"], [1, 0])
)

# Preprocess the string inputs, turning them into int sequences
train_dataset = train_dataset.batch(2).map(lambda x, y: (text_vectorizer(x), y))
# Train the model on the int sequences
print("\nTraining model...")
model.compile(optimizer="rmsprop", loss="mse")
model.fit(train_dataset)

# For inference, you can export a model that accepts strings as input
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = model(x)
end_to_end_model = keras.Model(inputs, outputs)

# Call the end-to-end model on test data (which includes unknown tokens)
print("\nCalling end-to-end model on test string...")
test_data = tf.constant(["The one the other will absorb"])
test_output = end_to_end_model(test_data)
print("Model output:", test_output)
WARNING:tensorflow:5 out of the last 1567 calls to <function PreprocessingLayer.make_adapt_function.<locals>.adapt_step at 0x7f1b9c5c5290> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
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.]]

Training model...
1/1 [==============================] - 0s 231ms/step - loss: 1.0046

Calling end-to-end model on test string...
Model output: tf.Tensor([[-0.54753447]], shape=(1, 1), dtype=float32)

Codifica del testo come matrice densa di ngrammi con ponderazione TF-IDF

Questo è un modo alternativo di preelaborazione testo prima di passare ad un Dense strato.

# Define some text data to adapt the layer
adapt_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 = layers.TextVectorization(output_mode="tf-idf", ngrams=2)
# Index the bigrams and learn the TF-IDF weights via `adapt()`

with tf.device("CPU"):
    # A bug that prevents this from running on GPU for now.
    text_vectorizer.adapt(adapt_data)

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

# Create a simple model
inputs = keras.Input(shape=(text_vectorizer.vocabulary_size(),))
outputs = layers.Dense(1)(inputs)
model = keras.Model(inputs, outputs)

# Create a labeled dataset (which includes unknown tokens)
train_dataset = tf.data.Dataset.from_tensor_slices(
    (["The Brain is deeper than the sea", "for if they are held Blue to Blue"], [1, 0])
)

# Preprocess the string inputs, turning them into int sequences
train_dataset = train_dataset.batch(2).map(lambda x, y: (text_vectorizer(x), y))
# Train the model on the int sequences
print("\nTraining model...")
model.compile(optimizer="rmsprop", loss="mse")
model.fit(train_dataset)

# For inference, you can export a model that accepts strings as input
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = model(x)
end_to_end_model = keras.Model(inputs, outputs)

# Call the end-to-end model on test data (which includes unknown tokens)
print("\nCalling end-to-end model on test string...")
test_data = tf.constant(["The one the other will absorb"])
test_output = end_to_end_model(test_data)
print("Model output:", test_output)
WARNING:tensorflow:6 out of the last 1568 calls to <function PreprocessingLayer.make_adapt_function.<locals>.adapt_step at 0x7f1b9f6eae60> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
Encoded text:
 [[5.461647  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.       ]]

Training model...
1/1 [==============================] - 0s 239ms/step - loss: 4.4868

Calling end-to-end model on test string...
Model output: tf.Tensor([[0.25670475]], shape=(1, 1), dtype=float32)

trucchi importanti

Lavorare con livelli di ricerca con vocabolari molto grandi

Potreste trovarvi a lavorare con un grande vocabolario in un TextVectorization , uno StringLookup livello, o un IntegerLookup livello. In genere, un vocabolario più grande di 500 MB sarebbe considerato "molto grande".

In tal caso, per le migliori prestazioni, si dovrebbe evitare l'uso di adapt() . Invece, pre-calcola il tuo vocabolario in anticipo (puoi usare Apache Beam o TF Transform per questo) e memorizzalo in un file. Quindi caricare il vocabolario nello strato in fase di costruzione passando il percorso del file come il vocabulary argomento.

Utilizzando strati di ricerca su un baccello TPU o con ParameterServerStrategy .

C'è una questione in sospeso che causa le prestazioni a degradare quando si utilizza un TextVectorization , StringLookup , o IntegerLookup strato, mentre la formazione su un pod in TPU o su più computer tramite ParameterServerStrategy . Questo dovrebbe essere risolto in TensorFlow 2.7.