Посмотреть на 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
Следующие шаги
Лучший способ узнать больше о классификации структурированных данных — попробовать это самостоятельно. Мы предлагаем найти другой набор данных для работы и обучить модель классифицировать его, используя код, аналогичный приведенному выше. Чтобы повысить точность, тщательно продумайте, какие функции следует включить в вашу модель и как они должны быть представлены.