Spaces:
Sleeping
A newer version of the Streamlit SDK is available: 1.59.1
Data-Scaling Study β U-Net vs SegFormer-B0
Controlled experiment: how does training-set size affect the performance of a CNN (U-Net) vs a transformer (SegFormer-B0) on the solar-panel segmentation task?
| Variable | Values |
|---|---|
| Architecture | U-Net, SegFormer-B0 (nvidia/mit-b0) |
| Train data share | 25 %, 50 %, 100 % (nested: 25 β 50 β 100) |
| Validation set | full final_data/val/ (1,331 samples), constant across all 6 runs |
| Image size | 128 Γ 128 |
| Optimizer | Adam, lr = 1e-4 |
| Scheduler | ReduceLROnPlateau(mode='max', patience=5, factor=0.5) on val Dice |
| Loss | 0.5 Β· BCE + 0.5 Β· Dice |
| Augment | HFlip, VFlip, Rot15 |
| Epochs | 50 |
| Batch size | 16 |
| Subset seed | 42 |
The hyperparameters mirror each model's existing trainer in pv_panel_models/ so the only varied factor is training-data volume.
What gets trained vs reused
| Share | U-Net | SegFormer-B0 |
|---|---|---|
| 25 % | trained from scratch | trained from scratch |
| 50 % | trained from scratch | trained from scratch |
| 100 % | reused from pv_panel_models/unet_model/checkpoints/best_model.pth |
reused from pv_panel_models/vit_model/checkpoints/best_model.pth |
For 100% we copy the existing best checkpoints and parse the existing text logs into JSON. Because the old trainers used per-batch-averaged metrics and never logged mIoU, bootstrap_100.py does a no-grad val pass on each copied checkpoint with the new metrics code so the scaling chart at 100% has a number that's directly comparable to the 25/50% points.
Only best-epoch checkpoints are kept ({model}_{share}_best.pth). No _final.pth is saved.
Layout
experiments/data_scaling_study/
βββ README.md # this file
βββ requirements.txt
βββ subsets/
β βββ make_subsets.py # builds the three subset files (idempotent)
β βββ subset_25.txt
β βββ subset_50.txt
β βββ subset_100.txt
βββ dataset.py # SubsetSolarPanelDataset (reads filename list)
βββ metrics.py # confusion-matrix-based mIoU / IoU / Dice / pixel acc
βββ models.py # re-exports U-Net + SegFormer-B0 builders
βββ train.py # unified trainer (--model, --share β {25,50})
βββ bootstrap_100.py # copy + parse + val-recompute for 100% point
βββ run_all.sh # runs all 4 trainings + bootstrap
βββ checkpoints/ # populated during training / bootstrap
β βββ unet_25_best.pth unet_50_best.pth unet_100_best.pth
β βββ segformer_b0_25_best.pth segformer_b0_50_best.pth segformer_b0_100_best.pth
βββ logs/ # per-run JSON metric history
β βββ unet_{25,50,100}.json
β βββ segformer_b0_{25,50,100}.json
β βββ *.stdout.log # captured stdout from run_all.sh
βββ dashboard/
βββ app.py # Streamlit dashboard
How to run
0 Β· Install
pip install -r requirements.txt
1 Β· Build the subsets (once)
python subsets/make_subsets.py
Outputs three text files; asserts 25 β 50 β 100. Re-running is safe and reproducible (seed = 42).
2 Β· Train the four 25/50% runs
One run at a time:
python train.py --model unet --share 25
python train.py --model unet --share 50
python train.py --model segformer_b0 --share 25
python train.py --model segformer_b0 --share 50
3 Β· Bootstrap the 100% point
python bootstrap_100.py # copy + parse + val-recompute (~30s GPU work)
python bootstrap_100.py --skip-val # if you don't want to run any inference
2+3 in one go
./run_all.sh # all 4 trainings + bootstrap
./run_all.sh unet # only U-Net 25/50% trainings
./run_all.sh segformer_b0 # only SegFormer-B0 25/50% trainings
./run_all.sh bootstrap # only the bootstrap step
Each training run writes:
checkpoints/{model}_{share}_best.pthβ highest val Dice across all 50 epochslogs/{model}_{share}.jsonβ per-epoch loss / dice / iou / miou / pixel_acc
Logs are written incrementally β safe to interrupt and inspect.
The bootstrap step writes:
checkpoints/{model}_100_best.pthβ copy of the existing baseline bestlogs/{model}_100.jsonβ parsed text-log epochs +recomputed_val_metrics
4 Β· Dashboard
streamlit run dashboard/app.py
Three tabs:
- Learning curves β every (model, share) combination, switchable metric, train/val/both. Per-epoch curves at 100% are old-definition (parsed from text log) and mIoU is omitted there.
- Data share vs final β best val mIoU/Dice/IoU/PixelAcc as a function of
% train data. All three points use the new global metric code (100% via bootstrap). - Inference β upload an image, see the 2Γ3 grid of predictions (both models Γ all shares), with a tweakable threshold.
The dashboard tolerates partial state β you can run it after one training is done and watch the picture fill in.
Metric definitions
We accumulate TP / FP / FN / TN over the full epoch and compute:
| Metric | Formula |
|---|---|
iou (foreground) |
TP / (TP + FP + FN) |
miou |
mean of foreground-IoU and background-IoU |
dice |
2Β·TP / (2Β·TP + FP + FN) |
pixel_acc |
(TP + TN) / total |
This differs subtly from the existing baselines in pv_panel_models/, which average per-batch IoU. For a controlled scaling study the global formulation is the standard (and matches PASCAL/Cityscapes-style mIoU reporting).
The 25/50% runs always use the new (global) formulation. The 100% per-epoch curves use the old (per-batch-averaged) formulation parsed from existing text logs β but the scaling-chart point at 100% is recomputed under the new formulation by bootstrap_100.py's val pass.
Reproducing the partition
subsets/make_subsets.py does:
- List all
*.jpgfiles infinal_data/train/images/(5,325 files). - Sort alphabetically (deterministic order).
- Shuffle with
random.Random(42). - Take the first 25 % / 50 % / 100 % β 1,331 / 2,662 / 5,325 files.
- Assert nesting (25 β 50 β 100).
Subsets are stored as plaintext filename lists β both train.py and the dashboard read them, so the partition is the single source of truth.