Deep Leaf is a deep learning-based image classification pipeline for detecting plant diseases using Transfer Learning (VGG16). It follows MLOps best practices, enabling:
- FastAPI access
- Containerization with Docker
- MLflow tracking
- Airflow orchestration
- CI/CD with Github Actions
We saved all necessary files for the python runs into the folder src/. To use these scripts in the FastAPI (folder app/), we create a local package of src that is built during installing the requirements (requirements.txt, requirements_mac.txt, and requirements_wsl2.txt).
The data/ is once build with the script raw_data_split.py and then saved into data/raw/. Since we simulate new data by adding to the first data set the each of the other data splits (up to 10) we create two new files train.tfrecord and valid.tfrecord that are saved in data/training/. The raw files for testing can be found in data/test/.
In model/, you can find the current production model (production_model.keras) as well as metadata (final validation accuracy score, metadata.txt).
In app/, you find the creation of the FastAPI.
In mlflow/, you can find the creation of the MLflow container.
In tests/, you can find simple test scripts.
In dockers/, you can find the containerization of the Airflow, FastAPI, and MLflow service.
There are some helper files:
CI-CD_pipeline.png: visualization of CI/CD processdocker_architecture1.png: visualization of containerizationmerge_progress.json: A file to check how far we have been so far with the new data simulationMLOps_infrastructure.png: visualization of the MLOps infrastructuresetup.py: for creation of the packagesrcto reference them inapp/
.
βββ LICENSE
βββ README.md
βββ app
β βββ __init__.py
β βββ main.py
βββ data
β βββ raw
β β βββ train_subset1.tfrecord
β β βββ ...
β β βββ train_subset10.tfrecord
β β βββ valid_subset1.tfrecord
β β βββ ...
β β βββ valid_subset10.tfrecord
β βββ test
β β βββ AppleCedarRust1.JPG
β β βββ ...
β β βββ AppleCedarRust4.JPG
β β βββ AppleScab1.JPG
β β βββ AppleScab2.JPG
β β βββ AppleScab3.JPG
β β βββ CornCommonRust1.JPG
β β βββ CornCommonRust2.JPG
β β βββ CornCommonRust3.JPG
β β βββ PotatoEarlyBlight1.JPG
β β βββ ...
β β βββ PotatoEarlyBlight5.JPG
β β βββ PotatoHealthy1.JPG
β β βββ PotatoHealthy2.JPG
β β βββ TomatoEarlyBlight1.JPG
β β βββ ...
β β βββ TomatoEarlyBlight6.JPG
β β βββ TomatoHealthy1.JPG
β β βββ ...
β β βββ TomatoHealthy4.JPG
β β βββ TomatoYellowCurlVirus1.JPG
β β βββ ...
β β βββ TomatoYellowCurlVirus6.JPG
β βββ training
β βββ train.tfrecord
β βββ valid.tfrecord
βββ data.dvc
βββ docker-compose.yaml
βββ dockers
β βββ airflow
β β βββ dags
β β β βββ dag_train.py
β β βββ logs
β β β βββ ...
β β β βββ dag_processor_manager
β β β β βββ dag_processor_manager.log
β β β βββ scheduler
β β β βββ 2025-03-20
β β β β βββ dag_train.py.log
β β β βββ latest -> 2025-03-20
β β βββ plugins
β βββ fastapi
β β βββ Dockerfile
β β βββ requirements.txt
β βββ mlflow
β βββ Dockerfile
βββ logs
β βββ ...
β βββ history_20250319_002320.json
βββ merge_progress.json
βββ mlflow
β βββ artifacts
β βββ ...
βββ mlflow.dvc
βββ MLOps_infrastructure.png
βββ models
β βββ metadata.txt
β βββ production_model.keras
β βββ production_model.keras.dvc
βββ requirements.txt
βββ requirements_mac.txt
βββ requirements_wsl2.txt
βββ setup.py
βββ src
β βββ __init__.py
β βββ local_dagshub
β β βββ data_loader.py
β β βββ prod_model_select_mlflow_dagshub.py
β β βββ train_mlflow_dagshub.py
β βββ config.py
β βββ data_loader.py
β βββ git_dvc_update.py
β βββ helpers.py
β βββ model.py
β βββ predict.py
β βββ prod_model_select.py
β βββ raw_data_split.py
β βββ test_config.py
β βββ train.py
β βββ trials.py
β βββ utils.py
βββ temp
β βββ current_accuracy.txt
β βββ current_model.keras
βββ tests
βββ api_server.py
βββ mlflow_server.py
βββ requirements_CI-CD.txt
βββ test_api.py
βββ test_load_model.py
The original data stems from Kaggle (New Plant Diseases Dataset). Before the project, we downloaded the data set once, and created 10 subsets of training and validation (script src/raw_data_split.py), saving the subsets as .tfrecord for versioning and then used this 10 subsets as fictional new data income. Therefore, we will add incrementally to the first data set, the other splits to simulate new data incoming.
This project provides a FastAPI-based web API. It includes endpoints for triggering model training and performing inference (predictions) on uploaded images.
This API allows you to:
- Train a new model using the latest data.
- Compare and promote the newly trained model to production if it outperforms the existing one.
- Predict the class of an uploaded leaf image.
The main endpoints are:
-
GET /
Returns a simple welcome message to confirm the API is live. -
POST /train
Runs a background training job to:- Load or merge new subsets of data.
- Train a new model.
- Compare the new model with the production model.
- If better, promote the new model and track it via DVC/Git.
-
POST /predict/
Accepts an image file (JPG, JPEG, or PNG up to 5MB) and returns the predicted class index.
Train:
curl -X POST http://127.0.0.1:8000/trainPredict:
curl -X POST "http://127.0.0.1:8000/predict/" \
-F "file=@path/to/local/image.jpg"The project uses a containerized architecture with the following breakdown and features:
- Services Implemented
- Two Postgres database services:
postgres: for Airflow to store metadata and task informationmlflow-postgres: for MLflow for tracking experiments
- Redis service for communication between Airflow scheduler and workers:
- central communication hub for Airflow
redis: used for Airflow Celery executor as a message broker and result backend- runs on port 6379
- MLflow server for experiments and tracking:
- mounted volumes of
mlflowfor: artifacts storage, model directory, logs and temp files - depends on
mlflow-postgresfor storage - exposes port 5001
- mounted volumes of
- FastAPI-app service to run all executables of the project:
- used to run web app services (swagger UI)
- mounted volumes: the entire root directory
- depends on MLflow service (in order to fetch artifacts and experiment related data)
- exposes pot 8001 (mapped 8000 internally)
- Airflow services, consists of the standard components:
airflow-webserver: Web interface for Airflow which exposes port 8080airflow-scheduler: Manages DAG schedulingairflow-worker: Celery workers for distributed task executionairflow-init: Initializes Airflow database and creates default userflower: Celery monitoring tool which exposes port 5555
- Comunication and Dependencies
- Airflow Components:
- Use
redisas a message broker and result backend - Communicate through
celeryfor distributed task processing - Share common configuration via
x-airflow-common
- Use
- MLflow and FastAPI integration:
- Share volumes for artifacts, models, and logs
- FastAPI depends on MLflow service
- Both use separate PostgreSQL databases
- Database Connections:
- Airflow uses
postgresdatabase - MLflow uses
mlflow-postgresdatabase - Both databases configured with healthchecks
- Airflow uses
- Dependencies
postgresandredismust be healthy for Airflow servicesmlflow-postgresmust be healthy for MLflowmlflowmust be running forfastapi-app
- Key Volumes and Mounts:
- Shared project root (.:/app) --> and as applicable
- Docker socket for DockerOperator in Airflow --> for DAG implementation
- Configuration:
- Uses environment variables for flexible configuration
- Mounted
.envfile for testing - Basic authentication for Airflow API
- Startup and Initialization
- Services configured with restart policies
- Healthchecks ensure proper startup sequence
- Airflow initialization creates default user
- Docker-in-Docker for model training within Airflow
- Using
fastapi-appcontainer for model training - Enabling running model training as a containerized Airflow task
- Mounting the entire project repository (
.:/app) into the container - Using Docker socket volume (/var/run/docker.sock) for container management
This project integrates MLflow to track model training, log hyperparameters, and store artifacts for versioning and reproducibility.
- Tracking and Logging
During training, MLflow logs:
- Metrics: Training loss, accuracy, F1-score, validation loss, validation accuracy, and validation F1-score for each epoch.
- Hyperparameters: Model type (VGG16), number of epochs, batch size, input shape, and number of classes.
- Artifacts: The trained model is saved using mlflow.keras.log_model() for reproducibility.
- Experiment Setup
Before training starts, MLflow:
- Sets the tracking URI from environment variables (MLFLOW_TRACKING_URI).
- Defines an experiment name (MLFLOW_EXPERIMENT_NAME).
- Initializes default logging for hyperparameters.
- Logging During Training
A custom callback (MLFlowLogger) logs training metrics at the end of each epoch:
- Tracks best validation accuracy and best F1-score across epochs.
- Logs final validation metrics after training.
- Model Storage & Comparison
- The trained model is saved in MLflowβs model registry.
- The current model is stored locally (temp/current_model.keras) for comparison with previous models.
- Reproducibility
The training history (accuracy, F1-score, loss) is saved as a JSON file, ensuring results can be analyzed later.
MLflow is set up in a container running the tracking server. We use PostgreSQL database (mlflow-postgres) as backend for tracking experiment metadata. The container stores artifacts in /app/mflow/artifacts/ which is mounted from the host machine. Access is given via port 5001.
Using a DAG to run model training for the project with the following Functionality and Features
- DAG Configuration of
model_training_and_update
- Scheduled run daily at 3:00 PM and one active run at a time
- Task Details
- Task ID: train_model
- Container Configuration:
- Uses the fastapi-app:latest image
- Executes python /app/src/train.py command
- Runs with bridge network mode
- Auto-removes container after execution
- Additional feratures
- XCom Push: to captures stdout for logging and tracking
- Error Handling
- Configured with 1 retry
- 5-minute delay between retries
- General Remarks:
- Uses DockerOperator for containerized task execution
- Leverages Docker socket for container management
- Enables reproducible and isolated model training workflows
Below is a high-level overview of the Continuous Integration (CI) and Continuous Deployment (CD) pipeline, as depicted in the diagram:
Trigger: On every push to any branch of the GitHub repository.
Steps:
- Linting (flake8)
- Validate Python code style and consistency using
flake8. - Catch syntax errors, unused imports, or other code-quality issues.
- Validate Python code style and consistency using
- Unit Tests (pytest)
- Run test suites using
pytestto ensure code functionality and logic. - Generate test reports (or coverage reports) to verify application stability.
- Run test suites using
- Image Build (Docker Compose)
- Build a Docker image of the application using Docker Compose.
- Ensure the application can be containerized successfully without errors.
Overall, if any step fails (linting, testing, or image build), the workflow is marked as failed and notifies the developer.
Trigger: On every push specifically to the main branch of the repository.
Steps:
- Linting (flake8)
- Same as in CI: enforce style guidelines and code consistency.
- Unit Tests (pytest)
- Same as in CI: verify application logic through automated tests.
- Image Build (Docker Compose)
- Same Docker build process as in CI.
- This ensures the image that will be deployed is production-ready.
- Deploy Image (Docker Hub)
- Upon successful build, the Docker image is pushed to a Docker Registry (Docker Hub).
- Facilitates deployment to production or other environments.
Once the build is pushed to Docker Hub, downstream environments can pull the latest image automatically or on-demand.
This pipeline ensures that every code change is linted, tested, and containerized. Only changes on the main branch will move on to the deployment step, publishing the Docker image for use in production or further integration.


