This project documents the full engineering process of building an image classification system for facial expression detection using PyTorch.
Rather than starting from a pretrained model, the workflow begins with a manually implemented CNN (TinyVGG-style architecture) and gradually evolves toward a transfer learning solution (ResNet18).
The repository captures:
- Dataset handling using Hugging Face
datasets - Custom PyTorch training loop (no high-level trainer)
- Debugging model shape mismatches
- Handling small-dataset instability
- Transition from scratch CNN → pretrained backbone
- Building a minimal inference app for demonstration
This is not just a demo app — it is a structured exploration of deep learning system design.
Instead of directly importing a pretrained model and calling .fit(), this project was built step-by-step:
- Implement a baseline CNN from scratch
- Write custom
train_step/test_steploops - Understand batching and transforms deeply
- Identify overfitting and data-size limitations
- Introduce pretrained transfer learning
- Wrap the final model into a lightweight demo interface
Each stage reveals a specific engineering or modeling insight.
The first implementation was a small CNN inspired by TinyVGG:
- Two convolutional blocks
- ReLU activations
- MaxPooling
- Fully connected classifier
Early issues encountered:
- Shape mismatch when flattening
- Hard-coded linear input size
- Sensitivity to input resolution
This was solved by introducing:
nn.AdaptiveAvgPool2d((1,1))which removes dependency on spatial input size.
BatchNorm was later added to improve stability.
Instead of using high-level training wrappers, the project defines:
train_step()test_step()train()
Key learning points:
- Correct device placement
- Separating
model.train()andmodel.eval() - Using
torch.inference_mode()for evaluation - Manual accuracy calculation
- Tracking metrics per epoch
Unlike ImageFolder, Hugging Face datasets return dictionary samples:
{"image": PIL.Image, "label": int}To integrate with PyTorch DataLoader, a custom collate_fn was required to:
- Apply transforms
- Stack tensors
- Construct label tensors
Critical realization:
Validation and test sets must NOT use random augmentation.
On the small dataset:
- Training accuracy quickly reached ~100%
- Validation accuracy fluctuated significantly
- Small test sets caused discrete accuracy jumps
This revealed:
- The danger of overfitting
- The instability of evaluation on small datasets
- The importance of best-checkpoint saving
To improve generalization, the model was switched to:
torchvision.models.resnet18(weights=ResNet18_Weights.DEFAULT)Key lessons:
- Always rebuild the optimizer when switching models
- Freeze backbone first, then fine-tune
- Use official ImageNet normalization for pretrained models
- Transfer learning significantly stabilizes validation accuracy
This marked the shift from experimental CNN design to practical deep learning engineering.
The final trained model is wrapped in a simple Gradio app.
The goal is demonstration — not production deployment.
The app:
- Loads trained checkpoint
- Applies correct preprocessing
- Returns predicted label and class probabilities
There are two ways to explore this project:
Open the notebook:
jupyter notebook Expression_detect.ipynbThe notebook walks through:
- Dataset loading
- Transform design
- TinyVGG implementation
- Debugging issues
- ResNet18 transition
- Training logs and evaluation
This is recommended if you want to understand the full reasoning process.
Install dependencies:
pip install torch torchvision datasets gradio tqdm pillowRun:
python demo.pyOpen in browser:
http://127.0.0.1:7860
Upload an image to see prediction results.
- Python 3.x
- PyTorch
- Torchvision
- Hugging Face Datasets
- Gradio
- Writing your own training loop builds deeper understanding.
- Small datasets amplify evaluation instability.
- Adaptive pooling prevents classifier shape bugs.
- Pretrained models dramatically improve small-data performance.
- Always verify optimizer parameter bindings.
- Separate train and evaluation transforms strictly.