Spaces:
Running on Zero
Running on Zero
| # --------------------------------------------------------------------------- | |
| # Model spaces, cross-validation protocol, and seeds. | |
| # | |
| # HARD CONSTRAINT: classical supervised ML only. NO NEURAL NETWORKS of any | |
| # kind. This is a deliberate methodological choice, not a limitation -- the | |
| # seminal result in this field was a regularized linear model, feature | |
| # engineering rather than estimator capacity is the bottleneck, and TreeSHAP | |
| # gives exact attributions, which matters when a process engineer must audit a | |
| # scrap decision. | |
| # | |
| # The `constraints.allow_neural_networks: false` flag below is validated by | |
| # src/utils/config.py, which REJECTS the config if it is ever set true. | |
| # --------------------------------------------------------------------------- | |
| constraints: | |
| allow_neural_networks: false | |
| target: | |
| name: "cycle_life" | |
| # Modelling target is log10(cycle life). WHY: cycle life spans 150-2300 | |
| # cycles and its error distribution is multiplicative rather than additive; | |
| # the source literature models it in log space, so this also keeps the | |
| # reproduction comparable. | |
| transform: "log10" | |
| # --------------------------------------------------------------------------- | |
| # Repeated grouped nested cross-validation. Grouping is by physical cell in | |
| # every scheme -- a cell must never appear on both sides of a split. | |
| # --------------------------------------------------------------------------- | |
| cv: | |
| group_key: "cell_id" | |
| outer: | |
| n_splits: 5 | |
| # Ten repeats with different seeds. WHY: with n ~ 124-169 cells, a single | |
| # 5-fold partition has enough sampling noise that two seeds can disagree | |
| # by more than the difference between models being compared. | |
| n_repeats: 10 | |
| inner: | |
| n_splits: 5 | |
| n_repeats: 1 | |
| # Hyperparameter search and feature selection happen HERE ONLY. | |
| purpose: "hyperparameter_search_and_selection_only" | |
| splits: | |
| # Phase 5 produces three schemes; see src/data/splitter.py. | |
| cell_grouped: | |
| enabled: true | |
| role: "primary evaluation" | |
| batch_holdout: | |
| enabled: true | |
| train_batches: [batch1, batch2] | |
| test_batches: [batch3] | |
| role: "RQ4 out-of-distribution: qualify on one campaign, deploy on the next" | |
| recipe_holdout: | |
| enabled: true | |
| # Hold out entire charging-policy families, analogous to introducing a new | |
| # process recipe. Family definition is derived in Phase 4 from the parsed | |
| # policy string and recorded in docs/02_feature_engineering.md. | |
| n_folds: 5 | |
| role: "RQ4 process-recipe shift" | |
| seeds: | |
| # Seed everything. Every artifact records the seed that produced it. | |
| global: 42 | |
| numpy: 42 | |
| # One seed per outer-CV repeat, fixed here so that a re-run reproduces the | |
| # same ten partitions exactly. | |
| outer_repeats: [101, 202, 303, 404, 505, 606, 707, 808, 909, 1010] | |
| bootstrap: 7 | |
| # --------------------------------------------------------------------------- | |
| # REPORTING RULE, NON-NEGOTIABLE: every headline number is mean +/- std across | |
| # outer folds, with a bootstrap 95% CI. A single test-set number is grounds for | |
| # rejection. | |
| # --------------------------------------------------------------------------- | |
| bootstrap: | |
| n_resamples: 10000 | |
| confidence_level: 0.95 | |
| method: "percentile" | |
| metrics: | |
| # Both families are reported everywhere. Reporting only the statistical | |
| # family would contradict the thesis of the project. | |
| statistical: [rmse_log10, mae_log10, mape_raw, spearman] | |
| manufacturing: [escape_rate, overkill_rate, yield, chamber_cycles_per_cell, expected_cost_per_cell] | |
| primary: "expected_cost_per_cell" | |
| hyperparameter_search: | |
| engine: "optuna" | |
| n_trials: 50 | |
| timeout_seconds: 600 | |
| # Optuna runs inside the INNER loop only. Tuning against outer-fold | |
| # performance would be tuning on the test set. | |
| scope: "inner_fold_only" | |
| sampler: "tpe" | |
| seed: 42 | |
| # --------------------------------------------------------------------------- | |
| # Model spaces. Search ranges live here so that no magic number sits in code. | |
| # --------------------------------------------------------------------------- | |
| models: | |
| dummy_mean: | |
| enabled: true | |
| estimator: "sklearn.dummy.DummyRegressor" | |
| role: "floor" | |
| params: {strategy: "mean"} | |
| dummy_median: | |
| enabled: true | |
| estimator: "sklearn.dummy.DummyRegressor" | |
| role: "floor" | |
| params: {strategy: "median"} | |
| elastic_net: | |
| enabled: true | |
| estimator: "sklearn.linear_model.ElasticNet" | |
| role: "severson_reproduction_and_baseline" | |
| search_space: | |
| alpha: {type: "float", low: 1.0e-4, high: 1.0e+1, log: true} | |
| l1_ratio: {type: "float", low: 0.01, high: 1.0, log: false} | |
| ridge: | |
| enabled: true | |
| estimator: "sklearn.linear_model.Ridge" | |
| search_space: | |
| alpha: {type: "float", low: 1.0e-4, high: 1.0e+3, log: true} | |
| lasso: | |
| enabled: true | |
| estimator: "sklearn.linear_model.Lasso" | |
| search_space: | |
| alpha: {type: "float", low: 1.0e-5, high: 1.0e+1, log: true} | |
| huber: | |
| enabled: true | |
| estimator: "sklearn.linear_model.HuberRegressor" | |
| # Robust to the handful of very-long-lived cells that otherwise dominate | |
| # squared-error loss. | |
| search_space: | |
| epsilon: {type: "float", low: 1.05, high: 3.0, log: false} | |
| alpha: {type: "float", low: 1.0e-5, high: 1.0e+0, log: true} | |
| random_forest: | |
| enabled: true | |
| estimator: "sklearn.ensemble.RandomForestRegressor" | |
| search_space: | |
| n_estimators: {type: "int", low: 200, high: 1000} | |
| max_depth: {type: "int", low: 2, high: 12} | |
| min_samples_leaf: {type: "int", low: 1, high: 8} | |
| max_features: {type: "float", low: 0.2, high: 1.0} | |
| extra_trees: | |
| enabled: true | |
| estimator: "sklearn.ensemble.ExtraTreesRegressor" | |
| search_space: | |
| n_estimators: {type: "int", low: 200, high: 1000} | |
| max_depth: {type: "int", low: 2, high: 12} | |
| min_samples_leaf: {type: "int", low: 1, high: 8} | |
| lightgbm: | |
| enabled: true | |
| estimator: "lightgbm.LGBMRegressor" | |
| search_space: | |
| n_estimators: {type: "int", low: 100, high: 1500} | |
| learning_rate: {type: "float", low: 0.005, high: 0.3, log: true} | |
| # Shallow trees and small leaves throughout: with ~124 training cells a | |
| # deep GBDT memorises the training set within a few dozen rounds. | |
| num_leaves: {type: "int", low: 4, high: 32} | |
| max_depth: {type: "int", low: 2, high: 6} | |
| min_child_samples: {type: "int", low: 2, high: 15} | |
| subsample: {type: "float", low: 0.6, high: 1.0} | |
| colsample_bytree: {type: "float", low: 0.4, high: 1.0} | |
| reg_alpha: {type: "float", low: 1.0e-8, high: 10.0, log: true} | |
| reg_lambda: {type: "float", low: 1.0e-8, high: 10.0, log: true} | |
| xgboost: | |
| enabled: true | |
| estimator: "xgboost.XGBRegressor" | |
| search_space: | |
| n_estimators: {type: "int", low: 100, high: 1500} | |
| learning_rate: {type: "float", low: 0.005, high: 0.3, log: true} | |
| max_depth: {type: "int", low: 2, high: 6} | |
| min_child_weight: {type: "float", low: 1.0, high: 10.0} | |
| subsample: {type: "float", low: 0.6, high: 1.0} | |
| colsample_bytree: {type: "float", low: 0.4, high: 1.0} | |
| reg_alpha: {type: "float", low: 1.0e-8, high: 10.0, log: true} | |
| reg_lambda: {type: "float", low: 1.0e-8, high: 10.0, log: true} | |
| catboost: | |
| enabled: true | |
| estimator: "catboost.CatBoostRegressor" | |
| search_space: | |
| iterations: {type: "int", low: 100, high: 1500} | |
| learning_rate: {type: "float", low: 0.005, high: 0.3, log: true} | |
| depth: {type: "int", low: 2, high: 6} | |
| l2_leaf_reg: {type: "float", low: 1.0, high: 30.0, log: true} | |
| quantile: | |
| # Feeds conformalized quantile regression in Phase 7. | |
| enabled: true | |
| estimator: "lightgbm.LGBMRegressor" | |
| objective: "quantile" | |
| quantiles: [0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95] | |
| grading_route: | |
| # --------------------------------------------------------------------- | |
| # REVIEWED DECISION (after Phase 3): grade by ORDINAL REGRESSION from | |
| # predicted cycle life, not by direct three-class classification. | |
| # | |
| # WHY. Phase 3 recomputed the realised class balance from data: | |
| # A = 11 cells (8.9%), B = 70 (56.5%), C = 43 (34.7%). Eleven grade-A cells | |
| # is roughly two per fold under 5-fold grouped CV. A direct three-class | |
| # classifier would be estimating a decision boundary for a class it sees | |
| # twice per fold, and its per-fold grade-A recall would be dominated by | |
| # sampling noise rather than by signal. | |
| # | |
| # Predicting log10(cycle life) and thresholding at the grade boundaries | |
| # avoids the small-class problem STRUCTURALLY rather than compensating for | |
| # it: every cell contributes to a single continuous target regardless of | |
| # which side of a boundary it falls, so the 11 grade-A cells inform the fit | |
| # without having to support a class of their own. The grade boundaries are | |
| # ordered and known, which is exactly the situation ordinal treatment is for. | |
| # | |
| # It also preserves what the decision layer needs: the CONTINUE action in | |
| # Phase 8 requires a continuous predictive distribution over cycle life to | |
| # compute value of information. A three-class posterior cannot supply that. | |
| primary: "ordinal_from_regression" | |
| # Direct three-class classification is retained as a SECONDARY, | |
| # INTERPRETABILITY-ONLY view and must be labelled as such wherever reported. | |
| # It is not the headline grading route and its grade-A metrics carry an | |
| # explicit small-class caveat. | |
| secondary: | |
| enabled: true | |
| role: "secondary interpretability view only - not the headline result" | |
| estimators: ["lightgbm.LGBMClassifier", "sklearn.linear_model.LogisticRegression"] | |
| class_weight: "balanced" | |
| min_class_count_warning: 20 # A has 11; reporting must carry the caveat | |
| ensemble: | |
| enabled: true | |
| # Linear meta-learner on OUT-OF-FOLD predictions only. Stacking on in-fold | |
| # predictions leaks the base models' training performance into the meta-model. | |
| meta_learner: "sklearn.linear_model.RidgeCV" | |
| use_out_of_fold_only: true | |
| registry: | |
| directory: "outputs/models" | |
| # Every persisted artifact records the full config and package versions. | |
| record_provenance: true | |