Classifique dados estruturados com colunas de recursos

Veja 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 código completo para:

  • Carregue um arquivo CSV usando 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 existem 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 (Cão, Gato) Categórico corda
Era Idade do animal de estimação Numérico inteiro
Raça1 Raça primária do animal de estimação Categórico corda
Cor1 Cor 1 do animal de estimação Categórico corda
Cor2 Cor 2 do animal de estimação Categórico corda
MaturitySize Tamanho na maturidade Categórico corda
FurLength Comprimento do pelo Categórico corda
Vacinado O animal de estimação foi vacinado Categórico corda
Esterilizado O animal de estimação foi esterilizado Categórico corda
Saúde Condição de saúde Categórico corda
Taxa Taxa de adoção Numérico inteiro
Descrição Descrição do perfil para este animal de estimação Texto corda
Valor da foto Total de fotos enviadas para este animal de estimação Numérico inteiro
Velocidade de Adoção 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 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 um 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
1679360/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 o 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.

Após modificar a coluna do rótulo, 0 indicará que o animal 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 treinar, validar e testar

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

Em seguida, envolveremos os dataframes com tf.data . Isso nos permitirá usar colunas de recursos como uma ponte para mapear das colunas no dataframe do Pandas para os 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([ 6  2 36  2  2], shape=(5,), dtype=int64)
A batch of targets: tf.Tensor([1 1 1 1 1], shape=(5,), dtype=int64)

Podemos ver que o dataset 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 fornece 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 se torna a entrada para o modelo (usando a função demo definida acima, poderemos ver exatamente como cada coluna do dataframe é transformada). Uma coluna numérica é o tipo mais simples de coluna. Ele é usado para representar recursos 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)
[[2.]
 [4.]
 [4.]
 [1.]
 [2.]]

No conjunto de dados PetFinder, a maioria das colunas do dataframe é categórica.

Colunas agrupadas

Muitas vezes, você não deseja inserir um número diretamente no modelo, mas, em vez disso, dividir seu valor em diferentes categorias com base em intervalos numéricos. Considere 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 blocos usando uma coluna com intervalos . Observe que os valores abaixo descrevem 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. 1. 0. 0.]
 [0. 0. 0. 1.]
 [0. 0. 1. 0.]
 [0. 1. 0. 0.]]

Colunas categóricas

Nesse conjunto de dados, Type é representado como uma string (por exemplo, 'Dog' ou 'Cat'). Não podemos alimentar strings diretamente em um modelo. Em vez disso, devemos primeiro mapeá-los para valores numéricos. As colunas de vocabulário categórico fornecem uma maneira de representar strings como um vetor one-hot (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)
[[1. 0.]
 [1. 0.]
 [1. 0.]
 [1. 0.]
 [0. 1.]]

Incorporando colunas

Suponha que, em vez de ter apenas algumas strings possíveis, tenhamos milhares (ou mais) valores por categoria. Por várias razões, à medida que 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 menor dimensão, no qual 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.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 ]]

Colunas de recursos com hash

Outra maneira de representar uma coluna categórica com um grande número de valores é usar um categorical_column_with_hash_bucket . Essa coluna de recurso calcula um valor de hash da entrada e seleciona um dos buckets hash_bucket_size para codificar uma string. Ao usar esta coluna, você não precisa fornecer o vocabulário e pode optar por 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. 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.]]

Colunas de recursos cruzados

Combinar recursos em um único recurso, mais conhecido como cruzamento de recursos, permite que um modelo aprenda pesos separados para cada combinação de recursos. Aqui, vamos criar um novo recurso que é o cruzamento de Idade e Tipo. Observe que a crossed_column não cria a tabela completa de todas as combinações possíveis (que podem ser muito grandes). Em vez disso, ele é apoiado por um 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))
[[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.]]

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

Criar uma camada de feição

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

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

Anteriormente, usamos um tamanho de lote pequeno 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)

Criar, compilar e treinar 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. 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

Próximos passos

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