Хабр, привет!

Меня зовут Семён Семёнов, я руковожу Data Science и Machine Learning в Страховом Доме ВСК. В этой статье расскажу, как мы создали систему автоматического обучения и развёртывания моделей машинного обучения с открытым исходным кодом.

Первый вопрос, который может задать себе читатель, знакомый с темой современного машинного обучения: «почему бы не взять одну из десятков (если не сотен) открытых AutoML-библиотек?»

Ответ прост: мы не стремились создать ещё один «стандартный» проект AutoML. Наша цель — сфокусироваться на вещах, которые редко встречаются в готовых решениях:

  • развёртывание системы,

  • мониторинг в реальном времени,

  • удобное написание собственных моделей внутри AutoML-каркаса,

  • конфигурация всех компонентов через простые скрипты и файлов конфигурации.

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

Перед тем как приступить к демонстрации примеров использования покажем основные компоненты системы.

Концептуально система состоит из основных компонентов:

  • ядра, включающего в себя логику машинного обучения при взаимодействии с другими компонентами,

  • докер компонентов базы данных (PostgreSQL),

  • трекинга экспериментов (MLFlow),

  • визуализации метрик (Grafana),

  • веб-сервисов (FastAPI) и вспомогательных компонентов.

Таким образом, ядро, написанное на Python, с одной стороны отвечает за все процессы моделирования, с другой взаимодействует с компонентами.

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

Цикл работ отображён на схеме ниже:

В статье рассматривается простое использование библиотеки для создания базовой модели в трёх вариациях:

  1. Целиком коробочный запуск с использованием файла конфигурации;

  2. С собственным препроцессором данных;

  3. С использованием собственных моделей.

В качестве датасета используются данные эффективности энергии.
Датасет используется для моделирования эффективности отопительной нагрузки и холодильной нагрузки зданий (то есть энергоэффективности) в зависимости от параметров здания. Датасет состоит из 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.

Будем рады услышать конструктивную критику и найти единомышленников для дальнейшего усовершенствования библиотеки!

Комментарии (0)