SmartPlate / DOC.md
Aishrica's picture
initial commit
50ba6d4
|
Raw
History Blame Contribute Delete
14.4 kB
# Smart Plate β€” Model Development Documentation
Technical documentation of the image-classification model that powers **Smart Plate**
(*Indian Food Classification and Calorie Estimation*). It covers the complete model
development workflow as implemented in
[`notebook/indian-food-image-detection-vit.ipynb`](notebook/indian-food-image-detection-vit.ipynb).
---
## Project Overview
**Objective.** Build an image-classification model that identifies an Indian dish from a
single food photograph, so that the dish can be mapped to its calorie / nutritional
profile in the Smart Plate application.
**Problem being solved.** Manually identifying a dish and looking up its nutritional value
is slow and error-prone. This project automates the first half of that task β€” *recognising
the dish from a photo* β€” across 80 popular Indian food categories. The predicted label is
later joined with a calorie database in the application layer to estimate nutrition.
The model is built by **fine-tuning a Vision Transformer (ViT)** image classifier.
---
## Dataset Information
| Property | Value |
|---|---|
| Source | Kaggle β€” *Indian Food Images Dataset* (`indian-food-images-dataset`) |
| Path used | `Indian Food Images/Indian Food Images/*/*.jpg` |
| Total samples | **4,000 images** |
| Classes | **80** Indian food categories |
| Samples per class | 50 (balanced) |
| Image format | RGB JPEG, variable resolution |
| Train / test split | **60% / 40%**, stratified by label, shuffled |
| Train samples | 2,400 |
| Test samples | 1,600 (20 per class) |
**Classes (80):** adhirasam, aloo_gobi, aloo_matar, aloo_methi, aloo_shimla_mirch,
aloo_tikki, anarsa, ariselu, bandar_laddu, basundi, bhatura, bhindi_masala, biryani,
boondi, butter_chicken, chak_hao_kheer, cham_cham, chana_masala, chapati, chhena_kheeri,
chicken_razala, chicken_tikka, chicken_tikka_masala, chikki, daal_baati_churma, daal_puri,
dal_makhani, dal_tadka, dharwad_pedha, doodhpak, double_ka_meetha, dum_aloo, gajar_ka_halwa,
gavvalu, ghevar, gulab_jamun, imarti, jalebi, kachori, kadai_paneer, kadhi_pakoda, kajjikaya,
kakinada_khaja, kalakand, karela_bharta, kofta, kuzhi_paniyaram, lassi, ledikeni, litti_chokha,
lyangcha, maach_jhol, makki_di_roti_sarson_da_saag, malapua, misi_roti, misti_doi, modak,
mysore_pak, naan, navrattan_korma, palak_paneer, paneer_butter_masala, phirni, pithe, poha,
poornalu, pootharekulu, qubani_ka_meetha, rabri, ras_malai, rasgulla, sandesh, shankarpali,
sheer_korma, sheera, shrikhand, sohan_halwa, sohan_papdi, sutar_feni, unni_appam.
**Preprocessing performed at load time:**
- `ImageFile.LOAD_TRUNCATED_IMAGES = True` to tolerate truncated/corrupted JPEGs.
- File paths and parent-folder names collected into a Pandas DataFrame (`image`, `label`).
- **Random oversampling** (`imblearn.RandomOverSampler`, `random_state=83`) applied for class
balancing. The dataset was already balanced (50/class), so the count remained 4,000.
- Conversion to a HuggingFace `Dataset` with the `image` column cast to `Image` and the
`label` column cast to a `ClassLabel` (string ↔ integer id mapping).
---
## Tech Stack
**Language:** Python 3
**Core libraries & frameworks:**
- **PyTorch** + **torchvision** β€” deep-learning backend and image transforms
- **HuggingFace Transformers** β€” `ViTForImageClassification`, `ViTImageProcessor`, `Trainer`, `TrainingArguments`, `pipeline`
- **HuggingFace Datasets** β€” dataset construction, `ClassLabel`, stratified split, on-the-fly transforms
- **HuggingFace Evaluate** β€” accuracy metric
- **Accelerate** β€” training backend
- **scikit-learn** β€” accuracy, F1, confusion matrix, classification report
- **imbalanced-learn (imblearn)** β€” `RandomOverSampler`
- **pandas / numpy** β€” data handling
- **matplotlib** β€” confusion-matrix visualisation
- **Pillow (PIL)** / **tqdm** β€” image I/O and progress bars
- **huggingface_hub** β€” model publishing
**Development environment:** Kaggle Notebook with a single CUDA GPU (`device=0`).
---
## Methodology
The workflow, in the order performed in the notebook:
### 1. Data Loading
All `*/*.jpg` files were globbed from the dataset directory; the parent folder name of each
file was used as its class label. Paths and labels were assembled into a DataFrame
(`4000 rows Γ— 2 columns`).
### 2. Data Exploration & Visualization
- Inspected the DataFrame head and the list of 80 unique labels.
- Rendered a sample image to confirm correct loading.
- Built and printed `id2label` / `label2id` mappings.
- After training, a **confusion matrix** (80Γ—80) and a per-class **classification report**
were generated for analysis.
### 3. Data Cleaning & Preprocessing
- Enabled truncated-image loading.
- Oversampled to enforce class balance.
- Cast columns to `Image` and `ClassLabel`; encoded string labels to integer ids.
### 4. Data Augmentation
Augmentation was applied **on-the-fly to the training split only**, via
`dataset.set_transform`:
| Transform | Train | Validation/Test |
|---|---|---|
| `Resize((224, 224))` | βœ… | βœ… |
| `RandomRotation(90)` | βœ… | β€” |
| `RandomAdjustSharpness(2)` | βœ… | β€” |
| `RandomHorizontalFlip(0.5)` | βœ… | β€” |
| `ToTensor()` | βœ… | βœ… |
| `Normalize(mean, std)` | βœ… | βœ… |
`mean`/`std` are taken from the pretrained `ViTImageProcessor`.
### 5. Feature Engineering
Not applicable in the classical sense β€” the Vision Transformer learns visual feature
representations directly from pixels. "Feature engineering" here is limited to the
resize/normalise/augment pipeline above.
### 6. Model Selection
A **Vision Transformer (ViT)** was selected for image classification. The underlying base
architecture is `google/vit-base-patch16-224-in21k`. In the notebook, training starts from
the task-specific checkpoint `aishrica/indian_food_prediction`, a ViT-Base classifier
already aligned to this exact 80-class Indian-food task.
### 7. Model Architecture
`ViTForImageClassification` β€” a ViT-Base encoder (patch size 16, 224Γ—224 input) with a
linear classification head re-initialised for `num_labels = 80`.
### 8. Training Procedure
- A custom `collate_fn` stacks `pixel_values` and `labels` into batches.
- The HuggingFace `Trainer` orchestrates training with `compute_metrics` (accuracy).
- The model was **evaluated before training** (baseline) and **after every epoch**; the best
checkpoint (by accuracy) was reloaded at the end.
- Total optimisation: **760 steps over 20 epochs**, ~31 minutes runtime on GPU.
### 9. Hyperparameter Configuration
| Hyperparameter | Value |
|---|---|
| Epochs | 20 |
| Learning rate | 1e-6 |
| Train batch size | 64 |
| Eval batch size | 32 |
| Weight decay | 0.02 |
| Warmup steps | 50 |
| Evaluation strategy | per epoch |
| Save strategy | per epoch (`save_total_limit=1`) |
| `load_best_model_at_end` | True (metric = accuracy) |
| `remove_unused_columns` | False |
### 10. Validation Strategy
A single **stratified 60/40 split** was used. The 40% split serves as the `eval_dataset`
during training (per-epoch evaluation) and as the final test set. Best-model selection is
driven by validation accuracy. *(No separate, fully held-out test set was reserved β€” see
Limitations.)*
### 11. Evaluation Metrics
Accuracy (HuggingFace `evaluate`) during training; **accuracy, macro-F1, confusion matrix,
and a full per-class precision/recall/F1 report** (scikit-learn) at the end.
### 12. Testing & Inference
`trainer.predict` produced final test metrics. A `transformers.pipeline`
(`image-classification`) was then used to run single-image inference, validated against the
ground-truth label.
---
## Model Details
| Attribute | Value |
|---|---|
| Base model | `google/vit-base-patch16-224-in21k` |
| Fine-tuning checkpoint | `indian_food_prediction` (published as `aishrica/indian_food_prediction`) |
| Architecture | Vision Transformer β€” ViT-Base, patch 16, 224Γ—224 (`ViTForImageClassification`) |
| Trainable parameters | **β‰ˆ 85.86 M** |
| Weights file | `model.safetensors` (~343 MB) |
| Input | RGB image, resized to 224Γ—224, normalised with ViT mean/std β†’ tensor `[3, 224, 224]` |
| Output | Logits over **80 classes** β†’ softmax probabilities; argmax = predicted dish |
---
## Results
**Baseline (before fine-tuning):**
- eval_loss: 3.1995 Β· eval_accuracy: **0.7494**
**After fine-tuning (20 epochs):**
- train_loss: 3.1225 (final)
- eval_loss: 3.1539 Β· eval_accuracy: **0.7519**
**Final test evaluation:**
| Metric | Value |
|---|---|
| Test accuracy | **0.7519** |
| Macro F1 | **0.7352** |
| Test loss | 3.1539 |
**Observations & findings:**
- The task-specific checkpoint was already fitted to this dataset, and the learning rate was very
small (1e-6); consequently fine-tuning produced only a marginal gain (**74.94% β†’ 75.19%**).
- Performance is **highly class-dependent.** Strong classes (F1 β‰ˆ 0.90+) include
bhindi_masala, boondi, chak_hao_kheer, jalebi, imarti, makki_di_roti_sarson_da_saag.
Weak classes include `chhena_kheeri` (F1 = 0.00), doodhpak, karela_bharta, ledikeni β€”
typically dishes that are visually similar to others (e.g. curries and milk-based sweets).
- **Predicted probabilities are poorly calibrated / very flat.** In the inference example the
correct top-1 prediction carried a confidence of only ~0.04, which is consistent with the
high cross-entropy loss (~3.15) despite ~75% top-1 accuracy. Top-1 *ranking* is reliable;
the absolute confidence scores should not be read as probabilities.
---
## Inference Pipeline
**How predictions are generated.** A `transformers.pipeline('image-classification')` loads
the saved model and label mappings. An input image is resized/normalised by the ViT
processor, passed through the encoder + classification head, and softmax scores are returned
ranked by probability.
**Input format:** a single image (PIL `Image`, file path, or URL).
**Output format:** a list of `{ "label": <class>, "score": <float> }` objects sorted by
descending score. Example:
```json
[
{ "label": "butter_chicken", "score": 0.0434 },
{ "label": "chicken_tikka_masala", "score": 0.0335 },
{ "label": "chana_masala", "score": 0.0264 },
{ "label": "chicken_tikka", "score": 0.0256 },
{ "label": "paneer_butter_masala", "score": 0.0226 }
]
```
In the Smart Plate application the top label (`raw_label`) is joined with `calorie_db.json`
to return the dish category and calorie estimate.
---
## Project Structure
```
indian-food-calories/
β”œβ”€β”€ notebook/
β”‚ └── indian-food-image-detection-vit.ipynb ← model development (this document)
β”œβ”€β”€ models/
β”‚ └── aishrica_food_predictor/ ← bundled ViT weights served by the app
β”œβ”€β”€ app.py ← FastAPI inference backend (/predict, /foods)
β”œβ”€β”€ calorie_db.json ← calorie data for the 80 classes
β”œβ”€β”€ static/ ← Smart Plate web UI (pages/css/js)
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ README.md
└── DOC.md ← this file
```
> Note: the notebook trains/publishes `indian_food_prediction`; the application serves
> a bundled equivalent ViT model from `models/aishrica_food_predictor/`. Both are ViT-Base
> classifiers over the same 80 Indian-food classes.
---
## Reproducibility
**Requirements.** Python 3, a CUDA GPU (recommended), and:
```bash
pip install -U evaluate "transformers" "datasets>=2.14.5" "accelerate>=0.27" \
torch torchvision scikit-learn imbalanced-learn pandas numpy matplotlib pillow tqdm
```
**Steps to run the notebook:**
1. Download the *Indian Food Images Dataset* from Kaggle and update the dataset glob path in
the data-loading cell if you are not running on Kaggle.
2. Open `notebook/indian-food-image-detection-vit.ipynb`.
3. Run the cells in order: install β†’ imports β†’ data loading β†’ preprocessing/oversampling β†’
split β†’ transforms β†’ model load β†’ baseline eval β†’ `trainer.train()` β†’ evaluation/report β†’
`trainer.save_model()`.
4. (Optional) Run the final cells to log in to the HuggingFace Hub and push the model.
The trained model is saved to the `indian_food_prediction/` output directory and can be
loaded directly for inference.
---
## Limitations
- **Marginal fine-tuning effect.** With a near-converged task-specific checkpoint and lr = 1e-6,
training added only ~0.25% accuracy β€” the run is closer to verification than substantive
fine-tuning.
- **Poorly calibrated confidence.** Softmax scores are very flat (top scores β‰ˆ 0.04), so they
cannot be presented to users as reliable probabilities without temperature scaling.
- **No held-out test set.** The same 40% split is used for per-epoch validation and final
testing, which can optimistically bias the reported metrics.
- **Class confusion.** Visually similar dishes (curries; milk-based sweets) are frequently
confused; at least one class (`chhena_kheeri`) scores F1 = 0.
- **Small dataset.** 50 images/class limits generalisation to real-world photos with varied
lighting, plating, and backgrounds.
- **Single-dish assumption.** The model classifies one dish per image; it does not segment or
detect multiple items on a plate.
---
## Future Improvements
- **Reserve a separate test set** (train/validation/test) for unbiased evaluation.
- **Tune the learning rate / schedule** (e.g. 1e-5–5e-5 with discriminative LR) and add early
stopping for meaningful fine-tuning.
- **Calibrate outputs** (temperature scaling or label smoothing) so confidence scores are
trustworthy in the UI.
- **Expand and diversify the dataset** β€” more images per class and real "in-the-wild" photos;
stronger augmentation (color jitter, random erasing, mixup/cutmix).
- **Address weak classes** with targeted data collection and per-class error analysis.
- **Move toward food segmentation / multi-item detection** (e.g. instance segmentation)
to handle full plates beyond the current single-dish classification setup.
- **Add portion/volume estimation** from images to improve the downstream calorie estimate.
- **Export an optimised model** (ONNX / quantisation) to speed up and shrink the served model.
---
*Document generated from the notebook’s executed code and outputs. Metrics reflect the recorded
run (20 epochs, test accuracy 0.7519, macro-F1 0.7352).*