Charger un DataFrame pandas

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier

Ce didacticiel fournit des exemples de chargement de pandas DataFrames dans TensorFlow.

Vous utiliserez un petit ensemble de données sur les maladies cardiaques fourni par le référentiel d'apprentissage automatique de l'UCI. Il y a plusieurs centaines de lignes dans le CSV. Chaque ligne décrit un patient et chaque colonne décrit un attribut. Vous utiliserez ces informations pour prédire si un patient a une maladie cardiaque, ce qui est une tâche de classification binaire.

Lire des données à l'aide de pandas

import pandas as pd
import tensorflow as tf

SHUFFLE_BUFFER = 500
BATCH_SIZE = 2

Téléchargez le fichier CSV contenant l'ensemble de données sur les maladies cardiaques :

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

Lisez le fichier CSV à l'aide de pandas :

df = pd.read_csv(csv_file)

Voici à quoi ressemblent les données :

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

Vous construirez des modèles pour prédire l'étiquette contenue dans la colonne target .

target = df.pop('target')

Un DataFrame sous forme de tableau

Si vos données ont un type de données uniforme, ou dtype , il est possible d'utiliser un pandas DataFrame partout où vous pourriez utiliser un tableau NumPy. Cela fonctionne car la classe pandas.DataFrame prend en charge le protocole __array__ et la fonction tf.convert_to_tensor de TensorFlow accepte les objets qui prennent en charge le protocole.

Prenez les caractéristiques numériques de l'ensemble de données (ignorez les caractéristiques catégorielles pour l'instant) :

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

Le DataFrame peut être converti en un tableau NumPy à l'aide de la propriété DataFrame.values ou numpy.array(df) . Pour le convertir en tenseur, utilisez 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 général, si un objet peut être converti en tenseur avec tf.convert_to_tensor , il peut être passé partout où vous pouvez passer un tf.Tensor .

Avec Model.fit

Un DataFrame, interprété comme un tenseur unique, peut être utilisé directement comme argument de la méthode Model.fit .

Vous trouverez ci-dessous un exemple d'entraînement d'un modèle sur les caractéristiques numériques de l'ensemble de données.

La première étape consiste à normaliser les plages d'entrée. Utilisez une couche tf.keras.layers.Normalization pour cela.

Pour définir la moyenne et l'écart-type de la couche avant de l'exécuter, assurez-vous d'appeler la méthode Normalization.adapt :

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

Appelez la couche sur les trois premières lignes du DataFrame pour visualiser un exemple de la sortie de cette couche :

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

Utilisez la couche de normalisation comme première couche d'un modèle 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

Lorsque vous transmettez le DataFrame comme argument x à Model.fit , Keras traite le DataFrame comme s'il s'agissait d'un tableau 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.6839 - accuracy: 0.7690
Epoch 2/15
152/152 [==============================] - 0s 2ms/step - loss: 0.5789 - accuracy: 0.7789
Epoch 3/15
152/152 [==============================] - 0s 2ms/step - loss: 0.5195 - accuracy: 0.7723
Epoch 4/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4814 - accuracy: 0.7855
Epoch 5/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4566 - accuracy: 0.7789
Epoch 6/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4427 - accuracy: 0.7888
Epoch 7/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4342 - accuracy: 0.7921
Epoch 8/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4290 - accuracy: 0.7855
Epoch 9/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4240 - accuracy: 0.7987
Epoch 10/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4232 - accuracy: 0.7987
Epoch 11/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4208 - accuracy: 0.7987
Epoch 12/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4186 - accuracy: 0.7954
Epoch 13/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4172 - accuracy: 0.8020
Epoch 14/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4156 - accuracy: 0.8020
Epoch 15/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4138 - accuracy: 0.8020
<keras.callbacks.History at 0x7f1ddc27b110>

Avec tf.data

Si vous souhaitez appliquer des transformations tf.data à un DataFrame d'un dtype uniforme, la méthode Dataset.from_tensor_slices créera un ensemble de données qui itère sur les lignes du DataFrame. Chaque ligne est initialement un vecteur de valeurs. Pour former un modèle, vous avez besoin de paires (inputs, labels) , donc passez (features, labels) et Dataset.from_tensor_slices renverra les paires de tranches nécessaires :

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.7677 - accuracy: 0.6865
Epoch 2/15
152/152 [==============================] - 0s 2ms/step - loss: 0.6319 - accuracy: 0.7591
Epoch 3/15
152/152 [==============================] - 0s 2ms/step - loss: 0.5717 - accuracy: 0.7459
Epoch 4/15
152/152 [==============================] - 0s 2ms/step - loss: 0.5228 - accuracy: 0.7558
Epoch 5/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4820 - accuracy: 0.7624
Epoch 6/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4584 - accuracy: 0.7657
Epoch 7/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4454 - accuracy: 0.7657
Epoch 8/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4379 - accuracy: 0.7789
Epoch 9/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4324 - accuracy: 0.7789
Epoch 10/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4282 - accuracy: 0.7756
Epoch 11/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4273 - accuracy: 0.7789
Epoch 12/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4268 - accuracy: 0.7756
Epoch 13/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4248 - accuracy: 0.7789
Epoch 14/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4235 - accuracy: 0.7855
Epoch 15/15
152/152 [==============================] - 0s 2ms/step - loss: 0.4223 - accuracy: 0.7888
<keras.callbacks.History at 0x7f1ddc406510>

Un DataFrame comme dictionnaire

Lorsque vous commencez à traiter des données hétérogènes, il n'est plus possible de traiter le DataFrame comme s'il s'agissait d'un seul tableau. Les tenseurs TensorFlow exigent que tous les éléments aient le même dtype .

Donc, dans ce cas, vous devez commencer à le traiter comme un dictionnaire de colonnes, où chaque colonne a un dtype uniforme. Un DataFrame ressemble beaucoup à un dictionnaire de tableaux, donc tout ce que vous avez à faire est généralement de convertir le DataFrame en un dict Python. De nombreuses API TensorFlow importantes prennent en charge les dictionnaires (imbriqués) de tableaux en tant qu'entrées.

Les pipelines d'entrée tf.data gèrent cela assez bien. Toutes les opérations tf.data gèrent automatiquement les dictionnaires et les tuples. Donc, pour créer un ensemble de données d'exemples de dictionnaires à partir d'un DataFrame, il suffit de le convertir en dict avant de le découper avec Dataset.from_tensor_slices :

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

Voici les trois premiers exemples de cet ensemble de données :

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

Dictionnaires avec Keras

En règle générale, les modèles et les couches Keras attendent un seul tenseur d'entrée, mais ces classes peuvent accepter et renvoyer des structures imbriquées de dictionnaires, de tuples et de tenseurs. Ces structures sont appelées "nids" (reportez-vous au module tf.nest pour plus de détails).

Il existe deux manières équivalentes d'écrire un modèle keras qui accepte un dictionnaire en entrée.

1. Le style Model-subclass

Vous écrivez une sous-classe de tf.keras.Model (ou tf.keras.Layer ). Vous gérez directement les entrées et créez les sorties :

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)

Ce modèle peut accepter soit un dictionnaire de colonnes, soit un ensemble de données d'éléments de dictionnaire pour la formation :

model.fit(dict(numeric_features), target, epochs=5, batch_size=BATCH_SIZE)
Epoch 1/5
152/152 [==============================] - 3s 17ms/step - loss: 0.6736 - accuracy: 0.7063
Epoch 2/5
152/152 [==============================] - 3s 17ms/step - loss: 0.5577 - accuracy: 0.7294
Epoch 3/5
152/152 [==============================] - 2s 16ms/step - loss: 0.4869 - accuracy: 0.7591
Epoch 4/5
152/152 [==============================] - 2s 16ms/step - loss: 0.4525 - accuracy: 0.7690
Epoch 5/5
152/152 [==============================] - 2s 16ms/step - loss: 0.4403 - accuracy: 0.7624
<keras.callbacks.History at 0x7f1de4fa9390>
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 15ms/step - loss: 0.4328 - accuracy: 0.7756
Epoch 2/5
152/152 [==============================] - 2s 14ms/step - loss: 0.4297 - accuracy: 0.7888
Epoch 3/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4270 - accuracy: 0.7888
Epoch 4/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4245 - accuracy: 0.8020
Epoch 5/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4240 - accuracy: 0.7921
<keras.callbacks.History at 0x7f1ddc0dba90>

Voici les prédictions pour les trois premiers exemples :

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

       [[0.60601974]],

       [[0.03647463]]], dtype=float32)

2. Le style fonctionnel 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

Vous pouvez entraîner le modèle fonctionnel de la même manière que la sous-classe de modèle :

model.fit(dict(numeric_features), target, epochs=5, batch_size=BATCH_SIZE)
Epoch 1/5
152/152 [==============================] - 2s 15ms/step - loss: 0.6529 - accuracy: 0.7492
Epoch 2/5
152/152 [==============================] - 2s 15ms/step - loss: 0.5448 - accuracy: 0.7624
Epoch 3/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4935 - accuracy: 0.7756
Epoch 4/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4650 - accuracy: 0.7789
Epoch 5/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4486 - accuracy: 0.7855
<keras.callbacks.History at 0x7f1ddc0d0f90>
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 15ms/step - loss: 0.4398 - accuracy: 0.7855
Epoch 2/5
152/152 [==============================] - 2s 15ms/step - loss: 0.4330 - accuracy: 0.7855
Epoch 3/5
152/152 [==============================] - 2s 16ms/step - loss: 0.4294 - accuracy: 0.7921
Epoch 4/5
152/152 [==============================] - 2s 16ms/step - loss: 0.4271 - accuracy: 0.7888
Epoch 5/5
152/152 [==============================] - 2s 16ms/step - loss: 0.4231 - accuracy: 0.7855
<keras.callbacks.History at 0x7f1d7c5d5d10>

Exemple complet

Si vous transmettez un DataFrame hétérogène à Keras, chaque colonne peut nécessiter un prétraitement unique. Vous pouvez effectuer ce prétraitement directement dans le DataFrame, mais pour qu'un modèle fonctionne correctement, les entrées doivent toujours être prétraitées de la même manière. Ainsi, la meilleure approche consiste à intégrer le prétraitement dans le modèle. Les couches de prétraitement Keras couvrent de nombreuses tâches courantes.

Construire la tête de prétraitement

Dans cet ensemble de données, certaines des caractéristiques "entières" des données brutes sont en fait des indices catégoriels. Ces indices ne sont pas vraiment des valeurs numériques ordonnées (reportez-vous à la description du jeu de données pour plus de détails). Parce que ceux-ci ne sont pas ordonnés, ils ne sont pas appropriés pour alimenter directement le modèle ; le modèle les interpréterait comme étant ordonnés. Pour utiliser ces entrées, vous devrez les encoder, soit en tant que vecteurs à chaud, soit en tant que vecteurs d'intégration. Il en va de même pour les fonctionnalités de catégorie de chaîne.

Les caractéristiques binaires, en revanche, n'ont généralement pas besoin d'être codées ou normalisées.

Commencez par créer une liste des fonctionnalités qui appartiennent à chaque groupe :

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

L'étape suivante consiste à créer un modèle de prétraitement qui appliquera un prétraitement approprié à chaque entrée et concaténera les résultats.

Cette section utilise l' API fonctionnelle Keras pour implémenter le prétraitement. Vous commencez par créer un tf.keras.Input pour chaque colonne du dataframe :

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

Pour chaque entrée, vous appliquerez des transformations à l'aide des couches Keras et des opérations TensorFlow. Chaque entité commence par un lot de scalaires ( shape=(batch,) ). La sortie pour chacun doit être un lot de vecteurs tf.float32 ( shape=(batch, n) ). La dernière étape concaténera tous ces vecteurs ensemble.

Entrées binaires

Étant donné que les entrées binaires n'ont pas besoin de prétraitement, ajoutez simplement l'axe vectoriel, convertissez-les en float32 et ajoutez-les à la liste des entrées prétraitées :

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

Entrées numériques

Comme dans la section précédente, vous souhaiterez exécuter ces entrées numériques via une couche tf.keras.layers.Normalization avant de les utiliser. La différence est que cette fois, ils sont entrés en tant que dict. Le code ci-dessous collecte les caractéristiques numériques du DataFrame, les empile et les transmet à la méthode Normalization.adapt .

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

Le code ci-dessous empile les caractéristiques numériques et les exécute à travers la couche de normalisation.

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

Caractéristiques catégorielles

Pour utiliser des fonctionnalités catégorielles, vous devez d'abord les encoder en vecteurs binaires ou en incorporations. Étant donné que ces entités ne contiennent qu'un petit nombre de catégories, convertissez les entrées directement en vecteurs one-hot à l'aide de l' output_mode='one_hot' , prise en charge par les couches tf.keras.layers.StringLookup et tf.keras.layers.IntegerLookup .

Voici un exemple du fonctionnement de ces calques :

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

Pour déterminer le vocabulaire de chaque entrée, créez une couche pour convertir ce vocabulaire en un vecteur unique :

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]

Assembler la tête de prétraitement

À ce stade, preprocessed n'est qu'une liste Python de tous les résultats de prétraitement, chaque résultat a une forme 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')>]

Concaténez toutes les caractéristiques prétraitées le long de l'axe de depth , de sorte que chaque exemple de dictionnaire est converti en un seul vecteur. Le vecteur contient des fonctionnalités catégorielles, des fonctionnalités numériques et des fonctionnalités ponctuelles catégorielles :

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

Créez maintenant un modèle à partir de ce calcul afin qu'il puisse être réutilisé :

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

png

Pour tester le préprocesseur, utilisez l'accesseur DataFrame.iloc pour découper le premier exemple à partir du DataFrame. Convertissez-le ensuite en dictionnaire et transmettez le dictionnaire au préprocesseur. Le résultat est un vecteur unique contenant les caractéristiques binaires, les caractéristiques numériques normalisées et les caractéristiques catégorielles uniques, dans cet ordre :

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

Créer et former un modèle

Construisez maintenant le corps principal du modèle. Utilisez la même configuration que dans l'exemple précédent : quelques couches linéaires rectifiées Dense et une couche de sortie Dense(1) pour la classification.

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

Maintenant, assemblez les deux pièces à l'aide de l'API fonctionnelle 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'])

Ce modèle attend un dictionnaire d'entrées. Le moyen le plus simple de lui transmettre les données consiste à convertir le DataFrame en dict et à transmettre ce dict comme argument x à Model.fit :

history = model.fit(dict(df), target, epochs=5, batch_size=BATCH_SIZE)
Epoch 1/5
152/152 [==============================] - 1s 4ms/step - loss: 0.6911 - accuracy: 0.6997
Epoch 2/5
152/152 [==============================] - 1s 4ms/step - loss: 0.5073 - accuracy: 0.7393
Epoch 3/5
152/152 [==============================] - 1s 4ms/step - loss: 0.4129 - accuracy: 0.7888
Epoch 4/5
152/152 [==============================] - 1s 4ms/step - loss: 0.3663 - accuracy: 0.7921
Epoch 5/5
152/152 [==============================] - 1s 4ms/step - loss: 0.3363 - accuracy: 0.8152

L'utilisation de tf.data fonctionne également :

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 5ms/step - loss: 0.3150 - accuracy: 0.8284
Epoch 2/5
152/152 [==============================] - 1s 5ms/step - loss: 0.2989 - accuracy: 0.8449
Epoch 3/5
152/152 [==============================] - 1s 5ms/step - loss: 0.2870 - accuracy: 0.8449
Epoch 4/5
152/152 [==============================] - 1s 5ms/step - loss: 0.2782 - accuracy: 0.8482
Epoch 5/5
152/152 [==============================] - 1s 5ms/step - loss: 0.2712 - accuracy: 0.8482