01The problem
EuroSAT has 27,000 RGB tiles of 64×64 pixels from the Sentinel-2 satellite, each labelled as one of ten land-use classes such as Forest, Highway, Residential or SeaLake. Every model is trained on the same stratified 80/20 split and scored once on the same 5,400 held-out tiles. Model selection only ever uses cross-validation or a validation split, so the test set never influences a choice.
02What I found
- The input representation mattered more than the model: Naive Bayes jumps from 32% on raw pixels to 75% on a 378-dimension descriptor of colour, HSV, HOG and LBP texture features.
- The classes overlap. k-means cleanly recovers SeaLake and Forest but blurs the crop and vegetation classes, and the data has no natural ten-cluster structure.
- A single decision tree overfits (99.9% train, ~78% cross-validated); random forests and gradient boosting fix that, reaching 88.4% and 92.0%.
- Letting the network learn its own features was the biggest step: a 289k-parameter CNN trained from scratch reaches 97.6%, and fine-tuning ResNet-18 adds only half a point more.
- Most of the remaining 102 errors are genuinely ambiguous tiles, such as a highway cutting through a forest, or crop types that look alike at 10 m resolution.
03The demo
Both CNNs are exported to ONNX and checked against PyTorch on the full test set, then served as a static Hugging Face Space. Inference runs in the browser with ONNX Runtime Web (WebAssembly), so there is no server: pick a tile or upload your own and the two models classify it side by side.
04Architecture
A reusable Python package for data, features, evaluation and models, with one script per stage of the analysis.
- Sentinel-2 tiles→
- Features & augmentation→
- NB → trees → CNNs→
- ONNX export→
- Browser demo
05Results
- 98.1%
- test accuracy, fine-tuned ResNet-18
- 97.6%
- CNN trained from scratch
- 92.0%
- best with hand-crafted features
- 32%
- starting baseline