Ver en TensorFlow.org | Ejecutar en Google Colab | Ver fuente en GitHub | Descargar libreta |
Este tutorial demuestra cómo clasificar datos estructurados (por ejemplo, datos tabulares en un CSV). Usaremos Keras para definir el modelo y tf.feature_column
como un puente para mapear desde las columnas en un CSV a las características utilizadas para entrenar el modelo. Este tutorial contiene código completo para:
- Cargue un archivo CSV usando Pandas .
- Cree una canalización de entrada para procesar por lotes y mezclar las filas con tf.data .
- Asigne desde columnas en el CSV a funciones utilizadas para entrenar el modelo usando columnas de funciones.
- Cree, entrene y evalúe un modelo con Keras.
El conjunto de datos
Usaremos una versión simplificada del conjunto de datos de PetFinder. Hay varios miles de filas en el CSV. Cada fila describe una mascota y cada columna describe un atributo. Usaremos esta información para predecir la velocidad a la que se adoptará la mascota.
A continuación se muestra una descripción de este conjunto de datos. Observe que hay columnas numéricas y categóricas. Hay una columna de texto libre que no usaremos en este tutorial.
Columna | Descripción | Tipo de función | Tipo de datos |
---|---|---|---|
Escribe | Tipo de animal (Perro, Gato) | Categórico | cuerda |
Envejecer | edad de la mascota | Numérico | entero |
raza1 | Raza primaria de la mascota. | Categórico | cuerda |
Color1 | Color 1 de mascota | Categórico | cuerda |
Color2 | Color 2 de mascota | Categórico | cuerda |
MadurezTamaño | Tamaño en la madurez | Categórico | cuerda |
Longitud del pelaje | Longitud de la piel | Categórico | cuerda |
vacunado | La mascota ha sido vacunada. | Categórico | cuerda |
Esterilizado | La mascota ha sido esterilizada. | Categórico | cuerda |
Salud | Estado de salud | Categórico | cuerda |
Tarifa | Tarifa de adopción | Numérico | entero |
Descripción | Redacción de perfil para esta mascota | Texto | cuerda |
PhotoAmt | Total de fotos subidas para esta mascota | Numérico | entero |
Velocidad de adopción | Velocidad de adopción | Clasificación | entero |
Importar TensorFlow y otras bibliotecas
pip install sklearn
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import feature_column
from tensorflow.keras import layers
from sklearn.model_selection import train_test_split
Use Pandas para crear un marco de datos
Pandas es una biblioteca de Python con muchas utilidades útiles para cargar y trabajar con datos estructurados. Usaremos Pandas para descargar el conjunto de datos desde una URL y cargarlo en un marco de datos.
import pathlib
dataset_url = 'http://storage.googleapis.com/download.tensorflow.org/data/petfinder-mini.zip'
csv_file = 'datasets/petfinder-mini/petfinder-mini.csv'
tf.keras.utils.get_file('petfinder_mini.zip', dataset_url,
extract=True, cache_dir='.')
dataframe = pd.read_csv(csv_file)
Downloading data from http://storage.googleapis.com/download.tensorflow.org/data/petfinder-mini.zip 1671168/1668792 [==============================] - 0s 0us/step 1679360/1668792 [==============================] - 0s 0us/step
dataframe.head()
Crear variable objetivo
La tarea en el conjunto de datos original es predecir la velocidad a la que se adoptará una mascota (por ejemplo, en la primera semana, el primer mes, los primeros tres meses, etc.). Simplifiquemos esto para nuestro tutorial. Aquí, transformaremos esto en un problema de clasificación binaria y simplemente predeciremos si la mascota fue adoptada o no.
Después de modificar la columna de la etiqueta, 0 indicará que la mascota no fue adoptada y 1 indicará que sí.
# In the original dataset "4" indicates the pet was not adopted.
dataframe['target'] = np.where(dataframe['AdoptionSpeed']==4, 0, 1)
# Drop un-used columns.
dataframe = dataframe.drop(columns=['AdoptionSpeed', 'Description'])
Dividir el marco de datos en tren, validación y prueba
El conjunto de datos que descargamos era un único archivo CSV. Dividiremos esto en conjuntos de entrenamiento, validación y prueba.
train, test = train_test_split(dataframe, test_size=0.2)
train, val = train_test_split(train, test_size=0.2)
print(len(train), 'train examples')
print(len(val), 'validation examples')
print(len(test), 'test examples')
7383 train examples 1846 validation examples 2308 test examples
Cree una tubería de entrada usando tf.data
A continuación, envolveremos los marcos de datos con tf.data . Esto nos permitirá usar columnas de características como un puente para mapear desde las columnas en el marco de datos de Pandas a las características utilizadas para entrenar el modelo. Si estuviéramos trabajando con un archivo CSV muy grande (tan grande que no cabe en la memoria), usaríamos tf.data para leerlo directamente desde el disco. Eso no está cubierto en este tutorial.
# A utility method to create a tf.data dataset from a Pandas Dataframe
def df_to_dataset(dataframe, shuffle=True, batch_size=32):
dataframe = dataframe.copy()
labels = dataframe.pop('target')
ds = tf.data.Dataset.from_tensor_slices((dict(dataframe), labels))
if shuffle:
ds = ds.shuffle(buffer_size=len(dataframe))
ds = ds.batch(batch_size)
return ds
batch_size = 5 # A small batch sized is used for demonstration purposes
train_ds = df_to_dataset(train, batch_size=batch_size)
val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)
Comprender la canalización de entrada
Ahora que hemos creado la canalización de entrada, llamémosla para ver el formato de los datos que devuelve. Hemos utilizado un tamaño de lote pequeño para mantener la salida legible.
for feature_batch, label_batch in train_ds.take(1):
print('Every feature:', list(feature_batch.keys()))
print('A batch of ages:', feature_batch['Age'])
print('A batch of targets:', label_batch )
Every feature: ['Type', 'Age', 'Breed1', 'Gender', 'Color1', 'Color2', 'MaturitySize', 'FurLength', 'Vaccinated', 'Sterilized', 'Health', 'Fee', 'PhotoAmt'] A batch of ages: tf.Tensor([ 6 2 36 2 2], shape=(5,), dtype=int64) A batch of targets: tf.Tensor([1 1 1 1 1], shape=(5,), dtype=int64)
Podemos ver que el conjunto de datos devuelve un diccionario de nombres de columnas (del marco de datos) que se asignan a valores de columna de filas en el marco de datos.
Demostrar varios tipos de columnas de características
TensorFlow proporciona muchos tipos de columnas de funciones. En esta sección, crearemos varios tipos de columnas de características y demostraremos cómo transforman una columna del marco de datos.
# We will use this batch to demonstrate several types of feature columns
example_batch = next(iter(train_ds))[0]
# A utility method to create a feature column
# and to transform a batch of data
def demo(feature_column):
feature_layer = layers.DenseFeatures(feature_column)
print(feature_layer(example_batch).numpy())
Columnas numéricas
La salida de una columna de características se convierte en la entrada del modelo (usando la función de demostración definida anteriormente, podremos ver exactamente cómo se transforma cada columna del marco de datos). Una columna numérica es el tipo de columna más simple. Se utiliza para representar características de valor real. Al usar esta columna, su modelo recibirá el valor de la columna del marco de datos sin cambios.
photo_count = feature_column.numeric_column('PhotoAmt')
demo(photo_count)
[[2.] [4.] [4.] [1.] [2.]]
En el conjunto de datos de PetFinder, la mayoría de las columnas del marco de datos son categóricas.
Columnas divididas en cubos
A menudo, no desea ingresar un número directamente en el modelo, sino dividir su valor en diferentes categorías según los rangos numéricos. Considere los datos sin procesar que representan la edad de una persona. En lugar de representar la edad como una columna numérica, podríamos dividir la edad en varios segmentos usando una columna segmentada . Observe que los valores únicos a continuación describen qué rango de edad coincide con cada fila.
age = feature_column.numeric_column('Age')
age_buckets = feature_column.bucketized_column(age, boundaries=[1, 3, 5])
demo(age_buckets)
[[0. 0. 0. 1.] [0. 1. 0. 0.] [0. 0. 0. 1.] [0. 0. 1. 0.] [0. 1. 0. 0.]]
Columnas categóricas
En este conjunto de datos, Tipo se representa como una cadena (por ejemplo, 'Perro' o 'Gato'). No podemos alimentar cadenas directamente a un modelo. En cambio, primero debemos asignarlos a valores numéricos. Las columnas de vocabulario categórico proporcionan una manera de representar cadenas como un vector único (muy parecido a lo que ha visto anteriormente con los cubos de edad). El vocabulario se puede pasar como una lista usando categorical_column_with_vocabulary_list , o cargar desde un archivo usando categorical_column_with_vocabulary_file .
animal_type = feature_column.categorical_column_with_vocabulary_list(
'Type', ['Cat', 'Dog'])
animal_type_one_hot = feature_column.indicator_column(animal_type)
demo(animal_type_one_hot)
[[1. 0.] [1. 0.] [1. 0.] [1. 0.] [0. 1.]]
Columnas incrustadas
Supongamos que en lugar de tener solo unas pocas cadenas posibles, tenemos miles (o más) valores por categoría. Por varias razones, a medida que aumenta la cantidad de categorías, se vuelve inviable entrenar una red neuronal utilizando codificaciones one-hot. Podemos usar una columna de incrustación para superar esta limitación. En lugar de representar los datos como un vector único de muchas dimensiones, una columna incrustada representa esos datos como un vector denso de menor dimensión en el que cada celda puede contener cualquier número, no solo 0 o 1. El tamaño de la incrustación ( 8, en el ejemplo siguiente) es un parámetro que debe ajustarse.
# Notice the input to the embedding column is the categorical column
# we previously created
breed1 = feature_column.categorical_column_with_vocabulary_list(
'Breed1', dataframe.Breed1.unique())
breed1_embedding = feature_column.embedding_column(breed1, dimension=8)
demo(breed1_embedding)
[[-0.22380038 -0.09379731 0.21349265 0.33451992 -0.49730566 0.05174963 0.2668497 0.27391028] [-0.5484653 -0.03492585 0.05648395 -0.09792244 0.02530896 -0.15477926 -0.10695003 -0.45474145] [-0.22380038 -0.09379731 0.21349265 0.33451992 -0.49730566 0.05174963 0.2668497 0.27391028] [ 0.10050306 0.43513173 0.375823 0.5652766 0.40925583 -0.03928828 0.4901914 0.20637617] [-0.2319875 -0.21874283 0.12272807 0.33345345 -0.4563055 0.21609035 -0.2410521 0.4736915 ]]
Columnas de características con hash
Otra forma de representar una columna categórica con una gran cantidad de valores es usar categorical_column_with_hash_bucket . Esta columna de características calcula un valor hash de la entrada, luego selecciona uno de los cubos hash_bucket_size
para codificar una cadena. Al usar esta columna, no es necesario que proporcione el vocabulario y puede optar por hacer que la cantidad de hash_buckets sea significativamente menor que la cantidad de categorías reales para ahorrar espacio.
breed1_hashed = feature_column.categorical_column_with_hash_bucket(
'Breed1', hash_bucket_size=10)
demo(feature_column.indicator_column(breed1_hashed))
[[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.] [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.] [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]]
Columnas de características cruzadas
La combinación de características en una sola característica, más conocida como cruces de características , permite que un modelo aprenda pesos separados para cada combinación de características. Aquí, crearemos una nueva característica que es el cruce de Edad y Tipo. Tenga en cuenta que crossed_column
no crea la tabla completa de todas las combinaciones posibles (que podrían ser muy grandes). En su lugar, está respaldado por una hashed_column
, por lo que puede elegir el tamaño de la tabla.
crossed_feature = feature_column.crossed_column([age_buckets, animal_type], hash_bucket_size=10)
demo(feature_column.indicator_column(crossed_feature))
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.] [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]]
Elija qué columnas usar
Hemos visto cómo usar varios tipos de columnas de características. Ahora los usaremos para entrenar un modelo. El objetivo de este tutorial es mostrarle el código completo (por ejemplo, la mecánica) necesario para trabajar con columnas de funciones. Hemos seleccionado algunas columnas para entrenar nuestro modelo a continuación de manera arbitraria.
feature_columns = []
# numeric cols
for header in ['PhotoAmt', 'Fee', 'Age']:
feature_columns.append(feature_column.numeric_column(header))
# bucketized cols
age = feature_column.numeric_column('Age')
age_buckets = feature_column.bucketized_column(age, boundaries=[1, 2, 3, 4, 5])
feature_columns.append(age_buckets)
# indicator_columns
indicator_column_names = ['Type', 'Color1', 'Color2', 'Gender', 'MaturitySize',
'FurLength', 'Vaccinated', 'Sterilized', 'Health']
for col_name in indicator_column_names:
categorical_column = feature_column.categorical_column_with_vocabulary_list(
col_name, dataframe[col_name].unique())
indicator_column = feature_column.indicator_column(categorical_column)
feature_columns.append(indicator_column)
# embedding columns
breed1 = feature_column.categorical_column_with_vocabulary_list(
'Breed1', dataframe.Breed1.unique())
breed1_embedding = feature_column.embedding_column(breed1, dimension=8)
feature_columns.append(breed1_embedding)
# crossed columns
age_type_feature = feature_column.crossed_column([age_buckets, animal_type], hash_bucket_size=100)
feature_columns.append(feature_column.indicator_column(age_type_feature))
Crear una capa de entidades
Ahora que hemos definido nuestras columnas de características, usaremos una capa DenseFeatures para ingresarlas en nuestro modelo de Keras.
feature_layer = tf.keras.layers.DenseFeatures(feature_columns)
Anteriormente, usamos un tamaño de lote pequeño para demostrar cómo funcionaban las columnas de funciones. Creamos una nueva tubería de entrada con un tamaño de lote más grande.
batch_size = 32
train_ds = df_to_dataset(train, batch_size=batch_size)
val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)
Crear, compilar y entrenar el modelo.
model = tf.keras.Sequential([
feature_layer,
layers.Dense(128, activation='relu'),
layers.Dense(128, activation='relu'),
layers.Dropout(.1),
layers.Dense(1)
])
model.compile(optimizer='adam',
loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
metrics=['accuracy'])
model.fit(train_ds,
validation_data=val_ds,
epochs=10)
Epoch 1/10 WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'Type': <tf.Tensor 'IteratorGetNext:11' shape=(None,) dtype=string>, 'Age': <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=int64>, 'Breed1': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>, 'Gender': <tf.Tensor 'IteratorGetNext:6' shape=(None,) dtype=string>, 'Color1': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'Color2': <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>, 'MaturitySize': <tf.Tensor 'IteratorGetNext:8' shape=(None,) dtype=string>, 'FurLength': <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=string>, 'Vaccinated': <tf.Tensor 'IteratorGetNext:12' shape=(None,) dtype=string>, 'Sterilized': <tf.Tensor 'IteratorGetNext:10' shape=(None,) dtype=string>, 'Health': <tf.Tensor 'IteratorGetNext:7' shape=(None,) dtype=string>, 'Fee': <tf.Tensor 'IteratorGetNext:4' shape=(None,) dtype=int64>, 'PhotoAmt': <tf.Tensor 'IteratorGetNext:9' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'Type': <tf.Tensor 'IteratorGetNext:11' shape=(None,) dtype=string>, 'Age': <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=int64>, 'Breed1': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>, 'Gender': <tf.Tensor 'IteratorGetNext:6' shape=(None,) dtype=string>, 'Color1': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'Color2': <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>, 'MaturitySize': <tf.Tensor 'IteratorGetNext:8' shape=(None,) dtype=string>, 'FurLength': <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=string>, 'Vaccinated': <tf.Tensor 'IteratorGetNext:12' shape=(None,) dtype=string>, 'Sterilized': <tf.Tensor 'IteratorGetNext:10' shape=(None,) dtype=string>, 'Health': <tf.Tensor 'IteratorGetNext:7' shape=(None,) dtype=string>, 'Fee': <tf.Tensor 'IteratorGetNext:4' shape=(None,) dtype=int64>, 'PhotoAmt': <tf.Tensor 'IteratorGetNext:9' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. 231/231 [==============================] - ETA: 0s - loss: 0.6759 - accuracy: 0.6802WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'Type': <tf.Tensor 'IteratorGetNext:11' shape=(None,) dtype=string>, 'Age': <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=int64>, 'Breed1': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>, 'Gender': <tf.Tensor 'IteratorGetNext:6' shape=(None,) dtype=string>, 'Color1': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'Color2': <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>, 'MaturitySize': <tf.Tensor 'IteratorGetNext:8' shape=(None,) dtype=string>, 'FurLength': <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=string>, 'Vaccinated': <tf.Tensor 'IteratorGetNext:12' shape=(None,) dtype=string>, 'Sterilized': <tf.Tensor 'IteratorGetNext:10' shape=(None,) dtype=string>, 'Health': <tf.Tensor 'IteratorGetNext:7' shape=(None,) dtype=string>, 'Fee': <tf.Tensor 'IteratorGetNext:4' shape=(None,) dtype=int64>, 'PhotoAmt': <tf.Tensor 'IteratorGetNext:9' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. 231/231 [==============================] - 4s 10ms/step - loss: 0.6759 - accuracy: 0.6802 - val_loss: 0.5361 - val_accuracy: 0.7351 Epoch 2/10 231/231 [==============================] - 2s 9ms/step - loss: 0.5742 - accuracy: 0.7054 - val_loss: 0.5178 - val_accuracy: 0.7411 Epoch 3/10 231/231 [==============================] - 2s 9ms/step - loss: 0.5369 - accuracy: 0.7231 - val_loss: 0.5031 - val_accuracy: 0.7438 Epoch 4/10 231/231 [==============================] - 2s 9ms/step - loss: 0.5161 - accuracy: 0.7214 - val_loss: 0.5115 - val_accuracy: 0.7259 Epoch 5/10 231/231 [==============================] - 2s 9ms/step - loss: 0.5034 - accuracy: 0.7296 - val_loss: 0.5173 - val_accuracy: 0.7237 Epoch 6/10 231/231 [==============================] - 2s 8ms/step - loss: 0.4983 - accuracy: 0.7301 - val_loss: 0.5153 - val_accuracy: 0.7254 Epoch 7/10 231/231 [==============================] - 2s 9ms/step - loss: 0.4912 - accuracy: 0.7412 - val_loss: 0.5258 - val_accuracy: 0.7010 Epoch 8/10 231/231 [==============================] - 2s 9ms/step - loss: 0.4890 - accuracy: 0.7360 - val_loss: 0.5066 - val_accuracy: 0.7221 Epoch 9/10 231/231 [==============================] - 2s 9ms/step - loss: 0.4824 - accuracy: 0.7443 - val_loss: 0.5091 - val_accuracy: 0.7481 Epoch 10/10 231/231 [==============================] - 2s 9ms/step - loss: 0.4758 - accuracy: 0.7466 - val_loss: 0.5159 - val_accuracy: 0.7492 <keras.callbacks.History at 0x7f06b52a1810>
loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)
73/73 [==============================] - 0s 6ms/step - loss: 0.4812 - accuracy: 0.7543 Accuracy 0.7543327808380127
Próximos pasos
La mejor manera de aprender más sobre la clasificación de datos estructurados es probarlo usted mismo. Sugerimos encontrar otro conjunto de datos con el que trabajar y entrenar un modelo para clasificarlo usando un código similar al anterior. Para mejorar la precisión, piense detenidamente qué características incluir en su modelo y cómo deben representarse.