Trabalhar com camadas de pré-processamento

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Camadas de pré-processamento Keras

A API de camadas de pré-processamento Keras permite que os desenvolvedores criem pipelines de processamento de entrada nativos Keras. Esses pipelines de processamento de entrada podem ser usados ​​como código de pré-processamento independente em fluxos de trabalho não Keras, combinados diretamente com modelos Keras e exportados como parte de um Keras SavedModel.

Com as camadas de pré-processamento Keras, você pode construir e exportar modelos que são verdadeiramente ponta a ponta: modelos que aceitam imagens brutas ou dados estruturados brutos como entrada; modelos que lidam com normalização de recurso ou indexação de valor de recurso por conta própria.

Camadas de pré-processamento disponíveis

Camadas centrais de pré-processamento

  • TextVectorization camada: transforma cordas brutos em uma representação codificada que pode ser lido por uma Embedding camada ou Dense camada.
  • Normalization camada: característica executa-sábio normalizar de características de entrada.

Camadas de pré-processamento de dados estruturados

Essas camadas são para codificação de dados estruturados e engenharia de recursos.

  • CategoryEncoding camada: turnos inteiros características categóricas em representações densas one-quentes, multi-quente, ou TF-IDF.
  • Hashing camada: Realiza hashing característica categórica, também conhecido como o "truque hash".
  • Discretization camada: transforma características numéricas contínuas em inteiro características categóricas.
  • StringLookup camada: transforma valores categóricos cordas em índices inteiros.
  • IntegerLookup camada: voltas valores inteiros categóricas em índices de números inteiros.
  • CategoryCrossing camada: combina características categóricas em recursos de co-ocorrência. Por exemplo, se você tiver valores de recurso "a" e "b", ele pode fornecer o recurso de combinação "a e b estão presentes ao mesmo tempo".

Camadas de pré-processamento de imagem

Essas camadas são para padronizar as entradas de um modelo de imagem.

  • Resizing camada: redimensionado um lote de imagens para um tamanho alvo.
  • Rescaling camada: rescales e offsets os valores de um grupo de imagem (por exemplo, ir de entradas no [0, 255] gama de entradas no [0, 1] gama.
  • CenterCrop camada: retorna uma colheita centro de um grupo de imagens.

Camadas de aumento de dados de imagem

Essas camadas aplicam transformações de aumento aleatórias a um lote de imagens. Eles estão ativos apenas durante o treinamento.

  • RandomCrop camada
  • RandomFlip camada
  • RandomTranslation camada
  • RandomRotation camada
  • RandomZoom camada
  • RandomHeight camada
  • RandomWidth camada

A adapt() Método

Algumas camadas de pré-processamento têm um estado interno que deve ser calculado com base em uma amostra dos dados de treinamento. A lista de camadas de pré-processamento com estado é:

  • TextVectorization : mantém um mapeamento entre fichas de cordas e índices inteiros
  • Normalization : detém a média e desvio padrão das características
  • StringLookup e IntegerLookup : realizar um mapeamento entre os valores de entrada e índices de saída.
  • CategoryEncoding : mantém um índice de valores de entrada.
  • Discretization : contém informações sobre limites de balde de valor.

Fundamentalmente, essas camadas não são orientáveis. Seu estado não é definido durante o treinamento; ele deve ser definido antes do treino, um passo chamado "adaptação".

Você define o estado de uma camada pré-processamento, expondo-o aos dados de treinamento, através da adapt() método:

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

A adapt() método leva qualquer uma matriz Numpy ou um tf.data.Dataset objecto. No caso de StringLookup e TextVectorization , você também pode passar uma lista de strings:

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)

Além disso, as camadas adaptáveis ​​sempre expõem uma opção para definir o estado diretamente por meio de argumentos do construtor ou atribuição de peso. Se os valores de estado pretendidos são conhecidos no tempo de construção da camada, ou são calculadas fora da adapt() chamada, que pode ser ajustado, sem depender de computação interna da camada. Por exemplo, se arquivos de vocabulário externo para os TextVectorization , StringLookup , ou IntegerLookup camadas já existem, aqueles que podem ser carregados diretamente nas tabelas de pesquisa por meio de um caminho para o arquivo de vocabulário em argumentos do construtor da camada.

Aqui está um exemplo em que instanciar um StringLookup camada com vocabulário pré-computados:

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)

Dados de pré-processamento antes do modelo ou dentro do modelo

Existem duas maneiras de usar camadas de pré-processamento:

Opção 1: Faça-os parte do modelo, como este:

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

Com esta opção, o pré-processamento acontecerá no dispositivo, de forma síncrona com o resto da execução do modelo, o que significa que ele se beneficiará da aceleração da GPU. Se você está treinando em GPU, esta é a melhor opção para a Normalization camada, e para toda imagem de pré-processamento e camadas de aumento de dados.

Opção 2: aplicá-lo ao seu tf.data.Dataset , de modo a obter um conjunto de dados que produz lotes de dados pré-processados, como este:

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

Com esta opção, seu pré-processamento acontecerá na CPU, de forma assíncrona, e será armazenado em buffer antes de entrar no modelo.

Esta é a melhor opção para TextVectorization , e todos os dados estruturados pré-processamento de camadas. Também pode ser uma boa opção se você estiver treinando na CPU e usar camadas de pré-processamento de imagem.

Benefícios de fazer o pré-processamento dentro do modelo no momento da inferência

Mesmo se você escolher a opção 2, pode querer exportar posteriormente um modelo de ponta a ponta somente inferência que incluirá as camadas de pré-processamento. O benefício chave para fazer isso é que faz o seu modelo de portátil e ajuda a reduzir a formação / servindo de inclinação .

Quando todo o pré-processamento de dados faz parte do modelo, outras pessoas podem carregar e usar seu modelo sem precisar estar ciente de como cada recurso deve ser codificado e normalizado. Seu modelo de inferência será capaz de processar imagens brutas ou dados estruturados brutos e não exigirá que os usuários do modelo estejam cientes dos detalhes, por exemplo, o esquema de tokenização usado para texto, o esquema de indexação usado para características categóricas, sejam valores de pixel de imagem são normalizados para [-1, +1] ou [0, 1] , etc. Isto é especialmente poderoso se você está exportando o seu modelo para outro tempo de execução, tais como TensorFlow.js: você não terá que reimplementar o seu pré-processamento pipeline em JavaScript.

Se você inicialmente colocou suas camadas de pré-processamento em sua tf.data gasoduto, você pode exportar um modelo de inferência de que os pacotes de pré-processamento. Basta instanciar um novo modelo que encadeia suas camadas de pré-processamento e seu modelo de treinamento:

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

Receitas rápidas

Aumento de dados de imagem (no dispositivo)

Note-se que as camadas de aumento de dados de imagem são apenas activo durante o treino (semelhante ao Dropout camada).

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)

Você pode ver uma configuração semelhante em ação no exemplo classificação de imagens a partir do zero .

Normalizando recursos numéricos

# 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 [==============================] - 11s 0us/step
1563/1563 [==============================] - 3s 1ms/step - loss: 2.1776
<tensorflow.python.keras.callbacks.History at 0x7f58c5f44208>

Codificação de recursos categóricos de string por meio de codificação 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)

Note-se que o índice 0 é reservada para os valores (que você deve especificar como a cadeia vazia faltando "" ), e índice 1 é reservado para fora de vocabulário valores (valores que não foram vistos durante a adapt() ). Você pode configurar isso usando o mask_token e oov_token argumentos do construtor de StringLookup .

Você pode ver os StringLookup e CategoryEncoding camadas em ação no exemplo estruturado classificação de dados a partir do zero .

Codificação de recursos categóricos inteiros por meio de codificação 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)

Note-se que o índice 0 é reservado para valores em falta (que você deve especificar como o valor 0), e índice 1 é reservado para fora de vocabulário valores (valores que não foram vistos durante a adapt() ). Você pode configurar isso usando o mask_value e oov_value argumentos do construtor de IntegerLookup .

Você pode ver os IntegerLookup e CategoryEncoding camadas em ação no exemplo estruturado classificação de dados a partir do zero .

Aplicando o truque de hashing a um recurso categórico de número inteiro

Se você tiver um recurso categórico que pode assumir muitos valores diferentes (na ordem de 10e3 ou mais), em que cada valor aparece apenas algumas vezes nos dados, torna-se impraticável e ineficaz indexar e codificar os valores do recurso em um único momento. Em vez disso, pode ser uma boa ideia aplicar o "truque de hashing": hash os valores para um vetor de tamanho fixo. Isso mantém o tamanho do espaço do recurso gerenciável e elimina a necessidade de indexação explícita.

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

Codificando texto como uma sequência de índices de token

Isto é como você deve pré-processar o texto a ser passado para um Embedding camada.

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

Você pode ver o TextVectorization camada em ação, combinada com uma Embedding modo, no exemplo de classificação de texto a partir do zero .

Note-se que quando o treinamento tal modelo um, para um melhor desempenho, você deve usar o TextVectorization camada como parte do pipeline de entrada (que é o que fazemos no exemplo de classificação de texto acima).

Codificando texto como uma matriz densa de ngrams com codificação multi-hot

Isto é como você deve pré-processar o texto a ser passado para uma Dense camada.

# 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.6381588]], shape=(1, 1), dtype=float32)

Codificando texto como uma matriz densa de ngrams com ponderação TF-IDF

Esta é uma forma alternativa de pré-processamento de texto antes de passá-la para uma Dense camada.

# 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.2379041]], shape=(1, 1), dtype=float32)