Как и зачем автоматизировать предварительную обработку таблиц в машинном обучении

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

Отказ от ответственности: все подходы, которые мы описываем ниже, не единственные. Мы использовали их при разработке нашего фреймворка AutoML с открытым исходным кодом FEDOT. Этот проект имеет свою специфику как в архитектуре, так и в подходах к проектированию разработки. Мы реализовали довольно большой модуль препроцессинга ортогонально основной части фреймворка. Тем не менее, он по-прежнему захватывает душу ядра библиотеки. Давайте приступим к делу!

Введение («Я не чувствую своих черт. — Их там нет!»)

Во-первых, стоит уточнить (на всякий случай), что понимается под предварительной обработкой табличных данных в Data Science. Это набор операций, которые преобразуют данные, чтобы модели машинного обучения могли правильно соответствовать. В широком смысле предварительная обработка включает в себя очистку исходных данных (удаление нечитаемых ячеек и столбцов и т. д.), устранение пробелов (вменение), кодирование категориальных признаков, нормализацию данных и удаление выбросов. Ниже в этом посте мы будем называть только первые три блока предварительной обработкой. Если вы хотите узнать больше о предварительной обработке табличных данных и о том, что это такое, начните с Предварительная обработка данных: концепции и продолжите Всеобъемлющее руководство по предварительной обработке данных.

Итак, потребность во внедрении расширенной предобработки в нашем AutoML-фреймворке (который в итоге получился достаточно большим) у нашей команды появилась не сразу. Поначалу нас устраивала минимальная функциональность: заполнить пробелы, закодировать категориальные признаки, нормализовать и передать на вход модели. Однако за последние несколько месяцев перед нами стояла задача запустить наш фреймворк на большом количестве наборов данных из OpenML, Kaggle и других источников (например, из ресурса «Departamento de ciencia de computadores»). Только тогда мы поняли, насколько комфортные условия мы создали для фреймворка раньше — запускали фреймворк только на очищенных наборах данных. Таким образом, алгоритм не смог запустить процесс AutoML более чем на половине наборов данных. Мы взяли для запуска 46 наборов табличных данных, большинство из которых предназначались для задачи классификации. Здесь начинается наше путешествие.

Немного информации о наборе данных: размер таблиц варьировался от совсем небольшого (748 строк на 5 столбцов для «центр службы переливания крови») до большого (4,9 миллиона строк на 42 столбца для «KDDCup99_full»). Общее количество элементов (умножение количества строк на количество столбцов) варьировалось от нескольких тысяч до нескольких сотен миллионов. Все это было необходимо для проверки производительности алгоритмов на разных объемах данных (рис. 2).

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

Ссылки на наборы данных, которые использовались в экспериментах:

Исследование («Кстати, есть ли кто-нибудь, кто знает, как запустить AutoML?»)

Перед тем, как приступить к доработке системы препроцессинга, было решено посмотреть, как коллеги из других команд решили аналогичную задачу. Правда, мы будем рассматривать самые критические изменения в данных; преобразования одномерных целевых массивов в формат вектор-столбец неявно учитываются любой крупномасштабной библиотекой AutoML, если это необходимо.

Начнем с фреймворка H2O, который следует этой структуре. Предварительная обработка предшествует запуску алгоритма AutoML. Препроцессинг имеет классический джентльменский набор: автоматическое определение типа, категориальное кодирование с помощью OneHotEncoding, методы вменения и нормализация, если она требуется. Также есть возможность выполнить необязательную предобработку для некоторых моделей — например, нормализация не требуется для моделей деревьев решений и случайных лесов и т. д. Несколько интересных фактов: H2O версии 3.28.1.2 успешно работала на 24 наборах данных из 46.

Популярная академическая структура TPOT не является полностью «сквозным» решением, в отличие от H2O. Поэтому библиотека оставляет большую часть предварительной обработки пользователю, вместо этого концентрируясь на оптимизации конвейера. Тем не менее, в структуре ТПОТ есть методы усвоения входных данных. В частности, препроцессор проверяет набор данных на наличие пропусков и вменяет их. Существуют различные стандартизаторы и нормализаторы. Разделение на категориальные и плавающие функции выполняется на основе количества уникальных значений в столбце. В отличие от H2O, для TPOT не предусмотрена необязательная предварительная обработка — предварительная обработка действует как единый блок, предшествующий запуску эволюционного алгоритма поиска модели. Хотя предварительная обработка не является сильной стороной этого фреймворка, она широко известна и достаточно надежна. Однако перед отправкой данных рекомендуется очистить исходный набор данных. Например, фреймворк не обрабатывает наличие строковых типов данных в массивах.

Платформа LightAutoML также имеет стандартную предварительную обработку: заполнение пробелов, обработку категориальных признаков и различных типов. Типы называются «ролями» с точки зрения фреймворка, где каждая «роль» (числовой или категориальный тип) имеет свой собственный способ предварительной обработки. Числовые признаки проходят процедуру масштабирования. Запуску моделей предшествует несколько этапов обработки: выбор подходящих признаков и генерация новых. Первый блок состоит в основном из методов предварительной обработки, таких как масштабирование, кодирование и вменение. Предварительная обработка обязательна для моделей, но может быть различной.

Фреймворк AutoGluon реализует два уровня предварительной обработки: модельно-независимый и модельно-специфический. Такое деление позволяет избежать ненужных вычислений, когда они не требуются. Это также позволяет сохранить гибкость подхода. В дополнение к классическим операциям, таким как нормализация, заполнение пробелов и кодирование категорий, фреймворк имеет модуль для преобразования индексов даты и времени в числовые функции. В наших экспериментах наиболее надежным оказался AutoGluon.

На основании нашего краткого обзора можно сделать вывод, что наиболее распространенной схемой препроцессинга является «гибкий» вариант. Набор преобразований для входных данных определяется на основе моделей.

P.S. Краткий обзор здесь не касается специфики технических реализаций. Более того, текущие обзоры могут утратить актуальность из-за модификации рамок. Также легко упустить некоторые детали, когда вы не разработчик, а просто делаете обзор. Поэтому, если вы найдете какие-то нестыковки и предложите что-то добавить в описание методов препроцессинга, будем рады.

Реализация («Надежность как швейцарские часы»)

Устранение неполадок было повторяющимся; таблица за таблицей, мы попытались запустить библиотеку. Если что-то пошло не так, мы внесли улучшения. Было принято решение реализовать предобработку в отдельном блоке перед подачей данных в пайплайн.

Рассмотрим препроцессинг на примере таблицы ниже. Допустим, мы решаем задачу бинарной классификации, и таблица хранится в csv-файле:

Какие особенности можно выделить в этой таблице:

  • В первой функции есть бесконечные значения;
  • В признаке 2 слишком много пропущенных значений;
  • В признаке 3 есть упущение. Также обратите внимание, что признак принимает только два возможных значения: 1 и 10;
  • В функции 4 есть три категории, но из-за отступов в строках количество категорий можно определить как 5;
  • Признак 5 является бинарным категориальным и имеет пропуск;
  • Функция 6 содержит типы данных float и string;
  • Целевая переменная содержит пробелы. При этом он представлен в виде необработанных меток, представляющих собой строки.

Начинаем движение слева направо. Во-первых, замените все ячейки со значениями бесконечности в признаке 1 на NaN. Затем мы удаляем функцию два, так как она содержит слишком много пропущенных значений (наш порог в фреймворке установлен на 90% объектов).

После этого удаляем из обучающей выборки объект с неизвестной целью — id 2.

Затем примените LabelEncoder для преобразования меток «> 10» и «‹= 10» в 0 и 1 соответственно. Удалим отступы в строках в признаке 4.

Давайте немного поразмыслим. Реализация OneHotEncoding в sklearn (которую мы используем в AutoML) поддерживает обработку как бинарных категориальных функций, так и новых категорий, которые операция предварительной обработки не увидела в обучающей выборке. Первое означает, что если в таблице есть бинарная категориальная функция, она не будет увеличена за счет расширения до нескольких столбцов. Второе означает, что если в обучающей выборке были такие категории, как «средний» и «маленький», а в тестовой — «большой», то алгоритм сможет продолжать работу, не вызывая ошибок. Единственная проблема в том, что эти два варианта не работают одновременно: нужно выбрать либо один, либо другой. Мы решили, что важнее, чтобы фреймворк не падал при обработке новых категорий.

Поэтому бинарные категориальные признаки (количество уникальных категорий не более двух) должны обрабатываться отдельно. Мы делаем это с помощью функции 5: преобразование значений в «1» и «0».

Наконец, есть функция 6. Критически важным является вывод системы обработки типов, которая также была реализована как часть предварительной обработки. Он работает одинаково для всех столбцов. Но сначала давайте определим проблему: функция со значениями с плавающей запятой имеет знак «?», и при загрузке такого фрейма данных было обнаружено, что панды тщательно преобразовали все числа в строки. Однако, если количество уникальных значений в столбце велико и для кодирования используется One Hot Encoding, вы будете удивлены размером результирующей таблицы.

Используя функцию карты, мы смотрим на каждый элемент в столбце и определяем, из какого типа данных состоит столбец. В случае с этой колонкой получается, что количество уникальных значений гораздо больше, чем должно быть в категориальном признаке (может быть, не в этом конкретном фрагменте выше, но если бы таблица была немного больше, вы бы это увидели сразу). Затем алгоритм пытается преобразовать столбец в тип float и при этом помечает все значения, которые не могут быть преобразованы в этот тип, как nan (см. знак «?» в табл. 6).

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

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

Еще одна реализация («Вы не можете справиться с пробелами!»)

После того как данные прошли обязательную предварительную обработку, запускается сердце AutoML — эволюционный алгоритм. Именно этот «движок» генерирует несколько конвейеров. Каждому из них даются первичные предварительно обработанные данные. Помните, что данные прошли только грубую очистку и все еще содержат пробелы и строковые типы данных. Поэтому к каждой сгенерированной модели применяется отдельный этап предварительной обработки.

Основная цель предварительной обработки таблиц — предотвратить сбой модели с ошибкой. Рассмотрим пример заполнения пробелов. Если алгоритм понимает, что конвейеры не могут быть установлены, если промежутки не заполнены, то применяется стратегия заполнения промежутков по умолчанию. Если модели в структуре пайплайна смогут уместить данные с пропусками, то дальше таблица пойдет необработанной. Тогда возникает вопрос: Так почему бы не провести предварительную обработку данных сразу, заполнить пробелы и применить кодировщик? Это надежнее. Дело в том, что существует довольно много способов восполнения пробелов и множество более чем двух способов кодирования. Ограничение пространства поиска только одним способом предварительной обработки не является гибким подходом; — Алгоритм AutoML определит, как лучше преобразовать данные, чтобы получить максимально точную модель.

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

Анализ структуры конвейера основан на последовательном обходе графа от каждого основного узла (те, что слева) к корневому узлу. Корневой узел — это тот, который всегда единственный и расположен на графике справа) (рисунок 3). Операции в этом случае делятся по тегам на те, которые:

  • может обработать данные и устранить проблему (наличие пробелов, например);
  • может пропустить данные, но не устранить источник ошибки;
  • при выполнении операций над данными вызывает ошибку.

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

Описанный выше подход обеспечивает гибкость решения. Алгоритм автоматической идентификации структур составной модели позволяет найти оптимальные методы предварительной обработки и комбинации конфигураций модели. В процессе поиска структуры конвейера операции предварительной обработки могут быть заменены аналогичными. Если модель достаточна для удовлетворительной ошибки прогноза для выполнения предварительной обработки по умолчанию, составная модель будет построена только на основе моделей.

Предположим, что в пайплайнах нет операций предварительной обработки, а алгоритму нужно по умолчанию заполнять пробелы и кодировать категориальные значения. Сначала разберемся с пропусками: они присутствуют в признаках 1, 3, 5 и 6. Для столбцов 1 и 6 применяется обычная стратегия заполнения пропусков средним значением. Столбцы 3 и 5 более интригующие: столбцы содержат числовые значения, но количество уникальных значений, которые могут принимать индикаторы в этих столбцах, не превышает двух. Сколько числовых признаков вы знаете, которые содержат только два значения? — Предположим — признак, скорее всего, обозначает наличие или отсутствие чего-либо в объекте. Например, у объекта либо есть хвост, либо его нет. Здесь не нужно вводить полутона. Итак, в таких столбцах давайте заполним значения классом большинства. Если количество категорий равно (это случается очень редко), то пробелы все равно заполняются средним значением для бинарного признака.

С категориальными значениями i проще — стратегия кодирования по умолчанию — OneHotEncoding (таблица 8).

Финал («Честно говоря, дорогой, мне наплевать»)

Давайте воспроизведем все настройки, которые мы реализовали при разработке блока предварительной обработки в среде AutoML.

Итак, препроцессинг делится на два уровня: обязательный и необязательный. Первый всегда и навсегда для всех данных. Второй делается только в том случае, если в структуре пайплайна нет моделей для усвоения данных, чтобы не возникло ошибки при попытке обучить пайплайн.

В обязательный модуль входят:

  • Замена значений бесконечности в функциях и целевом столбце на nan.
  • Удаление функций, количество отсутствующих значений которых превышает 90%
  • Удаление признаков из таблицы, для которых значение целевой переменной неизвестно (в target есть NaN)
  • Преобразование меток в целевом столбце из строкового в целочисленный формат
  • Устранение отступов в строковых объектах в функциях
  • Ассимиляция столбцов с разными типами данных заключается в обнаружении столбцов со смешанными типами и преобразовании их в большинство, любой возможный тип или удаление столбца из таблицы.

Дополнительный модуль:

  • Анализ структуры трубопровода для операций по заполнению пробелов — при необходимости заполнение пробелов
  • Конвейерный анализ операций категориального кодирования — применение кодирования при необходимости

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

Я хочу остановиться и немного подумать здесь. Все описанное выше является сложной технической задачей (не шучу). Но привлекательна ли она для исследователя или бизнес-клиента? - Возможно нет. Всю эту возню с данными обычно хочется поскорее пропустить и перейти к разработке модели, потому что это самая увлекательная часть. Только представьте, сколько кода вам нужно написать для предварительной обработки данных таким образом. Так не лучше ли оставить всю предварительную обработку алгоритмам AutoML?

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

import numpy as np
from fedot.core.data.data import InputData
from fedot.core.pipelines.node import PrimaryNode
from fedot.core.pipelines.pipeline import Pipeline
from fedot.core.repository.dataset_types import DataTypesEnum
from fedot.core.repository.tasks import Task, TaskTypesEnum
train_input = InputData(
idx=np.arange(0, len(features)),                
features=features, target=target, task=Task(TaskTypesEnum.classification), data_type=DataTypesEnum.table
)
pipeline = Pipeline(PrimaryNode('scaling'))
pipeline.fit(train_input)
preprocessed_output = pipeline.predict(train_input)
transformed_data = preprocessed_output.predict

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

Эпилог. Эксперимент («Набор данных был похож на коробку конфет. Никогда не знаешь, что получишь».

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

Добро пожаловать, если вы хотите запустить свой любимый фреймворк на этих данных. Набрать ROC AUC 1.0 для тестовой выборки для этой таблицы можно только корректно предварительно обработав хотя бы некоторые столбцы, отбросив все ненужные столбцы и оставив нужные. Пройти такой краш-тест не так просто, как кажется. А получить хорошую оценку (ROC AUC 1.0) — отличный результат.

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

train_features, train_target = get_train_data()
test_features, test_target = get_test_data()
# Task selection, initialisation of the framework
fedot_model = Fedot(problem='classification', timeout=timeout)
# Fit model
obtained_pipeline = fedot_model.fit(features=train_features, target=train_target) 
obtained_pipeline.show()
# Evaluate the prediction with test data
predict = fedot_model.predict(test_features)
predict_probs = fedot_model.predict_proba(test_features)

Окончательная метрика в тестовой части — 1,0 ROC AUC.

Заключение («Лучший друг мальчика — это его AutoML»).

Подводя итог, можно сказать, что все вышеперечисленные преобразования, реализованные в AutoML, не являются современными технологиями. Однако их удачное сочетание позволяет запускать модели машинного обучения даже в тех случаях, когда табличные данные — это «мусор».

Стоит помнить, что все реализованные алгоритмы были разработаны при модификации AutoML framework. Хороший фреймворк AutoML должен надежно работать там, где младший специалист по данным не может работать быстро и надежно (или может, но не очень надежно или очень быстро). И, возможно, если эта предварительная обработка сработала для наших моделей, она может сработать и для вашей.

Полезные ссылки:

В спектакле со своеобразным руководством по предобработке табличных данных приняли участие: Михаил Сарафанов и Команда NSS Lab.