dispcraft.ml¶
ml ¶
PyTorch/Lightning residual-correction MLP.
Replaces the sklearn.neural_network.MLPRegressor used for residual
correction in Stage 4 (notebooks/4.1-4.3) with a PyTorch + Lightning
equivalent, validated in notebooks/5.1-PyTorch_Migration.ipynb to
reproduce Stage 4's frozen sklearn results within tolerance across all
three of its notebooks. This is the generic building block Stage 5's
Phases 2-3 build the field-dependent, physics-structured models on top of;
the reproduction/comparison logic itself (loading frozen physical fits,
sklearn-vs-pytorch tables) stays notebook-local, same as Stage 4's own
per-dataset/joint-fit orchestration never left its notebooks.
ResidualMLP mirrors MLPRegressor's architecture (a stack of
Linear+activation hidden layers from hidden_layer_sizes, output dim 1 for
a per-axis candidate or 2 for a joint one). LitResidualRegressor wraps it
to match MLPRegressor's training behavior as closely as possible, not
just its architecture -- two details matter and are easy to get wrong:
- Loss is
0.5 * MSE, matching sklearn'ssquared_lossconvention (a bareMSELossdiffers by a factor of 2 in the data-term gradient). alpha(sklearn's L2 penalty) is not the same as PyTorchAdam(weight_decay=alpha). sklearn's_backpropaddsalpha * wto the gradient and then divides the whole gradient (data term + penalty) by the current minibatch size (n_samplesinside_backprop, i.e. the batch size, not the full training-set size) -- seesklearn.neural_network._multilayer_perceptron.BaseMultilayerPerceptron ._backprop/_compute_loss_grad. The weight_decay-equivalent isalpha / batch_size, computed bytrain_residual_mlpand passed toLitResidualRegressorexplicitly. Using rawalphaover-regularizes by ~batch_sizex -- this was a real bug caught while validating Checkpoint 2 of5.1-PyTorch_Migration.ipynb(the joint-model candidate, which had the largestalphaof the three configs compared, came out 2.3x worse than sklearn until fixed).
LitResidualRegressor ¶
LitResidualRegressor(n_inputs, n_outputs, hidden_layer_sizes, activation='relu', alpha=0.0001, lr=0.001, batch_size=200)
Bases: LightningModule
Lightning wrapper around ResidualMLP: 0.5*MSE loss + Adam, with
alpha/batch_size matching sklearn MLPRegressor's L2 penalty exactly
(see module docstring). batch_size must be the actual training
DataLoader batch size for the weight_decay scaling to match.
Source code in dispcraft/ml.py
70 71 72 73 74 | |
ResidualMLP ¶
ResidualMLP(n_inputs, n_outputs, hidden_layer_sizes, activation='relu')
Bases: Module
Feed-forward regressor matching sklearn MLPRegressor's shape: a stack
of Linear+activation hidden layers from hidden_layer_sizes, ending in
a plain linear output layer of size n_outputs (1 per-axis, 2 joint).
Source code in dispcraft/ml.py
50 51 52 53 54 55 56 57 58 | |
make_loaders ¶
make_loaders(X_train, y_train, val_frac=0.1, batch_size=200, seed=0)
Split (X_train, y_train) into train/val DataLoaders.
sklearn's early_stopping=True carves off validation_fraction
(default 0.1) of the training data for its internal stopping criterion
-- mirrored here with the same fraction so both frameworks see the same
effective train/val split. batch_size should be sklearn's
batch_size="auto" default, min(200, n_samples), computed by the
caller from the post-validation-split training set size (see
train_residual_mlp).
Source code in dispcraft/ml.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |
predict ¶
predict(lit_model, X)
Run lit_model in inference mode over X, returning a NumPy array.
Source code in dispcraft/ml.py
162 163 164 165 | |
train_residual_mlp ¶
train_residual_mlp(X_train, y_train, params, n_outputs, max_epochs=2000, patience=20, val_frac=0.1)
Train a LitResidualRegressor to convergence on (X_train, y_train).
params : dict with hidden_layer_sizes, activation, alpha,
random_state, early_stopping -- the same shape as an
MLPRegressor(**params) call, e.g. pulled from an MLflow run's
logged hyperparameters (see notebooks/4.2-Per_Dataset_Pipeline
.ipynb's best_mlp_params()).
n_outputs : 1 for a per-axis candidate, 2 for a joint (y, z) candidate.
| Returns: |
|
|---|
Source code in dispcraft/ml.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | |