Classifique dados estruturados usando camadas de pré-processamento Keras

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

Este tutorial demonstra como classificar dados estruturados, como dados tabulares, usando uma versão simplificada do conjunto de dados PetFinder de uma competição Kaggle armazenada em um arquivo CSV.

Você usará o Keras para definir o modelo e as camadas de pré-processamento do Keras como uma ponte para mapear as colunas em um arquivo CSV para os recursos usados ​​para treinar o modelo. O objetivo é prever se um animal de estimação será adotado.

Este tutorial contém código completo para:

  • Carregando um arquivo CSV em um DataFrame usando pandas .
  • Construindo um pipeline de entrada para agrupar e embaralhar as linhas usando tf.data . (Visite tf.data: criar pipelines de entrada do TensorFlow para obter mais detalhes.)
  • Mapeamento de colunas no arquivo CSV para recursos usados ​​para treinar o modelo com as camadas de pré-processamento Keras.
  • Construindo, treinando e avaliando um modelo usando os métodos integrados do Keras.

O miniconjunto de dados PetFinder.my

Existem milhares de linhas no arquivo de conjunto de dados CSV do PetFinder.my mini, onde cada linha descreve um animal de estimação (um cachorro ou um gato) e cada coluna descreve um atributo, como idade, raça, cor e assim por diante.

No resumo do conjunto de dados abaixo, observe que há principalmente colunas numéricas e categóricas. Neste tutorial, você estará lidando apenas com esses dois tipos de recursos, descartando Description (um recurso de texto livre) e AdoptionSpeed (um recurso de classificação) durante o pré-processamento de dados.

Coluna Descrição do animal de estimação Tipo de recurso Tipo de dados
Type Tipo de animal ( Dog , Cat ) Categórico Corda
Age Era Numérico inteiro
Breed1 Raça primária Categórico Corda
Color1 Cor 1 Categórico Corda
Color2 Cor 2 Categórico Corda
MaturitySize Tamanho na maturidade Categórico Corda
FurLength Comprimento do pelo Categórico Corda
Vaccinated O animal de estimação foi vacinado Categórico Corda
Sterilized O animal de estimação foi esterilizado Categórico Corda
Health Condição de saúde Categórico Corda
Fee Taxa de adoção Numérico inteiro
Description Redação de perfil Texto Corda
PhotoAmt Total de fotos enviadas Numérico inteiro
AdoptionSpeed Velocidade categórica de adoção Classificação inteiro

Importar TensorFlow e outras bibliotecas

import numpy as np
import pandas as pd
import tensorflow as tf

from tensorflow.keras import layers
tf.__version__
'2.8.0-rc1'

Carregue o conjunto de dados e leia-o em um DataFrame pandas

pandas é uma biblioteca Python com muitos utilitários úteis para carregar e trabalhar com dados estruturados. Use tf.keras.utils.get_file para baixar e extrair o arquivo CSV com o mini conjunto de dados PetFinder.my e carregue-o em um DataFrame com pandas.read_csv :

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

Inspecione o conjunto de dados verificando as cinco primeiras linhas do DataFrame:

dataframe.head()

Criar uma variável de destino

A tarefa original na competição PetFinder.my Adoption Prediction do Kaggle era prever a velocidade com que um animal de estimação seria adotado (por exemplo, na primeira semana, no primeiro mês, nos primeiros três meses e assim por diante).

Neste tutorial, você simplificará a tarefa transformando-a em um problema de classificação binária, onde você simplesmente terá que prever se um animal de estimação foi adotado ou não.

Após modificar a coluna AdoptionSpeed , 0 indicará que o animal não foi adotado e 1 indicará que foi.

# In the original dataset, `'AdoptionSpeed'` of `4` indicates
# a pet was not adopted.
dataframe['target'] = np.where(dataframe['AdoptionSpeed']==4, 0, 1)

# Drop unused features.
dataframe = dataframe.drop(columns=['AdoptionSpeed', 'Description'])

Divida o DataFrame em conjuntos de treinamento, validação e teste

O conjunto de dados está em um único DataFrame de pandas. Divida-o em conjuntos de treinamento, validação e teste usando, por exemplo, uma proporção de 80:10:10, respectivamente:

train, val, test = np.split(dataframe.sample(frac=1), [int(0.8*len(dataframe)), int(0.9*len(dataframe))])
print(len(train), 'training examples')
print(len(val), 'validation examples')
print(len(test), 'test examples')
9229 training examples
1154 validation examples
1154 test examples

Crie um pipeline de entrada usando tf.data

Em seguida, crie uma função de utilitário que converta cada DataFrame de treinamento, validação e conjunto de teste em um tf.data.Dataset e, em seguida, embaralhe e agrupa os dados.

def df_to_dataset(dataframe, shuffle=True, batch_size=32):
  df = dataframe.copy()
  labels = df.pop('target')
  df = {key: value[:,tf.newaxis] for key, value in dataframe.items()}
  ds = tf.data.Dataset.from_tensor_slices((dict(df), labels))
  if shuffle:
    ds = ds.shuffle(buffer_size=len(dataframe))
  ds = ds.batch(batch_size)
  ds = ds.prefetch(batch_size)
  return ds

Agora, use a função recém-criada ( df_to_dataset ) para verificar o formato dos dados que a função auxiliar do pipeline de entrada retorna chamando-a nos dados de treinamento e use um tamanho de lote pequeno para manter a saída legível:

batch_size = 5
train_ds = df_to_dataset(train, batch_size=batch_size)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:4: FutureWarning: Support for multi-dimensional indexing (e.g. `obj[:, None]`) is deprecated and will be removed in a future version.  Convert to a numpy array before indexing instead.
  after removing the cwd from sys.path.
[(train_features, label_batch)] = train_ds.take(1)
print('Every feature:', list(train_features.keys()))
print('A batch of ages:', train_features['Age'])
print('A batch of targets:', label_batch )
Every feature: ['Type', 'Age', 'Breed1', 'Gender', 'Color1', 'Color2', 'MaturitySize', 'FurLength', 'Vaccinated', 'Sterilized', 'Health', 'Fee', 'PhotoAmt', 'target']
A batch of ages: tf.Tensor(
[[84]
 [ 1]
 [ 5]
 [ 1]
 [12]], shape=(5, 1), dtype=int64)
A batch of targets: tf.Tensor([1 1 0 1 0], shape=(5,), dtype=int64)

Como a saída demonstra, o conjunto de treinamento retorna um dicionário de nomes de coluna (do DataFrame) que mapeia para valores de coluna de linhas.

Aplique as camadas de pré-processamento Keras

As camadas de pré-processamento Keras permitem que você crie pipelines de processamento de entrada nativos do Keras, que podem ser usados ​​como código de pré-processamento independente em fluxos de trabalho não Keras, combinados diretamente com modelos Keras e exportados como parte de um Keras SavedModel.

Neste tutorial, você usará as quatro camadas de pré-processamento a seguir para demonstrar como realizar o pré-processamento, a codificação de dados estruturados e a engenharia de recursos:

Você pode aprender mais sobre as camadas disponíveis no guia Trabalhando com camadas de pré-processamento .

  • Para recursos numéricos do mini conjunto de dados PetFinder.my, você usará uma camada tf.keras.layers.Normalization para padronizar a distribuição dos dados.
  • Para recursos categóricos , como pet Type s (cadeias Dog e Cat ), você os transformará em tensores codificados multi-hot com tf.keras.layers.CategoryEncoding .

Colunas numéricas

Para cada recurso numérico no miniconjunto de dados PetFinder.my, você usará uma camada tf.keras.layers.Normalization para padronizar a distribuição dos dados.

Defina uma nova função de utilitário que retorna uma camada que aplica a normalização de recursos a recursos numéricos usando essa camada de pré-processamento Keras:

def get_normalization_layer(name, dataset):
  # Create a Normalization layer for the feature.
  normalizer = layers.Normalization(axis=None)

  # Prepare a Dataset that only yields the feature.
  feature_ds = dataset.map(lambda x, y: x[name])

  # Learn the statistics of the data.
  normalizer.adapt(feature_ds)

  return normalizer

Em seguida, teste a nova função chamando-a no total de recursos de fotos de animais de estimação enviados para normalizar 'PhotoAmt' :

photo_count_col = train_features['PhotoAmt']
layer = get_normalization_layer('PhotoAmt', train_ds)
layer(photo_count_col)
<tf.Tensor: shape=(5, 1), dtype=float32, numpy=
array([[-0.8272058 ],
       [-0.19125296],
       [ 1.3986291 ],
       [-0.19125296],
       [-0.50922936]], dtype=float32)>

Colunas categóricas

Type de animais de estimação no conjunto de dados são representados como strings — Dog s e Cat s — que precisam ser codificados multi-hot antes de serem inseridos no modelo. O recurso Age

Defina outra nova função de utilitário que retorne uma camada que mapeie valores de um vocabulário para índices inteiros e multi-hot codifica os recursos usando o pré-processamento tf.keras.layers.StringLookup , tf.keras.layers.IntegerLookup e tf.keras.CategoryEncoding camadas:

def get_category_encoding_layer(name, dataset, dtype, max_tokens=None):
  # Create a layer that turns strings into integer indices.
  if dtype == 'string':
    index = layers.StringLookup(max_tokens=max_tokens)
  # Otherwise, create a layer that turns integer values into integer indices.
  else:
    index = layers.IntegerLookup(max_tokens=max_tokens)

  # Prepare a `tf.data.Dataset` that only yields the feature.
  feature_ds = dataset.map(lambda x, y: x[name])

  # Learn the set of possible values and assign them a fixed integer index.
  index.adapt(feature_ds)

  # Encode the integer indices.
  encoder = layers.CategoryEncoding(num_tokens=index.vocabulary_size())

  # Apply multi-hot encoding to the indices. The lambda function captures the
  # layer, so you can use them, or include them in the Keras Functional model later.
  return lambda feature: encoder(index(feature))

Teste a função get_category_encoding_layer chamando-a em recursos pet 'Type' para transformá-los em tensores codificados multi-hot:

test_type_col = train_features['Type']
test_type_layer = get_category_encoding_layer(name='Type',
                                              dataset=train_ds,
                                              dtype='string')
test_type_layer(test_type_col)
<tf.Tensor: shape=(5, 3), dtype=float32, numpy=
array([[0., 1., 0.],
       [0., 1., 0.],
       [0., 1., 0.],
       [0., 1., 0.],
       [0., 1., 0.]], dtype=float32)>

Repita o processo nos recursos 'Age' do animal de estimação:

test_age_col = train_features['Age']
test_age_layer = get_category_encoding_layer(name='Age',
                                             dataset=train_ds,
                                             dtype='int64',
                                             max_tokens=5)
test_age_layer(test_age_col)
<tf.Tensor: shape=(5, 5), dtype=float32, numpy=
array([[1., 0., 0., 0., 0.],
       [0., 0., 0., 1., 0.],
       [1., 0., 0., 0., 0.],
       [0., 0., 0., 1., 0.],
       [1., 0., 0., 0., 0.]], dtype=float32)>

Pré-processar recursos selecionados para treinar o modelo

Você aprendeu como usar vários tipos de camadas de pré-processamento Keras. A seguir, você irá:

  • Aplique as funções de utilitário de pré-processamento definidas anteriormente em 13 recursos numéricos e categóricos do mini conjunto de dados PetFinder.my.
  • Adicione todas as entradas de recursos a uma lista.

Como mencionado no início, para treinar o modelo, você usará o mini conjunto de dados do PetFinder.my numérico ( 'PhotoAmt' , 'Fee' ) e categórico ( 'Age' , 'Type' , 'Color1' , 'Color2 'Color2' , 'Gender' , 'MaturitySize' , 'FurLength' , 'Vaccinated' , 'Sterilized' , 'Health' , 'Breed1' ).

Anteriormente, você usou um tamanho de lote pequeno para demonstrar o pipeline de entrada. Vamos agora criar um novo pipeline de entrada com um tamanho de lote maior de 256:

batch_size = 256
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)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:4: FutureWarning: Support for multi-dimensional indexing (e.g. `obj[:, None]`) is deprecated and will be removed in a future version.  Convert to a numpy array before indexing instead.
  after removing the cwd from sys.path.

Normalize os recursos numéricos (o número de fotos de animais de estimação e a taxa de adoção) e adicione-os a uma lista de entradas chamada encoded_features :

all_inputs = []
encoded_features = []

# Numerical features.
for header in ['PhotoAmt', 'Fee']:
  numeric_col = tf.keras.Input(shape=(1,), name=header)
  normalization_layer = get_normalization_layer(header, train_ds)
  encoded_numeric_col = normalization_layer(numeric_col)
  all_inputs.append(numeric_col)
  encoded_features.append(encoded_numeric_col)

Transforme os valores categóricos inteiros do conjunto de dados (a idade do animal de estimação) em índices inteiros, execute a codificação multi-hot e adicione as entradas de recursos resultantes a encoded_features :

age_col = tf.keras.Input(shape=(1,), name='Age', dtype='int64')

encoding_layer = get_category_encoding_layer(name='Age',
                                             dataset=train_ds,
                                             dtype='int64',
                                             max_tokens=5)
encoded_age_col = encoding_layer(age_col)
all_inputs.append(age_col)
encoded_features.append(encoded_age_col)

Repita a mesma etapa para os valores categóricos de string:

categorical_cols = ['Type', 'Color1', 'Color2', 'Gender', 'MaturitySize',
                    'FurLength', 'Vaccinated', 'Sterilized', 'Health', 'Breed1']

for header in categorical_cols:
  categorical_col = tf.keras.Input(shape=(1,), name=header, dtype='string')
  encoding_layer = get_category_encoding_layer(name=header,
                                               dataset=train_ds,
                                               dtype='string',
                                               max_tokens=5)
  encoded_categorical_col = encoding_layer(categorical_col)
  all_inputs.append(categorical_col)
  encoded_features.append(encoded_categorical_col)

Criar, compilar e treinar o modelo

A próxima etapa é criar um modelo usando a API funcional Keras . Para a primeira camada em seu modelo, mescle a lista de entradas de recursos encoded_features —em um vetor via concatenação com tf.keras.layers.concatenate .

all_features = tf.keras.layers.concatenate(encoded_features)
x = tf.keras.layers.Dense(32, activation="relu")(all_features)
x = tf.keras.layers.Dropout(0.5)(x)
output = tf.keras.layers.Dense(1)(x)

model = tf.keras.Model(all_inputs, output)

Configure o modelo com Keras Model.compile :

model.compile(optimizer='adam',
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=["accuracy"])

Vamos visualizar o gráfico de conectividade:

# Use `rankdir='LR'` to make the graph horizontal.
tf.keras.utils.plot_model(model, show_shapes=True, rankdir="LR")

png

Em seguida, treine e teste o modelo:

model.fit(train_ds, epochs=10, validation_data=val_ds)
Epoch 1/10
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/engine/functional.py:559: UserWarning: Input dict contained keys ['target'] which did not match any model input. They will be ignored by the model.
  inputs = self._flatten_to_reference_inputs(inputs)
37/37 [==============================] - 2s 19ms/step - loss: 0.6524 - accuracy: 0.5034 - val_loss: 0.5887 - val_accuracy: 0.6941
Epoch 2/10
37/37 [==============================] - 0s 8ms/step - loss: 0.5906 - accuracy: 0.6648 - val_loss: 0.5627 - val_accuracy: 0.7218
Epoch 3/10
37/37 [==============================] - 0s 8ms/step - loss: 0.5697 - accuracy: 0.6924 - val_loss: 0.5463 - val_accuracy: 0.7504
Epoch 4/10
37/37 [==============================] - 0s 8ms/step - loss: 0.5558 - accuracy: 0.6978 - val_loss: 0.5346 - val_accuracy: 0.7504
Epoch 5/10
37/37 [==============================] - 0s 8ms/step - loss: 0.5502 - accuracy: 0.7105 - val_loss: 0.5272 - val_accuracy: 0.7487
Epoch 6/10
37/37 [==============================] - 0s 8ms/step - loss: 0.5415 - accuracy: 0.7123 - val_loss: 0.5210 - val_accuracy: 0.7608
Epoch 7/10
37/37 [==============================] - 0s 8ms/step - loss: 0.5354 - accuracy: 0.7171 - val_loss: 0.5152 - val_accuracy: 0.7435
Epoch 8/10
37/37 [==============================] - 0s 8ms/step - loss: 0.5301 - accuracy: 0.7214 - val_loss: 0.5113 - val_accuracy: 0.7513
Epoch 9/10
37/37 [==============================] - 0s 8ms/step - loss: 0.5286 - accuracy: 0.7189 - val_loss: 0.5087 - val_accuracy: 0.7574
Epoch 10/10
37/37 [==============================] - 0s 8ms/step - loss: 0.5252 - accuracy: 0.7260 - val_loss: 0.5058 - val_accuracy: 0.7539
<keras.callbacks.History at 0x7f5f9fa91c50>
loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)
5/5 [==============================] - 0s 6ms/step - loss: 0.5012 - accuracy: 0.7626
Accuracy 0.762565016746521

Realizar inferência

O modelo que você desenvolveu agora pode classificar uma linha de um arquivo CSV diretamente depois de incluir as camadas de pré-processamento dentro do próprio modelo.

Agora você pode salvar e recarregar o modelo Keras com Model.save e Model.load_model antes de realizar a inferência em novos dados:

model.save('my_pet_classifier')
reloaded_model = tf.keras.models.load_model('my_pet_classifier')
2022-01-26 06:20:08.013613: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
WARNING:absl:Function `_wrapped_model` contains input name(s) PhotoAmt, Fee, Age, Type, Color1, Color2, Gender, MaturitySize, FurLength, Vaccinated, Sterilized, Health, Breed1 with unsupported characters which will be renamed to photoamt, fee, age, type, color1, color2, gender, maturitysize, furlength, vaccinated, sterilized, health, breed1 in the SavedModel.
INFO:tensorflow:Assets written to: my_pet_classifier/assets
INFO:tensorflow:Assets written to: my_pet_classifier/assets

Para obter uma previsão para uma nova amostra, basta chamar o método Keras Model.predict . Há apenas duas coisas que você precisa fazer:

  1. Envolva escalares em uma lista para ter uma dimensão de lote (os Model processam apenas lotes de dados, não amostras únicas).
  2. Chame tf.convert_to_tensor em cada recurso.
sample = {
    'Type': 'Cat',
    'Age': 3,
    'Breed1': 'Tabby',
    'Gender': 'Male',
    'Color1': 'Black',
    'Color2': 'White',
    'MaturitySize': 'Small',
    'FurLength': 'Short',
    'Vaccinated': 'No',
    'Sterilized': 'No',
    'Health': 'Healthy',
    'Fee': 100,
    'PhotoAmt': 2,
}

input_dict = {name: tf.convert_to_tensor([value]) for name, value in sample.items()}
predictions = reloaded_model.predict(input_dict)
prob = tf.nn.sigmoid(predictions[0])

print(
    "This particular pet had a %.1f percent probability "
    "of getting adopted." % (100 * prob)
)
This particular pet had a 77.7 percent probability of getting adopted.

Próximos passos

Para saber mais sobre como classificar dados estruturados, tente trabalhar com outros conjuntos de dados. Para melhorar a precisão durante o treinamento e teste de seus modelos, pense cuidadosamente sobre quais recursos incluir em seu modelo e como eles devem ser representados.

Abaixo estão algumas sugestões para conjuntos de dados: