
Хабр, привет!
Меня зовут Семён Семёнов, я руковожу Data Science и Machine Learning в Страховом Доме ВСК. В этой статье расскажу, как мы создали систему автоматического обучения и развёртывания моделей машинного обучения с открытым исходным кодом.
Первый вопрос, который может задать себе читатель, знакомый с темой современного машинного обучения: «почему бы не взять одну из десятков (если не сотен) открытых AutoML-библиотек?»
Ответ прост: мы не стремились создать ещё один «стандартный» проект AutoML. Наша цель — сфокусироваться на вещах, которые редко встречаются в готовых решениях:
развёртывание системы,
мониторинг в реальном времени,
удобное написание собственных моделей внутри AutoML-каркаса,
конфигурация всех компонентов через простые скрипты и файлов конфигурации.

Итак, перед нами стоит амбициозная цель — создать систему, которая в состоянии развернуть весь спектр решений машинного обучения за несколько минут.
Перед тем как приступить к демонстрации примеров использования покажем основные компоненты системы.
Концептуально система состоит из основных компонентов:
ядра, включающего в себя логику машинного обучения при взаимодействии с другими компонентами,
докер компонентов базы данных (PostgreSQL),
трекинга экспериментов (MLFlow),
визуализации метрик (Grafana),
веб-сервисов (FastAPI) и вспомогательных компонентов.
Таким образом, ядро, написанное на Python, с одной стороны отвечает за все процессы моделирования, с другой взаимодействует с компонентами.
Стандартный цикл работы библиотеки состоит из следующих этапов: на вход подаётся файл с данными в формате csv, далее запускается процесс автоматического машинного обучения, результаты которого сохраняются в MLflow, выводятся в консоль и в интерфейс в виде графиков. В случае необходимости запускается дальнейший автоматический процесс загрузки в базу данных с использованием мониторинга результатов в Grafana. При этом саму модель можно развернуть с помощью веб-сервиса на основе FastAPI.
Цикл работ отображён на схеме ниже:

В статье рассматривается простое использование библиотеки для создания базовой модели в трёх вариациях:
Целиком коробочный запуск с использованием файла конфигурации;
С собственным препроцессором данных;
С использованием собственных моделей.
В качестве датасета используются данные эффективности энергии.
Датасет используется для моделирования эффективности отопительной нагрузки и холодильной нагрузки зданий (то есть энергоэффективности) в зависимости от параметров здания. Датасет состоит из 767 строк и десяти колонок, восемь из которых используются как фичи в модели и две (Heating Load и Cooling Load) – в качестве таргета.

Покажем использование библиотеки на примере данного датасета.
1. Клонируем проект, переходим на ветку v.0.8.0;
2. Заходим в папку outboxml/app, запускаем create-folder.bat (Windows) или create-folder.sh (Linux);
3. Запускаем docker compose up -d;
4. Для загрузки артефактов в MLFlow, открываем MinIO по адресу http://localhost:9001 (login: minio, password: Strong#Pass#2022), нажимаем "Create Bucket" с именем "mlflow" и меняем настройку Access Policy на public.
Инфраструктура для работы развёрнута. Теперь переходим в JupyterLab, открывая первый пример по адресу: http://localhost:8889/lab/tree/work/examples/energy_efficiency/1.energy\_efficiency\_basic.ipynb
В основе библиотеки лежат файлы конфигурации. Для примера нам нужно использовать два файла – файл конфигурации предподготовки модели и файл AutoML. Отложим описание конфига AutoML и сфокусируемся на конфиге модели. Для базовой версии примера используем следующий файл конфигурации energy_efficiency/configs/config-energy-efficiency-basic.json
Ключевым для примера является описание модели.
"models_configs": [
{
"name": "heating", // Название модели
"column_target": "Heating Load", // Название колонки-таргета
"wrapper": "catboost", // Выбор «коробочной» модели, в нашем случае catboost
"relative_features": [], // Выбор фичей-отношений, в нашем случае остаётся пустым
"features": [ // Список фичей для модели, в нашем случае все фичи численные
{
"name": "Relative Compactness", // Название колонки-фичи
"default": 0, // Значение по умолчание
"replace": {"_TYPE_": "_NUM_"} // Тип фичи, численная фича
},
{
"name": "Roof Area",
"default": 0,
"replace": {"_TYPE_": "_NUM_"}
}
]
}
]
Запускаем код, выполняющий обучение модели. Код состоит всего из нескольких строк. Запускается максимально полный процесс обучения с подбором гиперпараметров для демонстрации функциональности.
from outboxml.automl_manager import AutoMLManager
config_name = './configs/config-energy-efficiency-basic.json'
auto_ml_config = './configs/automl-energy-efficiency-automl.json'
auto_ml = AutoMLManager(auto_ml_config=auto_ml_config,models_config=config_name)
auto_ml.update_models()
Во время запуска мы видим лог запуска, содержащий всю основную информацию об обработке данных, обучении и запуске модели. Пример начала такого лога можно увить на картинке:

После довольно продолжительного (из-за подбора гиперпараметров) запуска мы получаем информацию о завершении процесса.
Таким образом, пройден полный цикл обучения модели в рамках библиотеки.
auto_ml.get_result()['heating'].metrics
Теперь мы можем получить результат всех основных метрик с помощью вызова переменной для конкретной модели.
Далее, рассмотрим возможности улучшения процесса.
Во-первых, стоит добавить возможность преобразования фичей и инжиниринг таргета. Такая возможность необходима практически во всех проектах по моделированию. Кроме того, мы хотим добавить возможность считать свою метрику.
Откроем вторую тетрадку: http://localhost:8889/lab/tree/work/examples/energy_efficiency/2.energy_efficiency_extractor.ipynb
Для преобразования фичей и таргета используется класс Extractor.
class EnergyEfficiencyExtractor(Extractor):
def __init__(self,
path_to_file: str
):
self.__path_to_file = path_to_file
super().__init__()
def extract_dataset(self) -> pd.DataFrame:
data = pd.read_csv(self.__path_to_file) # Читаем файл на прямую, данная функциональность «переписывает» файл из конфига.
data['EEI'] = (data['Heating Load'] + data['Cooling Load'])/data['Surface Area'] # Считаем Индекс энергетической эффективности или Energy Efficiency Index (EEI) в качестве нового таргета
data['Wall_Roof_Ratio'] = data['Wall Area'] / data['Roof Area'] #Посчитаем отношение площади стен к площади крыши
data['Compactness_Height'] = data['Relative Compactness'] * data['Overall Height'] # Посчитаем комбинированный эффект компактности и высоты
final_features = ['Wall_Roof_Ratio', 'Compactness_Height','Glazing Area Distribution','Orientation', 'EEI']
return data[final_features]
Для расчёта собственной метрики используем класс BaseMetric, считаем MAPE в качестве метрики:
class EnergyEfficiencyMetrics(BaseMetric):
def __init__(self):
pass
def calculate_metric(self, result1: dict, result2: dict = None) -> dict:
y_true = result1['EEI'].y
y_pred = result1['EEI'].y_pred
return {'MAPE': np.mean(np.abs((y_true - y_pred) / y_true)) * 100}
Так как мы изменили ряд фичей и таргет модели, нам необходимо изменить часть конфига модели. Конфиг после изменений находится по адресу: energy_efficiency/configs/config-energy-efficiency-extractor.json.
"models_configs": [
{
"name": "EEI", // Название новой модели
"column_target": "EEI", // Название нового таргета, созданного в extractor
"wrapper": "catboost",
"relative_features": [],
"features": [
{
"name": "Wall_Roof_Ratio",
"default": 0,
"replace": {"_TYPE_": "_NUM_"}
},
{
"name": "Compactness_Height",
"default": 0,
"replace": {"_TYPE_": "_NUM_"}
},
{
"name": "Glazing Area Distribution",
"default": 0,
"replace": {"_TYPE_": "_NUM_"}
},
{
"name": "Orientation",
"default": 0,
"replace": {"_TYPE_": "_NUM_"},
"encoding" : "WoE_cat_to_num" // Интерпретируем Orientation в качестве категориальной и преобразуем её с помощью WoE
}
]
}
]
Далее мы можем визуализировать результаты с помощью ResultExport.plots. Функция предоставляет возможность визуализировать как стандартные графики «из коробки», так и передавать свою собственную функцию для отрисовки результатов.
Покажем результаты запуска модели на графике, передадим функцию plot_info для визуализации.
В тетрадке показаны три типа графиков. Ниже приведём визуализацию с собственной функцией с помощью метода:
ResultExport(ds_manager=auto_ml).plots('EEI', user_plot_func = plot_info)

Наконец, предположим, что мы хотим использовать ещё более сложную версию процесса, включающую в себя целиком настраиваемую модель и множество других параметров, Например, используем регрессию гауссовского процесса в качестве модели и настроим тип репорта с результатами для отправки по email или сохранения на диск. http://localhost:8889/lab/tree/work/examples/energy_efficiency/3.energy_efficiency_custom.ipynb
Мы используем модель регрессии гауссовского процесса и передаём модель как параметр.
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RBF, ConstantKernel as C
kernel = C(1.0, (1e-3, 1e3)) * RBF(length_scale=1.0)
model_energy_efficiency = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=10)
models_dict={'EEI':model_energy_efficiency}
Вызываем AutoMLManager с параметро models_dict=models_dict
Далее загружаем результат запуска в виде отчёта с настроенным внутренним форматированием. Мы можем отправить этот отчёт по электронной почте, либо сохранить как HTML файл. В результате запуска кода — auto_ml.review(email=EnergyEfficiencyEMail(config), send_mail=False)
— сохраняется отчёт по адресу /home/jovyan/work/results/automl_report.html

Таким образом мы продемонстрировали, как можно использовать базовую функциональность библиотеки от простейшего варианта с минимальным кодом до сложного варианта со своей моделью, препроцессингом данных и множеством других параметров.
В статье были показаны базовые возможности использования библиотеки. В следующих статьях мы планируем продемонстрировать более сложную логику, показывая полный цикл AutoML, использование Grafana, MLFlow и FastAPI, проработку дата дрифта, A/B тестирования и другие аспекты библиотеки.
Присоединяйтесь к нашему проекту на GitHub https://github.com/SVSemyonov/outboxml и в Telegram https://t.me/outboxml.
Будем рады услышать конструктивную критику и найти единомышленников для дальнейшего усовершенствования библиотеки!