Классифицировать структурированные данные с помощью столбцов функций

Посмотреть на TensorFlow.org Запустить в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

В этом руководстве показано, как классифицировать структурированные данные (например, табличные данные в формате CSV). Мы будем использовать Keras для определения модели и tf.feature_column в качестве моста для сопоставления столбцов в CSV с функциями, используемыми для обучения модели. Этот учебник содержит полный код для:

  • Загрузите файл CSV с помощью Pandas .
  • Создайте входной конвейер для пакетной обработки и перемешивания строк с помощью tf.data .
  • Сопоставьте столбцы в CSV с функциями, используемыми для обучения модели, используя столбцы функций.
  • Создавайте, обучайте и оценивайте модель с помощью Keras.

Набор данных

Мы будем использовать упрощенную версию набора данных PetFinder. В CSV несколько тысяч строк. Каждая строка описывает питомца, а каждый столбец описывает атрибут. Мы будем использовать эту информацию, чтобы предсказать скорость, с которой питомец будет усыновлен.

Ниже приводится описание этого набора данных. Обратите внимание, что есть как числовые, так и категориальные столбцы. Существует столбец произвольного текста, который мы не будем использовать в этом уроке.

Столбец Описание Тип функции Тип данных
Тип Тип животного (собака, кошка) Категориальный нить
Возраст Возраст питомца Числовой целое число
Порода1 Основная порода питомца Категориальный нить
Цвет1 Цвет 1 питомца Категориальный нить
Цвет2 Цвет 2 питомца Категориальный нить
ЗрелостьРазмер Размер при погашении Категориальный нить
МехДлина Длина меха Категориальный нить
привит Животное было вакцинировано Категориальный нить
Стерилизованный животное было стерилизовано Категориальный нить
Здоровье Состояние здоровья Категориальный нить
Платеж Плата за принятие Числовой целое число
Описание Написание профиля для этого питомца Текст нить
ФотоАмт Всего загруженных фотографий для этого питомца Числовой целое число
ПринятиеСкорость Скорость принятия Классификация целое число

Импорт TensorFlow и других библиотек

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

Используйте Pandas для создания фрейма данных

Pandas — это библиотека Python с множеством полезных утилит для загрузки и работы со структурированными данными. Мы будем использовать Pandas для загрузки набора данных с URL-адреса и загрузки его в фрейм данных.

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

Создать целевую переменную

Задача в исходном наборе данных состоит в том, чтобы предсказать скорость, с которой домашнее животное будет принято (например, в первую неделю, в первый месяц, в первые три месяца и т. д.). Давайте упростим это для нашего урока. Здесь мы превратим это в задачу бинарной классификации и просто предскажем, было ли домашнее животное усыновлено или нет.

После изменения столбца метки 0 будет означать, что питомец не был усыновлен, а 1 — что да.

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

Разделите фрейм данных на обучение, проверку и тестирование.

Набор данных, который мы загрузили, представлял собой один CSV-файл. Мы разделим это на обучающие, проверочные и тестовые наборы.

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

Создайте входной конвейер, используя tf.data

Далее мы обернем кадры данных с помощью tf.data . Это позволит нам использовать столбцы функций в качестве моста для сопоставления столбцов в кадре данных Pandas с функциями, используемыми для обучения модели. Если бы мы работали с очень большим файлом CSV (настолько большим, что он не помещается в память), мы бы использовали tf.data для чтения его напрямую с диска. Это не рассматривается в этом уроке.

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

Понимание входного конвейера

Теперь, когда мы создали входной конвейер, давайте вызовем его, чтобы увидеть формат возвращаемых им данных. Мы использовали небольшой размер пакета, чтобы вывод оставался читабельным.

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)

Мы видим, что набор данных возвращает словарь имен столбцов (из фрейма данных), которые сопоставляются со значениями столбцов из строк в фрейме данных.

Продемонстрируйте несколько типов столбцов функций

TensorFlow предоставляет множество типов столбцов функций. В этом разделе мы создадим несколько типов столбцов функций и продемонстрируем, как они преобразуют столбец из фрейма данных.

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

Числовые столбцы

Выходные данные столбца функций становятся входными данными для модели (используя определенную выше демонстрационную функцию, мы сможем точно увидеть, как преобразуется каждый столбец из фрейма данных). Числовой столбец — это самый простой тип столбца. Он используется для представления реальных ценных функций. При использовании этого столбца ваша модель получит значение столбца из фрейма данных без изменений.

photo_count = feature_column.numeric_column('PhotoAmt')
demo(photo_count)
[[2.]
 [4.]
 [4.]
 [1.]
 [2.]]

В наборе данных PetFinder большинство столбцов из фрейма данных являются категориальными.

Сегментированные столбцы

Часто вы не хотите вводить число непосредственно в модель, а вместо этого разделяете его значение на разные категории на основе числовых диапазонов. Рассмотрим необработанные данные, которые представляют возраст человека. Вместо того, чтобы представлять возраст в виде числового столбца, мы могли бы разделить возраст на несколько сегментов, используя столбец с сегментами. Обратите внимание, что приведенные ниже однозначные значения описывают, какому возрастному диапазону соответствует каждая строка.

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.]]

Категориальные столбцы

В этом наборе данных тип представлен строкой (например, «собака» или «кошка»). Мы не можем передавать строки непосредственно в модель. Вместо этого мы должны сначала сопоставить их с числовыми значениями. Столбцы категориального словаря предоставляют способ представления строк в виде однократного вектора (так же, как вы видели выше с сегментами возраста). Словарь может быть передан в виде списка с помощью categorical_column_with_vocabulary_list или загружен из файла с помощью 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.]]

Встраивание столбцов

Предположим, что вместо нескольких возможных строк у нас есть тысячи (или более) значений для каждой категории. По ряду причин, когда количество категорий становится большим, становится невозможным обучение нейронной сети с использованием однократного кодирования. Мы можем использовать столбец внедрения, чтобы обойти это ограничение. Вместо того, чтобы представлять данные в виде однонаправленного вектора многих измерений, столбец встраивания представляет эти данные в виде плотного вектора меньшей размерности, в котором каждая ячейка может содержать любое число, а не только 0 или 1. Размер вложения ( 8 в приведенном ниже примере) — это параметр, который необходимо настроить.

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

Столбцы с хешированными функциями

Другой способ представить категориальный столбец с большим количеством значений — использовать categorical_column_with_hash_bucket . Этот столбец функций вычисляет хэш-значение входных данных, а затем выбирает один из hash_bucket_size для кодирования строки. При использовании этого столбца вам не нужно указывать словарь, и вы можете сделать количество hash_buckets значительно меньшим, чем количество фактических категорий, чтобы сэкономить место.

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.]]

Столбцы с перекрестными функциями

Объединение функций в одну функцию, более известное как пересечение функций , позволяет модели изучать отдельные веса для каждой комбинации функций. Здесь мы создадим новую функцию, представляющую собой сочетание возраста и типа. Обратите внимание, что crossed_column не строит полную таблицу всех возможных комбинаций (которая может быть очень большой). Вместо этого он поддерживается hashed_column , так что вы можете выбрать размер таблицы.

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.]]

Выберите, какие столбцы использовать

Мы увидели, как использовать несколько типов столбцов функций. Теперь мы будем использовать их для обучения модели. Цель этого руководства — показать вам полный код (например, механику), необходимый для работы со столбцами функций. Мы выбрали несколько столбцов для обучения нашей модели ниже произвольно.

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

Создайте векторный слой

Теперь, когда мы определили наши столбцы функций, мы будем использовать слой DenseFeatures , чтобы ввести их в нашу модель Keras.

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

Ранее мы использовали небольшой пакет, чтобы продемонстрировать, как работают столбцы функций. Мы создаем новый входной конвейер с большим размером пакета.

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)

Создание, компиляция и обучение модели

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

Следующие шаги

Лучший способ узнать больше о классификации структурированных данных — попробовать это самостоятельно. Мы предлагаем найти другой набор данных для работы и обучить модель классифицировать его, используя код, аналогичный приведенному выше. Чтобы повысить точность, тщательно продумайте, какие функции следует включить в вашу модель и как они должны быть представлены.