Visualizza su TensorFlow.org | Esegui in Google Colab | Visualizza l'origine su GitHub | Scarica quaderno |
Questo tutorial mostra come classificare i dati strutturati (ad es. i dati tabulari in un CSV). Useremo Keras per definire il modello e tf.feature_column
come ponte per mappare dalle colonne in un CSV alle funzionalità utilizzate per addestrare il modello. Questo tutorial contiene il codice completo per:
- Carica un file CSV utilizzando Pandas .
- Crea una pipeline di input per raggruppare e mescolare le righe usando tf.data .
- Eseguire il mapping dalle colonne nel CSV alle funzionalità utilizzate per addestrare il modello utilizzando le colonne delle funzionalità.
- Crea, addestra e valuta un modello utilizzando Keras.
Il set di dati
Utilizzeremo una versione semplificata del set di dati PetFinder. Ci sono diverse migliaia di righe nel CSV. Ogni riga descrive un animale domestico e ogni colonna descrive un attributo. Utilizzeremo queste informazioni per prevedere la velocità con cui l'animale verrà adottato.
Di seguito è riportata una descrizione di questo set di dati. Si noti che ci sono colonne sia numeriche che categoriali. C'è una colonna di testo libera che non useremo in questo tutorial.
Colonna | Descrizione | Tipo di caratteristica | Tipo di dati |
---|---|---|---|
Tipo | Tipo di animale (cane, gatto) | Categorico | corda |
Età | Età dell'animale | Numerico | numero intero |
Razza1 | Razza primaria dell'animale domestico | Categorico | corda |
Colore1 | Colore 1 dell'animale domestico | Categorico | corda |
Colore2 | Colore 2 dell'animale domestico | Categorico | corda |
MaturitàTaglia | Taglia a maturità | Categorico | corda |
FurLength | Lunghezza della pelliccia | Categorico | corda |
Vaccinato | L'animale è stato vaccinato | Categorico | corda |
Sterilizzato | L'animale è stato sterilizzato | Categorico | corda |
Salute | Condizione di salute | Categorico | corda |
Tassa | Tassa di adozione | Numerico | numero intero |
Descrizione | Redazione del profilo per questo animale domestico | Testo | corda |
FotoAmt | Totale foto caricate per questo animale domestico | Numerico | numero intero |
Velocità di adozione | Velocità di adozione | Classificazione | numero intero |
Importa TensorFlow e altre librerie
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
Usa Panda per creare un dataframe
Pandas è una libreria Python con molte utili utilità per caricare e lavorare con dati strutturati. Useremo Pandas per scaricare il set di dati da un URL e caricarlo in un dataframe.
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()
Crea variabile di destinazione
L'attività nel set di dati originale è prevedere la velocità con cui verrà adottato un animale domestico (ad esempio, nella prima settimana, nel primo mese, nei primi tre mesi e così via). Semplifichiamo questo per il nostro tutorial. Qui, lo trasformeremo in un problema di classificazione binaria e prevediamo semplicemente se l'animale è stato adottato o meno.
Dopo aver modificato la colonna dell'etichetta, 0 indicherà che l'animale non è stato adottato e 1 indicherà che lo era.
# 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'])
Suddividi il dataframe in treno, convalida e test
Il set di dati che abbiamo scaricato era un singolo file CSV. Lo suddivideremo in set di treni, convalida e 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
Crea una pipeline di input usando tf.data
Successivamente, avvolgeremo i dataframe con tf.data . Ciò ci consentirà di utilizzare le colonne delle caratteristiche come ponte per mappare dalle colonne nel dataframe Pandas alle funzioni utilizzate per addestrare il modello. Se stessimo lavorando con un file CSV molto grande (così grande da non entrare nella memoria), useremmo tf.data per leggerlo direttamente dal disco. Questo non è trattato in questo 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)
Comprendere la pipeline di input
Ora che abbiamo creato la pipeline di input, chiamiamola per vedere il formato dei dati che restituisce. Abbiamo utilizzato un batch di piccole dimensioni per mantenere leggibile l'output.
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)
Possiamo vedere che il set di dati restituisce un dizionario di nomi di colonna (dal dataframe) che mappano i valori di colonna dalle righe nel dataframe.
Dimostra diversi tipi di colonne di funzionalità
TensorFlow fornisce molti tipi di colonne di funzionalità. In questa sezione creeremo diversi tipi di colonne di funzionalità e dimostreremo come trasformano una colonna dal frame di dati.
# 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())
Colonne numeriche
L'output di una colonna caratteristica diventa l'input per il modello (usando la funzione demo definita sopra, saremo in grado di vedere esattamente come viene trasformata ogni colonna del dataframe). Una colonna numerica è il tipo più semplice di colonna. È usato per rappresentare caratteristiche di valore reale. Quando si utilizza questa colonna, il modello riceverà il valore della colonna dal frame di dati invariato.
photo_count = feature_column.numeric_column('PhotoAmt')
demo(photo_count)
[[2.] [4.] [4.] [1.] [2.]]
Nel set di dati PetFinder, la maggior parte delle colonne del dataframe sono categoriali.
Colonne a secchiello
Spesso non si desidera inserire un numero direttamente nel modello, ma dividere il suo valore in diverse categorie in base a intervalli numerici. Considera i dati grezzi che rappresentano l'età di una persona. Invece di rappresentare l'età come una colonna numerica, potremmo dividere l'età in più bucket utilizzando una colonna con bucket . Si noti che i valori one-hot di seguito descrivono a quale fascia di età corrisponde ogni riga.
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.]]
Colonne categoriali
In questo set di dati, Tipo è rappresentato come una stringa (ad es. 'Cane' o 'Gatto'). Non possiamo inviare stringhe direttamente a un modello. Invece, dobbiamo prima mapparli su valori numerici. Le colonne del vocabolario categoriale forniscono un modo per rappresentare le stringhe come un vettore unico (molto simile a quello che hai visto sopra con i bucket di età). Il vocabolario può essere passato come un elenco utilizzando categorical_column_with_vocabulary_list o caricato da un file utilizzando 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.]]
Incorporamento di colonne
Supponiamo invece di avere solo poche possibili stringhe, abbiamo migliaia (o più) valori per categoria. Per una serie di motivi, con l'aumento del numero di categorie, diventa impossibile addestrare una rete neurale utilizzando codifiche one-hot. Possiamo utilizzare una colonna di incorporamento per superare questa limitazione. Invece di rappresentare i dati come un vettore caldo di molte dimensioni, una colonna di incorporamento rappresenta quei dati come un vettore denso di dimensioni inferiori in cui ogni cella può contenere qualsiasi numero, non solo 0 o 1. La dimensione dell'incorporamento ( 8, nell'esempio seguente) è un parametro che deve essere regolato.
# 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 ]]
Colonne delle funzioni con hash
Un altro modo per rappresentare una colonna categoriale con un numero elevato di valori consiste nell'utilizzare un categorical_column_with_hash_bucket . Questa colonna di funzionalità calcola un valore hash dell'input, quindi seleziona uno dei bucket hash_bucket_size
per codificare una stringa. Quando utilizzi questa colonna, non è necessario fornire il vocabolario e puoi scegliere di ridurre notevolmente il numero di hash_bucket rispetto al numero di categorie effettive per risparmiare spazio.
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.]]
Colonne delle funzioni incrociate
La combinazione di funzionalità in un'unica funzionalità, meglio nota come feature crosses , consente a un modello di apprendere pesi separati per ciascuna combinazione di funzionalità. Qui creeremo una nuova funzionalità che è l'incrocio di Età e Tipo. Nota che crossed_column
non crea la tabella completa di tutte le possibili combinazioni (che potrebbero essere molto grandi). Invece, è supportato da una hashed_column
, quindi puoi scegliere quanto è grande la tabella.
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.]]
Scegli quali colonne utilizzare
Abbiamo visto come utilizzare diversi tipi di colonne di funzionalità. Ora li useremo per addestrare un modello. L'obiettivo di questo tutorial è mostrarti il codice completo (es. meccanica) necessario per lavorare con le colonne delle funzionalità. Abbiamo selezionato alcune colonne per addestrare arbitrariamente il nostro modello di seguito.
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))
Crea un livello di funzionalità
Ora che abbiamo definito le nostre colonne di funzionalità, utilizzeremo un livello DenseFeatures per inserirle nel nostro modello Keras.
feature_layer = tf.keras.layers.DenseFeatures(feature_columns)
In precedenza, abbiamo utilizzato una piccola dimensione batch per dimostrare come funzionavano le colonne di funzionalità. Creiamo una nuova pipeline di input con una dimensione batch maggiore.
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)
Crea, compila e addestra il modello
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
Prossimi passi
Il modo migliore per saperne di più sulla classificazione dei dati strutturati è provarlo tu stesso. Suggeriamo di trovare un altro set di dati con cui lavorare e di addestrare un modello per classificarlo utilizzando un codice simile al precedente. Per migliorare la precisione, pensa attentamente a quali caratteristiche includere nel tuo modello e come dovrebbero essere rappresentate.