Clasificar datos estructurados con columnas de características

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.