Plant Disease Detection
End-to-end deep-learning pipeline that classifies a leaf photograph into one of 38 disease classes across 14 crop species, trained on the ~88,000-image New Plant Diseases Dataset (an augmented PlantVillage derivative). EfficientNetB0 ImageNet backbone with the last 20 layers fine-tuned, GAP + Dense(38, softmax) head, EarlyStopping / ReduceLROnPlateau / ModelCheckpoint callbacks. Reference run reaches ~99.84% validation accuracy. Production-quality inference module with thread-safe lazy loading and structured top-1 + top-3 + healthy-flag output, served by a Gradio app deployed on Hugging Face Spaces. Large model artifacts ship via a GitHub Release rather than the repo, with cold-boot weight download on first launch.
Technology stack
Problem statement
Crop disease diagnosis in the field is mostly manual — a farmer or extension worker looks at a leaf, compares it against memory or a printed reference, and decides what to spray. That works for common diseases on familiar crops and fails on unfamiliar ones, on early-stage symptoms, and on the 14 different crops a smallholder might be growing in parallel. A leaf-photo classifier that runs on a phone via a web demo is a useful triage layer: not a replacement for an agronomist, but a fast first opinion that can flag economically important diseases like Tomato Yellow Leaf Curl Virus or Late Blight before they spread.
Dataset & data
The New Plant Diseases Dataset (Augmented) — about 88,000 labelled leaf images across 38 classes spanning 14 crop species (Apple, Tomato, Grape, Corn, Potato, Pepper, Strawberry, Cherry, Peach, Soybean, Squash, Raspberry, Blueberry, Orange). An augmented derivative of the PlantVillage dataset. Images are resized to 224×224 and batched at 32 with categorical labels. The dataset is gitignored — pulled via the Kaggle API at training time so the repository stays clones-fast.
Architecture & design
EfficientNetB0 backbone with ImageNet weights and include_top=False, fed into GlobalAveragePooling2D and a Dense(38, softmax) head. The last 20 backbone layers are unfrozen for fine-tuning while the earlier layers stay locked — the standard "preserve general features, adapt high-level features" transfer-learning recipe. Inference is wrapped in a thread-safe lazy loader that returns a structured dict: top-1 class with crop + condition + confidence, a top-3 breakdown, a healthy-flag derived from the class label, and the raw label for downstream logging.
Training pipeline
Adam optimizer with categorical_crossentropy loss. Three callbacks: EarlyStopping(patience=3) to halt when validation accuracy plateaus, ReduceLROnPlateau(factor=0.2, patience=2) to drop the learning rate when progress stalls, and ModelCheckpoint(save_best_only=True) so only the best epoch survives. TensorBoard logs are written alongside for inspection. The training notebook is preserved verbatim; export_model.py packages the trained weights into a single .keras artifact and writes the canonical class_names.json that the inference path reads.
Results & performance
Reference validation accuracy ~99.84% on the held-out set. Live on Hugging Face Spaces at huggingface.co/spaces/workface/plant-disease-detection — drop a leaf photo, get top-3 predictions in a few seconds. The top-3 panel matters: on a Tomato Late Blight image the model predicted Late Blight at 89% with Early Blight at 11% as the runner-up, which is a sensible confusion (both diseases produce dark lesions) and exactly the kind of edge case a flat top-1 would hide. Weight artifacts ship through a GitHub Release with a cold-boot download on first launch, so the repo stays small and the deployed Space pulls the model once per container lifetime.