Skip to content

Commit 759882b

Browse files
chore: Readme
1 parent 5fa041e commit 759882b

1 file changed

Lines changed: 132 additions & 0 deletions

File tree

README.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
[![Python](https://img.shields.io/badge/Python-3.11%20%7C%203.12%20%7C%203.14-blue.svg)](https://www.python.org/)
2+
[![Framework](https://img.shields.io/badge/Framework-PyTorch-red.svg)](https://pytorch.org/)
3+
[![Hugging Face Datasets](https://img.shields.io/badge/Hugging%20Face-Datasets-blue.svg)](https://huggingface.co/)
4+
[![GPU Support](https://img.shields.io/badge/Acceleration-CUDA%20%7C%20MPS-yellowgreen.svg)](https://pytorch.org/)
5+
[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
6+
[![Tests](https://img.shields.io/badge/Tests-PyTest-green.svg)](tests/)
7+
8+
9+
# Facial Expression Recognition
10+
11+
A compact end-to-end training and evaluation pipeline for facial expression classification using a Hugging Face dataset and a ResNet50 backbone.
12+
13+
## Installation
14+
15+
1. Create and activate a Python virtual environment (recommended):
16+
17+
```bash
18+
python3 -m venv .venv
19+
source .venv/bin/activate
20+
```
21+
22+
2. Install dependencies:
23+
24+
```bash
25+
pip install -U pip
26+
pip install -r requirements.txt
27+
```
28+
29+
## Quick run
30+
31+
Run the full pipeline (download dataset, train, evaluate, export metrics and plots):
32+
33+
```bash
34+
python main.py --epochs 1 --batch_size 8
35+
```
36+
37+
Configuration options are available via CLI flags in `main.py` or by calling `run_pipeline(config)` in `src/pipeline.py`.
38+
39+
Downloaded dataset artifacts are saved to `data/raw` by `src/data_loader.DataLoader.download()` using Hugging Face `datasets.save_to_disk()`.
40+
41+
## Running tests
42+
43+
Unit tests use `pytest` and are located in the `tests/` folder. Run them with:
44+
45+
```bash
46+
pytest -q
47+
```
48+
49+
Key tests:
50+
- `tests/test_data_loader.py`: dataset download/load and DataLoader constructions (uses mocks)
51+
- `tests/test_facial_recognition.py`: model shapes and frozen backbone checks
52+
- `tests/test_trainer.py`: training loop behaviours
53+
54+
## Project layout
55+
56+
Top-level files and folders:
57+
58+
- `main.py`: CLI entrypoint to run the pipeline
59+
- `src/`: application code
60+
- `data_loader.py`: dataset download/persistence and PyTorch `DataLoader` wrapping
61+
- `paths.py`: project path constants (e.g., `data/raw`)
62+
- `pipeline.py`: orchestrates data download, training, evaluation, artifact export
63+
- `trainer.py`: training loop that consumes a `DataProvider` (returns a PyTorch `DataLoader`)
64+
- `evaluator.py`: evaluation helpers and artifact export (metrics, confusion matrix)
65+
- `models/`: model definitions (ResNet50 backbone + classifier head)
66+
- `data/`: storage for raw and processed datasets
67+
- `data/raw/`: persisted Hugging Face dataset (created by `save_to_disk()`)
68+
- `data/processed/`: optional processed artifacts
69+
- `outputs/`: saved artifacts: `metrics.json`, `confusion_matrix.png`, etc.
70+
- `tests/`: unit tests
71+
- `requirements.txt` and `requirements-dev.txt`
72+
73+
## Architecture & data flow
74+
75+
1. Data download & persistence
76+
- `DataLoader.download()` calls `datasets.load_dataset(REPO_ID)` and then `dataset.save_to_disk(data/raw)`.
77+
2. Data loading & transforms
78+
- `DataLoader.load()` uses `load_from_disk(data/raw)`.
79+
- `HuggingFaceImageDataset` converts HF rows to PIL/Numpy images, applies `Grayscale -> ToTensor -> Normalize` transforms, and returns `(image_tensor, label)`.
80+
3. Model
81+
- Backbone: pretrained `resnet50` (most layers frozen except `layer4` by default).
82+
- Embedding head: `Linear(in_features, embedding_size)` followed by `ReLU`.
83+
- Classifier head: `Linear(embedding_size, num_classes)` returning logits for `CrossEntropyLoss`.
84+
4. Training
85+
- `Trainer.fit()` fetches `train_loader` and runs forward → loss (`CrossEntropyLoss`) → backward → optimizer.step().
86+
5. Evaluation
87+
- `Evaluator` runs model on the test loader, computes metrics and writes `outputs/metrics.json` and `outputs/confusion_matrix.png`.
88+
89+
## Rationale: classification head & choice of loss
90+
91+
This project treats facial expression recognition as a supervised multi-class classification task because the dataset provides per-image categorical labels (e.g., happy, sad, angry). The model uses a small classifier head on top of a pretrained ResNet50 backbone and is trained with `nn.CrossEntropyLoss`. Reasons for this design:
92+
93+
- **Direct supervision and metrics:** `CrossEntropyLoss` expects raw class logits and pairs naturally with evaluation metrics like accuracy, precision, and F1, making progress easy to interpret.
94+
- **Numerical stability and simplicity:** `CrossEntropyLoss` implements `log_softmax` + `nll_loss` in a stable, optimized form and is the standard choice for multi-class classification.
95+
- **Practicality and reproducibility:** A classification head requires less engineering than metric-learning pipelines (which need careful positive/negative mining or contrastive sampling) and trains efficiently using standard PyTorch optimizers.
96+
97+
Alternative (embedding / metric-learning) approaches have advantages for retrieval, few-shot learning, or when labels are unreliable, but they require different losses (contrastive, triplet, NT-Xent), different sampling strategies, and different evaluation protocols. I opted for a classifier-first approach to match the dataset's supervised labels and to keep the pipeline simple and reproducible.
98+
99+
Common causes for unexpectedly large loss (what to check):
100+
101+
- **Model-output vs. loss mismatch:** returning normalized embeddings while using `CrossEntropyLoss` will produce meaningless loss values. Ensure model outputs are logits with shape `(batch_size, num_classes)`.
102+
- **Label issues:** verify labels are integer `torch.long` values in the range `[0, num_classes-1]`.
103+
- **Shape/dtype mismatches:** confirm `outputs.shape` and `targets.shape` match expectations and dtypes are correct.
104+
- **Data problems:** corrupted images, missing data, or incorrect normalization can destabilize training.
105+
- **Optimization settings:** too-large learning rates, incorrect optimizer setup, or missing gradient zeroing can cause loss explosion.
106+
- **Numerical instability:** NaNs in inputs/outputs or extremely large activations (inspect `torch.isnan()` and output statistics).
107+
108+
If you'd like to experiment with embeddings instead, I can add a configurable option to switch between `CrossEntropyLoss` and a metric loss, implement simple contrastive sampling, or add runtime checks/logging to `Trainer.fit()` to surface the most common issues.
109+
110+
111+
## Debugging tips
112+
113+
- Print shapes and types for a single batch:
114+
115+
```py
116+
print(inputs.shape, inputs.dtype)
117+
print(targets.shape, targets.dtype, targets.min(), targets.max())
118+
```
119+
120+
- Inspect model outputs:
121+
122+
```py
123+
o = model(inputs)
124+
print(o.shape, o.mean().item(), o.std().item(), torch.isnan(o).any())
125+
```
126+
127+
- Check loss value for a single batch:
128+
129+
```py
130+
loss = criterion(o, targets)
131+
print(loss.item())
132+
```

0 commit comments

Comments
 (0)