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.