02. Busca de híbridos e seleção incremental

A busca roda em estágios, sempre na validação temporal:

  1. Bases individuais de várias famílias (estatística, linear, kernel, árvore, neural).

  2. Retenção: as duas melhores de cada família, o ARIMA e a melhor base geral.

  3. Híbridos residuais: ŷ = base + α · corretor(resíduos), com corretores Linear, KNN e MLP, janelas de 7 ou 28 resíduos e α ∈ {0,5; 1}.

  4. Refinamento das duas melhores estruturas residuais.

  5. Seleção incremental: cada família desafia o campeão atual e só o substitui com ganho mediano ≥ 5% e vitórias em ≥ 60% das origens (regra calibrada, ADR-0008).

Perfil quickstart: o critério de variabilidade do protocolo fica desligado, e isso aparece no relatório. O perfil research_strict exige esse critério definido.

import autohybts

print("AutoHybTS", autohybts.__version__)
AutoHybTS 1.0.0rc4

1. Dados e experimento

O ARIMA com ordem automática é o modelo mais caro; aqui ele aparece só nos exemplos manuais da seção 3, e a busca automática fica com os demais modelos.

budget limita o número de candidatas (total, por família e por etapa; max_trials substitui o total), então a mesma série e a mesma semente dão a mesma busca em qualquer máquina. Um prazo de relógio é opcional (max_wall_time_s=...): nenhuma candidata começa depois dele, e as que ficarem de fora aparecem no ledger como STOPPED.

progress=True mostra uma barra do tqdm por etapa da busca e outra para as finalistas em V_select (extra progress). É só relatório: as candidatas avaliadas e o campeão não mudam.

from autohybts import AutoHybridExperiment
from autohybts.datasets import get_data

y = get_data("synthetic_daily")

exp = AutoHybridExperiment(
    fh=14,
    session_id=42,
    progress=True,
    fold=3,
    fold_strategy="expanding",
    seasonal_period=7,
    profile="quickstart",
    base_models=["naive", "seasonal_naive", "linear", "knn", "mlp"],
    correctors=["linear", "knn", "mlp"],
    residual_windows=[7, 28],
    residual_sources=["historical_oof_1step"],
    alphas=[0.5, 1.0],
    budget="small",
    max_trials=40,
).fit(y)
exp.splits
layer train_start origin first_target last_target n_train horizon
fold_id
search-0 search 2024-01-01 2025-09-23 2025-09-24 2025-10-07 632 14
search-1 search 2024-01-01 2025-10-07 2025-10-08 2025-10-21 646 14
search-2 search 2024-01-01 2025-10-21 2025-10-22 2025-11-04 660 14
select-0 select 2024-01-01 2025-11-04 2025-11-05 2025-11-18 674 14
select-1 select 2024-01-01 2025-11-18 2025-11-19 2025-12-02 688 14
select-2 select 2024-01-01 2025-12-02 2025-12-03 2025-12-16 702 14

2. Catálogo de modelos

exp.models()
family multi_step online_state_update in_sample_fitted deterministic requires_extra training_scope output_type horizon_strategy max_horizon target_mode accepts_exog requires version description
kind name
base adaboost_r2 tree True True False False NaN local point recursive None univariate True None None AdaBoost.R2 (scikit-learn) sobre lags.
adida intermittent True True False True NaN local point recursive None univariate False None None ADIDA: suavização em blocos de k períodos.
arima statistical True True True True stats local point recursive None univariate True None None ARIMA (statsmodels); ordem automática por KPSS...
catboost_regressor tree True True False False boosting local point recursive None univariate True None None CatBoost sobre lags (extra boosting).
croston_sba intermittent True True False True NaN local point recursive None univariate False None None Croston-SBA: tamanho / intervalo entre demandas.
elastic_net_regression linear True True False True NaN local point recursive None univariate True None None Elastic Net sobre lags.
exponential_smoothing statistical True True False True stats local point recursive None univariate False None None Suavização exponencial aditiva (Holt, Holt-Win...
extra_trees_regressor tree True True False False NaN local point recursive None univariate True None None Árvores extremamente aleatórias sobre lags.
harmonic_regression statistical True True False True NaN local point recursive None univariate False None None Regressão harmônica dinâmica: tendência + Four...
hist_gradient_boosting tree True True False False NaN local point recursive None univariate True None None HistGradientBoosting (scikit-learn) sobre lags.
historic_average statistical True True False True NaN local point recursive None univariate False None None Média do treino (parâmetro congelado).
imapa intermittent True True False True NaN local point recursive None univariate False None None IMAPA: média do ADIDA nos níveis 1..k.
knn kernel True True False True NaN local point recursive None univariate True None None k vizinhos mais próximos sobre lags.
lasso_regression linear True True False True NaN local point recursive None univariate True None None Regressão Lasso sobre lags.
lightgbm_regressor tree True True False False boosting local point recursive None univariate True None None LightGBM sobre lags (extra boosting).
linear linear True True False True NaN local point recursive None univariate True None None Regressão linear sobre lags.
mlp neural True True False False NaN local point recursive None univariate True None None Perceptron multicamadas sobre lags.
naive statistical True True False True NaN local point recursive None univariate False None None Repete o último valor observado.
random_forest tree True True False False NaN local point recursive None univariate True None None Floresta aleatória sobre lags.
random_walk_drift statistical True True False True NaN local point recursive None univariate False None None Passeio aleatório com drift: último valor + h ...
ridge_regression linear True True False True NaN local point recursive None univariate True None None Regressão Ridge sobre lags.
sarima statistical True True True True stats local point recursive None univariate True None None ARIMA sazonal (statsmodels); D pela força sazo...
seasonal_naive statistical True True False True NaN local point recursive None univariate False None None Repete a última temporada (season_length).
seasonal_window_average statistical True True False True NaN local point recursive None univariate False None None Média de cada posição sazonal nas últimas wind...
sf_auto_arima statistical True True False True nixtla-stats local point recursive None univariate False None None AutoARIMA do StatsForecast (ordem automática, ...
sf_auto_ces statistical True True False True nixtla-stats local point recursive None univariate False None None AutoCES do StatsForecast (suavização exponenci...
sf_auto_ets statistical True True False True nixtla-stats local point recursive None univariate False None None AutoETS do StatsForecast (erro, tendência e sa...
sf_auto_theta statistical True True False True nixtla-stats local point recursive None univariate False None None AutoTheta do StatsForecast (variantes do métod...
svm kernel True True False True NaN local point recursive None univariate True None None SVR sobre lags.
tree tree True True False True NaN local point recursive None univariate True None None Árvore de decisão sobre lags.
tsb intermittent True True False True NaN local point recursive None univariate False None None TSB: probabilidade de demanda × tamanho.
window_average statistical True True False True NaN local point recursive None univariate False None None Média das últimas window observações.
xgboost_regressor tree True True False False boosting local point recursive None univariate True None None XGBoost sobre lags (extra boosting).
corrector adaboost_r2 corrector False False False False NaN local point recursive None univariate False None None AdaBoost.R2 sobre janelas de resíduos.
arima_sequence corrector True True False True stats local point recursive None univariate False None None ARIMA sobre a série de resíduos (horizonte int...
catboost_regressor corrector False False False False boosting local point recursive None univariate False None None CatBoost sobre janelas de resíduos (extra boos...
exponential_smoothing_sequence corrector True True False True stats local point recursive None univariate False None None Suavização exponencial sobre a série de resídu...
hist_gradient_boosting corrector False False False False NaN local point recursive None univariate False None None HistGradientBoosting sobre janelas de resíduos.
knn corrector False False False True NaN local point recursive None univariate False None None k vizinhos mais próximos sobre janelas de resí...
lightgbm_regressor corrector False False False False boosting local point recursive None univariate False None None LightGBM sobre janelas de resíduos (extra boos...
linear corrector False False False True NaN local point recursive None univariate False None None Regressão linear sobre janelas de resíduos.
mlp corrector False False False False NaN local point recursive None univariate False None None Perceptron multicamadas sobre janelas de resíd...
random_forest corrector False False False False NaN local point recursive None univariate False None None Floresta aleatória sobre janelas de resíduos.
svm corrector False False False True NaN local point recursive None univariate False None None SVR sobre janelas de resíduos.
tree corrector False False False True NaN local point recursive None univariate False None None Árvore de decisão sobre janelas de resíduos.
xgboost_regressor corrector False False False False boosting local point recursive None univariate False None None XGBoost sobre janelas de resíduos (extra boost...

3. Um modelo individual e um híbrido montado à mão

Útil para entender as peças antes da busca automática. Os resíduos do corretor são erros de previsão fora da amostra (historical_oof_1step), nunca valores ajustados no próprio treino.

arima = exp.create_model("arima")
arima.cv_results
layer status MASE MAE RMSE sMAPE
search-0 search SUCCEEDED 1.846794 2.865174 3.100497 0.040837
search-1 search SUCCEEDED 0.554999 0.861073 1.100535 0.012383
search-2 search SUCCEEDED 1.668095 2.569822 2.698243 0.037090
select-0 select SUCCEEDED 2.174782 3.346846 3.488244 0.047806
select-1 select SUCCEEDED 2.195924 3.418777 3.807198 0.048332
select-2 select SUCCEEDED 0.387069 0.607182 0.739234 0.008558
Mean NaN NaN 1.471277 2.278146 2.488992 0.032501
Std NaN NaN 0.801855 1.238794 1.276195 0.017626
hybrid = exp.create_hybrid(
    base="arima",
    corrector="mlp",
    window=7,
    alpha=1.0,
    residual_source="historical_oof_1step",
)
hybrid.cv_results
layer status MASE MAE RMSE sMAPE
search-0 search SUCCEEDED 1.866445 2.895661 3.093652 0.041158
search-1 search SUCCEEDED 0.555764 0.862260 1.086767 0.012409
search-2 search SUCCEEDED 1.668606 2.570609 2.692855 0.037114
select-0 select SUCCEEDED 2.083504 3.206376 3.343848 0.045730
select-1 select SUCCEEDED 2.161925 3.365846 3.776502 0.047617
select-2 select SUCCEEDED 0.365805 0.573826 0.722414 0.008059
Mean NaN NaN 1.450342 2.245763 2.452673 0.032015
Std NaN NaN 0.787974 1.217761 1.254970 0.017317

4. Busca automática

result = exp.compare_models(include=["standalone", "residual"], sort="MASE")
result.leaderboard
busca S0 bases:   0%|          | 0/5 [00:00<?, ?cand/s]
busca S0 bases:  80%|████████  | 4/5 [00:00<00:00, 25.08cand/s]
busca S0 bases: 100%|██████████| 5/5 [00:00<00:00,  5.07cand/s]

busca S2 resíduos:   0%|          | 0/11 [00:00<?, ?cand/s]
busca S2 resíduos:   9%|▉         | 1/11 [00:02<00:20,  2.08s/cand]
busca S2 resíduos:  18%|█▊        | 2/11 [00:03<00:13,  1.52s/cand]
busca S2 resíduos:  27%|██▋       | 3/11 [00:03<00:07,  1.11cand/s]
busca S2 resíduos:  36%|███▋      | 4/11 [00:03<00:05,  1.35cand/s]
busca S2 resíduos:  45%|████▌     | 5/11 [00:04<00:03,  1.85cand/s]
busca S2 resíduos:  55%|█████▍    | 6/11 [00:05<00:04,  1.21cand/s]
busca S2 resíduos:  64%|██████▎   | 7/11 [00:06<00:03,  1.15cand/s]
busca S2 resíduos:  73%|███████▎  | 8/11 [00:06<00:01,  1.54cand/s]
busca S2 resíduos:  82%|████████▏ | 9/11 [00:07<00:01,  1.64cand/s]
busca S2 resíduos:  91%|█████████ | 10/11 [00:07<00:00,  2.04cand/s]
busca S2 resíduos: 100%|██████████| 11/11 [00:08<00:00,  1.23cand/s]
busca S2 resíduos: 100%|██████████| 11/11 [00:08<00:00,  1.24cand/s]

busca S3 refino:   0%|          | 0/2 [00:00<?, ?cand/s]
busca S3 refino:  50%|█████     | 1/2 [00:02<00:02,  2.08s/cand]
busca S3 refino: 100%|██████████| 2/2 [00:02<00:00,  1.05cand/s]
busca S3 refino: 100%|██████████| 2/2 [00:02<00:00,  1.12s/cand]

seleção V_select:   0%|          | 0/5 [00:00<?, ?cand/s]
seleção V_select:  20%|██        | 1/5 [00:00<00:01,  2.12cand/s]
seleção V_select:  40%|████      | 2/5 [00:01<00:02,  1.07cand/s]
seleção V_select: 100%|██████████| 5/5 [00:01<00:00,  2.80cand/s]

name model family strategy base corrector residual_source window alpha mean_rank ... MAE RMSE sMAPE coverage n_units n_failed n_skipped fit_seconds predict_seconds is_champion
candidate_id
7dd1082a4ee4fe9df20683376c5688b4a23322240929c4c44a8140e266a10ae2 mlp mlp(lags=7) neural standalone mlp NaN NaN NaN NaN 1.0 ... 1.329098 1.590181 0.019224 1.0 3 0 0 0.807899 0.017187 False
9fd962cca51322c0e3697027ac96d1fa8d35c2672647b0030737ef663dde6df6 mlp-linear mlp(lags=7) + linear [w=7, alpha=0.5, historic... residual residual_additive mlp linear historical_oof_1step 7.0 0.5 2.0 ... 1.335050 1.622713 0.019335 1.0 3 0 0 2.061273 0.012969 True
459af2c77fe43d1936669c337e695b972b6de79264cfa329ed349dfc20dc5172 mlp-linear mlp(lags=7) + linear [w=28, alpha=0.5, histori... residual residual_additive mlp linear historical_oof_1step 28.0 0.5 3.0 ... 1.536977 1.800618 0.022398 1.0 3 0 0 1.533249 0.012512 False
70ad174e1cc49eb256646399fb3e4b7d9df82f14f2978d09d35dac0019c7ef37 knn knn(lags=7) kernel standalone knn NaN NaN NaN NaN 4.0 ... 1.563061 1.966950 0.022621 1.0 3 0 0 0.050587 0.036638 False
d90528888825a4459816afbcf249ff909747924f0a2bbaa6e09d027e31342238 seasonal_naive seasonal_naive(season_length=7) statistical standalone seasonal_naive NaN NaN NaN NaN 5.0 ... 1.614291 1.909265 0.023346 1.0 3 0 0 0.000080 0.000144 False
e9e747a9648ad8d377f20122dce611a43dd3bf85a64c950a8e1ef246ebc032b4 linear linear(lags=7) linear standalone linear NaN NaN NaN NaN 6.0 ... 1.649719 1.852607 0.023763 1.0 3 0 0 0.057596 0.008549 False
773d3b8c8d625571d4d4b4f4aa234d9f88b9b7fc0ceb3101c126e49e100378fc seasonal_naive-linear seasonal_naive(season_length=7) + linear [w=7,... residual residual_additive seasonal_naive linear historical_oof_1step 7.0 0.5 7.0 ... 1.673298 1.998234 0.024205 1.0 3 0 0 0.164522 0.005550 False
c4821c0a39e39a26ca17be31d8298f1ebcaed90d98e0a1fd3722f7c6ed592f9b linear-linear linear(lags=7) + linear [w=7, alpha=0.5, histo... residual residual_additive linear linear historical_oof_1step 7.0 0.5 8.0 ... 1.698826 1.914754 0.024472 1.0 3 0 0 0.481665 0.012821 False
972a9d57c62d5265bf431a410bf8ccd45ae226f2e6bb124bc9d763258cbdbae4 mlp-linear mlp(lags=7) + linear [w=7, alpha=1.0, historic... residual residual_additive mlp linear historical_oof_1step 7.0 1.0 9.0 ... 1.725096 2.009527 0.024833 1.0 3 0 0 1.366397 0.012967 False
b678627c69f0bc5eb419617a2119103a8a5340ac96c0551898c62a1d76dc0179 linear-linear linear(lags=7) + linear [w=7, alpha=1.0, histo... residual residual_additive linear linear historical_oof_1step 7.0 1.0 10.0 ... 1.748087 1.988548 0.025178 1.0 3 0 0 0.504137 0.018069 False
507265ea41efe47e82119ba51bc162c1cd983e9fd72f0a1b25e7ca1c8d95b229 seasonal_naive-linear seasonal_naive(season_length=7) + linear [w=7,... residual residual_additive seasonal_naive linear historical_oof_1step 7.0 1.0 11.0 ... 1.768259 2.152438 0.025573 1.0 3 0 0 0.159355 0.005538 False
bbd8aa955229e78f79ee95ec40b350010922f578702fd6a464ea2ea729023f34 seasonal_naive-linear seasonal_naive(season_length=7) + linear [w=7,... residual residual_additive seasonal_naive linear historical_oof_multistep 7.0 0.5 12.0 ... 2.617302 2.887196 0.037349 1.0 3 0 0 0.148647 0.006292 False
60fa40d778d88419b30bab18cd7f1fd7cad8e46e100cfe53d3a7b3f2fcc845b4 naive-linear naive + linear [w=7, alpha=1.0, historical_oof... residual residual_additive naive linear historical_oof_1step 7.0 1.0 13.0 ... 3.903393 4.441350 0.055259 1.0 3 0 0 0.210182 0.009204 False
b78538dee2cc85795a8cc90439de94a964e7a02a28eff7a3f960c947a2e9e699 naive-linear naive + linear [w=7, alpha=0.5, historical_oof... residual residual_additive naive linear historical_oof_1step 7.0 0.5 14.0 ... 3.916747 4.375241 0.055684 1.0 3 0 0 0.174942 0.005655 False
2117d1027abc8c33b855cff511914a6917df041e30dd98281ad26a716bae3b78 naive naive statistical standalone naive NaN NaN NaN NaN 15.0 ... 3.961290 4.498781 0.056375 1.0 3 0 0 0.000119 0.000137 False
bf5bff5cb0bc3a6ce953ea9188dae5a44f9f780d655ab31857957fa89a1f2250 knn-linear knn(lags=7) + linear [w=7, alpha=0.5, historic... residual residual_additive knn linear historical_oof_1step 7.0 0.5 16.0 ... 4.072110 4.339678 0.057173 1.0 3 0 0 1.077592 0.042116 False
747968575a6cd0d95bbee95e83321806fed5a85e1007f47989c2adf289a7f495 mlp-linear mlp(lags=7) + linear [w=7, alpha=0.5, historic... residual residual_additive mlp linear historical_oof_multistep 7.0 0.5 17.0 ... 4.429702 4.649585 0.061832 1.0 3 0 0 2.063942 0.014461 False
df3ec82ac4a2623a390d9a7ee621e53fda49f4f8b54f4d411b38c569f7b2af05 knn-linear knn(lags=7) + linear [w=7, alpha=1.0, historic... residual residual_additive knn linear historical_oof_1step 7.0 1.0 18.0 ... 8.484694 8.671081 0.115437 1.0 3 0 0 0.925168 0.036879 False

18 rows × 21 columns

5. Por que o campeão venceu

decisions lista cada desafiante, se foi aceito, o ganho mediano, a taxa de vitórias e os motivos. Rejeições também ficam registradas. audit conta todas as tentativas, inclusive as podadas e as que falharam.

Este notebook usa fold=3 para rodar rápido: são 3 unidades em V_select, e 60% de vitórias exige vencer 2 das 3. Com tão poucas unidades a regra é um protocolo, não um teste estatístico. O padrão fold="auto" usa 7 origens quando a série comporta, o número em que a regra foi calibrada; o notebook 11 mede quantas vezes cada configuração aceita um híbrido espúrio.

result.decisions
family champion_before challenger accepted reason_codes median_gain win_rate variability_status variability_value p_value challenger_coverage challenger_label champion_before_id challenger_id
0 statistical mlp seasonal_naive False (median_gain_below_threshold,) 0.034103 0.666667 disabled None None 1.0 seasonal_naive(season_length=7) 7dd1082a4ee4fe9df20683376c5688b4a23322240929c4... d90528888825a4459816afbcf249ff909747924f0a2bba...
1 linear mlp linear False (median_gain_below_threshold, win_rate_below_t... -0.026810 0.333333 disabled None None 1.0 linear(lags=7) 7dd1082a4ee4fe9df20683376c5688b4a23322240929c4... e9e747a9648ad8d377f20122dce611a43dd3bf85a64c95...
2 kernel mlp knn False (median_gain_below_threshold, win_rate_below_t... -0.109042 0.333333 disabled None None 1.0 knn(lags=7) 7dd1082a4ee4fe9df20683376c5688b4a23322240929c4... 70ad174e1cc49eb256646399fb3e4b7d9df82f14f2978d...
3 residual mlp mlp-linear True (accepted,) 0.142037 0.666667 disabled None None 1.0 mlp(lags=7) + linear [w=7, alpha=0.5, historic... 7dd1082a4ee4fe9df20683376c5688b4a23322240929c4... 9fd962cca51322c0e3697027ac96d1fa8d35c2672647b0...

Tudo o que cada decisão usou, unidade por unidade: os dois erros, o ganho e se houve vitória (empate não conta). Dá para recalcular o ganho mediano e a taxa de vitórias, ou reamostrar, fora da biblioteca.

units = result.selection.decision_units_frame()
units[units["family"] == "residual"].round(4)
family challenger_id accepted unit_id series_id champion_error challenger_error gain win
9 residual 9fd962cca51322c0e3697027ac96d1fa8d35c2672647b0... True select-0 None 2.3449 2.0119 0.1420 True
10 residual 9fd962cca51322c0e3697027ac96d1fa8d35c2672647b0... True select-1 None 1.2018 1.6898 -0.4061 False
11 residual 9fd962cca51322c0e3697027ac96d1fa8d35c2672647b0... True select-2 None 1.5715 1.1243 0.2846 True
result.audit
SelectionAudit(protocol_hash='aa88c5a5ada48e7970d42ca6d46bace46c42668c9f6b3ad47986044ed0787f56', search_protocol_hash='dd40456eb2da818cf575c202bb45b3c9f8473119d8df55ac0ae006dbd977e0d3', total_trials=18, tried_by_family={'statistical': 2, 'linear': 1, 'kernel': 1, 'neural': 1, 'residual': 13}, pruned_trials=49, failed_trials=0, finalist_ids=('7dd1082a4ee4fe9df20683376c5688b4a23322240929c4c44a8140e266a10ae2', '9fd962cca51322c0e3697027ac96d1fa8d35c2672647b0030737ef663dde6df6', '70ad174e1cc49eb256646399fb3e4b7d9df82f14f2978d09d35dac0019c7ef37', 'd90528888825a4459816afbcf249ff909747924f0a2bbaa6e09d027e31342238', 'e9e747a9648ad8d377f20122dce611a43dd3bf85a64c950a8e1ef246ebc032b4'), winner_id='9fd962cca51322c0e3697027ac96d1fa8d35c2672647b0030737ef663dde6df6', comparison_origin_ids=('select-0', 'select-1', 'select-2'), decision_reasons=('statistical: median_gain_below_threshold', 'linear: median_gain_below_threshold+win_rate_below_threshold', 'kernel: median_gain_below_threshold+win_rate_below_threshold', 'residual: accepted'), exploratory=False, search_best_score=0.8585660174722802, winner_search_score=0.8628987716271869, winner_select_score=1.6086533406757795)

6. Diagnósticos da validação

Todos os gráficos usam previsões fora da amostra nas origens de validação, nunca o teste selado.

exp.plot_model(result.best, plot="cv_forecasts")
../../_images/978d69942343de07531223e4b76e2e710e91225a9831ef414fc4b6bc7ea7e0b4.png
exp.plot_model(result.best, plot="residuals")
../../_images/670f2106fea4d6d67de79672993f4a5c9d5232fbd58b18a78b9943c773e910ee.png
exp.plot_model(result.best, plot="selection_trace")
../../_images/a85103568cc7375a57af368194f31fa0a3e7ff0a929e354bcc36a5b617b54733.png

7. Eventos do experimento

for event in exp.events:
    duration = f"{event.duration_ms:9.1f} ms" if event.duration_ms else " " * 12
    print(f"{event.kind:32s} {duration}  {event.message}")
experiment.fitted                     38.7 ms  1 série(s), 716 pontos de desenvolvimento, teste selado de 14 por série, 6 origens
model.created                      30679.7 ms  arima
hybrid.created                     21592.0 ms  arima + mlp [w=7, alpha=1.0, historical_oof_1step]
search.finished                    12167.0 ms  18 tentativas (budget_limited)
selection.finished                  1817.0 ms  campeão: mlp(lags=7) + linear [w=7, alpha=0.5, historical_oof_1step]