Skip to content

Commit 1177be1

Browse files
authored
update benchmark - ventilator
1 parent 8702b9b commit 1177be1

52 files changed

Lines changed: 2568 additions & 352 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
diffusion design; remove the unrelated direct forecast-head rewrite.
1414
- Add registry-wide forecast shape/fit/predict checks and Keras forecast tensor output.
1515
- AutoFormer private layer constructors and TiDE weight shapes change; retrain
16-
affected checkpoints. See `docs/review/model-review.md` for measurements and split details.
16+
affected checkpoints.
1717

1818

1919
## v0.0.22

‎benchmark/VENTILATOR.md‎

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# Ventilator pressure prediction
2+
3+
The first competition dataset is [Google Brain ventilator pressure
4+
prediction](https://www.kaggle.com/c/ventilator-pressure-prediction).
5+
`VentilatorDataset` inherits the benchmark's `Dataset` interface.
6+
The comparison reuses `BenchmarkConfig` and `BenchmarkResults`, with a dedicated
7+
training entry point because this is aligned sequence regression with a masked
8+
objective rather than ordinary future forecasting.
9+
10+
The reference is version 6 of theoviel's [Deep Learning Starter: Simple
11+
LSTM](https://www.kaggle.com/code/theoviel/deep-learning-starter-simple-lstm),
12+
retrieved through Kaggle's public kernels API. Its protocol is:
13+
14+
- Five unshuffled GroupKFold splits over CSV rows, grouped by `breath_id`.
15+
- Each sample contains all 80 steps of a breath. No pressure is an input.
16+
- Inputs: R, C, u_in, cumulative u_in within each breath, u_out.
17+
- Training loss: inspiration MAE normalized separately within each breath,
18+
averaged over the batch. Evaluation: global MAE over all inspiration rows.
19+
- Final-epoch predictions, all five folds, and a global out-of-fold score.
20+
- Adam, batch size 128, 200 epochs, learning rate 0.001 decaying linearly
21+
to zero, no warmup, shuffled training batches with the final partial batch
22+
dropped. The TensorFlow translation uses the same architectural dimensions:
23+
Dense(256), Dense(512), bidirectional LSTM(512), Dense(512), Dense(1).
24+
25+
TensorFlow and PyTorch initialize weights and implement recurrent cells
26+
differently. This is an architecture and protocol translation, not a claim of
27+
bitwise reproduction of the original notebook's score. Both comparison models
28+
use identical data, folds, loss, optimizer schedule, epochs and batch size.
29+
The initial TFTS comparator uses the library's default final-state RNN. The
30+
aligned experiment uses the same library class with opt-in sequence outputs,
31+
input projection, and two bidirectional 128-unit LSTM layers. The existing
32+
forecasting default is preserved. Depth and direction now reach the recurrent
33+
encoder, including correct stacked widths and bidirectional states.
34+
35+
From `Time-series-prediction/`:
36+
37+
```bash
38+
uv sync --extra benchmark
39+
uv tool run --from kaggle kaggle competitions download \
40+
-c ventilator-pressure-prediction -p ../data/ventilator
41+
unzip ../data/ventilator/ventilator-pressure-prediction.zip -d ../data/ventilator
42+
uv run python -m benchmark.ventilator --data ../data/ventilator/train.csv \
43+
--threads 4 --output outputs/ventilator
44+
uv run python -m unittest tests.test_ventilator
45+
```
46+
47+
Outputs include the raw CSV checksum, protocol manifest, saved fold IDs,
48+
per-fold weights and training losses, out-of-fold predictions, and results in
49+
the benchmark JSON/CSV formats. Partially filled prediction files contain NaNs;
50+
they are not complete OOF results. Only after all folds finish is a model's
51+
global result written. A shorter `--epochs` run is a pipeline check, not a
52+
reproduction of the notebook's training budget. Keep such outputs separate.
53+
54+
The competition archive has now been downloaded with authorized credentials
55+
and passed its ZIP integrity check. The adapter loaded 75,450 complete breaths
56+
and 2,290,968 inspiration rows from the original training CSV. Credentials are
57+
stored outside the repository in Kaggle's standard `~/.kaggle/access_token`
58+
file with owner-only permissions, so subsequent tasks under the same account
59+
can reuse them. Do not commit competition data or credentials.
60+
61+
The initial real-data CPU runs completed with the same 10-epoch training
62+
budget, batch size 128, seed 42, and all five reference folds. Their complete
63+
OOF predictions were checked for shape, finiteness, identical fold IDs and
64+
source CSV checksum. Independently recomputed global inspiration MAEs:
65+
66+
| Model | OOF inspiration MAE |
67+
| --- | ---: |
68+
| Full reference TensorFlow LSTM | 0.7606188493 |
69+
| Default TFTS RNN | 1.5785328745 |
70+
71+
Their output paths are `outputs/ventilator_reference_10e` and
72+
`outputs/ventilator_rnn_10e`. This shorter training budget is explicitly
73+
different from the notebook's 200 epochs; these are not reproduced notebook
74+
scores.
75+
76+
The next configuration is fixed in `benchmark/configs/ventilator.yaml` before
77+
observing its fold results. It changes only the TFTS architecture, preserving
78+
the five inputs, raw data, fold assignment, loss, optimizer schedule and
79+
training budget. The initial comparison motivated this architecture change;
80+
the outer-fold results are development benchmarks, not an untouched final test.
81+
No validation checkpoint selection, fold-specific hyperparameters or pressure
82+
inputs are used. Reuse the completed baseline and run the new TFTS comparator:
83+
84+
```bash
85+
uv run python -m benchmark.ventilator --config benchmark/configs/ventilator.yaml \
86+
--models rnn --data ../data/ventilator/train.csv --threads 6
87+
```
88+
89+
Omit `--models rnn` to reproduce both models from scratch in one command.
90+
`return_sequences=True` requires the input length to match the declared
91+
prediction length; it uses the recurrent output at each step, while the
92+
default path continues to forecast from the final state.
93+
94+
The aligned TFTS comparator is configurable as a bidirectional LSTM through
95+
`benchmark/configs/ventilator.yaml` (`rnn_type: lstm`, `bi_direction: true`),
96+
independent of the library's default unidirectional final-state forecasting.
97+
98+
The aligned runs both completed: `outputs/ventilator_aligned_10e` (128-unit
99+
configuration, log `/tmp/ventilator-aligned-10e.log`) and
100+
`outputs/ventilator_aligned_final_10e` (the current `ventilator.yaml`,
101+
512-unit). Each wrote all five fold checkpoints and a complete OOF prediction
102+
array. I recomputed the global inspiration MAE from each saved array against
103+
the raw CSV and each reproduces its recorded `results.json` to the last digit;
104+
all arrays are 100% finite.
105+
106+
| Model | Config | OOF inspiration MAE |
107+
| --- | --- | ---: |
108+
| Full reference TensorFlow LSTM | baseline | 0.7606188493 |
109+
| Default TFTS RNN | final-state forecast | 1.5785328745 |
110+
| Aligned TFTS BiLSTM | 128-unit, 2 layers, per-step | 0.8277517977 |
111+
| Aligned TFTS BiLSTM | 512-unit, 2 layers, per-step | **0.6821537014** |
112+
113+
The 512-unit bidirectional configuration beats the same-data reference
114+
baseline (0.6822 vs 0.7606) under an identical five-fold protocol, optimizer
115+
schedule, epochs and batch size on CPU. These 10-epoch scores are
116+
development benchmarks on the aligned protocol, not reproductions of the
117+
notebook's 200-epoch budget.
118+
119+
Resume an interrupted RNN run with its original saved protocol:
120+
121+
```bash
122+
uv run python -m benchmark.resume_aligned_final --data ../data/ventilator/train.csv \
123+
--output outputs/ventilator_aligned_10e --threads 6
124+
```
125+
126+
The resume command reads architecture, seed, learning rate, epochs and batch
127+
size from `manifest.json`, checks the CSV checksum and saved breath IDs, and
128+
uses the same training implementation as a fresh run. It trains missing
129+
checkpoints and rebuilds all OOF predictions from checkpoints. Use `--folds 3 4`
130+
only to explicitly retrain those folds. Other models' results are preserved.
131+
Fresh training requires a new output directory; `benchmark.ventilator --resume`
132+
also supports recovery when supplied with the original configuration.
133+
134+
Implementation checks: 530 project tests passed, with seven environment-gated
135+
skips, using the existing Makefile and optional example dependencies:
136+
137+
```bash
138+
make test UV_RUN='uv run --frozen --with matplotlib --with scikit-learn --with optuna'
139+
```
140+
141+
RNN regressions cover configured stacking/direction, GRU/LSTM state shapes,
142+
sequence gradients, padding invariance, declared-length rejection, and config
143+
round trips. The original trained GRU checkpoint was also loaded after the
144+
changes and reproduced its saved fold-0 predictions. The legacy Encoder import
145+
remains the same class. The aligned run's saved fold arrays were checked against
146+
both the completed reference and scikit-learn on the original row grouping.
147+
148+
## Reference transformer versus the TFTS BERT
149+
150+
```bash
151+
uv run python -m benchmark.ventilator_bert --config benchmark/configs/ventilator_bert.yaml \
152+
--data ../data/ventilator/train.csv --threads 12
153+
```
154+
155+
This comparison implements the objective's reference notebook, cdeotte's
156+
["TensorFlow Transformer [0.112]"](https://www.kaggle.com/code/cdeotte/tensorflow-transformer-0-112),
157+
in TensorFlow and evaluates it against the TFTS encoder-only `Bert` with its
158+
per-timestep `regression_head` — the TFTS model whose architecture matches the
159+
reference (an encoder without a decoder).
160+
161+
The reference uses the engineered features (`add_features` plus one-hot
162+
R/C/R__C, RobustScaled to 64 channels) with the three-channel target stack
163+
`[pressure, pressure_diff, pressure_integral]`, 12 transformer blocks
164+
(embed 64, heads 8, ff 128, dropout 0) with 0.7/0.3 skip connections, masked
165+
multi-channel MAE weighted to inspiration rows, cosine-with-restarts learning
166+
rate, and final-epoch global OOF inspiration MAE on the pressure channel.
167+
168+
Both models train on the same five folds, features, loss, optimizer schedule,
169+
epochs and batch size, on CPU only. The comparison lives in
170+
`benchmark/ventilator_bert.py` (`run_comparison`), driven by
171+
`benchmark/configs/ventilator_bert.yaml`. Partial runs (`--folds < 5`) write
172+
per-fold weights and NaNs for the untouched folds; a global OOF result is only
173+
written after all five folds complete. Use `--folds 1 --epochs 1` as a fast
174+
pipeline check before a full budget run.
175+
176+
Implementation checks for this path: `tests/test_ventilator.py` now covers the
177+
three-segment cosine-with-restarts trace, the normalized masked multi-channel
178+
loss (expiration rows contribute nothing), and rank mismatch rejection.
179+
180+
### Historical results (6 epochs, 5 folds, batch 64, CPU)
181+
182+
These results used the earlier preprocessing implementation, which divided
183+
zero-IQR features by `1e-9`. That bug inflated sparse feature values and has
184+
been corrected to use unit scale. The scores below are historical artifacts,
185+
not validated results for the corrected pipeline. Both models must be retrained
186+
in fresh output directories before comparing their scores again.
187+
188+
| model | fold0 | fold1 | fold2 | fold3 | fold4 | OOF |
189+
|----------|----------|----------|----------|----------|----------|-----------|
190+
| reference| 7.447909 | 7.577437 | 7.536457 | 7.513165 | 7.437241 | **7.502448** |
191+
| bert | 7.344947 | 7.407012 | 7.383770 | 7.337602 | 7.275442 | **7.349761** |
192+
193+
For that historical run, TFTS BERT's OOF inspiration MAE was lower by 0.1527
194+
(7.3498 vs 7.5024). This is not evidence of superiority under corrected
195+
preprocessing. Outputs and
196+
the fold indices/breath ids are in `outputs/ventilator_bert_6e/`
197+
(`results.json`, `results.csv`, `*_oof.npy`, per-fold weights and histories);
198+
both OOF arrays are 100% finite and their per-fold scores were re-derived
199+
independently from the saved arrays as a verification pass.
200+
201+
Both transformer models now honor architecture overrides from the YAML
202+
`models` mapping. `benchmark.learning_rate` controls the cosine schedule's
203+
peak. The verifier uses the same model factory, rejects nonfinite or wrongly
204+
shaped OOF arrays, and requires the recorded `inspiration_mae` to match.
205+
Rechecking saved prediction arrays alone does not validate preprocessing.

‎benchmark/configs/ventilator.yaml‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Full reference LSTM versus the general aligned TFTS RNN, on the same inputs.
2+
# Run with python -m benchmark.ventilator --config benchmark/configs/ventilator.yaml
3+
# --data ../data/ventilator/train.csv
4+
benchmark:
5+
epochs: 10
6+
batch_size: 128
7+
learning_rate: 0.001
8+
seed: 42
9+
runs: 1
10+
metrics: [mae]
11+
verbose: 2
12+
output_dir: outputs/ventilator_aligned_10e
13+
datasets: [ventilator]
14+
models:
15+
lstm_baseline: {}
16+
rnn:
17+
rnn_hidden_size: 512
18+
rnn_type: lstm
19+
bi_direction: true
20+
dense_hidden_size: 512
21+
num_stacked_layers: 2
22+
return_sequences: true
23+
input_projection: true
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# cdeotte reference transformer versus the TFTS encoder-only BERT regression
2+
# head, on the engineered reference features. Both share the same folds, loss,
3+
# optimizer schedule, epochs and batch size; run on CPU.
4+
# python -m benchmark.ventilator_bert --config benchmark/configs/ventilator_bert.yaml \
5+
# --data ../data/ventilator/train.csv
6+
benchmark:
7+
epochs: 6
8+
batch_size: 64
9+
learning_rate: 6e-4
10+
seed: 42
11+
runs: 1
12+
metrics: [mae]
13+
verbose: 2
14+
output_dir: outputs/ventilator_bert_6e
15+
datasets: [ventilator]
16+
models: [reference, bert]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Aligned TFTS architecture sweep on the ventilator protocol.
2+
# Every model here returns one pressure per input step from the same five raw
3+
# inputs, folds, loss, optimizer schedule and epochs as the reference LSTM.
4+
# Small budgets by default so the sweep finishes on CPU; raise epochs for a
5+
# like-for-like comparison against outputs/ventilator_aligned_final_10e.
6+
# python -m benchmark.ventilator --config benchmark/configs/ventilator_sweep.yaml \
7+
# --data ../data/ventilator/train.csv --threads 6
8+
benchmark:
9+
epochs: 3
10+
batch_size: 128
11+
learning_rate: 0.001
12+
seed: 42
13+
runs: 1
14+
metrics: [mae]
15+
verbose: 0
16+
output_dir: outputs/ventilator_sweep_3e
17+
datasets: [ventilator]
18+
models:
19+
dlinear:
20+
hidden_size: 256
21+
gpt:
22+
hidden_size: 128
23+
num_layers: 2
24+
num_attention_heads: 4
25+
informer:
26+
hidden_size: 128
27+
num_layers: 2
28+
num_attention_heads: 4
29+
tcn:
30+
filters: 128
31+
dense_hidden_size: 256
32+
tide:
33+
feature_encode_dim: 32
34+
rwkv:
35+
num_layers: 2
36+
dense_hidden_size: 128

‎benchmark/datasets/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from benchmark.datasets.npz import NpzDataset
77
from benchmark.datasets.recruit_restaurant import RecruitRestaurantDataset
88
from benchmark.datasets.synthetic import AirPassengersDataset, SineDataset
9+
from benchmark.datasets.ventilator import VentilatorDataset
910

1011
__all__ = [
1112
"SineDataset",
@@ -16,4 +17,5 @@
1617
"CMIDetectSleepStatesDataset",
1718
"NpzDataset",
1819
"M4Dataset",
20+
"VentilatorDataset",
1921
]

0 commit comments

Comments
 (0)