In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Классифицируй структурированные данные

Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует официальной документации на английском языке. Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в tensorflow/docs репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на docs-ru@tensorflow.org list.

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

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

Набор данных

Мы будем использовать небольшой датасет предоставленный Кливлендской клиникой (Cleveland Clinic Foundation for Heart Disease). Датасет содержит несколько сотен строк в формате CSV. Каждая строка описывает пациента, а каждая колонка характеризует свойство. Мы будем использовать эту информацию чтобы предсказать, есть ли у пациента сердечное заболевание, что в этом наборе данных является задачей бинарной классификации.

По ссылке описание этого датасета. Обратите внимание что в нем есть и числовые и категорийные столбцы.

Column Description Feature Type Data Type
Age Age in years Numerical integer
Sex (1 = male; 0 = female) Categorical integer
CP Chest pain type (0, 1, 2, 3, 4) Categorical integer
Trestbpd Resting blood pressure (in mm Hg on admission to the hospital) Numerical integer
Chol Serum cholestoral in mg/dl Numerical integer
FBS (fasting blood sugar > 120 mg/dl) (1 = true; 0 = false) Categorical integer
RestECG Resting electrocardiographic results (0, 1, 2) Categorical integer
Thalach Maximum heart rate achieved Numerical integer
Exang Exercise induced angina (1 = yes; 0 = no) Categorical integer
Oldpeak ST depression induced by exercise relative to rest Numerical integer
Slope The slope of the peak exercise ST segment Numerical float
CA Number of major vessels (0-3) colored by flourosopy Numerical integer
Thal 3 = normal; 6 = fixed defect; 7 = reversable defect Categorical string
Target Diagnosis of heart disease (1 = true; 0 = false) Classification integer

Импортируйте TensorFlow и прочие библиотеки


In [ ]:
!pip install sklearn

In [ ]:
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 для скачивания данных по ссылке и выгрузки их в датафрейм.


In [ ]:
URL = 'https://storage.googleapis.com/applied-dl/heart.csv'
dataframe = pd.read_csv(URL)
dataframe.head()

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

Датасет который мы скачали был одним CSV файлом. Мы разделим его на тренировочную, проверочную и тестовую выборки.


In [ ]:
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')

Создайте входной пайплайн с помощью tf.data

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


In [ ]:
# Вспомогательный метод для создания tf.data dataset из датафрейма Pandas
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

In [ ]:
batch_size = 5 # Небольшой размер пакета используется для демонстрационных целей
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)

Поймите входной пайплайн

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


In [ ]:
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 )

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

Продемонстрируем несколько видов столбцов признаков (feature columns)

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


In [ ]:
# Мы используем этот пакте для демонстрации нескольких видов столбцов признаков
example_batch = next(iter(train_ds))[0]

In [ ]:
# Служебный метод для создания столбца признаков
# и преобразования пакета данных
def demo(feature_column):
  feature_layer = layers.DenseFeatures(feature_column)
  print(feature_layer(example_batch).numpy())

Численные столбцы (numeric columns)

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


In [ ]:
age = feature_column.numeric_column("age")
demo(age)

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

Сгруппированные столбцы (bucketized columns)

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


In [ ]:
age_buckets = feature_column.bucketized_column(age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])
demo(age_buckets)

Категориальные столбцы (categorical columns)

В этом датасете thal представлен в виде строк (например 'fixed', 'normal', или 'reversible'). Мы не можем передать строки напрямую в модель. Вместо этого мы должны сперва поставить им в соответствие численные значения. Словарь категориальных столбцов (categorical vocabulary columns) обеспечивает способ представления строк в виде one-hot векторов (как вы видели выше для возраста разбитого на категории). Справочник может быть передан как список с использованием categorical_column_with_vocabulary_list, или загружен из файла с использованием categorical_column_with_vocabulary_file.


In [ ]:
thal = feature_column.categorical_column_with_vocabulary_list(
      'thal', ['fixed', 'normal', 'reversible'])

thal_one_hot = feature_column.indicator_column(thal)
demo(thal_one_hot)

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

Столбцы векторных представлений (embedding column)

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

Ключевой момент: использование столбца векторных представлений лучше всего, когда у категориального столбца много возможных значений. Здесь мы используем его только для демонстрационных целей, чтобы у вас был полный пример, который вы можете использовать в будущем для другого набора данных.


In [ ]:
# Обратите внимание, что входными данными для столбца векторных представлений является категориальный столбец
# который мы создали до этого
thal_embedding = feature_column.embedding_column(thal, dimension=8)
demo(thal_embedding)

Хэшированные столбцы признаков

Другим способом представления категориального столбца с большим количеством значений является использование categorical_column_with_hash_bucket. This feature column calculates a hash value of the input, then selects one of the hash_bucket_size buckets to encode a string. When using this column, you do not need to provide the vocabulary, and you can choose to make the number of hash_buckets significantly smaller than the number of actual categories to save space.

Ключевой момент: Важным недостатком этого метода является то, что возможны коллизии, при которых разные строки отображаются в одну и ту же категорию. На практике метод хорошо работает для некоторых наборов данных.


In [ ]:
thal_hashed = feature_column.categorical_column_with_hash_bucket(
      'thal', hash_bucket_size=1000)
demo(feature_column.indicator_column(thal_hashed))

Пересеченные столбцы признаков (crossed feature columns)

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


In [ ]:
crossed_feature = feature_column.crossed_column([age_buckets, thal], hash_bucket_size=1000)
demo(feature_column.indicator_column(crossed_feature))

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

Мы увидели как использовать несколько видов столбцов признаков. Сейчас мы их используем для обучения модели. Данное руководство покажет Вам полный код (т.е. механизм) необходимый для работы со столбцами признаков. Мы ниже случайно выбрали несколько столбцов для обучения нашей модели.

Ключевой момент: если вы собиратесь построить точную модель, попробуйте сами больший набор данных и тщательно подумайте о том, какие признаки являются наиболее значимыми для включения и как они должны быть представлены.


In [ ]:
feature_columns = []

# численные столбцы
for header in ['age', 'trestbps', 'chol', 'thalach', 'oldpeak', 'slope', 'ca']:
  feature_columns.append(feature_column.numeric_column(header))

# группировка значений столбцов
age_buckets = feature_column.bucketized_column(age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])
feature_columns.append(age_buckets)

# столбцы индикаторы
thal = feature_column.categorical_column_with_vocabulary_list(
      'thal', ['fixed', 'normal', 'reversible'])
thal_one_hot = feature_column.indicator_column(thal)
feature_columns.append(thal_one_hot)

# столбцы векторных представлений
thal_embedding = feature_column.embedding_column(thal, dimension=8)
feature_columns.append(thal_embedding)

# столбцы пересечений свойств
crossed_feature = feature_column.crossed_column([age_buckets, thal], hash_bucket_size=1000)
crossed_feature = feature_column.indicator_column(crossed_feature)
feature_columns.append(crossed_feature)

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

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


In [ ]:
feature_layer = tf.keras.layers.DenseFeatures(feature_columns)

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


In [ ]:
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)

Создайте, скомпилируйте и обучите модель


In [ ]:
model = tf.keras.Sequential([
  feature_layer,
  layers.Dense(128, activation='relu'),
  layers.Dense(128, activation='relu'),
  layers.Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'],
              run_eagerly=True)

model.fit(train_ds,
          validation_data=val_ds,
          epochs=5)

In [ ]:
loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)

Ключевой момент: вы, как правило, получите лучшие результаты при использовании глубокого обучения с гораздо большими и более сложными датасетами. При работе с небольшими наборами данных, подобным этому, мы рекомендуем использовать дерево решений или случайный лес в качестве надежной базовой модели. Цель этого руководства - не обучить точную модель, а продемонстрировать механику работы со структурированными данными, дать вам код, который можно использовать в качестве старта при работе с вашими собственными наборами данных в будущем.

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

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