Меню Закрити

Фреймворки для управління моделями машинного навчання

Джерело зображення: inovex

Розповідає Nico Kreiling

У цьому пості  ми порівняємо три різні інструменти, створені для підтримки розроблення відтворюваної моделі машинного навчання:

  • MLFlow, розроблений компанією DataBricks (компанія, створена засновниками Apache Spark);
  • DVC — проект із відкритим вихідним кодом, який отримує основну підтримку від стартапу iterative.ai;
  • Sacred — академічний проект, розроблений різними дослідниками.

На початку буде один абзац про кожен фреймворк, який описує проект та демонструє кілька прикладів коду. Наприкінці статті ви знайдете порівняння фреймворків та рекомендації, коли кожен із них краще використовувати. За основу взято пакет даних scikit-learn з цінами на нерухомість Boston-Housing. У цьому репозиторії ви можете знайти блокнот (Notebook), аби трохи повправлятися. Цей блокнот також містить інструкції, як встановити фреймворки, та деякі інші функції, які ми використовуватимемо в прикладах коду нижче, але ми не будемо на цьому зупинятися, аби зосередити увагу на окремих частинах фреймворку й пропустити шаблонний код.

DVC

DVC означає “контроль версій (аналізу) даних” (Data Version Control) та має на меті реалізувати для аналізу даних те, що git уже робить для розробки програмного забезпечення: створити процеси розробки відстежуваними й відтворюваними. Тому він спирається на git для відстеження вихідних файлів, а також використовує його як приклад для власного CLI: щоб почати проект, запустіть dvc init, який ініціалізує приховані файли у директорії .dvc, — як це робиться в git. DVC є мовно-незалежним, оскільки ним управляють за допомогою команд терміналу й він не має доступу до значень змінних мови програмування.

У DVC є три основні типи об’єктів:

  • залежності: усе, на що посилається ваш код для запуску, наприклад інші бібліотеки, зовнішні скрипти або джерела даних;
  • вихідні файли: усе, що буде створено вашою програмою й має бути записано — це можуть бути результати попередньої обробки, остаточні прогнози тощо;
  • файл метрики для зберігання KPI (ключових індикаторів продуктивності).

Тож вихідний код, використаний у прикладі, має такий вигляд:

def logDVC(model,param=dict(),metrics=dict(),features=None, tags=dict()):
    # Imports
    import json
    from sklearn.externals import joblib
    
    # Get some general information
    type = model.__module__.split(".")[0]
 
    # No option to set some tags to identify the experiment
 
    # Save Model
    if type=="sklearn":
        _ = joblib.dump(model,"tmp/mymodel")
    if type=="lgb":
        model.save_model("tmp/mymodel")
 
    # Log metrics
    with open('tmp/metrics.txt', 'w') as f:
        f.write(json.dumps(metrics))
 
    # plot Feature importances if avaible
    plotFeatureImportances(model, features, type)
 
    # Create file about features
    if features is not None:
        with open("tmp/features.txt", "w+") as f: 
            f.write(",".join(features))
            
if __name__ == "__main__":
    from sklearn.model_selection import train_test_split
    from sklearn.linear_model import Lasso
    # We need to import utils here, since it is an own script and the execution environment has no access to the jupyter execution environment
    from utils import *
 
    # Do a train_test_split
    data = getData()
    x_train, x_test, y_train, y_test = train_test_split(data.iloc[:,:-1], data.iloc[:,-1], test_size=10, random_state=42)
 
    # Define the details of our run
    params=dict(alpha=0.4)
    clf = Lasso(**params)
    clf.fit(x_train, y_train)
    predictions = clf.predict(x_test)
 
    metrics = eval_metrics(y_test, predictions)
    logDVC(clf,params,metrics,features=x_test.columns.values)

Ми завантажуємо дані, робимо scikit-learn регресію та зберігаємо інформацію.

Запускатимемо програму, як і більшість інших програм Python, але з використанням команди dvc run для відстеження результатів. Тому визначаємо три типи об’єктів, про які ми згадували вище:

dvc run \
 -d dvc_simple.py \
 -f simple.dvc \
 -o tmp/featureimportance.png \
 -M tmp/metrics.txt \
 -o tmp/features.txt \
 -o tmp/mymodel
 python dvc_simple.py

Аргумент -f дозволяє вказати ім’я файлу dvc для відстеження цієї команди. У цьому файлі він зберігатиме хеш-значення для всіх згаданих файлів, тож dvc зможе відстежувати, якщо якийсь із них зміниться. За допомогою команди:

dvc repro simple.dvc

можна відтворити таке ж дослідження. DVC перевірить, чи змінились залежності, і якщо це так, повторно запустить дослідження. Він навіть виявить, що залежний файл також був зазначений як вихідний файл іншого запуску DVC, і якщо так, перевірить ці залежності. Так, можна будувати конвеєри різних запусків.

Використовуючи команду на зразок:

dvc metrics show

можна перевірити результати та порівняти їх із попереднім запуском (але переконайтеся, що ви комітили стан файлу метрик, оскільки dvc не зробить цього сам)

До того ж за допомогою dvc можна керувати версіями навіть великих залежних файлів, таких як вхідні дані. Тому потрібно налаштувати віддалене сховище — наприклад, хмарне, а потім скористатися git-подібним синтаксисом, щоб додавати та передавати відповідні файли.

dvc remote add myremote s3://bucket/path
dvc add data.xml
dvc push

Попри те, що DVC чудово працює у відтворюваних дослідженнях й дозволяє обмінюватись цими даними з колегами, він не дуже підходить для аналізу результатів. Якщо ви хочете зробити десять запусків, використовуючи різні налаштування, треба буде робити якесь порівняння самостійно. Складніші завдання з кількома залежностями та вихідними результатами роблять cli досить багатозначним, але тут є ідеї, як це змінити. Допоки їх не буде реалізовано, вдалий трюк полягає в тому, щоб розмістити всі залежності в каталозі, а потім просто зв’язати всю папку. Добре мати змогу міксувати кілька мов, але якщо ви використовуєте лише Python, наприклад, то якось дивно записувати всю інформацію, яку потрібно зберегти у файлах. Зрештою, ця конструкція також заважає тісній інтеграції з блокнотами, що робить dvc менш придатним для швидкого прототипування ідей.

MLFlow

Коли я вперше почув, що Databricks створюють MLFlow, я чекав на важкий інструмент із тісною інтеграцією зі Spark, але я дуже помилявся. MLFlow надає API для Python, R та Java, а також REST-запити — з усього я тестував тільки версію для Python. Він встановлюється за допомогою pip і містить три підпроекти:

  1. Відстеження — виклики функцій для сервера реєстрації та візуалізації (на цьому ми і зосередимося).
  2. Проект — визначення проекту й залежностей, щоб зробити їх легко виконуваними (розглянемо це коротко).
  3. Модель — стандарт для зберігання та розгортання моделі (це дослідження лишається на вас).

Перейдемо до коду. Ми збережемо основну функцію й використаємо лише нову функцію для логування:

def logMlflow(model,data,output_folder="mlflow_out", param=dict(),metrics=dict(),features=None, tags=dict(),run_name=None):
    # Imports
    from sklearn.externals import joblib
    import mlflow
    import os
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
    
    # Get some general information
    type = model.__module__.split(".")[0]
    modelname = model.__class__.__name__
    sha, remoteurl = getGitInfos()
    
    # Start actual logging
    mlflow.set_experiment(experiment_name="demo")
    if not run_name:
        run_name = modelname
    with mlflow.start_run(source_name=remoteurl,source_version=sha, run_name=run_name):
        
        # Log Parameters
        for k,v in param.items():
            mlflow.log_param(k, v)
 
        # Track dependencies
        import pkg_resources
        with open("{}/dependencies.txt".format(output_folder), "w+") as f: 
            for d in pkg_resources.working_set:
                f.write("{}={}\n".format(d.project_name,d.version))
        mlflow.log_artifact("{}/dependencies.txt".format(output_folder))
        
        # Track data
        data.to_csv("{}/data".format(output_folder))
        mlflow.log_artifact("{}/data".format(output_folder))
        
        if type=="sklearn":
            _ = joblib.dump(model,"{}/sklearn".format(output_folder))
            mlflow.log_artifact("{}/sklearn".format(output_folder))
        if type=="lgb":
            model.save_model("{}/lghtgbm.txt".format(output_folder))
            mlflow.log_artifact("{}/lghtgbm.txt".format(output_folder))
        
        # Log metrics
        for k,v in metrics.items():
            mlflow.log_metric(k,v)
 
        # plot Feature importances if avaible
        featurePlot = plotFeatureImportances(model, features, type)
        if featurePlot:
            mlflow.log_artifact("{}.png".format(featurePlot))
            
        # Set some tags to identify the experiment
        mlflow.set_tag("model",modelname)
        for tag, v in tags.items():
            mlflow.set_tag(t,v)

Використовуючи with у поєднанні з mlflow.start_run, можна створити новий запуск, якщо його ще немає, і зареєструвати результати дослідження. Крім того, існують три різних функціональних можливості log: параметри для конфігурації моделі, метрики для оцінювання та артефакти для всіх файлів, вартих зберігання, вхідних і вихідних.

Уся ця інформація зберігається в локальному каталозі mlruns, який також є джерелом інформації для візуалізації mlflows. Для початку:

mlflow ui

запустить користувацький інтерфейс, який показує різні прогони в дослідженні, а також використані параметри та створені показники. Можна також фільтрувати їх на основі параметрів, показників або тегів. Крім того, можна детально порівняти кілька прогонів, використовуючи просту, але ефективну діаграму розсіювання.

MLFlow не пропонує простої команди відтворення, такої як DVC, щоб повторно запустити попередньо визначену установку. Однак його “проектний” компонент пропонує спосіб збільшення стандартизації. Тож можна розмістити файл під назвою MLProject у кореневому каталозі, який служить точкою входу до вашого проекту. Вигляд буде такий:

name: tutorial
 
conda_env: conda.yaml
 
entry_points:
  main:
    parameters:
      alpha: float
      l1_ratio: {type: float, default: 0.1}
    command: "python train.py {alpha} {l1_ratio}"

Він посилається на файл середовища для залежностей і виконує головну команду з певними модифікованими параметрами, у цьому випадку alpha та l1_ratio — гіперпараметр для управління моделлю ElasticNet. За допомогою клієнта mlflow можна відтворити дослідження навіть без перевірки вихідного коду, але безпосередньо звертаючись за URL:

mlflow run [email protected]:inovex/machine-learning-model-management.git -P alpha=0.4

Mlflow легко зрозуміти завдяки його чітко вираженій функції реєстрації. Його інтеграція потребує кількох додаткових рядків коду, але його функції порівняння й візуалізації цього варті, навіть у фазі швидкого прототипування.

Sacred

Sacred розробили в академічному середовищі та представили на конференції SciPy conference 2017. Він лише надає інтерфейс Python і використовує анотації, щоб визначити частини та показники для відстеження. Тобто користувачам не потрібно визначати кожен параметр, який слід відстежувати вручну, і в такий спосіб можна мінімізувати витрати. Sacred не підтримує захоплення інформації, розробленої в блокноті, оскільки інтерактивність таких блокнотів робить код менш відтворюваним, що є основною метою фреймворка. Хоча ви можете протестувати свій Sacred код у блокноті, він насправді не відстежує інформації, а лише перевіряє синтаксис. З цієї причини ми знову використовуємо магію write_file з Jupyter, щоб створити з ним скрипт у блокноті та повернутися до нього після цього.

У блокноті ви знайдете дві версії подібного коду для відстеження досліджень із Sacred. Перший аналогічний коду відстеження mlflow і використовує спеціальні функції реєстрації. Друга версія (наведена нижче), напевно, більше спрямована на розробників Sacred і використовує анотації й спеціальні функції Sacred.

#!/usr/bin/env python
from __future__ import division, print_function, unicode_literals
from sacred import Experiment
 
# Imports need to be done in the beginning of the file, since sacred won't recognize them, if they occur within a function
from sklearn.externals import joblib
 
ex = Experiment('Boston Housing Prices')
from utils import *
        
@ex.capture
def capturestuff(_seed):
    print(_seed)
 
def getData():
    from sklearn.datasets import load_boston
    boston = load_boston()
 
    data = pd.DataFrame(boston.data,columns=boston.feature_names)
    data['target'] = pd.Series(boston.target)
    return data
 
@ex.config
def cfg(_log):
    alpha= 0.5
    
def logSacred(run,model,data,output_folder="sacred_out", param=dict(),metrics=dict(),features=None, tags=dict()):
    # Get some general information
    import os
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
    type = model.__module__.split(".")[0]
    modelname = model.__class__.__name__
    
    # Config will be tracked automatically
    
    # Dependencies will also be tracked automatically
    
    # Track source code
    data.to_csv("{}/data".format(output_folder))
    #ex.add_source_file("{}/data".format(output_folder))
    ab = ex.open_resource("{}/data".format(output_folder))
    # Create file about features
    if features is not None:
        with open("{}/features.txt".format(output_folder), "w+") as f: 
            f.write(",".join(features))
        ex.add_artifact("{}/features.txt".format(output_folder))
        
    # plot Feature importances if avaible
    if plotFeatureImportances(model, features, type):
        ex.add_artifact("{}/featureimportance.png".format(output_folder))
 
    # Track Model binary
    if type=="sklearn":
        _ = joblib.dump(model,"{}/sklearn".format(output_folder))
        ex.add_artifact("{}/sklearn".format(output_folder))
    if type=="lgb":
        model.save_model("{}/lghtgbm.txt".format(output_folder))
        ex.add_artifact("{}/lghtgbm.txt".format(output_folder))
        
    # Log metrics
    for k,v in metrics.items():
        ex.log_scalar(k,v)
        
    # Set some tags to identify the experiment
    for tag, v in tags.items():
        ex.add.set_tag(t,v)
 
@ex.automain
def run(_run, alpha):
    # Setup
    from sklearn.model_selection import train_test_split
    from sklearn.linear_model import Lasso
    from sklearn.metrics import mean_absolute_error 
 
    # Do a train_test_split on my Data
    data = getData()
    x_train, x_test, y_train, y_test = train_test_split(data.iloc[:,:-1], data.iloc[:,-1], test_size=10, random_state=42)
    
    # Define my params
    params=dict(alpha=alpha)
    
    clf = Lasso(**params)
    clf.fit(x_train, y_train)
    predictions = clf.predict(x_test)
    metrics = eval_metrics(y_test, predictions)
        
    logSacred(_run,clf,data,param=params,metrics=metrics,features=x_test.columns.values)

Можливо, це просто тому, що я використовую Sacred уперше і вже написав код для двох інших фреймворків, але використання Sacred анотацій не було дуже зручним. І хоча Sacred-магія автоматичного збору важливої ​​інформації спочатку здається переконливою, вона далека від досконалості, і я часто ловив себе на тому, що контролюю, чи правильно простежуються мої залежності й файли. Наприклад, Sacred буде відстежувати залежності, якщо ви імпортуєте їх у верхній частині скрипта, але якщо ви робите це в межах функції — не буде. Він також використовує певні команди, як open_resource для читання файлів та одночасного їх відстеження, але ви маєте пам’ятати ці спеціальні команди, і, якщо ваші дані не зберігаються у файлі, а отримані, наприклад, з API, вам усе одно треба буде обов’язково реєструвати залежність. Для мене метод анотацій коду в Sacred був більше тягарем, ніж полегшенням порівняно з чіткою реєстрацією.

Проте Sacred, напевно, буде вдалим вибором, якщо хочете створити власний фреймворк для своєї організації, оскільки він використовує модульний підхід до проектування. Його API надає безліч функцій і засобів для створення власних фреймворків. Хоча кількість варіантів перевершує потреби більшості розробників, які просто хочуть стежити за певними бібліотеками й залежностями, це дає вам змогу формувати інструмент відповідно до ваших потреб. Його функціональність не лише може бути розширена кількома способами, але Sacred ще й підтримує різні резервні копії, такі як зберігання файлів, MongoDB, TinyDB, SQL та інші. Приклади в блокноті використовують MongoDB, оскільки це єдина підтримка наявних засобів візуалізації, які також є зовнішніми компонентами.

Хоча жоден із трьох зазначених інструментів візуалізації не пропонує стількох функцій, скільки mlflow, різні бекенди мають полегшити створення власної візуалізації. Скріншоти нижче показують omniboard, який дає змогу створювати нові стовпці для реєстрації метрик і підтримує впорядкування, але в ньому немає порівняння різних деталей.

Висновок

Три різні фреймворки призначені для різних цілей. У той час як DVC спрямований на широкий спектр застосування й робить ставку на відтворюваність замість простоти використання, mlflow є гарним та зручним інструментом для відстеження параметрів і порівняння продуктивності моделі під час створення прототипів. Використання Sacred більш обмежене, він надає лише інтерфейс Python, не підтримує використання блокнотів і не пропонує простої команди для відтворення дослідження. Проте він приваблює своїми відкритими стандартами й розширюваністю.

DVC mlFlow Sacred
Підтримка мов Universal using Shell Python, R, Java, REST Python
Контроль DAG using Scripts Python Package Annotations
Backend зберігання даних File storage File storage MongoDB та ін
Github зірки 2400 3400 1700
Ліцензія Apache 2 Apache 2 MIT

Якщо ви шукаєте інструмент, що дасть вам простий спосіб реєстрації важливих атрибутів і допоможе організувати ваші моделі під час створення прототипів, зверніться до mlflow.

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

Заглиблюйтесь у Sacred, якщо хочете створити власний інструмент та отримати віддачу від попередньої роботи або якщо інші не забезпечують достатню гнучкість.

Параметри DVC mlFlow Sacred
Підтримка блокнота (Notebook) Так Так Тільки Dry-Run
Зберігання моделі Так Як артефакти Тільки артефакти
Зберігання ввідних даних Так Тільки артефакти Так
Візуалізація Ні Так Так (Зовнішня)
Відстеження параметрів Так Так Так
Метрики продуктивності Так Так Так
Статистика виконання Мало Мало Так
Відстеження джерела коду Через Git Вручну (Git Hashes) Так
Залежності Вручну (Env-File) Вручну (Env-File) Так

 

Якщо ви знайшли помилку, будь ласка, виділіть фрагмент тексту та натисніть Ctrl+Enter.

0

Повідомити про помилку

Текст, який буде надіслано нашим редакторам: