Thibaut Forest Claude Fable 5 commited on
Commit ·
edd5d5e
1
Parent(s): 6627f14
Phase 52: building-grouped CV adopted - screen gains leave-building-out column; contamination gradient rf +.194 (scenario) -> ~0 (building); logbag choice confirmed, no reversal
Browse files- EXPERIMENTS.md +43 -0
- batteryswap_example/cv.py +53 -11
EXPERIMENTS.md
CHANGED
|
@@ -2644,3 +2644,46 @@ catch. It caught ZERO. The local -37.9 was wobbler catches that the public
|
|
| 2644 |
fleet does not offer below the cut; a guard cannot create catches that are
|
| 2645 |
not there. Cert-first family closed entirely - the label-lag population
|
| 2646 |
either does not exist on the scored fleets or is already inside the head.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2644 |
fleet does not offer below the cut; a guard cannot create catches that are
|
| 2645 |
not there. Cert-first family closed entirely - the label-lag population
|
| 2646 |
either does not exist on the scored fleets or is already inside the head.
|
| 2647 |
+
|
| 2648 |
+
## Phase 52 - building-grouped CV adopted; the contamination gradient quantified
|
| 2649 |
+
|
| 2650 |
+
Trigger: a sweep of every public competitor artifact (HF repos, hidden HF
|
| 2651 |
+
branches, GitHub repos, and deleted-HF-repo cards recovered via the
|
| 2652 |
+
686f6c61/radar-huggingface-dataset archive) established that the scored
|
| 2653 |
+
public/private fleets hold FOREIGN BUILDINGS: the Attention-Heads card states
|
| 2654 |
+
it outright, and the FAQ transcription in ziadnasif77-debug's repo puts the
|
| 2655 |
+
full pool at ~200 buildings vs train's 24. Both serious hidden teams
|
| 2656 |
+
(YassY-The-AlchemYst's RobustHazardPlanner lineage, ziad) validate and
|
| 2657 |
+
calibrate building-grouped. Organizer-shaped conclusion, user directive:
|
| 2658 |
+
the trusted offline fold must be leave-BUILDING-out.
|
| 2659 |
+
|
| 2660 |
+
Change (cv.py): `screen` gains a third grouping column, whole buildings
|
| 2661 |
+
assigned to 5 folds greedily balanced by unique observed deaths (deaths are
|
| 2662 |
+
lumpy - the top building holds 15/82, nine hold exactly one; random building
|
| 2663 |
+
assignment starves folds). `static`/`ablate` already split by building
|
| 2664 |
+
(fleet_halves), so this closes the last battery-grouped selection surface.
|
| 2665 |
+
Selection guidance in the module now reads: building > battery > scenario.
|
| 2666 |
+
|
| 2667 |
+
Result, 3 fold-assignment seeds - rec12 margin over logistic (AUC margins
|
| 2668 |
+
agree in sign; levels are NOT comparable across columns, margins are the
|
| 2669 |
+
decision quantity):
|
| 2670 |
+
|
| 2671 |
+
| model | scenario | battery (s0/s1/s2) | building (s0/s1/s2) |
|
| 2672 |
+
|---|---|---|---|
|
| 2673 |
+
| logbag | +.003 | -.006 / -.004 / +.001 | -.002 / +.000 / +.000 |
|
| 2674 |
+
| rf | **+.194** | +.023 / +.031 / -.008 | +.005 / -.005 / -.014 |
|
| 2675 |
+
| gbm | **+.148** | -.000 / -.016 / -.026 | -.022 / -.039 / -.030 |
|
| 2676 |
+
|
| 2677 |
+
The contamination gradient is monotone: rf's +.194 phantom margin on
|
| 2678 |
+
scenario folds collapses to ~+.02 on battery folds and to zero-or-negative
|
| 2679 |
+
on building folds; gbm goes outright negative. The residual rf margin in the
|
| 2680 |
+
battery column (sign-unstable across seeds) is sibling leakage - a held-out
|
| 2681 |
+
device's building/batch mates stay in train. logbag == logistic (margin ~0)
|
| 2682 |
+
in every column and every seed: the champion's model choice never rested on
|
| 2683 |
+
fold luck, and the deployment-shaped fold CONFIRMS it rather than reversing
|
| 2684 |
+
anything. No selection changes; the protocol does.
|
| 2685 |
+
|
| 2686 |
+
Standing rule upgrade (supersedes the blind-loop "group by battery" rule):
|
| 2687 |
+
**group by BUILDING when the deployment is cross-building.** Battery
|
| 2688 |
+
grouping is necessary but not sufficient; it still leaks the environment/
|
| 2689 |
+
install-batch channel that phase 10/13 closed on the feature side.
|
batteryswap_example/cv.py
CHANGED
|
@@ -12,15 +12,19 @@ real leaderboard points to learn.
|
|
| 12 |
median rank of missed deaths beside the cost. Implemented in
|
| 13 |
`models_cv.rank_stats`, so `dev`/`panel`/`test` get it too.
|
| 14 |
|
| 15 |
-
**2. Group by
|
| 16 |
Each battery contributes one near-duplicate row per closed origin (~3600
|
| 17 |
rows from ~460 batteries), so holding out a SCENARIO leaves the same
|
| 18 |
batteries in train and test and tree models score their own memorised
|
| 19 |
signatures. Measured in the blind loop: leave-scenario-out put HGB ahead of
|
| 20 |
linear (AUC .9907 vs .9843); leave-battery-out REVERSED it (.9769 vs .9845)
|
| 21 |
and the cost harness agreed with the clean split. The effective sample size
|
| 22 |
-
is ~460, not ~3600.
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
**3. The deployment gate: would this fire in the static branch?**
|
| 26 |
The scored splits are fleets the model has never seen; on them `submit.py`
|
|
@@ -205,14 +209,44 @@ def run_static(variant='base', seed=0, origins=range(48),
|
|
| 205 |
|
| 206 |
|
| 207 |
# --------------------------------------------------------------------------
|
| 208 |
-
# protocol 3: offline model screen, grouped by battery
|
| 209 |
# --------------------------------------------------------------------------
|
| 210 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
def screen(kinds=('logistic', 'logbag', 'rf', 'gbm'), n_folds=5, seed=0,
|
| 212 |
dataset_path=Path('dataset')):
|
| 213 |
-
"""Leave-BATTERY-out vs leave-SCENARIO-out on the
|
| 214 |
-
models. The
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
| 216 |
from sklearn.metrics import roc_auc_score
|
| 217 |
|
| 218 |
t = load_table(dataset_path)
|
|
@@ -223,14 +257,20 @@ def screen(kinds=('logistic', 'logbag', 'rf', 'gbm'), n_folds=5, seed=0,
|
|
| 223 |
dev_fold = pd.Series(rng.permutation(len(devs)) % n_folds, index=devs)
|
| 224 |
org = np.sort(closed['origin_idx'].unique())
|
| 225 |
org_fold = pd.Series(np.arange(len(org)) % n_folds, index=org)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
n_dev_pos = closed.loc[closed['dies42'] == 1, 'device_id'].nunique()
|
| 228 |
print(f'pairs {len(closed)} batteries {len(devs)} origins {len(org)} '
|
|
|
|
| 229 |
f'positives {int(closed["dies42"].sum())} rows / {n_dev_pos} batteries')
|
| 230 |
print('effective n is the BATTERY count, not the row count - regularisation '
|
| 231 |
'tuned against rows is ~8x too weak here.\n')
|
| 232 |
|
| 233 |
-
schemes = {'
|
|
|
|
| 234 |
'scenario': closed['origin_idx'].map(org_fold)}
|
| 235 |
out = {}
|
| 236 |
for scheme, fold in schemes.items():
|
|
@@ -279,9 +319,11 @@ def screen(kinds=('logistic', 'logbag', 'rf', 'gbm'), n_folds=5, seed=0,
|
|
| 279 |
a0, r0, _ = out[(s, ref)]
|
| 280 |
cells += [f'{a - a0:+16.4f}', f'{r - r0:+16.3f}']
|
| 281 |
print(f'{kind:10s} ' + ' '.join(cells))
|
| 282 |
-
print('\nA model whose margin shrinks or reverses
|
| 283 |
-
'
|
| 284 |
-
'
|
|
|
|
|
|
|
| 285 |
return out
|
| 286 |
|
| 287 |
|
|
|
|
| 12 |
median rank of missed deaths beside the cost. Implemented in
|
| 13 |
`models_cv.rank_stats`, so `dev`/`panel`/`test` get it too.
|
| 14 |
|
| 15 |
+
**2. Group by BUILDING > battery > scenario.**
|
| 16 |
Each battery contributes one near-duplicate row per closed origin (~3600
|
| 17 |
rows from ~460 batteries), so holding out a SCENARIO leaves the same
|
| 18 |
batteries in train and test and tree models score their own memorised
|
| 19 |
signatures. Measured in the blind loop: leave-scenario-out put HGB ahead of
|
| 20 |
linear (AUC .9907 vs .9843); leave-battery-out REVERSED it (.9769 vs .9845)
|
| 21 |
and the cost harness agreed with the clean split. The effective sample size
|
| 22 |
+
is ~460, not ~3600. And battery grouping is still not the deployment split:
|
| 23 |
+
the scored public/private fleets hold FOREIGN BUILDINGS (organizer design;
|
| 24 |
+
also how `static` splits), and siblings in one building share install batch
|
| 25 |
+
and thermal environment, so a battery fold keeps a held-out device's
|
| 26 |
+
building-mates in train. `screen` runs all three groupings side by side;
|
| 27 |
+
selection follows the BUILDING column (2026-08-22 protocol change).
|
| 28 |
|
| 29 |
**3. The deployment gate: would this fire in the static branch?**
|
| 30 |
The scored splits are fleets the model has never seen; on them `submit.py`
|
|
|
|
| 209 |
|
| 210 |
|
| 211 |
# --------------------------------------------------------------------------
|
| 212 |
+
# protocol 3: offline model screen, grouped by building > battery
|
| 213 |
# --------------------------------------------------------------------------
|
| 214 |
|
| 215 |
+
def building_folds(closed, dev_bldg, n_folds=5, seed=0):
|
| 216 |
+
"""Assign whole BUILDINGS to folds, balancing observed deaths.
|
| 217 |
+
|
| 218 |
+
Deaths are lumpy across the 24 buildings (top building holds 15/82, nine
|
| 219 |
+
hold exactly one), so a random building assignment routinely starves a
|
| 220 |
+
fold of positives. Greedy balance instead: buildings in descending
|
| 221 |
+
unique-death order, each to the death-lightest fold so far (ties broken
|
| 222 |
+
by device load, then by the seed's shuffle).
|
| 223 |
+
"""
|
| 224 |
+
per_dev = (closed[['device_id', 'dies42']]
|
| 225 |
+
.assign(bldg=closed['device_id'].map(dev_bldg).values)
|
| 226 |
+
.groupby('device_id').agg(bldg=('bldg', 'first'),
|
| 227 |
+
died=('dies42', 'max')))
|
| 228 |
+
per_b = per_dev.groupby('bldg').agg(devices=('died', 'size'),
|
| 229 |
+
deaths=('died', 'sum'))
|
| 230 |
+
order = (per_b.sample(frac=1, random_state=np.random.RandomState(seed))
|
| 231 |
+
.sort_values(['deaths', 'devices'], ascending=False, kind='stable'))
|
| 232 |
+
deaths_in, devs_in = np.zeros(n_folds), np.zeros(n_folds)
|
| 233 |
+
fold_of = {}
|
| 234 |
+
for b, row in order.iterrows():
|
| 235 |
+
f = int(np.lexsort((devs_in, deaths_in))[0])
|
| 236 |
+
fold_of[b] = f
|
| 237 |
+
deaths_in[f] += row['deaths']
|
| 238 |
+
devs_in[f] += row['devices']
|
| 239 |
+
return pd.Series(fold_of, name='fold')
|
| 240 |
+
|
| 241 |
+
|
| 242 |
def screen(kinds=('logistic', 'logbag', 'rf', 'gbm'), n_folds=5, seed=0,
|
| 243 |
dataset_path=Path('dataset')):
|
| 244 |
+
"""Leave-BUILDING-out vs leave-BATTERY-out vs leave-SCENARIO-out on the
|
| 245 |
+
same pairs and the same models. The gaps between columns are contamination;
|
| 246 |
+
trust the BUILDING column - the scored public/private fleets hold foreign
|
| 247 |
+
buildings, and building-mates share install batch and thermal environment,
|
| 248 |
+
so battery folds still leak sibling identity. Headline is recall at the
|
| 249 |
+
operating N, AUC secondary."""
|
| 250 |
from sklearn.metrics import roc_auc_score
|
| 251 |
|
| 252 |
t = load_table(dataset_path)
|
|
|
|
| 257 |
dev_fold = pd.Series(rng.permutation(len(devs)) % n_folds, index=devs)
|
| 258 |
org = np.sort(closed['origin_idx'].unique())
|
| 259 |
org_fold = pd.Series(np.arange(len(org)) % n_folds, index=org)
|
| 260 |
+
dev_bldg = (pd.read_csv(dataset_path / 'train' / 'devices.csv')
|
| 261 |
+
.drop_duplicates('device_id')
|
| 262 |
+
.set_index('device_id')['building_id'])
|
| 263 |
+
bldg_fold = building_folds(closed, dev_bldg, n_folds, seed)
|
| 264 |
|
| 265 |
n_dev_pos = closed.loc[closed['dies42'] == 1, 'device_id'].nunique()
|
| 266 |
print(f'pairs {len(closed)} batteries {len(devs)} origins {len(org)} '
|
| 267 |
+
f'buildings {bldg_fold.size} '
|
| 268 |
f'positives {int(closed["dies42"].sum())} rows / {n_dev_pos} batteries')
|
| 269 |
print('effective n is the BATTERY count, not the row count - regularisation '
|
| 270 |
'tuned against rows is ~8x too weak here.\n')
|
| 271 |
|
| 272 |
+
schemes = {'building': closed['device_id'].map(dev_bldg).map(bldg_fold),
|
| 273 |
+
'battery': closed['device_id'].map(dev_fold),
|
| 274 |
'scenario': closed['origin_idx'].map(org_fold)}
|
| 275 |
out = {}
|
| 276 |
for scheme, fold in schemes.items():
|
|
|
|
| 319 |
a0, r0, _ = out[(s, ref)]
|
| 320 |
cells += [f'{a - a0:+16.4f}', f'{r - r0:+16.3f}']
|
| 321 |
print(f'{kind:10s} ' + ' '.join(cells))
|
| 322 |
+
print('\nA model whose margin shrinks or reverses as the grouping goes '
|
| 323 |
+
'scenario -> battery -> building was reading memorised identity at '
|
| 324 |
+
'that level (battery signatures, then building/batch siblings). '
|
| 325 |
+
'Selection follows the BUILDING column; it is the only one shaped '
|
| 326 |
+
'like the scored splits, which hold foreign buildings.')
|
| 327 |
return out
|
| 328 |
|
| 329 |
|