Classifique dados estruturados com colunas de recursos

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Este tutorial demonstra como classificar dados estruturados (por exemplo, dados tabulares em um CSV). Usaremos Keras para definir o modelo e tf.feature_column como uma ponte para mapear de colunas em um CSV para recursos usados ​​para treinar o modelo. Este tutorial contém o código completo para:

  • Carregue um arquivo CSV usando o Pandas .
  • Crie um pipeline de entrada para agrupar e embaralhar as linhas usando tf.data .
  • Mapeie de colunas no CSV para recursos usados ​​para treinar o modelo usando colunas de recursos.
  • Construa, treine e avalie um modelo usando Keras.

O conjunto de dados

Usaremos uma versão simplificada do conjunto de dados PetFinder. Existem vários milhares de linhas no CSV. Cada linha descreve um animal de estimação e cada coluna descreve um atributo. Usaremos essas informações para prever a velocidade com que o animal será adotado.

A seguir está uma descrição deste conjunto de dados. Observe que há colunas numéricas e categóricas. Há uma coluna de texto livre que não usaremos neste tutorial.

Coluna Descrição Tipo de recurso Tipo de dados
Modelo Tipo de animal (cachorro, gato) Categórico fragmento
Era Idade do animal de estimação Numérico inteiro
Raça 1 Raça primária do animal de estimação Categórico fragmento
Cor1 Cor 1 do animal de estimação Categórico fragmento
Cor 2 Cor 2 do animal de estimação Categórico fragmento
MaturitySize Tamanho na maturidade Categórico fragmento
FurLength Comprimento do pelo Categórico fragmento
Vacinado Animal de estimação foi vacinado Categórico fragmento
Esterilizado Animal de estimação foi esterilizado Categórico fragmento
Saúde Condição de saúde Categórico fragmento
Taxa Taxa de adoção Numérico inteiro
Descrição Escrita do perfil deste animal de estimação Texto fragmento
PhotoAmt Total de fotos enviadas para este animal de estimação Numérico inteiro
AdoptionSpeed Velocidade de adoção Classificação inteiro

Importar TensorFlow e outras bibliotecas

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

Use o Pandas para criar um dataframe

Pandas é uma biblioteca Python com muitos utilitários úteis para carregar e trabalhar com dados estruturados. Usaremos o Pandas para baixar o conjunto de dados de uma URL e carregá-lo em um 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
dataframe.head()

Criar variável de destino

A tarefa no conjunto de dados original é prever a velocidade com que um animal de estimação será adotado (por exemplo, na primeira semana, no primeiro mês, nos primeiros três meses e assim por diante). Vamos simplificar isso para nosso tutorial. Aqui, vamos transformar isso em um problema de classificação binária e simplesmente prever se o animal de estimação foi adotado ou não.

Depois de modificar a coluna do rótulo, 0 indicará que o animal de estimação não foi adotado e 1 indicará que foi.

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

Divida o dataframe em treinamento, validação e teste

O conjunto de dados que baixamos era um único arquivo CSV. Vamos dividir isso em conjuntos de treinamento, validação e teste.

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

Crie um pipeline de entrada usando tf.data

A seguir, envolveremos os dataframes com tf.data . Isso nos permitirá usar colunas de recursos como uma ponte para mapear a partir das colunas no dataframe do Pandas para recursos usados ​​para treinar o modelo. Se estivéssemos trabalhando com um arquivo CSV muito grande (tão grande que não cabe na memória), usaríamos tf.data para lê-lo diretamente do disco. Isso não é abordado neste 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)

Entenda o pipeline de entrada

Agora que criamos o pipeline de entrada, vamos chamá-lo para ver o formato dos dados que ele retorna. Usamos um tamanho de lote pequeno para manter a saída legível.

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([9 6 5 3 3], shape=(5,), dtype=int64)
A batch of targets: tf.Tensor([0 0 1 1 1], shape=(5,), dtype=int64)

Podemos ver que o conjunto de dados retorna um dicionário de nomes de coluna (do dataframe) que mapeia para valores de coluna de linhas no dataframe.

Demonstrar vários tipos de colunas de recursos

O TensorFlow oferece muitos tipos de colunas de recursos. Nesta seção, criaremos vários tipos de colunas de recursos e demonstraremos como eles transformam uma coluna do 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())

Colunas numéricas

A saída de uma coluna de recurso torna-se a entrada para o modelo (usando a função de demonstração definida acima, seremos capazes de ver exatamente como cada coluna do dataframe é transformada). Uma coluna numérica é o tipo de coluna mais simples. É usado para representar características de valor real. Ao usar esta coluna, seu modelo receberá o valor da coluna do dataframe inalterado.

photo_count = feature_column.numeric_column('PhotoAmt')
demo(photo_count)
[[24.]
 [ 2.]
 [11.]
 [ 5.]
 [ 4.]]

No conjunto de dados PetFinder, a maioria das colunas do dataframe são categóricas.

Colunas segmentadas

Freqüentemente, você não deseja alimentar um número diretamente no modelo, mas sim dividir seu valor em diferentes categorias com base em intervalos numéricos. Considere os dados brutos que representam a idade de uma pessoa. Em vez de representar a idade como uma coluna numérica, poderíamos dividir a idade em vários segmentos usando uma coluna segmentada . Observe que os valores one-hot abaixo descrevem a qual faixa etária cada linha corresponde.

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. 0. 0. 1.]
 [0. 0. 0. 1.]
 [0. 0. 0. 1.]
 [0. 0. 0. 1.]]

Colunas categóricas

Neste conjunto de dados, o tipo é representado como uma string (por exemplo, 'Cachorro' ou 'Gato'). Não podemos alimentar strings diretamente para um modelo. Em vez disso, devemos primeiro mapeá-los para valores numéricos. As colunas de vocabulário categóricas fornecem uma maneira de representar strings como um vetor único (muito parecido com o que você viu acima com intervalos de idade). O vocabulário pode ser passado como uma lista usando categorical_column_with_vocabulary_list , ou carregado de um arquivo usando 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)
[[0. 1.]
 [1. 0.]
 [1. 0.]
 [1. 0.]
 [0. 1.]]

Colunas de incorporação

Suponha que, em vez de apenas algumas strings possíveis, tenhamos milhares (ou mais) valores por categoria. Por uma série de razões, conforme o número de categorias aumenta, torna-se inviável treinar uma rede neural usando codificações one-hot. Podemos usar uma coluna de incorporação para superar essa limitação. Em vez de representar os dados como um vetor único de muitas dimensões, uma coluna de incorporação representa esses dados como um vetor denso e de dimensão inferior em que cada célula pode conter qualquer número, não apenas 0 ou 1. O tamanho da incorporação ( 8, no exemplo abaixo) é um parâmetro que deve ser ajustado.

# 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.38030246  0.34633866 -0.5518794  -0.40159607  0.34524533  0.38142323
  -0.17445108 -0.17150676]
 [ 0.10612908  0.06629402 -0.0950259  -0.06276089  0.24865325  0.5768149
   0.14053977  0.03032764]
 [ 0.10612908  0.06629402 -0.0950259  -0.06276089  0.24865325  0.5768149
   0.14053977  0.03032764]
 [ 0.10612908  0.06629402 -0.0950259  -0.06276089  0.24865325  0.5768149
   0.14053977  0.03032764]
 [ 0.10291246 -0.51296854  0.16194355  0.3013709  -0.50001127 -0.42457762
  -0.09823764  0.5075511 ]]

Colunas de recursos com hash

Outra maneira de representar uma coluna categórica com um grande número de valores é usar categorical_column_with_hash_bucket . Esta coluna de recurso calcula um valor hash da entrada e, a seguir, seleciona um dos hash_bucket_size para codificar uma string. Ao usar esta coluna, você não precisa fornecer o vocabulário e pode escolher tornar o número de hash_buckets significativamente menor do que o número de categorias reais para economizar espaço.

breed1_hashed = feature_column.categorical_column_with_hash_bucket(
      'Breed1', hash_bucket_size=10)
demo(feature_column.indicator_column(breed1_hashed))
[[0. 0. 1. 0. 0. 0. 0. 0. 0. 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. 0. 1.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]]

Colunas de feições cruzadas

Combinar recursos em um único recurso, mais conhecido como cruzamentos de recursos , permite que um modelo aprenda pesos separados para cada combinação de recursos. Aqui, criaremos um novo recurso que é o cruzamento de Idade e Tipo. Observe que a crossed_column não constrói a tabela completa de todas as combinações possíveis (que podem ser muito grandes). Em vez disso, ele é apoiado por uma hashed_column , para que você possa escolher o tamanho da tabela.

crossed_feature = feature_column.crossed_column([age_buckets, animal_type], hash_bucket_size=10)
demo(feature_column.indicator_column(crossed_feature))
[[0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
 [1. 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. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]]

Escolha quais colunas usar

Vimos como usar vários tipos de colunas de recursos. Agora vamos usá-los para treinar um modelo. O objetivo deste tutorial é mostrar o código completo (por exemplo, mecânica) necessário para trabalhar com colunas de recursos. Selecionamos algumas colunas para treinar nosso modelo abaixo arbitrariamente.

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

Crie uma camada de feição

Agora que definimos nossas colunas de recursos, usaremos uma camada DenseFeatures para inseri -los em nosso modelo Keras.

feature_layer = tf.keras.layers.DenseFeatures(feature_columns)

Anteriormente, usamos um pequeno tamanho de lote para demonstrar como as colunas de recursos funcionavam. Criamos um novo pipeline de entrada com um tamanho de lote maior.

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)

Crie, compile e treine o modelo

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, but we receive a <class 'dict'> input: {'Type': <tf.Tensor 'ExpandDims_11:0' shape=(None, 1) dtype=string>, 'Age': <tf.Tensor 'ExpandDims:0' shape=(None, 1) dtype=int64>, 'Breed1': <tf.Tensor 'ExpandDims_1:0' shape=(None, 1) dtype=string>, 'Gender': <tf.Tensor 'ExpandDims_6:0' shape=(None, 1) dtype=string>, 'Color1': <tf.Tensor 'ExpandDims_2:0' shape=(None, 1) dtype=string>, 'Color2': <tf.Tensor 'ExpandDims_3:0' shape=(None, 1) dtype=string>, 'MaturitySize': <tf.Tensor 'ExpandDims_8:0' shape=(None, 1) dtype=string>, 'FurLength': <tf.Tensor 'ExpandDims_5:0' shape=(None, 1) dtype=string>, 'Vaccinated': <tf.Tensor 'ExpandDims_12:0' shape=(None, 1) dtype=string>, 'Sterilized': <tf.Tensor 'ExpandDims_10:0' shape=(None, 1) dtype=string>, 'Health': <tf.Tensor 'ExpandDims_7:0' shape=(None, 1) dtype=string>, 'Fee': <tf.Tensor 'ExpandDims_4:0' shape=(None, 1) dtype=int64>, 'PhotoAmt': <tf.Tensor 'ExpandDims_9:0' shape=(None, 1) dtype=int64>}
Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'dict'> input: {'Type': <tf.Tensor 'ExpandDims_11:0' shape=(None, 1) dtype=string>, 'Age': <tf.Tensor 'ExpandDims:0' shape=(None, 1) dtype=int64>, 'Breed1': <tf.Tensor 'ExpandDims_1:0' shape=(None, 1) dtype=string>, 'Gender': <tf.Tensor 'ExpandDims_6:0' shape=(None, 1) dtype=string>, 'Color1': <tf.Tensor 'ExpandDims_2:0' shape=(None, 1) dtype=string>, 'Color2': <tf.Tensor 'ExpandDims_3:0' shape=(None, 1) dtype=string>, 'MaturitySize': <tf.Tensor 'ExpandDims_8:0' shape=(None, 1) dtype=string>, 'FurLength': <tf.Tensor 'ExpandDims_5:0' shape=(None, 1) dtype=string>, 'Vaccinated': <tf.Tensor 'ExpandDims_12:0' shape=(None, 1) dtype=string>, 'Sterilized': <tf.Tensor 'ExpandDims_10:0' shape=(None, 1) dtype=string>, 'Health': <tf.Tensor 'ExpandDims_7:0' shape=(None, 1) dtype=string>, 'Fee': <tf.Tensor 'ExpandDims_4:0' shape=(None, 1) dtype=int64>, 'PhotoAmt': <tf.Tensor 'ExpandDims_9:0' shape=(None, 1) dtype=int64>}
Consider rewriting this model with the Functional API.
226/231 [============================>.] - ETA: 0s - loss: 0.6471 - accuracy: 0.6869WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'dict'> input: {'Type': <tf.Tensor 'ExpandDims_11:0' shape=(None, 1) dtype=string>, 'Age': <tf.Tensor 'ExpandDims:0' shape=(None, 1) dtype=int64>, 'Breed1': <tf.Tensor 'ExpandDims_1:0' shape=(None, 1) dtype=string>, 'Gender': <tf.Tensor 'ExpandDims_6:0' shape=(None, 1) dtype=string>, 'Color1': <tf.Tensor 'ExpandDims_2:0' shape=(None, 1) dtype=string>, 'Color2': <tf.Tensor 'ExpandDims_3:0' shape=(None, 1) dtype=string>, 'MaturitySize': <tf.Tensor 'ExpandDims_8:0' shape=(None, 1) dtype=string>, 'FurLength': <tf.Tensor 'ExpandDims_5:0' shape=(None, 1) dtype=string>, 'Vaccinated': <tf.Tensor 'ExpandDims_12:0' shape=(None, 1) dtype=string>, 'Sterilized': <tf.Tensor 'ExpandDims_10:0' shape=(None, 1) dtype=string>, 'Health': <tf.Tensor 'ExpandDims_7:0' shape=(None, 1) dtype=string>, 'Fee': <tf.Tensor 'ExpandDims_4:0' shape=(None, 1) dtype=int64>, 'PhotoAmt': <tf.Tensor 'ExpandDims_9:0' shape=(None, 1) dtype=int64>}
Consider rewriting this model with the Functional API.
231/231 [==============================] - 4s 9ms/step - loss: 0.6457 - accuracy: 0.6864 - val_loss: 0.5370 - val_accuracy: 0.7004
Epoch 2/10
231/231 [==============================] - 2s 7ms/step - loss: 0.5387 - accuracy: 0.7166 - val_loss: 0.5447 - val_accuracy: 0.7140
Epoch 3/10
231/231 [==============================] - 2s 7ms/step - loss: 0.5112 - accuracy: 0.7237 - val_loss: 0.5115 - val_accuracy: 0.7216
Epoch 4/10
231/231 [==============================] - 2s 7ms/step - loss: 0.4985 - accuracy: 0.7360 - val_loss: 0.5152 - val_accuracy: 0.7226
Epoch 5/10
231/231 [==============================] - 2s 8ms/step - loss: 0.4898 - accuracy: 0.7406 - val_loss: 0.5191 - val_accuracy: 0.6874
Epoch 6/10
231/231 [==============================] - 2s 7ms/step - loss: 0.4871 - accuracy: 0.7440 - val_loss: 0.5104 - val_accuracy: 0.6966
Epoch 7/10
231/231 [==============================] - 2s 7ms/step - loss: 0.4812 - accuracy: 0.7493 - val_loss: 0.5056 - val_accuracy: 0.7486
Epoch 8/10
231/231 [==============================] - 2s 7ms/step - loss: 0.4752 - accuracy: 0.7563 - val_loss: 0.5037 - val_accuracy: 0.7319
Epoch 9/10
231/231 [==============================] - 2s 7ms/step - loss: 0.4704 - accuracy: 0.7542 - val_loss: 0.4983 - val_accuracy: 0.7319
Epoch 10/10
231/231 [==============================] - 2s 7ms/step - loss: 0.4669 - accuracy: 0.7561 - val_loss: 0.5072 - val_accuracy: 0.7167
<tensorflow.python.keras.callbacks.History at 0x7f3ac9d3e710>
loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)
73/73 [==============================] - 0s 5ms/step - loss: 0.5107 - accuracy: 0.7192
Accuracy 0.7192374467849731

Próximos passos

A melhor maneira de aprender mais sobre como classificar dados estruturados é experimentando você mesmo. Sugerimos encontrar outro conjunto de dados para trabalhar e treinar um modelo para classificá-lo usando um código semelhante ao anterior. Para melhorar a precisão, pense cuidadosamente sobre quais recursos incluir em seu modelo e como eles devem ser representados.