¡El Día de la Comunidad de ML es el 9 de noviembre! Únase a nosotros para recibir actualizaciones de TensorFlow, JAX, y más Más información

Cargar un DataFrame de pandas

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Este tutorial proporciona ejemplos de cómo cargar pandas tramas de datos en TensorFlow.

Que va a utilizar un pequeño conjunto de datos enfermedades del corazón proporcionada por la UCI Machine Learning Repositorio. Hay varios cientos de filas en el CSV. Cada fila describe un paciente y cada columna describe un atributo. Utilizará esta información para predecir si un paciente tiene una enfermedad cardíaca, que es una tarea de clasificación binaria.

Leer datos usando pandas

import pandas as pd
import tensorflow as tf

SHUFFLE_BUFFER = 500
BATCH_SIZE = 2

Descargue el archivo CSV que contiene el conjunto de datos de enfermedades cardíacas:

csv_file = tf.keras.utils.get_file('heart.csv', 'https://storage.googleapis.com/download.tensorflow.org/data/heart.csv')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/heart.csv
16384/13273 [=====================================] - 0s 0us/step
24576/13273 [=======================================================] - 0s 0us/step

Lea el archivo CSV usando pandas:

df = pd.read_csv(csv_file)

Así es como se ven los datos:

df.head()
df.dtypes
age           int64
sex           int64
cp            int64
trestbps      int64
chol          int64
fbs           int64
restecg       int64
thalach       int64
exang         int64
oldpeak     float64
slope         int64
ca            int64
thal         object
target        int64
dtype: object

Usted va a construir modelos para predecir la etiqueta contenida en el target la columna.

target = df.pop('target')

Un DataFrame como una matriz

Si los datos tienen un tipo de datos uniforme, o dtype , es posible el uso de un pandas trama de datos en cualquier lugar que podría utilizar una matriz NumPy. Esto funciona porque el pandas.DataFrame clase admite el __array__ protocolo y de TensorFlow tf.convert_to_tensor función acepta objetos que soportan el protocolo.

Tome las características numéricas del conjunto de datos (omita las características categóricas por ahora):

numeric_feature_names = ['age', 'thalach', 'trestbps',  'chol', 'oldpeak']
numeric_features = df[numeric_feature_names]
numeric_features.head()

La trama de datos se puede convertir en una matriz NumPy usando el DataFrame.values propiedad o numpy.array(df) . Para convertirlo en un tensor, el uso tf.convert_to_tensor :

tf.convert_to_tensor(numeric_features)
<tf.Tensor: shape=(303, 5), dtype=float64, numpy=
array([[ 63. , 150. , 145. , 233. ,   2.3],
       [ 67. , 108. , 160. , 286. ,   1.5],
       [ 67. , 129. , 120. , 229. ,   2.6],
       ...,
       [ 65. , 127. , 135. , 254. ,   2.8],
       [ 48. , 150. , 130. , 256. ,   0. ],
       [ 63. , 154. , 150. , 407. ,   4. ]])>

En general, si un objeto se puede convertir en un tensor con tf.convert_to_tensor se puede pasar en cualquier lugar que puede pasar un tf.Tensor .

Con Model.fit

Una trama de datos, interpretado como un único tensor, se puede utilizar directamente como un argumento a la Model.fit método.

A continuación, se muestra un ejemplo de entrenamiento de un modelo en las características numéricas del conjunto de datos.

El primer paso es normalizar los rangos de entrada. Use un tf.keras.layers.Normalization capa para eso.

Para establecer la media y la desviación estándar de la capa antes de ejecutarlo, asegúrese de llamar a la Normalization.adapt método:

normalizer = tf.keras.layers.Normalization(axis=-1)
normalizer.adapt(numeric_features)

Llame a la capa en las primeras tres filas del DataFrame para visualizar un ejemplo de la salida de esta capa:

normalizer(numeric_features.iloc[:3])
<tf.Tensor: shape=(3, 5), dtype=float32, numpy=
array([[ 0.93383914,  0.03480718,  0.74578077, -0.26008663,  1.0680453 ],
       [ 1.3782105 , -1.7806165 ,  1.5923285 ,  0.7573877 ,  0.38022864],
       [ 1.3782105 , -0.87290466, -0.6651321 , -0.33687714,  1.3259765 ]],
      dtype=float32)>

Utilice la capa de normalización como la primera capa de un modelo simple:

def get_basic_model():
  model = tf.keras.Sequential([
    normalizer,
    tf.keras.layers.Dense(10, activation='relu'),
    tf.keras.layers.Dense(10, activation='relu'),
    tf.keras.layers.Dense(1)
  ])

  model.compile(optimizer='adam',
                loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
                metrics=['accuracy'])
  return model

Cuando se pasa la trama de datos como el x argumento para Model.fit , golosinas Keras la trama de datos como lo haría una matriz NumPy:

model = get_basic_model()
model.fit(numeric_features, target, epochs=15, batch_size=BATCH_SIZE)
Epoch 1/15
152/152 [==============================] - 1s 2ms/step - loss: 0.6405 - accuracy: 0.7789
Epoch 2/15
152/152 [==============================] - 0s 2ms/step - loss: 0.5510 - accuracy: 0.7426
Epoch 3/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4935 - accuracy: 0.7459
Epoch 4/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4649 - accuracy: 0.7558
Epoch 5/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4492 - accuracy: 0.7624
Epoch 6/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4404 - accuracy: 0.7624
Epoch 7/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4350 - accuracy: 0.7789
Epoch 8/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4307 - accuracy: 0.7888
Epoch 9/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4291 - accuracy: 0.7756
Epoch 10/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4262 - accuracy: 0.7822
Epoch 11/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4242 - accuracy: 0.7855
Epoch 12/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4227 - accuracy: 0.7789
Epoch 13/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4229 - accuracy: 0.7987
Epoch 14/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4207 - accuracy: 0.7954
Epoch 15/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4212 - accuracy: 0.7822
<keras.callbacks.History at 0x7f52dc52b8d0>

Con tf.data

Si desea aplicar tf.data transformaciones a una trama de datos de un uniforme dtype , la Dataset.from_tensor_slices método creará un conjunto de datos que itera sobre las filas de la trama de datos. Cada fila es inicialmente un vector de valores. Para entrenar un modelo, es necesario (inputs, labels) pares, por lo que pasa (features, labels) y Dataset.from_tensor_slices devolverá los pares necesarios de rebanadas:

numeric_dataset = tf.data.Dataset.from_tensor_slices((numeric_features, target))

for row in numeric_dataset.take(3):
  print(row)
(<tf.Tensor: shape=(5,), dtype=float64, numpy=array([ 63. , 150. , 145. , 233. ,   2.3])>, <tf.Tensor: shape=(), dtype=int64, numpy=0>)
(<tf.Tensor: shape=(5,), dtype=float64, numpy=array([ 67. , 108. , 160. , 286. ,   1.5])>, <tf.Tensor: shape=(), dtype=int64, numpy=1>)
(<tf.Tensor: shape=(5,), dtype=float64, numpy=array([ 67. , 129. , 120. , 229. ,   2.6])>, <tf.Tensor: shape=(), dtype=int64, numpy=0>)
numeric_batches = numeric_dataset.shuffle(1000).batch(BATCH_SIZE)

model = get_basic_model()
model.fit(numeric_batches, epochs=15)
Epoch 1/15
152/152 [==============================] - 1s 2ms/step - loss: 0.6827 - accuracy: 0.7393
Epoch 2/15
152/152 [==============================] - 0s 2ms/step - loss: 0.6239 - accuracy: 0.7327
Epoch 3/15
152/152 [==============================] - 0s 2ms/step - loss: 0.5740 - accuracy: 0.7327
Epoch 4/15
152/152 [==============================] - 0s 2ms/step - loss: 0.5316 - accuracy: 0.7360
Epoch 5/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4957 - accuracy: 0.7426
Epoch 6/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4726 - accuracy: 0.7591
Epoch 7/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4569 - accuracy: 0.7822
Epoch 8/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4482 - accuracy: 0.7723
Epoch 9/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4417 - accuracy: 0.7855
Epoch 10/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4379 - accuracy: 0.7855
Epoch 11/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4356 - accuracy: 0.7888
Epoch 12/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4326 - accuracy: 0.7888
Epoch 13/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4308 - accuracy: 0.7855
Epoch 14/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4288 - accuracy: 0.8020
Epoch 15/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4267 - accuracy: 0.7888
<keras.callbacks.History at 0x7f52dc35db10>

Un DataFrame como diccionario

Cuando comienza a trabajar con datos heterogéneos, ya no es posible tratar el DataFrame como si fuera una única matriz. Tensores TensorFlow exigir que todos los elementos tienen la misma dtype .

Entonces, en este caso, debe comenzar a tratarlo como un diccionario de columnas, donde cada columna tiene un dtype uniforme. Un DataFrame se parece mucho a un diccionario de matrices, por lo que, por lo general, todo lo que necesita hacer es convertir el DataFrame en un dictado de Python. Muchas API de TensorFlow importantes admiten diccionarios (anidados) de matrices como entradas.

tf.data tuberías de entrada de manejar esto muy bien. Todos tf.data operaciones manejan los diccionarios y las tuplas de forma automática. Por lo tanto, para que un conjunto de datos del diccionario-ejemplos de una trama de datos, simplemente echarlo a un diccionario antes de cortarlo con Dataset.from_tensor_slices :

numeric_dict_ds = tf.data.Dataset.from_tensor_slices((dict(numeric_features), target))

Aquí están los primeros tres ejemplos de ese conjunto de datos:

for row in numeric_dict_ds.take(3):
  print(row)
({'age': <tf.Tensor: shape=(), dtype=int64, numpy=63>, 'thalach': <tf.Tensor: shape=(), dtype=int64, numpy=150>, 'trestbps': <tf.Tensor: shape=(), dtype=int64, numpy=145>, 'chol': <tf.Tensor: shape=(), dtype=int64, numpy=233>, 'oldpeak': <tf.Tensor: shape=(), dtype=float64, numpy=2.3>}, <tf.Tensor: shape=(), dtype=int64, numpy=0>)
({'age': <tf.Tensor: shape=(), dtype=int64, numpy=67>, 'thalach': <tf.Tensor: shape=(), dtype=int64, numpy=108>, 'trestbps': <tf.Tensor: shape=(), dtype=int64, numpy=160>, 'chol': <tf.Tensor: shape=(), dtype=int64, numpy=286>, 'oldpeak': <tf.Tensor: shape=(), dtype=float64, numpy=1.5>}, <tf.Tensor: shape=(), dtype=int64, numpy=1>)
({'age': <tf.Tensor: shape=(), dtype=int64, numpy=67>, 'thalach': <tf.Tensor: shape=(), dtype=int64, numpy=129>, 'trestbps': <tf.Tensor: shape=(), dtype=int64, numpy=120>, 'chol': <tf.Tensor: shape=(), dtype=int64, numpy=229>, 'oldpeak': <tf.Tensor: shape=(), dtype=float64, numpy=2.6>}, <tf.Tensor: shape=(), dtype=int64, numpy=0>)

Diccionarios con Keras

Normalmente, los modelos y capas de Keras esperan un solo tensor de entrada, pero estas clases pueden aceptar y devolver estructuras anidadas de diccionarios, tuplas y tensores. Estas estructuras se conocen como "nidos" (consulte la tf.nest módulo para más detalles).

Hay dos formas equivalentes de escribir un modelo de keras que acepte un diccionario como entrada.

1. El estilo de la subclase modelo

Se escribe una subclase de tf.keras.Model (o tf.keras.Layer ). Usted maneja directamente las entradas y crea las salidas:

def stack_dict(inputs, fun=tf.stack):
    values = []
    for key in sorted(inputs.keys()):
      values.append(tf.cast(inputs[key], tf.float32))

    return fun(values, axis=-1)
class MyModel(tf.keras.Model):
  def __init__(self):
    # Create all the internal layers in init.
    super().__init__(self)

    self.normalizer = tf.keras.layers.Normalization(axis=-1)

    self.seq = tf.keras.Sequential([
      self.normalizer,
      tf.keras.layers.Dense(10, activation='relu'),
      tf.keras.layers.Dense(10, activation='relu'),
      tf.keras.layers.Dense(1)
    ])

  def adapt(self, inputs):
    # Stach the inputs and `adapt` the normalization layer.
    inputs = stack_dict(inputs)
    self.normalizer.adapt(inputs)

  def call(self, inputs):
    # Stack the inputs
    inputs = stack_dict(inputs)
    # Run them through all the layers.
    result = self.seq(inputs)

    return result

model = MyModel()

model.adapt(dict(numeric_features))

model.compile(optimizer='adam',
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=['accuracy'],
              run_eagerly=True)

Este modelo puede aceptar un diccionario de columnas o un conjunto de datos de elementos de diccionario para el entrenamiento:

model.fit(dict(numeric_features), target, epochs=5, batch_size=BATCH_SIZE)
Epoch 1/5
152/152 [==============================] - 2s 16ms/step - loss: 0.5968 - accuracy: 0.7261
Epoch 2/5
152/152 [==============================] - 2s 15ms/step - loss: 0.5182 - accuracy: 0.7261
Epoch 3/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4738 - accuracy: 0.7360
Epoch 4/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4515 - accuracy: 0.7525
Epoch 5/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4405 - accuracy: 0.7756
<keras.callbacks.History at 0x7f52dc165990>
numeric_dict_batches = numeric_dict_ds.shuffle(SHUFFLE_BUFFER).batch(BATCH_SIZE)
model.fit(numeric_dict_batches, epochs=5)
Epoch 1/5
152/152 [==============================] - 3s 17ms/step - loss: 0.4345 - accuracy: 0.7789
Epoch 2/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4298 - accuracy: 0.7822
Epoch 3/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4273 - accuracy: 0.7789
Epoch 4/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4246 - accuracy: 0.7855
Epoch 5/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4225 - accuracy: 0.7921
<keras.callbacks.History at 0x7f52dc0d4690>

Aquí están las predicciones para los primeros tres ejemplos:

model.predict(dict(numeric_features.iloc[:3]))
array([[[ 0.14884435]],

       [[ 0.37460226]],

       [[-0.08134247]]], dtype=float32)

2. El estilo funcional de Keras

inputs = {}
for name, column in numeric_features.items():
  inputs[name] = tf.keras.Input(
      shape=(1,), name=name, dtype=tf.float32)

inputs
{'age': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'age')>,
 'thalach': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'thalach')>,
 'trestbps': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'trestbps')>,
 'chol': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'chol')>,
 'oldpeak': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'oldpeak')>}
x = stack_dict(inputs, fun=tf.concat)

normalizer = tf.keras.layers.Normalization(axis=-1)
normalizer.adapt(stack_dict(dict(numeric_features)))

x = normalizer(x)
x = tf.keras.layers.Dense(10, activation='relu')(x)
x = tf.keras.layers.Dense(10, activation='relu')(x)
x = tf.keras.layers.Dense(1)(x)

model = tf.keras.Model(inputs, x)

model.compile(optimizer='adam',
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=['accuracy'],
              run_eagerly=True)
tf.keras.utils.plot_model(model, rankdir="LR", show_shapes=True)

png

Puede entrenar el modelo funcional de la misma manera que la subclase del modelo:

model.fit(dict(numeric_features), target, epochs=5, batch_size=BATCH_SIZE)
Epoch 1/5
152/152 [==============================] - 2s 14ms/step - loss: 0.7151 - accuracy: 0.7030
Epoch 2/5
152/152 [==============================] - 2s 14ms/step - loss: 0.5883 - accuracy: 0.7360
Epoch 3/5
152/152 [==============================] - 2s 14ms/step - loss: 0.5205 - accuracy: 0.7360
Epoch 4/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4838 - accuracy: 0.7492
Epoch 5/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4622 - accuracy: 0.7492
<keras.callbacks.History at 0x7f52bc7b2690>
numeric_dict_batches = numeric_dict_ds.shuffle(SHUFFLE_BUFFER).batch(BATCH_SIZE)
model.fit(numeric_dict_batches, epochs=5)
Epoch 1/5
152/152 [==============================] - 2s 14ms/step - loss: 0.4487 - accuracy: 0.7723
Epoch 2/5
152/152 [==============================] - 2s 14ms/step - loss: 0.4392 - accuracy: 0.7789
Epoch 3/5
152/152 [==============================] - 2s 14ms/step - loss: 0.4339 - accuracy: 0.7789
Epoch 4/5
152/152 [==============================] - 2s 14ms/step - loss: 0.4303 - accuracy: 0.7789
Epoch 5/5
152/152 [==============================] - 2s 14ms/step - loss: 0.4269 - accuracy: 0.7855
<keras.callbacks.History at 0x7f52dc0d4910>

Ejemplo completo

Que estás pasando una heterogénea DataFrame a Keras, cada columna puede necesitar preprocesamiento único. Puede hacer este preprocesamiento directamente en el DataFrame, pero para que un modelo funcione correctamente, las entradas siempre deben preprocesarse de la misma manera. Por tanto, el mejor enfoque es incorporar el preprocesamiento en el modelo. Capas de pre-procesamiento Keras cubren muchas tareas comunes.

Construye el cabezal de preprocesamiento

En este conjunto de datos, algunas de las características "enteras" de los datos brutos son en realidad índices categóricos. Estos índices no son realmente ordenaron valores numéricos (consulte la descripción del conjunto de datos para más detalles). Debido a que están desordenados, no son apropiados para alimentar directamente al modelo; el modelo los interpretaría como ordenados. Para usar estas entradas, deberá codificarlas, ya sea como vectores one-hot o incrustando vectores. Lo mismo se aplica a las características de cadenas de caracteres.

Por otro lado, las características binarias generalmente no necesitan codificarse o normalizarse.

Empiece por crear una lista de las características que pertenecen a cada grupo:

binary_feature_names = ['sex', 'fbs', 'exang']
categorical_feature_names = ['cp', 'restecg', 'slope', 'thal', 'ca']

El siguiente paso es construir un modelo de preprocesamiento que aplicará el preprocesamiento apropiado a cada una de las entradas y concatenará los resultados.

En esta sección se utiliza la API funcional Keras para implementar el procesamiento previo. Se empieza por la creación de una tf.keras.Input para cada columna de la trama de datos:

inputs = {}
for name, column in df.items():
  if type(column[0]) == str:
    dtype = tf.string
  elif (name in categorical_feature_names or
        name in binary_feature_names):
    dtype = tf.int64
  else:
    dtype = tf.float32

  inputs[name] = tf.keras.Input(shape=(), name=name, dtype=dtype)
inputs
{'age': <KerasTensor: shape=(None,) dtype=float32 (created by layer 'age')>,
 'sex': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'sex')>,
 'cp': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'cp')>,
 'trestbps': <KerasTensor: shape=(None,) dtype=float32 (created by layer 'trestbps')>,
 'chol': <KerasTensor: shape=(None,) dtype=float32 (created by layer 'chol')>,
 'fbs': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'fbs')>,
 'restecg': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'restecg')>,
 'thalach': <KerasTensor: shape=(None,) dtype=float32 (created by layer 'thalach')>,
 'exang': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'exang')>,
 'oldpeak': <KerasTensor: shape=(None,) dtype=float32 (created by layer 'oldpeak')>,
 'slope': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'slope')>,
 'ca': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'ca')>,
 'thal': <KerasTensor: shape=(None,) dtype=string (created by layer 'thal')>}

Para cada entrada, aplicará algunas transformaciones utilizando capas de Keras y operaciones de TensorFlow. Cada característica se inicia como un lote de escalares ( shape=(batch,) ). La salida para cada uno debe ser un lote de tf.float32 vectores ( shape=(batch, n) ). El último paso concatenará todos esos vectores juntos.

Entradas binarias

Dado que las entradas binarias no necesitan ningún procesamiento previo, sólo tiene que añadir el eje vector, las echó en float32 y añadirlos a la lista de entradas que se procesan:

preprocessed = []

for name in binary_feature_names:
  inp = inputs[name]
  inp = inp[:, tf.newaxis]
  float_value = tf.cast(inp, tf.float32)
  preprocessed.append(float_value)

preprocessed
[<KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'tf.cast_5')>,
 <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'tf.cast_6')>,
 <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'tf.cast_7')>]

Entradas numéricas

Al igual que en la sección anterior que querrá ejecutar estas entradas numéricas a través de un tf.keras.layers.Normalization capa antes de usarlos. La diferencia es que esta vez se ingresan como un dictado. El código siguiente recoge las características numéricas de la trama de datos, los apila juntos y pasa a aquellos a la Normalization.adapt método.

normalizer = tf.keras.layers.Normalization(axis=-1)
normalizer.adapt(stack_dict(dict(numeric_features)))

El siguiente código apila las características numéricas y las ejecuta a través de la capa de normalización.

numeric_inputs = {}
for name in numeric_feature_names:
  numeric_inputs[name]=inputs[name]

numeric_inputs = stack_dict(numeric_inputs)
numeric_normalized = normalizer(numeric_inputs)

preprocessed.append(numeric_normalized)

preprocessed
[<KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'tf.cast_5')>,
 <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'tf.cast_6')>,
 <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'tf.cast_7')>,
 <KerasTensor: shape=(None, 5) dtype=float32 (created by layer 'normalization_3')>]

Características categóricas

Para usar características categóricas, primero deberá codificarlas en vectores binarios o incrustaciones. Puesto que éstos sólo presenta contienen un pequeño número de categorías, convertir las entradas directamente a vectores de una sola calientes usando el output_mode='one_hot' opción, apoyada byy tanto los tf.keras.layers.StringLookup y tf.keras.layers.IntegerLookup capas.

A continuación, se muestra un ejemplo de cómo funcionan estas capas:

vocab = ['a','b','c']
lookup = tf.keras.layers.StringLookup(vocabulary=vocab, output_mode='one_hot')
lookup(['c','a','a','b','zzz'])
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[0., 0., 0., 1.],
       [0., 1., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [1., 0., 0., 0.]], dtype=float32)>
vocab = [1,4,7,99]
lookup = tf.keras.layers.IntegerLookup(vocabulary=vocab, output_mode='one_hot')

lookup([-1,4,1])
<tf.Tensor: shape=(3, 5), dtype=float32, numpy=
array([[1., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0.],
       [0., 1., 0., 0., 0.]], dtype=float32)>

Para determinar el vocabulario de cada entrada, cree una capa para convertir ese vocabulario en un vector one-hot:

for name in categorical_feature_names:
  vocab = sorted(set(df[name]))
  print(f'name: {name}')
  print(f'vocab: {vocab}\n')

  if type(vocab[0]) is str:
    lookup = tf.keras.layers.StringLookup(vocabulary=vocab, output_mode='one_hot')
  else:
    lookup = tf.keras.layers.IntegerLookup(vocabulary=vocab, output_mode='one_hot')

  x = inputs[name][:, tf.newaxis]
  x = lookup(x)
  preprocessed.append(x)
name: cp
vocab: [0, 1, 2, 3, 4]

name: restecg
vocab: [0, 1, 2]

name: slope
vocab: [1, 2, 3]

name: thal
vocab: ['1', '2', 'fixed', 'normal', 'reversible']

name: ca
vocab: [0, 1, 2, 3]

Ensamble el cabezal de preprocesamiento

En este punto preprocessed es sólo una lista Python de todos los resultados de preprocesamiento, cada resultado tiene una forma de (batch_size, depth) :

preprocessed
[<KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'tf.cast_5')>,
 <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'tf.cast_6')>,
 <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'tf.cast_7')>,
 <KerasTensor: shape=(None, 5) dtype=float32 (created by layer 'normalization_3')>,
 <KerasTensor: shape=(None, 6) dtype=float32 (created by layer 'integer_lookup_1')>,
 <KerasTensor: shape=(None, 4) dtype=float32 (created by layer 'integer_lookup_2')>,
 <KerasTensor: shape=(None, 4) dtype=float32 (created by layer 'integer_lookup_3')>,
 <KerasTensor: shape=(None, 6) dtype=float32 (created by layer 'string_lookup_1')>,
 <KerasTensor: shape=(None, 5) dtype=float32 (created by layer 'integer_lookup_4')>]

Concatenar todas las características preprocesados lo largo de la depth del eje, por lo que cada diccionario-ejemplo se convierte en un único vector. El vector contiene características categóricas, características numéricas y características únicas categóricas:

preprocesssed_result = tf.concat(preprocessed, axis=-1)
preprocesssed_result
<KerasTensor: shape=(None, 33) dtype=float32 (created by layer 'tf.concat_1')>

Ahora cree un modelo a partir de ese cálculo para poder reutilizarlo:

preprocessor = tf.keras.Model(inputs, preprocesssed_result)
tf.keras.utils.plot_model(preprocessor, rankdir="LR", show_shapes=True)

png

Para probar el preprocesador, utilice el DataFrame.iloc de acceso para cortar el primer ejemplo de la trama de datos. Luego conviértalo en un diccionario y páselo al preprocesador. El resultado es un único vector que contiene las características binarias, las características numéricas normalizadas y las características categóricas one-hot, en ese orden:

preprocessor(dict(df.iloc[:1]))
<tf.Tensor: shape=(1, 33), dtype=float32, numpy=
array([[ 1.        ,  1.        ,  0.        ,  0.93383914, -0.26008663,
         1.0680453 ,  0.03480718,  0.74578077,  0.        ,  0.        ,

         1.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        ,  0.        ,  1.        ,  0.        ,  0.        ,
         0.        ,  1.        ,  0.        ,  0.        ,  0.        ,
         1.        ,  0.        ,  0.        ,  0.        ,  1.        ,
         0.        ,  0.        ,  0.        ]], dtype=float32)>

Crea y entrena un modelo

Ahora construya el cuerpo principal del modelo. Usar la misma configuración que en el ejemplo anterior: Un par de Dense rectificada lineales capas y una Dense(1) capa de salida para la clasificación.

body = tf.keras.Sequential([
  tf.keras.layers.Dense(10, activation='relu'),
  tf.keras.layers.Dense(10, activation='relu'),
  tf.keras.layers.Dense(1)
])

Ahora junte las dos piezas usando la API funcional de Keras.

inputs
{'age': <KerasTensor: shape=(None,) dtype=float32 (created by layer 'age')>,
 'sex': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'sex')>,
 'cp': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'cp')>,
 'trestbps': <KerasTensor: shape=(None,) dtype=float32 (created by layer 'trestbps')>,
 'chol': <KerasTensor: shape=(None,) dtype=float32 (created by layer 'chol')>,
 'fbs': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'fbs')>,
 'restecg': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'restecg')>,
 'thalach': <KerasTensor: shape=(None,) dtype=float32 (created by layer 'thalach')>,
 'exang': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'exang')>,
 'oldpeak': <KerasTensor: shape=(None,) dtype=float32 (created by layer 'oldpeak')>,
 'slope': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'slope')>,
 'ca': <KerasTensor: shape=(None,) dtype=int64 (created by layer 'ca')>,
 'thal': <KerasTensor: shape=(None,) dtype=string (created by layer 'thal')>}
x = preprocessor(inputs)
x
<KerasTensor: shape=(None, 33) dtype=float32 (created by layer 'model_1')>
result = body(x)
result
<KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'sequential_3')>
model = tf.keras.Model(inputs, result)

model.compile(optimizer='adam',
                loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
                metrics=['accuracy'])

Este modelo espera un diccionario de entradas. La forma más sencilla de pasar los datos es convertir la trama de datos a un diccionario y pasar esa dict como el x argumento para Model.fit :

history = model.fit(dict(df), target, epochs=5, batch_size=BATCH_SIZE)
Epoch 1/5
152/152 [==============================] - 1s 4ms/step - loss: 0.5340 - accuracy: 0.7558
Epoch 2/5
152/152 [==============================] - 1s 4ms/step - loss: 0.4038 - accuracy: 0.7789
Epoch 3/5
152/152 [==============================] - 1s 4ms/step - loss: 0.3515 - accuracy: 0.8218
Epoch 4/5
152/152 [==============================] - 1s 4ms/step - loss: 0.3262 - accuracy: 0.8482
Epoch 5/5
152/152 [==============================] - 1s 4ms/step - loss: 0.3087 - accuracy: 0.8680

Usando tf.data funciona tan bien:

ds = tf.data.Dataset.from_tensor_slices((
    dict(df),
    target
))

ds = ds.batch(BATCH_SIZE)
import pprint

for x, y in ds.take(1):
  pprint.pprint(x)
  print()
  print(y)
{'age': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([63, 67])>,
 'ca': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([0, 3])>,
 'chol': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([233, 286])>,
 'cp': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([1, 4])>,
 'exang': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([0, 1])>,
 'fbs': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([1, 0])>,
 'oldpeak': <tf.Tensor: shape=(2,), dtype=float64, numpy=array([2.3, 1.5])>,
 'restecg': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([2, 2])>,
 'sex': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([1, 1])>,
 'slope': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([3, 2])>,
 'thal': <tf.Tensor: shape=(2,), dtype=string, numpy=array([b'fixed', b'normal'], dtype=object)>,
 'thalach': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([150, 108])>,
 'trestbps': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([145, 160])>}

tf.Tensor([0 1], shape=(2,), dtype=int64)
history = model.fit(ds, epochs=5)
Epoch 1/5
152/152 [==============================] - 1s 4ms/step - loss: 0.2982 - accuracy: 0.8581
Epoch 2/5
152/152 [==============================] - 1s 5ms/step - loss: 0.2898 - accuracy: 0.8647
Epoch 3/5
152/152 [==============================] - 1s 5ms/step - loss: 0.2831 - accuracy: 0.8680
Epoch 4/5
152/152 [==============================] - 1s 5ms/step - loss: 0.2772 - accuracy: 0.8680
Epoch 5/5
152/152 [==============================] - 1s 4ms/step - loss: 0.2720 - accuracy: 0.8680