Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/guide/migrate/migrating_feature_columns.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Como migrar de tf.feature_column para camadas de pré-processamento Keras

O treinamento de um modelo geralmente vem com uma certa quantidade de pré-processamento de características, principalmente ao lidar com dados estruturados. Ao treinar um tf.estimator.Estimator no TensorFlow 1, você geralmente realiza o pré-processamento de características com a API tf.feature_column. No TensorFlow 2, você pode fazer isso diretamente com as camadas de pré-processamento do Keras.

Este guia de migração demonstra transformações de características comuns tanto usando colunas de características como camadas de pré-processamento, seguidas pelo treinamento de um modelo completo com ambas as APIs.

Primeiro, comece com algumas importações necessárias:

import tensorflow as tf import tensorflow.compat.v1 as tf1 import math

Agora, adicione uma função de utilidade para chamar uma coluna de característica para demonstração:

def call_feature_columns(feature_columns, inputs): # This is a convenient way to call a `feature_column` outside of an estimator # to display its output. feature_layer = tf1.keras.layers.DenseFeatures(feature_columns) return feature_layer(inputs)

Tratamento das entradas

Para usar colunas de características com um estimador, sempre se espera que as entradas do modelo sejam um dicionário de tensores:

input_dict = { 'foo': tf.constant([1]), 'bar': tf.constant([0]), 'baz': tf.constant([-1]) }

Cada coluna de característica precisa ser criada com uma chave para indexar nos dados-fonte. A saída de todas as colunas de características é concatenada e usada pelo modelo estimador.

columns = [ tf1.feature_column.numeric_column('foo'), tf1.feature_column.numeric_column('bar'), tf1.feature_column.numeric_column('baz'), ] call_feature_columns(columns, input_dict)

No Keras, a entrada do modelo é muito mais flexível. Um tf.keras.Model pode lidar com uma única entrada de tensor, uma lista de características de tensor ou um dicionário de características de tensor. Você pode manipular a entrada do dicionário passando um dicionário de tf.keras.Input na criação do modelo. As entradas não serão concatenadas automaticamente, o que permite que sejam usadas de maneiras muito mais flexíveis. Elas podem ser concatenadas com tf.keras.layers.Concatenate.

inputs = { 'foo': tf.keras.Input(shape=()), 'bar': tf.keras.Input(shape=()), 'baz': tf.keras.Input(shape=()), } # Inputs are typically transformed by preprocessing layers before concatenation. outputs = tf.keras.layers.Concatenate()(inputs.values()) model = tf.keras.Model(inputs=inputs, outputs=outputs) model(input_dict)

One-hot encoding de IDs inteiros

Uma transformação de características comum é a one-hot encoding de entradas inteiras dentro um intervalo conhecido. Aqui está um exemplo usando colunas de características:

categorical_col = tf1.feature_column.categorical_column_with_identity( 'type', num_buckets=3) indicator_col = tf1.feature_column.indicator_column(categorical_col) call_feature_columns(indicator_col, {'type': [0, 1, 2]})

Usando camadas de pré-processamento Keras, essas colunas podem ser substituídas por uma única camada tf.keras.layers.CategoryEncoding com output_mode definido como 'one_hot':

one_hot_layer = tf.keras.layers.CategoryEncoding( num_tokens=3, output_mode='one_hot') one_hot_layer([0, 1, 2])

Observação: para grandes codificações one-hot, é muito mais eficiente usar uma representação esparsa da saída. Se você passar sparse=True para a camada CategoryEncoding, a saída da camada será um tf.sparse.SparseTensor, que pode ser tratado com eficiência como entrada para uma camada tf.keras.layers.Dense.

Normalizando características numéricas

Ao lidar com características contínuas, de ponto flutuante com colunas de características, você precisa usar um tf.feature_column.numeric_column. No caso em que a entrada já está normalizada, convertê-la em Keras é trivial. Você pode simplesmente usar um tf.keras.Input diretamente no seu modelo, conforme mostrado acima.

Uma numeric_column também pode ser usada para normalizar a entrada:

def normalize(x): mean, variance = (2.0, 1.0) return (x - mean) / math.sqrt(variance) numeric_col = tf1.feature_column.numeric_column('col', normalizer_fn=normalize) call_feature_columns(numeric_col, {'col': tf.constant([[0.], [1.], [2.]])})

Em contraste, com o Keras, essa normalização pode ser feita com tf.keras.layers.Normalization.

normalization_layer = tf.keras.layers.Normalization(mean=2.0, variance=1.0) normalization_layer(tf.constant([[0.], [1.], [2.]]))

Bucketing e one-hot encoding de características numéricas

Outra transformação comum de entradas contínuas de ponto flutuante é fazer bucketing para inteiros de um intervalo fixo com elas.

Em colunas de características, isto pode ser obtido com um tf.feature_column.bucketized_column:

numeric_col = tf1.feature_column.numeric_column('col') bucketized_col = tf1.feature_column.bucketized_column(numeric_col, [1, 4, 5]) call_feature_columns(bucketized_col, {'col': tf.constant([1., 2., 3., 4., 5.])})

No Keras, isto pode ser substituído por tf.keras.layers.Discretization:

discretization_layer = tf.keras.layers.Discretization(bin_boundaries=[1, 4, 5]) one_hot_layer = tf.keras.layers.CategoryEncoding( num_tokens=4, output_mode='one_hot') one_hot_layer(discretization_layer([1., 2., 3., 4., 5.]))

Fazendo one-hot encoding de dados de string com um vocabulário

A manipulação de características de string geralmente requer uma pesquisa de vocabulário para traduzir strings em índices. Aqui está um exemplo usando colunas de características para pesquisar strings e, em seguida, fazer one-hot encoding dos índices:

vocab_col = tf1.feature_column.categorical_column_with_vocabulary_list( 'sizes', vocabulary_list=['small', 'medium', 'large'], num_oov_buckets=0) indicator_col = tf1.feature_column.indicator_column(vocab_col) call_feature_columns(indicator_col, {'sizes': ['small', 'medium', 'large']})

Usando camadas de pré-processamento Keras, use a camada tf.keras.layers.StringLookup com output_mode definido como 'one_hot':

string_lookup_layer = tf.keras.layers.StringLookup( vocabulary=['small', 'medium', 'large'], num_oov_indices=0, output_mode='one_hot') string_lookup_layer(['small', 'medium', 'large'])

Observação: para grandes one-hot encodings, é muito mais eficiente usar uma representação esparsa da saída. Se você passar sparse=True para a camada StringLookup, a saída da camada será um tf.sparse.SparseTensor, que pode ser tratado com eficiência como entrada para uma camada tf.keras.layers.Dense.

Embedding de dados de string com um vocabulário

Para vocabulários maiores, geralmente é necessário incorporar um embedding para um bom desempenho. Aqui está um exemplo de embedding de uma característica de string usando colunas de características:

vocab_col = tf1.feature_column.categorical_column_with_vocabulary_list( 'col', vocabulary_list=['small', 'medium', 'large'], num_oov_buckets=0) embedding_col = tf1.feature_column.embedding_column(vocab_col, 4) call_feature_columns(embedding_col, {'col': ['small', 'medium', 'large']})

Usando camadas de pré-processamento Keras, isso pode ser obtido combinando uma camada tf.keras.layers.StringLookup e uma camada tf.keras.layers.Embedding. A saída padrão para o StringLookup serão índices inteiros que podem ser alimentados diretamente num embedding.

Observação: a camada Embedding contém parâmetros treináveis. Embora a camada StringLookup possa ser aplicada a dados dentro ou fora de um modelo, a Embedding deve sempre fazer parte de um modelo Keras treinável para que possa ser treinada corretamente.

string_lookup_layer = tf.keras.layers.StringLookup( vocabulary=['small', 'medium', 'large'], num_oov_indices=0) embedding = tf.keras.layers.Embedding(3, 4) embedding(string_lookup_layer(['small', 'medium', 'large']))

Soma de dados categóricos ponderados

Em alguns casos, você precisará lidar com dados categóricos onde cada ocorrência de uma categoria vem com um peso associado. Em colunas de características, isto é tratado com tf.feature_column.weighted_categorical_column. Quando emparelhado com um indicator_column, isto tem o efeito de somar os pesos por categoria.

ids = tf.constant([[5, 11, 5, 17, 17]]) weights = tf.constant([[0.5, 1.5, 0.7, 1.8, 0.2]]) categorical_col = tf1.feature_column.categorical_column_with_identity( 'ids', num_buckets=20) weighted_categorical_col = tf1.feature_column.weighted_categorical_column( categorical_col, 'weights') indicator_col = tf1.feature_column.indicator_column(weighted_categorical_col) call_feature_columns(indicator_col, {'ids': ids, 'weights': weights})

No Keras, isto pode ser feito passando uma entrada count_weights para tf.keras.layers.CategoryEncoding com output_mode='count'.

ids = tf.constant([[5, 11, 5, 17, 17]]) weights = tf.constant([[0.5, 1.5, 0.7, 1.8, 0.2]]) # Using sparse output is more efficient when `num_tokens` is large. count_layer = tf.keras.layers.CategoryEncoding( num_tokens=20, output_mode='count', sparse=True) tf.sparse.to_dense(count_layer(ids, count_weights=weights))

Embedding de dados categóricos ponderados

Alternativamente, você talvez queira fazer um embedding de entradas categóricas ponderadas. Em colunas de características, a embedding_column contém um argumento combiner. Se qualquer amostra contiver múltiplas entradas para uma categoria, elas serão combinadas de acordo com a configuração do argumento (por padrão 'mean').

ids = tf.constant([[5, 11, 5, 17, 17]]) weights = tf.constant([[0.5, 1.5, 0.7, 1.8, 0.2]]) categorical_col = tf1.feature_column.categorical_column_with_identity( 'ids', num_buckets=20) weighted_categorical_col = tf1.feature_column.weighted_categorical_column( categorical_col, 'weights') embedding_col = tf1.feature_column.embedding_column( weighted_categorical_col, 4, combiner='mean') call_feature_columns(embedding_col, {'ids': ids, 'weights': weights})

No Keras, não existe opção combiner para tf.keras.layers.Embedding, mas você consegue o mesmo efeito com tf.keras.layers.Dense. A embedding_column acima é simplesmente uma combinação linear de vetores de embedding de acordo com o peso da categoria. Embora não seja óbvio a princípio, é a mesma coisa que representar suas entradas categóricas como um vetor de peso esparso de tamanho (num_tokens) e multiplicá-los por um kernel Dense de formato (embedding_size, num_tokens).

ids = tf.constant([[5, 11, 5, 17, 17]]) weights = tf.constant([[0.5, 1.5, 0.7, 1.8, 0.2]]) # For `combiner='mean'`, normalize your weights to sum to 1. Removing this line # would be equivalent to an `embedding_column` with `combiner='sum'`. weights = weights / tf.reduce_sum(weights, axis=-1, keepdims=True) count_layer = tf.keras.layers.CategoryEncoding( num_tokens=20, output_mode='count', sparse=True) embedding_layer = tf.keras.layers.Dense(4, use_bias=False) embedding_layer(count_layer(ids, count_weights=weights))

Exemplo de treinamento completo

Para mostrar um workflow de treinamento completo, primeiro prepare alguns dados com três características de tipos diferentes:

features = { 'type': [0, 1, 1], 'size': ['small', 'small', 'medium'], 'weight': [2.7, 1.8, 1.6], } labels = [1, 1, 0] predict_features = {'type': [0], 'size': ['foo'], 'weight': [-0.7]}

Defina algumas constantes comuns para os workflows do TensorFlow 1 e do TensorFlow 2:

vocab = ['small', 'medium', 'large'] one_hot_dims = 3 embedding_dims = 4 weight_mean = 2.0 weight_variance = 1.0

Com colunas de características

As colunas de características devem ser passadas como uma lista para o estimador na criação e serão chamadas implicitamente durante o treinamento.

categorical_col = tf1.feature_column.categorical_column_with_identity( 'type', num_buckets=one_hot_dims) # Convert index to one-hot; e.g. [2] -> [0,0,1]. indicator_col = tf1.feature_column.indicator_column(categorical_col) # Convert strings to indices; e.g. ['small'] -> [1]. vocab_col = tf1.feature_column.categorical_column_with_vocabulary_list( 'size', vocabulary_list=vocab, num_oov_buckets=1) # Embed the indices. embedding_col = tf1.feature_column.embedding_column(vocab_col, embedding_dims) normalizer_fn = lambda x: (x - weight_mean) / math.sqrt(weight_variance) # Normalize the numeric inputs; e.g. [2.0] -> [0.0]. numeric_col = tf1.feature_column.numeric_column( 'weight', normalizer_fn=normalizer_fn) estimator = tf1.estimator.DNNClassifier( feature_columns=[indicator_col, embedding_col, numeric_col], hidden_units=[1]) def _input_fn(): return tf1.data.Dataset.from_tensor_slices((features, labels)).batch(1) estimator.train(_input_fn)

As colunas de características também serão usadas para transformar os dados de entrada ao executar a inferência no modelo.

def _predict_fn(): return tf1.data.Dataset.from_tensor_slices(predict_features).batch(1) next(estimator.predict(_predict_fn))

Com camadas de pré-processamento do Keras

As camadas de pré-processamento do Keras são mais flexíveis quanto a onde podem ser chamadas. Uma camada pode ser aplicada diretamente a tensores, usada dentro de um pipeline de entrada tf.data ou construída diretamente em um modelo Keras treinável.

Neste exemplo, você aplicará camadas de pré-processamento dentro de um pipeline de entrada tf.data. Para fazer isso, você pode definir um tf.keras.Model separado para pré-processar suas características de entrada. Este modelo não é treinável, mas é uma maneira conveniente de agrupar as camadas de pré-processamento.

inputs = { 'type': tf.keras.Input(shape=(), dtype='int64'), 'size': tf.keras.Input(shape=(), dtype='string'), 'weight': tf.keras.Input(shape=(), dtype='float32'), } # Convert index to one-hot; e.g. [2] -> [0,0,1]. type_output = tf.keras.layers.CategoryEncoding( one_hot_dims, output_mode='one_hot')(inputs['type']) # Convert size strings to indices; e.g. ['small'] -> [1]. size_output = tf.keras.layers.StringLookup(vocabulary=vocab)(inputs['size']) # Normalize the numeric inputs; e.g. [2.0] -> [0.0]. weight_output = tf.keras.layers.Normalization( axis=None, mean=weight_mean, variance=weight_variance)(inputs['weight']) outputs = { 'type': type_output, 'size': size_output, 'weight': weight_output, } preprocessing_model = tf.keras.Model(inputs, outputs)

Observação: Como alternativa para fornecer um vocabulário e estatísticas de normalização na criação de camadas, muitas camadas de pré-processamento fornecem um método adapt() para aprender o estado da camada diretamente dos dados de entrada. Consulte o guia de pré-processamento para obter mais detalhes.

Agora você pode aplicar esse modelo dentro de uma chamada para tf.data.Dataset.map. Observe que a função passada para map será convertida automaticamente em uma tf.function, e as advertências usuais para escrever o código tf.function se aplicam (sem efeitos colaterais).

# Apply the preprocessing in tf.data.Dataset.map. dataset = tf.data.Dataset.from_tensor_slices((features, labels)).batch(1) dataset = dataset.map(lambda x, y: (preprocessing_model(x), y), num_parallel_calls=tf.data.AUTOTUNE) # Display a preprocessed input sample. next(dataset.take(1).as_numpy_iterator())

Em seguida, você pode definir um Model separado contendo as camadas treináveis. Observe como as entradas para este modelo agora refletem os tipos e formatos de características pré-processadas.

inputs = { 'type': tf.keras.Input(shape=(one_hot_dims,), dtype='float32'), 'size': tf.keras.Input(shape=(), dtype='int64'), 'weight': tf.keras.Input(shape=(), dtype='float32'), } # Since the embedding is trainable, it needs to be part of the training model. embedding = tf.keras.layers.Embedding(len(vocab), embedding_dims) outputs = tf.keras.layers.Concatenate()([ inputs['type'], embedding(inputs['size']), tf.expand_dims(inputs['weight'], -1), ]) outputs = tf.keras.layers.Dense(1)(outputs) training_model = tf.keras.Model(inputs, outputs)

Agora você pode treinar o training_model com tf.keras.Model.fit.

# Train on the preprocessed data. training_model.compile( loss=tf.keras.losses.BinaryCrossentropy(from_logits=True)) training_model.fit(dataset)

Por fim, no momento da inferência, pode ser útil combinar esses estágios separados em um único modelo que lide com entradas de características brutas.

inputs = preprocessing_model.input outputs = training_model(preprocessing_model(inputs)) inference_model = tf.keras.Model(inputs, outputs) predict_dataset = tf.data.Dataset.from_tensor_slices(predict_features).batch(1) inference_model.predict(predict_dataset)

Este modelo composto pode ser salvo como um arquivo .keras para uso posterior.

inference_model.save('model.keras') restored_model = tf.keras.models.load_model('model.keras') restored_model.predict(predict_dataset)

Observação: as camadas de pré-processamento não podem ser treinadas, o que permite aplicá-las de forma assíncrona usando tf.data. Isso traz benefícios de desempenho, pois você pode pré-buscar lotes pré-processados ​​e liberar quaisquer aceleradores para se concentrar nas partes diferenciáveis ​​de um modelo (saiba mais na seção Pré-busca do guia Melhor desempenho com a API tf.data). Como mostra este guia, separar o pré-processamento durante o treinamento e compô-lo durante a inferência é uma maneira flexível de aproveitar esses ganhos de desempenho. No entanto, se o seu modelo for pequeno ou o tempo de pré-processamento for insignificante, pode ser mais simples fazer o embedding do pré-processamento num modelo completo desde o início. Para fazer isso, você pode criar um único modelo começando com tf.keras.Input, seguido por camadas de pré-processamento, seguidas por camadas treináveis.

Tabela de equivalência de colunas de características

Como referência, aqui está uma correspondência aproximada entre colunas de características e camadas de pré-processamento Keras:

Coluna de características Camada Keras
`tf.feature_column.bucketized_column` `tf.keras.layers.Discretization`
`tf.feature_column.categorical_column_with_hash_bucket` `tf.keras.layers.Hashing`
`tf.feature_column.categorical_column_with_identity` `tf.keras.layers.CategoryEncoding`
`tf.feature_column.categorical_column_with_vocabulary_file` `tf.keras.layers.StringLookup` ou `tf.keras.layers.IntegerLookup`
`tf.feature_column.categorical_column_with_vocabulary_list` `tf.keras.layers.StringLookup` ou `tf.keras.layers.IntegerLookup`
`tf.feature_column.crossed_column` `tf.keras.layers.experimental.preprocessing.HashedCrossing`
`tf.feature_column.embedding_column` `tf.keras.layers.Embedding`
`tf.feature_column.indicator_column` `output_mode='one_hot'` ou `output_mode='multi_hot'`*
`tf.feature_column.numeric_column` `tf.keras.layers.Normalization`
`tf.feature_column.sequence_categorical_column_with_hash_bucket` `tf.keras.layers.Hashing`
`tf.feature_column.sequence_categorical_column_with_identity` `tf.keras.layers.CategoryEncoding`
`tf.feature_column.sequence_categorical_column_with_vocabulary_file` `tf.keras.layers.StringLookup`, `tf.keras.layers.IntegerLookup`, ou `tf.keras.layer.TextVectorization`†
`tf.feature_column.sequence_categorical_column_with_vocabulary_list` `tf.keras.layers.StringLookup`, `tf.keras.layers.IntegerLookup`, ou `tf.keras.layer.TextVectorization`†
`tf.feature_column.sequence_numeric_column` `tf.keras.layers.Normalization`
`tf.feature_column.weighted_categorical_column` `tf.keras.layers.CategoryEncoding`
  • O output_mode pode ser passado para tf.keras.layers.CategoryEncoding, tf.keras.layers.StringLookup, tf.keras.layers.IntegerLookup e tf.keras.layers.TextVectorization.

tf.keras.layers.TextVectorization pode manipular entrada de texto de forma livre diretamente (por exemplo, frases inteiras ou parágrafos). Isto não é uma substituição um-para-um para manipulação de sequências categóricas no TensorFlow 1, mas pode oferecer uma substituição conveniente para o pré-processamento de texto ad-hoc.

Observação: os estimadores lineares, como tf.estimator.LinearClassifier, podem lidar com entrada categórica direta (índices inteiros) sem uma embedding_column ou indicator_column. No entanto, os índices inteiros não podem ser passados ​​diretamente para tf.keras.layers.Dense ou tf.keras.experimental.LinearModel. Essas entradas devem ser primeiro codificadas com tf.layers.CategoryEncoding com output_mode='count' (e sparse=True se os tamanhos das categorias forem grandes) antes de chamar Dense ou LinearModel.

Próximos passos