Voir sur TensorFlow.org | Exécuter dans Google Colab | Voir la source sur GitHub | Télécharger le cahier |
Ce didacticiel montre comment classer des données structurées (par exemple, des données tabulaires dans un CSV). Nous utiliserons Keras pour définir le modèle et tf.feature_column
comme pont pour mapper des colonnes dans un CSV aux fonctionnalités utilisées pour former le modèle. Ce tutoriel contient le code complet pour :
- Chargez un fichier CSV à l'aide de Pandas .
- Créez un pipeline d'entrée pour regrouper et mélanger les lignes à l'aide de tf.data .
- Mappez des colonnes du CSV aux fonctionnalités utilisées pour entraîner le modèle à l'aide de colonnes de fonctionnalités.
- Créez, entraînez et évaluez un modèle à l'aide de Keras.
L'ensemble de données
Nous utiliserons une version simplifiée du jeu de données PetFinder. Il y a plusieurs milliers de lignes dans le CSV. Chaque ligne décrit un animal de compagnie et chaque colonne décrit un attribut. Nous utiliserons ces informations pour prédire la vitesse à laquelle l'animal sera adopté.
Voici une description de cet ensemble de données. Notez qu'il existe des colonnes numériques et catégorielles. Il y a une colonne de texte libre que nous n'utiliserons pas dans ce tutoriel.
Colonne | La description | Type de fonctionnalité | Type de données |
---|---|---|---|
Taper | Type d'animal (Chien, Chat) | Catégorique | chaîne de caractères |
Âge | Âge de l'animal | Numérique | entier |
Race1 | Race principale de l'animal | Catégorique | chaîne de caractères |
Couleur1 | Couleur 1 de l'animal | Catégorique | chaîne de caractères |
Couleur2 | Couleur 2 de l'animal | Catégorique | chaîne de caractères |
MaturitéTaille | Taille à maturité | Catégorique | chaîne de caractères |
FourrureLongueur | Longueur de la fourrure | Catégorique | chaîne de caractères |
Vacciné | L'animal a été vacciné | Catégorique | chaîne de caractères |
Stérilisé | L'animal a été stérilisé | Catégorique | chaîne de caractères |
Santé | État de santé | Catégorique | chaîne de caractères |
Frais | Frais d'adoption | Numérique | entier |
La description | Rédaction du profil de cet animal de compagnie | Texte | chaîne de caractères |
PhotoAmt | Total de photos téléchargées pour cet animal de compagnie | Numérique | entier |
Vitesse d'adoption | Rapidité d'adoption | Classification | entier |
Importer TensorFlow et d'autres bibliothèques
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
Utiliser Pandas pour créer une dataframe
Pandas est une bibliothèque Python avec de nombreux utilitaires utiles pour charger et travailler avec des données structurées. Nous utiliserons Pandas pour télécharger l'ensemble de données à partir d'une URL et le charger dans une base de données.
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()
Créer une variable cible
La tâche dans l'ensemble de données d'origine est de prédire la vitesse à laquelle un animal de compagnie sera adopté (par exemple, la première semaine, le premier mois, les trois premiers mois, etc.). Simplifions cela pour notre tutoriel. Ici, nous allons transformer cela en un problème de classification binaire et prédire simplement si l'animal a été adopté ou non.
Après avoir modifié la colonne d'étiquette, 0 indiquera que l'animal n'a pas été adopté et 1 indiquera qu'il l'a été.
# 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'])
Diviser la trame de données en train, validation et test
L'ensemble de données que nous avons téléchargé était un seul fichier CSV. Nous diviserons cela en ensembles d'entraînement, de validation et de test.
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
Créer un pipeline d'entrée à l'aide de tf.data
Ensuite, nous allons envelopper les dataframes avec tf.data . Cela nous permettra d'utiliser les colonnes de caractéristiques comme un pont pour mapper les colonnes de la base de données Pandas aux caractéristiques utilisées pour former le modèle. Si nous travaillions avec un fichier CSV très volumineux (si volumineux qu'il ne rentre pas dans la mémoire), nous utiliserions tf.data pour le lire directement à partir du disque. Ce n'est pas couvert dans ce tutoriel.
# 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)
Comprendre le pipeline d'entrée
Maintenant que nous avons créé le pipeline d'entrée, appelons-le pour voir le format des données qu'il renvoie. Nous avons utilisé une petite taille de lot pour garder la sortie lisible.
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)
Nous pouvons voir que l'ensemble de données renvoie un dictionnaire de noms de colonnes (de la trame de données) qui correspondent aux valeurs de colonne des lignes de la trame de données.
Démontrer plusieurs types de colonnes de caractéristiques
TensorFlow fournit de nombreux types de colonnes de fonctionnalités. Dans cette section, nous allons créer plusieurs types de colonnes de caractéristiques et montrer comment elles transforment une colonne à partir du dataframe.
# 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())
Colonnes numériques
La sortie d'une colonne de caractéristiques devient l'entrée du modèle (en utilisant la fonction de démonstration définie ci-dessus, nous pourrons voir exactement comment chaque colonne de la trame de données est transformée). Une colonne numérique est le type de colonne le plus simple. Il est utilisé pour représenter des caractéristiques à valeur réelle. Lorsque vous utilisez cette colonne, votre modèle recevra la valeur de la colonne du dataframe inchangée.
photo_count = feature_column.numeric_column('PhotoAmt')
demo(photo_count)
[[2.] [4.] [4.] [1.] [2.]]
Dans le jeu de données PetFinder, la plupart des colonnes du dataframe sont catégorielles.
Colonnes compartimentées
Souvent, vous ne souhaitez pas introduire un nombre directement dans le modèle, mais plutôt diviser sa valeur en différentes catégories en fonction de plages numériques. Considérez les données brutes qui représentent l'âge d'une personne. Au lieu de représenter l'âge sous la forme d'une colonne numérique, nous pourrions diviser l'âge en plusieurs compartiments à l'aide d'une colonne compartimentée . Notez que les valeurs ponctuelles ci-dessous décrivent la tranche d'âge à laquelle chaque ligne correspond.
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.]]
Colonnes catégorielles
Dans cet ensemble de données, Type est représenté sous la forme d'une chaîne (par exemple, "Chien" ou "Chat"). Nous ne pouvons pas envoyer de chaînes directement à un modèle. Au lieu de cela, nous devons d'abord les mapper à des valeurs numériques. Les colonnes de vocabulaire catégoriel permettent de représenter les chaînes sous la forme d'un vecteur unique (un peu comme vous l'avez vu ci-dessus avec les tranches d'âge). Le vocabulaire peut être passé sous forme de liste en utilisant categorical_column_with_vocabulary_list , ou chargé à partir d'un fichier en utilisant 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.]]
Incorporer des colonnes
Supposons qu'au lieu d'avoir seulement quelques chaînes possibles, nous ayons des milliers (ou plus) de valeurs par catégorie. Pour un certain nombre de raisons, à mesure que le nombre de catégories augmente, il devient impossible de former un réseau de neurones à l'aide d'encodages à chaud. Nous pouvons utiliser une colonne d'intégration pour surmonter cette limitation. Au lieu de représenter les données sous la forme d'un vecteur unique de plusieurs dimensions, une colonne d'intégration représente ces données sous la forme d'un vecteur dense de dimension inférieure dans lequel chaque cellule peut contenir n'importe quel nombre, pas seulement 0 ou 1. La taille de l'intégration ( 8, dans l'exemple ci-dessous) est un paramètre qui doit être réglé.
# 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 ]]
Colonnes de caractéristiques hachées
Une autre façon de représenter une colonne catégorielle avec un grand nombre de valeurs consiste à utiliser un categorical_column_with_hash_bucket . Cette colonne de fonctionnalités calcule une valeur de hachage de l'entrée, puis sélectionne l'un des hash_bucket_size
pour encoder une chaîne. Lorsque vous utilisez cette colonne, vous n'avez pas besoin de fournir le vocabulaire et vous pouvez choisir de réduire considérablement le nombre de hash_buckets au nombre de catégories réelles pour économiser de l'espace.
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.]]
Colonnes de caractéristiques croisées
La combinaison de caractéristiques en une seule caractéristique, mieux connue sous le nom de croix de caractéristiques , permet à un modèle d'apprendre des pondérations distinctes pour chaque combinaison de caractéristiques. Ici, nous allons créer une nouvelle fonctionnalité qui est le croisement de l'âge et du type. Notez que crossed_column
ne construit pas la table complète de toutes les combinaisons possibles (qui peuvent être très grandes). Au lieu de cela, il est soutenu par un hashed_column
, vous pouvez donc choisir la taille de la table.
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.]]
Choisissez les colonnes à utiliser
Nous avons vu comment utiliser plusieurs types de colonnes de caractéristiques. Nous allons maintenant les utiliser pour former un modèle. Le but de ce didacticiel est de vous montrer le code complet (par exemple, la mécanique) nécessaire pour travailler avec des colonnes de fonctionnalités. Nous avons sélectionné quelques colonnes pour former notre modèle ci-dessous arbitrairement.
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))
Créer une couche d'entités
Maintenant que nous avons défini nos colonnes de caractéristiques, nous allons utiliser une couche DenseFeatures pour les entrer dans notre modèle Keras.
feature_layer = tf.keras.layers.DenseFeatures(feature_columns)
Auparavant, nous avons utilisé une petite taille de lot pour démontrer le fonctionnement des colonnes de caractéristiques. Nous créons un nouveau pipeline d'entrée avec une taille de lot plus 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)
Créer, compiler et former le modèle
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
Prochaines étapes
La meilleure façon d'en savoir plus sur la classification des données structurées est de l'essayer vous-même. Nous vous suggérons de trouver un autre ensemble de données avec lequel travailler et de former un modèle pour le classer à l'aide d'un code similaire à celui ci-dessus. Pour améliorer la précision, réfléchissez bien aux fonctionnalités à inclure dans votre modèle et à la manière dont elles doivent être représentées.