Skip to content

Стоимость обучения

Руководство описывает honestml 2.0.0. Изменения и переход с 1.x описаны в changelog. Инструменты benchmarks запускаются из checkout репозитория и не входят в wheel или sdist.

Время обучения и качество измеряются отдельно от детерминированного honesty-бенчмарка. benchmarks/training_cost.py создаёт локальные отчёты производительности; benchmarks/run.py и его baseline.json сохраняют собственный контракт без таймингов.

Быстрый поиск

SearchConfig включает ограниченный выбор модели и FS-рецепта. Числа в конфигурации задают вычислительный ресурс; они не являются подтверждёнными порогами качества или ускорения относительно прода.

from honestml import AutoML, SearchConfig, FeatureSelectionConfig, CVConfig

model = AutoML(
    models=("catboost", "lightgbm"),
    cv=CVConfig(scheme="timeseries_period", period="month", n_splits=3, n_es=1000),
    feature_selection=FeatureSelectionConfig(
        compare=("importance", "random_probe"),
        refine=True,
    ),
    search=SearchConfig(
        max_rows=4096,
        max_features=64,
        model_iterations=64,
        max_probe_fits=64,
        reserve_fraction=0.3,
        probe_fraction=0.2,
        threads=1,
    ),
    budget=600,
    cache=".cache/churn-training",
)
# time_values содержит даты наблюдений; CV и purge задаются под горизонт таргета.
model.fit(X_train, y_train, time=time_values)
print(model.run_report_["search"])
print(model.run_report_["cost"]["fit_counts"])

Для заранее выбранного алгоритма задают, например, models=("catboost",): пробы семейств пропускаются. Одна FS-процедура может состоять из ранжирования, порогового отбора и последовательного уточнения. compare перечисляет альтернативные рецепты, а strategy без compare фиксирует один рецепт.

Семейства сравниваются на ограниченных строках исходных folds с сохранением широкой матрицы. Первая стадия оставляет максимум двух финалистов; они проверяются с большим лимитом строк и итераций на репрезентативно выбранных исходных folds. Все модели в пробах обучаются на fit-строках; ES служит validation для поддерживающих его моделей. Выборка сохраняет пропорции классов с минимум одной строкой каждого присутствующего класса. В отчёте отдельно видны малый ES, недостаток событий, смена лидера и незавершённое подтверждение.

model_margin задаёт абсолютный допустимый проигрыш DEV-метрики при выборе по прогнозируемой стоимости завершения. По умолчанию он равен нулю. Более дешёвая модель принимается только после парной односторонней проверки non-inferiority. Это правило поиска на DEV; независимая приёмка итогового качества выполняется отдельно. Поле estimated_cv_refit_s описывает только CV/refit, а completion_cost_forecast добавляет условную стоимость поддержанных FS/HPO-процедур. CV оценивается по наблюдавшимся остановкам и потолку обучения; refit — отдельно по текущему известному бюджету фабрики без ES. Будущие параметры HPO неизвестны, поэтому прогноз остаётся условным. Если бюджет refit известен, но положительное число наблюдавшихся CV-итераций недоступно, оценка CV/refit равна None с причиной missing_cv_iterations: такая стоимость не служит основанием для замены лидера по качеству. DEV/full refit использует бюджет выбранной фабрики, включая явные параметры и результат HPO.

Общий train-only фильтр сокращает ширину FS-проб; рецепты подтверждаются выбранным семейством на отдельных контрольных строках. Пробы не используют target-encoded колонки; исходные категории доступны. Если классы не помещаются в FS-лимит строк, сохраняется готовый широкий контроль с явной причиной.

После выбора полный CV и HPO получают одно семейство. Выбранный FS-рецепт исполняется на ограниченном ресурсе внутри train-частей исходных folds: ранжирование, сокращение ширины и уточнение. Null/random-probe этапы используют до трёх повторов, прокси — лимит model_iterations; полный итоговый train использует весь предусмотренный объём данных. Широкий DEV-контроль сохраняется как опорный результат и переиспользуется при остановке FS. Подтверждение общего subset на том же DEV имеет статус post_search_dev.

Итоговый выбор между широким и сокращённым вариантом следует действующему статистическому правилу: внутри полосы эквивалентности допустим более компактный набор с несколько меньшим score. Эта проверка не устраняет адаптивность поиска. По умолчанию HPO выключен; при включении его IID inner folds выделяют ES внутри inner-train, сохраняя отдельный inner-test.

max_rows ограничивает суммарные размеры fit/ES/test частей первой стадии и строки каждого FS-прокси fit; max_folds — число folds первой стадии. confirmation_rows, confirmation_iterations, confirmation_folds задают второй ресурс, не меньший первого. Их значения по умолчанию — 65536, 256 и 2; первая стадия использует 4096 строк, 64 итерации и один fold. Лимит строк каждого этапа суммирует fit/ES/test по его folds для одной семьи, включая повторные вхождения строк между folds.

min_es_rows=10 и min_class_count=5 — диагностические минимумы, а не гарантии статистической мощности. max_features ограничивает ширину FS-проб; при уточнении сохраняются действующие минимумы признаков и важностной массы. max_probe_fits=64 ограничивает фактические обучения обеих модельных стадий и FS-проб вместе. Отдельный max_fs_fits=64 ограничивает исполнение выбранного рецепта. Лимиты считаются перед backend fit; незавершённый subset не становится итоговым.

Нативный fit прерывается только на границе следующей операции. Временной резерв сохраняет возможность завершения, но не гарантирует жёсткий deadline; overshoot_s раскрывает фактическое превышение. При budget=None общий временной бюджет отсутствует.

threads ограничивает поддерживающие этот контракт бустинги и ExtraTrees-прокси. Потоки BLAS, Polars и построения SHAP-background требуют отдельного ресурсного контракта. Память проверяется перед fit, включая широкий контроль и refit; уже выполняющийся нативный fit может превысить лимит до следующей проверки.

Завершённая подготовка FS сохраняется перед HPO, trials — по мере завершения, полный selection — после завершения поиска. Повтор с тем же вычислительным контекстом восстанавливает эти стадии; refit учитывается отдельно. Частичный поиск и временный отказ не закрепляют candidate/selection cache. Изменение данных, split, seed, конфигурации или значимых версий означает новый scope. При фиксированном числе trials HPO воспроизводит историю sampler, включая завершённые ошибки; для wall-clock режима идентичная последовательность не обещается.

Для регулярного переобучения фиксируют search.selected_model и итоговый блок feature_selection отчёта. Если FS принят, его strategy задаёт процедуру: устанавливают compare=None, сохраняют её параметры и исходный SearchConfig, чтобы сохранить вычислительные ограничения. Единственный models пропускает оба этапа выбора семейства; один рецепт пропускает сравнение FS-альтернатив. search=None выполняет процедуру с её полными FS-настройками.

Если итоговый блок feature_selection равен None, FS отключают. Поле search.selected_recipe описывает предварительный выбор и само по себе не задаёт итоговый рецепт: контроль может вернуть широкий вариант. Фиксированный набор признаков задают проекцией входной таблицы на сохранённые имена и отключением FS.

Оценки DEV после поиска отмечены как post_search_dev. Качество проверяют на отдельном итоговом периоде, который не участвовал в выборе модели, FS и порогов. Приёмка качества и численные цели времени остаются открытыми. Содержимое preset="fast" сохраняет отдельный существующий контракт; ограниченный поиск включается явно через search.

Прогноз FS/HPO до выбора семейства

После подтверждения максимум двух семейств поиск измеряет стоимость одной фиксированной FS-процедуры и HPO каждого финалиста. Измерения входят в общий max_probe_fits и временной бюджет проб. Основные FS/HPO запускаются после выбора семейства. Результаты профиля не используются как выбранный subset или история trials.

FS профилируется на двух лимитах train-строк первого исходного DEV fold с сохранением его test-части, seed и ограничений рецепта. Измеряется процедура, включая ранжирование, доступное уточнение и локальную статистику. Отчёт разделяет наблюдаемую траекторию и верхнюю границу числа fit; потолок не считается ожидаемым количеством обучений. Перенос времени на остальные folds предполагает сходную траекторию и пропорциональную стоимость строк.

HPO использует центральную конфигурацию пространства параметров на двух различных ресурсах. Прогноз предполагает выполнение всех запрошенных trials на этой конфигурации и полной ширине признаков. Фактические параметры, early stopping и выбранный FS-subset могут изменить стоимость. Подготовка матрицы, target encoding, sampler и checkpoint в эту компоненту не входят. timeout_s записывается как лимит, а не как оценка времени.

В run_report_["search"]["completion_cost_forecast"] для каждого семейства доступны:

  • fs и hpo: измерения, условная оценка секунд, seed, хэши контекста, ограничения и ошибка переноса между двумя ресурсами.
  • cv_refit_s, additional_cv_s, additional_cv_count: широкий контроль, предусмотренные повторные CV и refit. При HPO с keep_baseline=True учитываются оба последующих CV.
  • estimated_s: сумма включённых процедур; status="conditional" обозначает зависимость от перечисленных допущений.

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

Несколько альтернатив FS в compare, неподдержанный estimator, одинаковые фактические ресурсы или исчерпанный бюджет дают estimated_s=None с причиной. При неизвестной компоненте поиск сохраняет лидера подтверждения по качеству; время не служит основанием для его замены. Один явно заданный алгоритм пропускает пробы и прогноз вместе с ними, сохраняя быстрый путь переобучения.

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

Статистика и диагностические прогоны

Bootstrap встроенных accuracy, MAE и RMSE использует подготовленные построчные вклады на поддерживаемых входах. Для log loss этот путь проверен на scikit-learn 1.7.2; другие версии используют общий scorer. ROC-AUC и PR-AUC переиспользуют подготовленное ранжирование для поддержанных бинарных входов без весов на scikit-learn 1.7.2. Неподдержанные входы, пользовательские и изменённые scorers, включая аддитивные, используют общий путь. Число bootstrap-повторов, RNG, веса, классы и критерии выбора сохраняются.

Внутри построения ансамбля ограниченный кеш встроенного scorer переиспользует повторные оценки при фиксированных target и весах. Пользовательские и изменённые scorers обходят этот кеш. Статистический критерий и правило принятия ансамбля сохраняются.

benchmarks/bootstrap_scoring.py сравнивает статистику на фиксированных предсказаниях без обучения моделей. benchmarks/search_diagnostics.py выполняет локальные DEV-абляции ресурса и сопоставимые копии notebook-сценариев; исходные ноутбуки сохраняются. Native/BLAS threads, HPO до пяти trials на семейство, seeds, порядок и provenance записываются отдельно. Просмотренные holdout не используются для настройки политики.

Что означает отчёт AutoML

total_wall_s фиксирует завершённое обучение и сборку отчёта до его отправки во внешний tracker. Tracker и run_report_ получают один итоговый снимок; длительность внешней публикации измеряют вокруг вызова fit, если она входит в границы сравнения.

run_report_["cost"] содержит независимо измеренное total_wall_s, сумму attributed_wall_s и overhead_s. Последние два значения объясняют полное время. exclusive_timings исключает вложенную работу: HPO внутри selection не прибавляется к полной selection второй раз. stages сохраняет каждый повторный вызов с parent_id, временем и исходом, в том числе несколько refit. Поле timings содержит накопленные включающие длительности и не предназначено для суммирования полного времени.

В work записываются модель, стадия, recipe для FS-процедуры, trial/fold, число строк и столбцов, длительность, лимит деревьев и выполненные итерации, если backend их сообщает. fit_counts различает завершённые и неуспешные попытки. Записи работы уточняют стоимость стадий; их длительности не прибавляются ещё раз к total_wall_s. Историческое время из кеша помечается source="cache" и не входит в измеренное время текущего прогона.

RSS снимается на границах операций при доступном optional-пакете psutil. sampled_peak_rss_mb — максимум этих наблюдений, а не непрерывно измеренный пик. Без probe память обозначается null, а memory_measurement="unavailable". Отчёт включает CPU count, ОС, Python, версии библиотек и доступный общий объём RAM. Исходные строки, значения признаков и предсказания в отчёт стоимости не входят.

Сопоставимые сценарии

Сценарий Работа Назначение
manual_reference Принятый ручной рецепт с отключённым HPO База времени и качества
fixed_recipe Те же данные, признаки, split, модель, параметры, ES и число fit/refit Накладные расходы библиотеки
full_search Полный поиск с заранее заданным бюджетом Стоимость выбора с нуля
fast_search Ограниченный поиск с тем же независимым оценочным набором Компромисс времени и качества

Каждый сценарий измеряется отдельно в cold и warm. Подготовка состояния кеша выполняется явно перед каждым повтором и не входит в timed-область. Для холодного прогона используют отдельный пустой каталог кеша; для тёплого — заранее подготовленный кеш того же рецепта. Обёртка ничего не удаляет и не прогревает автоматически. Состояние кеша файловой системы/ОС и процесса следует фиксировать в протоколе среды: cold здесь характеризует объявленный прикладной кеш, а не принудительный сброс кеша ОС. Модели для каждого повтора создаются заново.

В ручном ориентире 03_train.ipynb, ветка TUNE=False, финальный CatBoost получает iterations=1500, learning_rate=0.03, depth=6, early_stopping_rounds=80. Валидация отделяется по двум последним уникальным значениям DATE; соответствие этих значений календарным месяцам проверяется на локальных данных. При включённом FS рецепт содержит random-baseline на подвыборке до 262000 строк с тремя seeds и двумя голосами, cumulative importance 0.99, затем backward elimination с 250 итерациями и относительным допуском 0.011. Отключение HPO не исключает стоимость FS.

Для измерения накладных расходов фиксируют итоговый набор признаков и сравнивают одинаковую работу. Для поиска с нуля стоимость FS включают в обе timed-области либо явно задают разные рецепты и сравнивают их как поисковые стратегии. Валидация, участвующая в early stopping или отборе признаков, не служит независимым итоговым тестом. Итоговое качество всех сценариев оценивается на одном неизменном OOT/holdout.

Локальный замер

Из корня проекта функции доступны через benchmarks.training_cost:

from pathlib import Path
import json

from benchmarks.training_cost import TrainingManifest, WorkRecipe, measure_training

manifest = TrainingManifest(
    dataset_id="sha256-of-local-data",
    evaluation_id="sha256-of-untouched-evaluation-partition",
    scenario="fixed_recipe",
    seed=42,
    cache_mode="cold",
    work=WorkRecipe(
        split_id="sha256-of-train-and-es-partitions",
        feature_set_id="sha256-of-ordered-feature-schema",
        estimator="catboost",
        parameters={"learning_rate": 0.03, "depth": 6},
        tree_budget=1500,
        early_stopping_rounds=80,
        fit_count=1,
        refit_count=0,
        fs_recipe_id=None,
    ),
    training_boundary="prepared matrices through fitted model; FS excluded",
)

# callables are supplied by the local experiment; no data is sent externally
report = measure_training(
    train_local_recipe,
    manifest=manifest,
    repeats=3,
    prepare=prepare_local_cache_state,
    save=save_local_model,
    predict=predict_untouched_evaluation,
    evaluate=evaluate_untouched_predictions,
)
Path("training-cost-fixed-cold.json").write_text(
    json.dumps(report, indent=2, allow_nan=False), encoding="utf-8"
)

train_local_recipe() возвращает новую обученную модель. prepare_local_cache_state(i) устанавливает состояние перед повтором i. save_local_model(model) и predict_untouched_evaluation(model) выполняют отдельные измеряемые операции; evaluate_untouched_predictions(model, predictions) возвращает только словарь численных метрик и вызывается после закрытия timed-области. Необязательные save/predict можно опустить, но состав операций должен совпадать в сравнении. Подготовка данных внутри train входит в обучение; подготовка снаружи отражается в training_boundary.

Отчёт сохраняет каждый повтор, медиану общего времени, медианы training/save/inference, агрегаты стоимости AutoML при наличии и медианы качества. Оценка качества и подготовка кеша явно исключены из wall time. Отдельные предсказания не сохраняются. В примере fit_count и refit_count являются декларацией рецепта; фактические события cost.work нужны для проверки выполненной работы.

Сравнение и критерии

python benchmarks/training_cost.py manual-cold.json fixed-cold.json --metric average_precision --output comparison.json

Для сравнения full и fast используется --search-comparison. Для метрик потерь передают --lower-is-better. average_precision и pr_auc различаются: binary_quality(y, scores) считает AP через average precision, а pr_auc через трапецеидальную площадь precision-recall. Эти значения не подменяют друг друга.

Сравнение требует одинаковых идентификаторов данных и итоговой оценки, seed, среды, состояния кеша и границ операций. Для fixed-рецепта дополнительно должны совпадать split, признаки, параметры, ES, лимит деревьев, FS-рецепт и число fit/refit. При расхождении выдаётся not_comparable; отношение времён остаётся диагностикой. Совпадение деклараций само по себе не доказывает равную выполненную работу.

Без согласованных --quality-tolerance и --max-time-ratio результат имеет статус pending_thresholds. Допуск качества задаётся в абсолютных единицах указанной метрики, отношение времени — candidate/reference. После сверки фактических операций можно передать --work-verified; до такой сверки успешный fixed-сценарий получает pending_work_review. Флаг фиксирует проверку владельца эксперимента, а не заменяет её.

Корпоративный коэффициент ускорения и допустимая потеря качества подтверждаются локальным запуском на принятом наборе данных. Synthetic/unit проверки доказывают арифметику и границы измерителя; они не подтверждают эти коэффициенты.

Парная проверка качества на внешнем наборе

QualityContract, PredictionBatch, freeze_quality_contract и evaluate_paired_quality из benchmarks.training_cost проверяют одну фиксированную пару предсказаний отдельно от времени обучения. До доступа к итоговым меткам согласуют метрику, положительный класс и порядок классов, абсолютный допуск, alpha, seed и число bootstrap-повторов; фиксируют обе модели, предсказания, веса, блоки и оценочную популяцию. Требуется n_bootstrap * alpha >= 50.

В этой API metric="pr_auc" означает библиотечную average precision, в отличие от трапециевидного поля binary_quality()["pr_auc"] выше. Направление берётся из выбранного библиотечного scorer. Для binary поддержаны roc_auc/pr_auc/log_loss/brier и одномерная P(positive); для multiclass — log_loss/brier и матрица вероятностей в явно заданном отсортированном порядке классов; для regression — rmse/mae, classes=(), positive=None.

Хранитель внешнего набора готовит NPZ ровно с массивами y_true и row_ids, без pickle, и заранее передаёт его SHA-256. Уникальные строковые row IDs должны иметь одну стабильную область идентификаторов с training/tuning inventories. Пример использует локальный согласованный quality-plan.json, отдельный used-row-ids.json с четырьмя inventories и две NPZ предсказаний с полями values, row_ids:

import json
from pathlib import Path

import numpy as np

from benchmarks.training_cost import (
    PredictionBatch, QualityContract, evaluate_paired_quality, freeze_quality_contract,
)

plan = json.loads(Path("quality-plan.json").read_text(encoding="utf-8"))
used = json.loads(Path("used-row-ids.json").read_text(encoding="utf-8"))
contract = QualityContract(
    task="binary", metric="pr_auc", classes=(0, 1), positive=1,
    margin=plan["absolute_margin"], alpha=plan["alpha"],
    seed=plan["bootstrap_seed"], n_bootstrap=plan["n_bootstrap"],
    baseline_artifact_sha256=plan["baseline_artifact_sha256"],
    candidate_artifact_sha256=plan["candidate_artifact_sha256"],
    external_partition_sha256=plan["labels_npz_sha256"],
    row_id_namespace=plan["row_id_namespace"],
)


def load_predictions(path: Path, artifact_sha: str) -> PredictionBatch:
    with np.load(path, allow_pickle=False) as data:
        return PredictionBatch(
            artifact_sha256=artifact_sha,
            row_ids=tuple(data["row_ids"].tolist()),
            values=data["values"].copy(),
            classes=contract.classes, positive=contract.positive,
        )


baseline = load_predictions(Path("baseline-predictions.npz"), contract.baseline_artifact_sha256)
candidate = load_predictions(Path("candidate-predictions.npz"), contract.candidate_artifact_sha256)
receipt = Path("quality-receipt.json")
receipt_sha = freeze_quality_contract(
    receipt, contract, baseline=baseline, candidate=candidate,
    baseline_training_ids=used["baseline_training"],
    baseline_tuning_ids=used["baseline_tuning"],
    candidate_training_ids=used["candidate_training"],
    candidate_tuning_ids=used["candidate_tuning"],
    provenance={"review_plan": "quality-plan.json", "lineage": "used-row-ids.json"},
)
# доступ к файлу меток предоставляется после фиксации receipt
result = evaluate_paired_quality(
    receipt, frozen_sha256=receipt_sha, baseline=baseline, candidate=candidate,
    labels_path=Path("external-labels.npz"),
)
Path("quality-result.json").write_text(
    json.dumps(result, indent=2, allow_nan=False), encoding="utf-8",
)

Freeze создаёт новый receipt эксклюзивно и не читает метки. Он связывает SHA артефактов, массивы предсказаний, порядок строк, веса/блоки и версии/scorer SHA. Evaluate проверяет их неизменность, затем сверяет SHA NPZ до загрузки тех же bytes с allow_pickle=False. Перестановка строк не исправляется автоматически; несовпадение строк/классов, комплексные или нечисловые предсказания и невалидные вероятности отвергаются.

При наличии весов передают одинаковые sample_weight в обеих PredictionBatch; блоки зависимости — одинаковые целочисленные block_index. Они фиксируются до меток. После проверки полных fingerprints строки с нулевым весом исключаются из score и парного bootstrap. Остаются минимум две строки положительного веса и, при блоках, минимум два непустых блока; каждый заданный класс должен иметь положительную массу. Отчёт раскрывает effective_rows, excluded_zero_weight_rows и effective_blocks.

Training/tuning inventories включают все строки, использованные в FS, ES, калибровке и выборе модели; известное пересечение с внешними row IDs блокирует freeze. None означает неизвестную историю, пустой список — декларацию отсутствия строк в данной роли. Проверка полноты истории, отсутствия прежнего просмотра меток, независимости групп/периодов и происхождения предсказаний от заявленных артефактов остаётся внешней обязанностью.

statistical_verdict="passed" означает успешную парную NI в согласованном абсолютном допуске; failed — что она не установлена. Это не совместная приёмка качества и скорости: independence_review.status="pending" и quality_acceptance="pending_review" сохраняются независимо от NI. Receipt и названия файлов сами по себе не доказывают независимость данных или production-готовность.

Измеренные результаты и границы их интерпретации приведены в сводке контрольных прогонов.