Skip to content

Fix efficientnet-lite0 fine-tuning regression from the timm backbone swap - #16

Merged
gomezzz merged 1 commit into
mainfrom
fix/efficientnet-lite0-training-regression
Jul 8, 2026
Merged

Fix efficientnet-lite0 fine-tuning regression from the timm backbone swap#16
gomezzz merged 1 commit into
mainfrom
fix/efficientnet-lite0-training-regression

Conversation

@gomezzz

@gomezzz gomezzz commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Switching the efficientnet-lite0 backbone to timm (tf_efficientnet_lite0) silently dropped three model-level defaults the previous efficientnet_lite_pytorch package applied, substantially regressing FixMatch fine-tuning quality. This restores all three:

  • Classifier head over-scaling (main cause). timm inits the head with TF-EfficientNet's 1/sqrt(fan_in+fan_out) scale (for the 1000-class ImageNet head) — ~num_classes× too large for the 2-class head (weight std ~0.4 vs PyTorch's ~0.016). The overconfident fresh head makes ~100% of unlabeled images clear the FixMatch p_cutoff (0.95) from the first step with random pseudo-labels, poisoning the backbone. Fix: reset the classifier to PyTorch's default init after create_model.
  • cfg.bn_momentum never applied (BN ran at timm's 0.1 default, ~10x too fast for the small batch). Now applied to both models' BatchNorm layers.
  • cfg.seed never applied (training was not reproducible). Now seeds all RNGs after config validation.

The timm and efficientnet_lite_pytorch weights are byte-identical and eval-forward is bit-identical, so no weight swap or dependency change is needed.

Validation (paper benchmark, miniImageNet AL, AUROC)

class before (broken) after fix
hourglass 0.81 / 0.84 / 0.88 0.96 / 0.94 / 0.94
piano 0.86 / 0.87 / 0.83 0.92 / 0.89 / 0.90

Test plan

  • ruff check/ruff format clean on changed files
  • New head-init regression test + existing unit tests pass (376 tests green)
  • End-to-end benchmark retrain confirms recovery to ~0.94-0.96 AUROC

… timm swap

Switching the efficientnet-lite0 backbone to timm silently dropped three
model-level defaults that the previous package applied, regressing FixMatch
fine-tuning AUROC (~0.96 -> ~0.81 on the internal benchmark):

- Classifier head over-scaling (main cause): timm initialises the head with
  TF-EfficientNet's 1/sqrt(fan_in+fan_out) scale tuned for 1000 classes, which
  is ~num_classes too large for the 2-class head (weight std ~0.4 vs PyTorch's
  ~0.016). The overconfident fresh head makes ~all unlabeled images clear the
  FixMatch p_cutoff from step 1 with random pseudo-labels, poisoning the
  backbone. Reset the classifier to PyTorch's default init after create_model.
- Unapplied bn_momentum: cfg.bn_momentum (= 1 - ema_m, ~0.01) was computed and
  validated but never set on the model, so BatchNorm ran at timm's 0.1 default
  (~10x too fast for the small batch size). Apply it to both models' BatchNorm.
- Unapplied seed: cfg.seed was defined and validated but set_seeds was never
  called, so training was not reproducible. Seed all RNGs after validation.

Add a regression test asserting the classifier head weight std stays near
PyTorch's Linear bound.
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Overall Coverage

Coverage Report
FileStmtsMissCoverMissing
__init__.py80100% 
data_io
   SessionIOHandler.py3264187%111, 128–129, 173–174, 184–185, 215–216, 282–283, 286, 326–328, 370, 388–390, 455, 530, 553, 567–568, 570–571, 573–575, 719–724, 738, 742–743, 747–749
   checkpoint_io.py1482583%68, 74, 76, 78, 95–109, 120, 122, 161, 182, 228, 281
   find_images_in_folder.py210100% 
   load_images.py912869%51–53, 58–60, 81, 93, 110, 140–147, 150, 156–157, 189, 248, 256, 277, 285, 292–294
   metadata_handler.py80890%70–71, 117, 122–123, 170–172
   save_config.py33391%92–94
datasets
   AnomalyDetectionDataset.py2483984%126–127, 356, 362–363, 365, 369, 371–372, 374, 395–396, 398, 404–405, 409, 422–423, 484–485, 491–496, 498–499, 503–506, 508–510, 512–514, 525
   BasicDataset.py52492%59, 61, 97, 103
   Label.py50100% 
   SSL_Dataset.py68396%136, 139, 209
   __init__.py00100% 
   data_utils.py56296%80, 199
datasets/augmentation
   randaugment.py921188%222, 225–226, 245, 328–330, 332–335
   randaugment_multispectral.py772370%77–79, 120–121, 134–135, 148–149, 166, 170, 174, 248, 253, 270–272, 274–277, 280–281
image_processing
   transforms.py57395%46–47, 76
models
   FixMatch.py2142986%108, 201–202, 217, 222, 254, 281–282, 285, 288–290, 293, 301, 306–307, 366, 404, 430–434, 495, 497–498, 502, 519–520
pipeline
   SessionTracker.py122398%127, 219–220
   session.py5418584%146–149, 399–402, 447, 460, 612, 669, 671–672, 680, 683, 694, 702, 732, 736, 740–741, 745, 750–751, 758, 762–763, 775, 785–786, 788, 792, 806–807, 809–811, 813, 816–817, 875–876, 878–881, 883–887, 893, 899, 904–908, 910, 918, 921–922, 938, 979–980, 986–987, 991–993, 995, 1020, 1046, 1080–1081, 1089, 1100, 1108, 1112, 1116, 1121, 1124, 1168, 1171
utils
   accuracy.py130100% 
   consistency_loss.py180100% 
   create_model_string.py40100% 
   cross_entropy_loss.py90100% 
   cutana_stream_utils.py70987%64–65, 88, 108, 118–122
   get_cosine_schedule_with_warmup.py11191%39
   get_default_cfg.py610100% 
   get_net_builder.py55787%81–82, 89, 92, 170–171, 175
   get_optimizer.py21290%54, 58
   print_cfg.py45491%89, 95, 117, 119
   set_log_level.py150100% 
   set_seeds.py13285%25–26
   validate_config.py1311192%180, 199, 214, 228, 244, 248, 264, 290, 379–381
TOTAL270534387% 

Tests Skipped Failures Errors Time
376 0 💤 0 ❌ 0 🔥 1m 0s ⏱️

@gomezzz
gomezzz merged commit 9bb455d into main Jul 8, 2026
9 of 10 checks passed
@gomezzz
gomezzz deleted the fix/efficientnet-lite0-training-regression branch July 8, 2026 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants