Mohamed-ENNHIRI
Initial commit: code, metric logs, and report
35839ff
|
Raw
History Blame Contribute Delete
6.53 kB
# 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/](../../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](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
```bash
pip install -r requirements.txt
```
### 1 Β· Build the subsets (once)
```bash
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:
```bash
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
```bash
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
```bash
./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 epochs
- `logs/{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 best
- `logs/{model}_100.json` β€” parsed text-log epochs + `recomputed_val_metrics`
### 4 Β· Dashboard
```bash
streamlit run dashboard/app.py
```
Three tabs:
1. **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.
2. **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).
3. **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/](../../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:
1. List all `*.jpg` files in `final_data/train/images/` (5,325 files).
2. Sort alphabetically (deterministic order).
3. Shuffle with `random.Random(42)`.
4. Take the first 25 % / 50 % / 100 % β†’ 1,331 / 2,662 / 5,325 files.
5. 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.