Classifica i dati strutturati con colonne di funzionalità

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.