A convolutional neural network that classifies Sentinel-2 satellite imagery into 10 land-use categories — built from scratch in PyTorch, with a full training pipeline, model evaluation, and an interactive web app for live predictions.
96.7% validation accuracy · 96% on the held-out test set, using a custom CNN with no pretrained weights and no transfer learning.
The model classifies 64×64 RGB satellite tiles into 10 classes: AnnualCrop, Forest, HerbaceousVegetation, Highway, Industrial, Pasture, PermanentCrop, Residential, River, and SeaLake. The project covers the full machine learning pipeline end to end:
- Data handling — folder scan → labelled DataFrame → stratified, reproducible train/validation split
- Exploratory analysis — sample grids, per-channel pixel distributions, per-class brightness comparisons
- A custom CNN — designed and trained from scratch (no pretrained models, no transfer learning)
- Training — augmentation, input normalisation, learning-rate scheduling, early stopping, best-model checkpointing
- Evaluation — loss/accuracy curves, confusion matrix, misclassified-sample analysis
- Deployment — a Shiny web app that predicts on user-uploaded images with a full probability breakdown
| Metric | Value |
|---|---|
| Validation accuracy | 96.70% |
| Test set accuracy | 96% |
| Trainable parameters | ~2.4M |
| Training hardware | NVIDIA RTX 3060 (CUDA 12.1) |
The gap between training and validation accuracy stays small throughout (≈1.4% at convergence), and the held-out test score matches validation almost exactly — indicating the model genuinely generalises rather than memorising the training data.
![]() |
![]() |
The confusion matrix shows the errors cluster exactly where you'd expect from overhead imagery — the vegetation classes (AnnualCrop / Pasture / PermanentCrop / HerbaceousVegetation) trade misclassifications among themselves, and Highway spreads slightly because a highway tile is mostly the surrounding terrain. Forest is classified near-perfectly, being the most visually distinct texture.
A VGG-style CNN: three convolutional blocks with the channel count growing as spatial resolution shrinks, then a compact fully-connected classifier.
Input [3 × 64 × 64]
→ Conv block 1 (→32 ch) → MaxPool → 32×32
→ Conv block 2 (→64 ch) → MaxPool → 16×16
→ Conv block 3 (→128 ch) → MaxPool → 8×8
→ Flatten (8192) → Linear(256) → Dropout(0.5) → Linear(10)
Output [10 logits]
Each block uses two 3×3 convolutions with BatchNorm and ReLU before pooling. The model outputs raw logits; softmax is applied by the loss function during training and explicitly at inference time in the web app.
eurosat-classifier/
├── eurosat_classifier.ipynb # full pipeline: data → EDA → training → evaluation
├── app/
│ ├── app.py # Shiny web app for live predictions
│ └── best_model.pth # trained model weights
├── demo/ # sample images for testing the app
├── images/ # figures used in this README
├── requirements.txt
└── README.md
1. Install dependencies
pip install -r requirements.txt
# then install torch + torchvision for your platform: https://pytorch.org/get-started/locally/2. Run the web app (works out of the box on the included weights — no dataset needed)
cd app
shiny run --launch-browser app.pyUpload a 64×64 satellite tile — a few samples are in demo/ — and see the predicted class, confidence score, and full probability table across all 10 classes.
3. Explore or retrain
Open eurosat_classifier.ipynb to walk through the complete pipeline. To retrain, download the EuroSAT dataset (see below) and place the RGB images under ./Dataset/data/ in 10 class subfolders.
This project uses the EuroSAT dataset (Helber et al., 2019) — Sentinel-2 satellite imagery, publicly available and also accessible through torchvision.datasets.EuroSAT. The dataset is not included in this repository due to size; the trained model weights are included so the app runs without it.
A few choices worth highlighting, and the reasoning behind them:
- From-scratch CNN over transfer learning — the goal was to understand and control the full architecture rather than fine-tune a pretrained backbone. BatchNorm and a small parameter count (~2.4M) keep it fast to train while still reaching the mid-90s.
- Streaming normalisation statistics — per-channel mean/std are computed over the training set without loading all images into memory, using running sums and the identity
Var(X) = E[X²] − E[X]². The exact statistics are reused in the web app so uploads are normalised identically to training. - Orientation-aware augmentation — horizontal and vertical flips, which are valid here because satellite tiles (unlike everyday photos) have no inherent "up".
- Full reproducibility — every source of randomness (split, weight initialisation, shuffling, cuDNN) is seeded, so results reproduce exactly across runs.
- Efficient inference — the model loads once at app startup; a reactive calculation runs a single forward pass per upload and shares the result across all three UI outputs.
Python · PyTorch · torchvision · Pandas · NumPy · scikit-learn · Matplotlib · Shiny for Python
Saqib Ali Khan BSc Artificial Intelligence, Johannes Kepler University Linz GitHub · LinkedIn


