1- """
2- Train the plant disease detection model.
3-
4- Usage:
5- export DATA_DIR="data/PlantVillage"
6- python src/train.py
7-
8- Or on Windows:
9- set DATA_DIR=data/PlantVillage
10- python src/train.py
11- """
121
132import os
143import json
176import numpy as np
187import tensorflow as tf
198import matplotlib .pyplot as plt
20- from src .data_loader import load_datasets
21- from src .model import build_model
9+
10+ from src .data_loader import load_datasets
11+ from src .model import build_model , unfreeze_top_layers
2212
2313
2414# ── Config ────────────────────────────────────────────────────────────────────
25- DATA_DIR = os .getenv ("DATA_DIR" , "data/PlantVillage" )
26- MODEL_DIR = Path ("models" )
27- MODEL_PATH = MODEL_DIR / "best_model.keras"
28- HISTORY_PATH = MODEL_DIR / "history.json"
29- PLOTS_DIR = Path ("reports/figures" )
30-
31- EPOCHS_HEAD = 15 # Phase 1: train only the classification head
32- EPOCHS_FINETUNE = 10 # Phase 2: fine-tune top layers of backbone
33- BATCH_SIZE = 32
15+ DATA_DIR = os .getenv ("DATA_DIR" , "data/PlantVillage" )
16+ MODEL_DIR = Path ("models" )
17+ MODEL_PATH = MODEL_DIR / "best_model.keras"
18+ HISTORY_PATH = MODEL_DIR / "history.json"
19+ PLOTS_DIR = Path ("reports/figures" )
20+
21+ EPOCHS_HEAD = 15 # Phase 1: train only the classification head
22+ EPOCHS_FINETUNE = 10 # Phase 2: fine-tune top layers of backbone
23+ BATCH_SIZE = 32
3424# ──────────────────────────────────────────────────────────────────────────────
3525
3626
@@ -40,24 +30,24 @@ def get_callbacks(model_path: Path):
4030 monitor = "val_accuracy" ,
4131 patience = 5 ,
4232 restore_best_weights = True ,
43- verbose = 1
33+ verbose = 1 ,
4434 ),
4535 tf .keras .callbacks .ModelCheckpoint (
4636 filepath = str (model_path ),
4737 monitor = "val_accuracy" ,
4838 save_best_only = True ,
49- verbose = 1
39+ verbose = 1 ,
5040 ),
5141 tf .keras .callbacks .ReduceLROnPlateau (
5242 monitor = "val_loss" ,
5343 factor = 0.5 ,
5444 patience = 3 ,
5545 min_lr = 1e-7 ,
56- verbose = 1
46+ verbose = 1 ,
5747 ),
5848 tf .keras .callbacks .TensorBoard (
5949 log_dir = "logs" ,
60- histogram_freq = 1
50+ histogram_freq = 1 ,
6151 ),
6252 ]
6353
@@ -66,16 +56,16 @@ def plot_training_history(history_dict: dict, save_dir: Path) -> None:
6656 save_dir .mkdir (parents = True , exist_ok = True )
6757 fig , axes = plt .subplots (1 , 2 , figsize = (14 , 5 ))
6858
69- axes [0 ].plot (history_dict ["accuracy" ], label = "Train accuracy" , marker = "o" )
70- axes [0 ].plot (history_dict ["val_accuracy" ], label = "Val accuracy" , marker = "o" )
59+ axes [0 ].plot (history_dict ["accuracy" ], label = "Train accuracy" , marker = "o" )
60+ axes [0 ].plot (history_dict ["val_accuracy" ], label = "Val accuracy" , marker = "o" )
7161 axes [0 ].set_title ("Accuracy" )
7262 axes [0 ].set_xlabel ("Epoch" )
7363 axes [0 ].set_ylabel ("Accuracy" )
7464 axes [0 ].legend ()
7565 axes [0 ].grid (True , alpha = 0.3 )
7666
77- axes [1 ].plot (history_dict ["loss" ], label = "Train loss" , marker = "o" )
78- axes [1 ].plot (history_dict ["val_loss" ], label = "Val loss" , marker = "o" )
67+ axes [1 ].plot (history_dict ["loss" ], label = "Train loss" , marker = "o" )
68+ axes [1 ].plot (history_dict ["val_loss" ], label = "Val loss" , marker = "o" )
7969 axes [1 ].set_title ("Loss" )
8070 axes [1 ].set_xlabel ("Epoch" )
8171 axes [1 ].set_ylabel ("Loss" )
@@ -100,15 +90,15 @@ def main():
10090
10191 # Save class names so predict.py and app.py can load them
10292 with open (MODEL_DIR / "class_names.json" , "w" ) as f :
103- json .dump (class_names , f )
93+ json .dump (class_names , f , indent = 2 )
10494
10595 # ── Phase 1: Train head only ───────────────────────────────────────────
10696 print ("\n Phase 1 — Training classification head (backbone frozen)..." )
10797 model = build_model (num_classes )
10898 model .compile (
10999 optimizer = tf .keras .optimizers .Adam (learning_rate = 1e-3 ),
110100 loss = "sparse_categorical_crossentropy" ,
111- metrics = ["accuracy" ]
101+ metrics = ["accuracy" ],
112102 )
113103 model .summary ()
114104
@@ -123,9 +113,9 @@ def main():
123113 print ("\n Phase 2 — Fine-tuning top layers of EfficientNetB0..." )
124114 model = unfreeze_top_layers (model , num_layers = 20 )
125115 model .compile (
126- optimizer = tf .keras .optimizers .Adam (learning_rate = 1e-5 ), # Lower LR!
116+ optimizer = tf .keras .optimizers .Adam (learning_rate = 1e-5 ), # lower LR for fine-tuning
127117 loss = "sparse_categorical_crossentropy" ,
128- metrics = ["accuracy" ]
118+ metrics = ["accuracy" ],
129119 )
130120
131121 history2 = model .fit (
@@ -141,15 +131,15 @@ def main():
141131 combined [key ] = history1 .history [key ] + history2 .history [key ]
142132
143133 with open (HISTORY_PATH , "w" ) as f :
144- json .dump (combined , f )
134+ json .dump (combined , f , indent = 2 )
145135
146136 plot_training_history (combined , PLOTS_DIR )
147137
148138 # ── Final evaluation ──────────────────────────────────────────────────
149139 print ("\n Evaluating on validation set..." )
150140 loss , acc = model .evaluate (val_ds , verbose = 1 )
151- print (f"\n Final val accuracy: { acc :.4f} ({ acc * 100 :.2f} %)" )
152- print (f"Final val loss: { loss :.4f} " )
141+ print (f"\n Final val accuracy : { acc :.4f} ({ acc * 100 :.2f} %)" )
142+ print (f"Final val loss : { loss :.4f} " )
153143 print (f"\n Model saved → { MODEL_PATH } " )
154144
155145
0 commit comments