A production-ready ML API that classifies fresh vs rotten fruits (apples, bananas, oranges) using a fine-tuned ResNet-18 model. The API is built with FastAPI and includes rate limiting, authentication, and comprehensive documentation.
This project demonstrates containerized ML deployment using Docker and is designed for easy deployment to cloud platforms like Google Cloud Run, showcasing modern MLOps practices for serving PyTorch models at scale.
- Install dependencies:
pip install -r requirements.txt- Create
.envfile:
WANDB_API_KEY=your-wandb-api-key
WANDB_ORG=your-org
WANDB_PROJECT=your-project
WANDB_MODEL_NAME=your-model-name
API_KEYS=key1,key2,key3
ENVIRONMENT=development
# Optional: Rate limiting (default: 2 requests per minute)
RATELIMIT_PER_MIN=2
# Optional: Baseline model for comparison
BASE_WANDB_ORG=your-org
BASE_WANDB_PROJECT=your-baseline-project
BASE_WANDB_MODEL_NAME=baseline-resnet18- Run the app:
fastapi run app/main.py --port 8080 --reload- Machine Learning: ResNet-18 based fruit classification model
- Model Caching: Smart in-memory caching to avoid reloading models between requests
- Baseline Comparison: Optional baseline model support for performance comparison
- Rate Limiting: 2 requests per minute per IP for prediction endpoint
- Authentication: API key-based authentication for protected endpoints
- Model Management: Automatic model download from Weights & Biases
- Docker Support: Containerized deployment ready
- Interactive Documentation: Auto-generated Swagger UI at
/docs
GET /- Smart redirect to/docsfor browsers, JSON response for API clientsGET /welcome- API information and available endpointsGET /health- Health check with system statusPOST /predict- Upload image for ML fruit classification (rate limited: 2/minute)
GET /categories- List all available fruit categories
Protected endpoints require an API key in the x-api-key header. Configure your API keys in the .env file.
The API uses a fine-tuned ResNet-18 model trained to classify:
- Fresh Fruits:
freshapple,freshbanana,freshorange - Rotten Fruits:
rottenapple,rottenbanana,rottenorange
- Base: ResNet-18 (ImageNet pre-trained)
- Custom classifier: 512 → 512 → 6 classes
- Input size: 224x224 RGB images
- Preprocessing: Resize(256) → CenterCrop(224) → Normalize(ImageNet stats)
The model was fine-tuned using the Apples, Bananas, and Oranges dataset from Kaggle. This dataset contains images of fresh and rotten fruits across three categories:
- Dataset: Kaggle - Apples, Bananas, and Oranges
- Classes: 6 total (fresh/rotten × apple/banana/orange)
- Training approach: Transfer learning from ImageNet-pretrained ResNet-18
- Custom head: Added fully connected layers (512 → ReLU → 512 → 6) for fruit classification
- Model storage: Trained weights stored and versioned in Weights & Biases
{
"category": "freshapple",
"confidence": 0.95
}docker build -t fruit-classifier-api .docker run -p 8080:8080 --env-file .env fruit-classifier-apiMake sure your .env file contains all required variables before running the container.
Run the test suite with pytest:
pytest app/tests/ -vThe API includes comprehensive prediction logging that adapts to the deployment environment:
- Local Development: Logs to both
logs/predictions.logfile and console with timestamps - Google Cloud Run: Logs only to stdout for Cloud Logging integration (auto-detected via
K_SERVICEenv var) - Log Content: Prediction results, confidence scores, timing data, image metadata, and quality flags for low-confidence predictions
The notebooks/ directory contains the complete training pipeline showing how the ResNet-18 model was fine-tuned on the fruit dataset. The notebook demonstrates transfer learning, WandB experiment tracking, and saves both the fine-tuned model and baseline model artifacts that the API uses.
app/
├── __init__.py
├── main.py # FastAPI application and endpoints
├── model.py # ML model loading and preprocessing
├── cache.py # In-memory model caching system
├── logger.py # Prediction logging with cloud adaptation
└── tests/ # Test suite
├── test_main.py # API endpoint tests
├── test_model.py # Model architecture tests
├── test_cache.py # Caching functionality tests
└── test_logging.py # Logging functionality tests
notebooks/ # Training pipeline and experimentation
└── scratchpadforfruitclassifer-set-2025.ipynb # Complete model training workflow
requirements.txt # Python dependencies
Dockerfile # Container configuration
.env # Environment variables (not tracked)
logs/ # Local log files (gitignored)
- Prediction endpoint: 2 requests per minute per IP address
- Based on client IP address
- Returns HTTP 429 when exceeded
Once running, visit:
- Swagger UI: http://localhost:8000/docs
- WANDB_API_KEY not found: Ensure all required environment variables are set in
.env - Model download fails: Check WANDB credentials and model path configuration
- Rate limit exceeded: Wait before making additional requests to
/predict - Invalid API key: Verify your API key is included in the
API_KEYSenvironment variable
The health check provides comprehensive status information about all system components:
{
"status": "healthy",
"timestamp": "2025-01-15T10:30:00.123456",
"components": {
"models_loaded": true,
"transforms_loaded": true,
"baseline_model_loaded": false,
"api_keys_configured": true,
"categories_available": true,
"imagenet_categories_loaded": true
},
"endpoints": {
"predict": true,
"categories": true,
"welcome": true,
"docs": true
}
}Status Values:
healthy- All critical components workingdegraded- Some non-critical components failing but core functionality available
