Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/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.

El entrenamiento de un modelo suele ir acompañado de una cierta cantidad de preprocesamiento de características, sobre todo cuando se trata de datos estructurados. Cuando se entrena un tf.estimator.Estimator en TensorFlow 1, normalmente se realiza el preprocesamiento de características con la API tf.feature_column. En TensorFlow 2, puede hacerlo directamente con las capas de preprocesamiento de Keras.

Esta guía de migración demuestra las transformaciones comunes de las características usando ambas columnas de características y capas de preprocesamiento, seguidas del entrenamiento de un modelo completo con ambas API.

En primer lugar, comience con un par de imports necesarios:

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

A continuación, añada una función de servicio para llamar a una columna de características para demostración:

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)

Manejo de entradas

Para usar columnas de características con un estimador, siempre se espera que las entradas del modelo sean un diccionario de tensores:

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

Cada columna de características debe crearse con una clave para indexarla en los datos de origen. La salida de todas las columnas de características se concatena y es usada por el modelo de estimación.

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)

En Keras, la entrada del modelo es mucho más flexible. Un tf.keras.Model puede manejar una única entrada de tensor, una lista de características de tensor o un diccionario de características de tensor. Usted puede manejar la entrada de diccionario pasando un diccionario de tf.keras.Input en la creación del modelo. Las entradas no se concatenarán automáticamente, lo que permite usarlas de formas mucho más flexibles. Pueden concatenarse con 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)

Codificación de un solo paso de ID enteros

Una transformación de características común es la codificación de un solo paso de entradas enteras de un rango conocido. He aquí un ejemplo usando columnas 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 capas de preprocesamiento Keras, estas columnas pueden ser sustituidas por una única capa tf.keras.layers.CategoryEncoding con output_mode configurado como 'one_hot':

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

Nota: Para codificaciones grandes de un solo paso, es mucho más eficiente usar una representación sparse de la salida. Si pasa sparse=True a la capa CategoryEncoding, la salida de la capa será un tf.sparse.SparseTensor, que puede manejarse eficientemente como entrada a una capa tf.keras.layers.Dense.

Normalización de características numéricas

Cuando maneje características continuas de punto flotante con columnas de características, deberá usar una tf.feature_column.numeric_column. En caso de que la entrada ya esté normalizada, convertir esto en Keras es trivial. Puede simplemente usar un tf.keras.Input directamente en su modelo, como se muestra arriba.

También se puede usar una numeric_column para normalizar la 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.]])})

En cambio, con Keras, esta normalización puede realizarse con tf.keras.layers.Normalization.

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

Cubicación y codificación de características numéricas en un solo paso

Otra transformación común de entradas continuas en punto flotante es cubicarlas a continuación a enteros de un rango fijo.

En las columnas de características, esto puede lograrse con una 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.])})

En Keras, esto puede ser sustituido 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.]))

Codificación de un solo paso para datos de cadena con un vocabulario

El manejo de características de cadena a menudo requiere una búsqueda de vocabulario para traducir las cadenas en índices. Aquí tiene un ejemplo en el que se usan columnas de características para buscar cadenas y luego codificar los índices de un solo paso:

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 capas de preprocesamiento Keras, use la capa tf.keras.layers.StringLookup con output_mode configurado a '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'])

Nota: Para codificaciones grandes de un solo paso, es mucho más eficiente usar una representación dispersa de la salida. Si pasa sparse=True a la capa StringLookup, la salida de la capa será un tf.sparse.SparseTensor, que puede manejarse eficientemente como entrada a una capa tf.keras.layers.Dense.

Incorporación de datos de cadena con un vocabulario

En el caso de vocabularios más extensos, suele ser necesaria una incorporación para obtener un buen rendimiento. Aquí tiene un ejemplo de incorporación de una característica de cadena usando columnas 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 capas de preprocesamiento Keras, esto se puede conseguir combinando una capa tf.keras.layers.StringLookup y una capa tf.keras.layers.Embedding. La salida por default de la capa StringLookup serán índices enteros que pueden introducirse directamente en una incorporación.

Nota: La capa Embedding contiene parámetros entrenables. Mientras que la capa StringLookup puede aplicarse a datos dentro o fuera de un modelo, la capa Embedding siempre debe formar parte de un modelo Keras entrenable a fin de entrenar correctamente.

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

Suma de datos categóricos ponderados

En algunos casos, es necesario tratar datos categóricos en los que cada aparición de una categoría lleva asociada una ponderación. En las columnas de características, esto se gestiona con tf.feature_column.weighted_categorical_column. Cuando se combina con una indicator_column, tiene el efecto de sumar las ponderaciones por categoría.

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

En Keras, esto puede hacerse pasando una entrada count_weights a tf.keras.layers.CategoryEncoding con 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))

Incorporación de datos categóricos ponderados

Es posible que, alternativamente, desee incorporar entradas categóricas ponderadas. En las columnas de características, la embedding_column contiene un argumento combiner. Si algún muestreo contiene varias entradas para una categoría, se combinarán según lo establecido en el argumento (por defecto '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})

En Keras, no existe la opción combiner para tf.keras.layers.Embedding, pero puede conseguir el mismo efecto con tf.keras.layers.Dense. La embedding_column anterior es simplemente la combinación lineal de vectores de incorporación según la ponderación de la categoría. Aunque no resulte obvio a primera vista, es exactamente equivalente a representar sus entradas categóricas como un vector de ponderación disperso de tamaño (num_tokens), y multiplicarlos por un kernel Dense de forma (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))

Ejemplo de entrenamiento completo

Para mostrar un flujo de trabajo de entrenamiento completo, prepare primero algunos datos con tres características de distintos tipos:

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 algunas constantes comunes para los flujos de trabajo TensorFlow 1 y TensorFlow 2:

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

Con columnas de características

Las columnas de características deben pasarse como una lista al estimador en el momento de la creación, y se llamarán implícitamente durante el entrenamiento.

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)

Las columnas de características también se usarán para transformar los datos de entrada cuando se ejecute la inferencia en el modelo.

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

Con las capas de preprocesamiento Keras

Las capas Keras de preprocesamiento son más flexibles en cuanto a dónde pueden ser llamadas. Una capa puede aplicarse directamente a los tensores, usarse dentro de una canalización de entrada tf.data, o integrarse directamente en un modelo Keras entrenable.

En este ejemplo, aplicará capas de preprocesamiento dentro de una canalización de entrada tf.data. Para ello, puede definir un tf.keras.Model separado para preprocesar sus características de entrada. Este modelo no es entrenable, pero es una forma conveniente de agrupar las capas de preprocesamiento.

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)

Nota: Como alternativa a suministrar un vocabulario y estadísticas de normalización en la creación de capas, muchas capas de preprocesamiento ofrecen un método adapt() para aprender el estado de la capa directamente a partir de los datos de entrada. Consulte la guía de preprocesamiento para más detalles.

Ahora puede aplicar este modelo dentro de una llamada a tf.data.Dataset.map. Tenga en cuenta que la función pasada a map se convertirá automáticamente en una tf.function, y se aplicarán las advertencias habituales para escribir código tf.function (sin efectos secundarios).

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

A continuación, puede definir un Model independiente que contenga las capas entrenables. Tenga en cuenta cómo las entradas a este modelo reflejan ahora los tipos de características y formas preprocesadas.

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)

Ahora puede entrenar el training_model con 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 último, en el momento de la inferencia, puede ser útil combinar estas etapas separadas en un único modelo que maneje las 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 compuesto puede guardarse como archivo .keras para usarlo posteriormente.

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

Nota: Las capas de preprocesamiento no son entrenables, lo que le permite aplicarlas asincrónicamente usando tf.data. Esto tiene beneficios para el rendimiento, ya que puede preextraer lotes preprocesados y liberar cualquier acelerador para que se enfoque en las partes diferenciables de un modelo (más información en la sección Preextracción de la guía Mejor rendimiento con la API tf.data). Como muestra esta guía, separar el preprocesamiento durante el entrenamiento y componerlo durante la inferencia es una forma flexible de aprovechar estas ganancias de rendimiento. Sin embargo, si su modelo es pequeño o el tiempo de preprocesamiento es insignificante, puede ser más sencillo construir el preprocesamiento en un modelo completo desde el principio. Para ello, puede construir un modelo único que comience con tf.keras.Input, seguido de las capas de preprocesamiento, seguidas de las capas entrenables.

Tabla de equivalencias de columnas de características

A modo de referencia, he aquí una correspondencia aproximada entre las columnas de características y las capas de preprocesamiento de Keras:

Columna de características Capas 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` o `tf.keras.layers.IntegerLookup`
`tf.feature_column.categorical_column_with_vocabulary_list` `tf.keras.layers.StringLookup` o `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'` o `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`, o `tf.keras.layer.TextVectorization`†
`tf.feature_column.sequence_categorical_column_with_vocabulary_list` `tf.keras.layers.StringLookup`, `tf.keras.layers.IntegerLookup`, o `tf.keras.layer.TextVectorization`†
`tf.feature_column.sequence_numeric_column` `tf.keras.layers.Normalization`
`tf.feature_column.weighted_categorical_column` `tf.keras.layers.CategoryEncoding`
  • El output_mode puede pasarse a tf.keras.layers.CategoryEncoding, tf.keras.layers.StringLookup, tf.keras.layers.IntegerLookup, y tf.keras.layers.TextVectorization.

tf.keras.layers.TextVectorization puede manejar directamente la entrada de texto de forma libre (por ejemplo, frases o párrafos enteros). Esto no es un reemplazo uno-a-uno para el manejo de secuencias categóricas en TensorFlow 1, pero puede ofrecer un reemplazo conveniente para el preprocesamiento de texto ad-hoc.

Nota: Los estimadores lineales, como tf.estimator.LinearClassifier, pueden manejar entradas categóricas directas (índices enteros) sin una embedding_column o indicator_column. Sin embargo, los índices enteros no pueden pasarse directamente a tf.keras.layers.Dense o tf.keras.experimental.LinearModel. Estas entradas deben codificarse primero con tf.layers.CategoryEncoding con output_mode='count' (y sparse=True si el tamaño de las categorías es grande) antes de llamar a Dense o LinearModel.

Siguientes pasos