An end-to-end dog breed classification app. Upload a photo of a dog and get its predicted breed, along with the top 5 most likely breeds and confidence scores.
- Backend: Flask REST API powered by a TensorFlow/Keras model
- Frontend: React + Vite single-page app that talks to the API
- Training: Original work in the
DOG_clf.ipynbnotebooks (Kaggle Dog Breed Identification dataset, 120 breeds)
- Predicts the breed of a dog from an image (PNG, JPG, JPEG, WEBP, GIF)
- Returns the top 5 breed predictions with confidence percentages
- Serves the built React frontend directly from the backend (production-ready single deployable)
- Lightweight health-check endpoint for load balancers / deploy platforms
- Docker image + GitHub Actions CI + Render/Railway deploy configs included
| Layer | Tech |
|---|---|
| Frontend | React 18, Vite 5, React Router, Tailwind CSS, Axios |
| Backend | Flask, Flask-CORS, gunicorn |
| ML | TensorFlow 2.20, Keras, TensorFlow Hub |
| Infra | Docker, GitHub Actions, Render / Railway |
The classifier is a transfer-learned MobileNetV2 model:
- Base:
MobileNetV2(input_shape=(224, 224, 3),include_top=False) with ImageNet-pretrained weights (kept frozen) - Head:
GlobalAveragePooling2D→Dense(120, activation="softmax")(120 = number of dog breeds) - Trained with the Adam optimizer and CategoricalFocalCrossentropy loss (see
DOG_clf.ipynb)
Two training variants were explored in the notebooks:
- TensorFlow Hub MobileNetV2 (
hub.KerasLayer) + Dense softmax head tf.keras.applications.MobileNetV2+ GlobalAveragePooling2D + Dense softmax head ← the one used in production
The saved weights live in backend/Models/:
backend/Models/
├── Full_img.h5 # Weights-only, used by the app
└── 20251226-05331766727215-1000-image-mobilenetv2-Adam.h5 # TF Hub variant checkpoint
Note:
Full_img.h5is a weights-only H5 file (no architecture). The app rebuilds the MobileNetV2 architecture and callsload_weights(), so no internet access is needed at startup.
.
├── backend/ # Flask API + ML model
│ ├── app.py # Entry point: Flask app, API routes, serves frontend build
│ ├── utils.py # Image preprocessing helper
│ ├── labels.json # Class index → breed name mapping (120 breeds)
│ ├── Models/ # Trained model weights (.h5)
│ ├── tests/test_smoke.py # Smoke tests (health, breeds, predict)
│ ├── requirements.txt # Pinned runtime deps
│ ├── requirements-dev.txt # Dev deps (ruff, pytest)
│ └── pyproject.toml # Ruff + pytest config
├── frontend/ # React SPA
│ ├── src/
│ │ ├── App.jsx # Routes (/ and /about)
│ │ ├── pages/ # Home, About
│ │ ├── components/ # Navbar, Hero, UploadCard, Loader, ResultCard
│ │ └── services/api.js # API client (VITE_API_URL)
│ ├── index.html
│ ├── package.json
│ └── eslint.config.js # ESLint (flat config)
├── Dockerfile # Multi-stage: build frontend → bundle with backend
├── .dockerignore
├── render.yaml # Render blueprint (auto-deploy)
├── .github/workflows/ci.yml # GitHub Actions CI
├── .python-version # 3.13
├── .env.example # Sample environment variables
├── DOG_clf.ipynb # Training notebook
└── custom.ipynb # Training experiments notebook
| Method | Route | Description |
|---|---|---|
GET |
/api/health |
Health check (alias: /health) |
POST |
/api/predict |
Upload an image (multipart/form-data, field name image) → top-5 predictions |
GET |
/api/breeds |
List all 120 supported breeds |
GET |
/ and any non-API path |
Serves the built React app (SPA fallback) |
Example POST /api/predict response:
{
"breed": "Rottweiler",
"confidence": 97.9,
"topPredictions": [
{ "breed": "Rottweiler", "confidence": 97.9 },
{ "breed": "Black-And-Tan Coonhound", "confidence": 1.12 }
]
}- Python 3.9+ (3.13 recommended — see
.python-version) - Node.js 18+ and npm (only needed to build the frontend)
- Git
git clone https://github.com/Skanda02/Dog_Breed_Identification.git
cd Dog_Breed_Identificationcd backend
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt # or requirements-dev.txt for dev + testing
cd ..cd frontend
npm install
npm run build # outputs frontend/dist
cd ..cd backend
FRONTEND_DIST=../frontend/dist python app.pyThe app is now served at http://localhost:5001 — both the API and the built frontend (FRONTEND_DIST points the backend at the frontend build output). Try it in your browser or with curl:
curl http://localhost:5001/api/health
curl -F "image=@Sample.jpg" http://localhost:5001/api/predictWhile developing the React app, run two processes:
# Terminal 1 — backend API
cd backend && python app.py
# Terminal 2 — Vite dev server (proxies /api to localhost:5001)
cd frontend && npm install && npm run devOpen http://localhost:3000. Optionally set VITE_API_URL in frontend/.env (copy from frontend/.env.example) to point the SPA at a different API.
Copy .env.example to .env and adjust as needed:
| Variable | Default | Description |
|---|---|---|
PORT |
5001 |
Port the backend listens on |
FLASK_DEBUG |
0 |
Set to 1 to enable Flask debug mode |
FRONTEND_DIST |
backend/dist |
Path to the built frontend (defaults to backend/dist; for a local npm run build use ../frontend/dist relative to backend/) |
VITE_API_URL |
/api |
(Frontend) API base URL — leave unset in production |
# Backend: tests + lint (from backend/)
pytest
ruff check .
ruff format --check .
# Frontend: lint + build (from frontend/)
npm run lint
npm run builddocker build -t dog-breed .
docker run -p 8000:8000 -e PORT=8000 dog-breed
# → http://localhost:8000The multi-stage Dockerfile builds the React app, then packages it with the Flask backend and runs it with gunicorn. The container reads the PORT environment variable (default 8000).
This repo is deploy-ready for any container platform.
Render — commit render.yaml (or connect the repo through the Render dashboard):
- The blueprint uses
runtime: dockerwithdockerfilePath: DockerfileandhealthCheckPath: /health - Render injects
PORTautomatically
Railway — Railway auto-detects the Dockerfile; no extra config needed.
GitHub Actions — .github/workflows/ci.yml runs lint + tests + Docker smoke test on every push/PR to main.
- Dataset: Kaggle — Dog Breed Identification
- Pretrained backbone: MobileNetV2 (ImageNet)