Lesson 5.1.3: Building a baseline crop disease classifier
Lesson 5.1.3: Building a baseline crop disease classifier
Learning Objectives
- Explain the role of a baseline model in crop disease classification.
- Identify key steps in preparing a dataset for a CNN classifier.
- Implement a simple convolutional neural network using TensorFlow/Keras.
- Evaluate model performance using appropriate metrics and visualisations.
Key Terms
| Baseline Model | A simple, often unoptimised model used as a reference point for further improvements. |
|---|---|
| Convolutional Neural Network (CNN) | A deep learning architecture specialised for processing grid‑like data such as images. |
| Data Augmentation | Techniques that expand a training set by applying transformations like rotation, scaling and flipping. |
| Accuracy | The proportion of correctly classified instances over the total number of instances. |
| Confusion Matrix | A table summarising the prediction outcomes for each class, used to compute metrics such as precision and recall. |
flowchart TD
A[Collect image data] --> B[Annotate with disease labels]
B --> C[Pre‑process images (resize, augment)]
C --> D[Split into training / validation / test]
D --> E[Train baseline classifier (CNN)]
E --> F[Validate & evaluate performance]
F --> G[Deploy model for inference]
Illustrative Code Snippet: A Simple Logistic Regression Baseline
Below is a minimal Python example that demonstrates how to construct a baseline classifier using histogram of oriented gradients (HOG) features and a logistic regression model. The code is deliberately kept simple so that the logic behind each step is transparent.
import cv2
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
1. Load image and convert to grayscale
image = cv2.imread('leaf.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
2. Compute HOG features
winSize = (64, 64)
blockSize = (16, 16)
blockStride = (8, 8)
cellSize = (8, 8)
nbins = 9
hog = cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins)
features = hog.compute(gray).flatten()
3. Prepare dataset (X, y) – here we mock a small array
X = np.array([features]) # feature matrix
y = np.array([0]) # 0 = healthy, 1 = diseased
4. Train-test split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
5. Train logistic regression
clf = LogisticRegression(max_iter=1000)
clf.fit(X_train, y_train)
6. Predict and evaluate
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred))
Line‑by‑line explanation:
import cv2– Imports OpenCV, a library for computer‑vision tasks.import numpy as np– Provides array operations.from sklearn.linear_model import LogisticRegression– Brings in the logistic regression classifier.from sklearn.model_selection import train_test_split– Utility to split the data.from sklearn.metrics import classification_report– Helper to summarise performance.image = cv2.imread('leaf.jpg')– Reads an image file into memory.gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)– Converts the image to greyscale, simplifying the feature space.- HOG parameters are defined to extract edge‑orientation statistics from the greyscale image.
features = hog.compute(gray).flatten()– Computes the HOG descriptor and flattens it into a 1‑D array.- Mock dataset creation:
Xholds the feature vector,yholds the label. - Data is split into training and testing sets using a 80/20 ratio.
- The logistic regression model is instantiated with a higher iteration limit to ensure convergence.
- The model is trained on the training subset.
- Predictions are made on the test set, and a classification report is printed, showing precision, recall, F1‑score, and support.
While this example uses a single image and a dummy label, the pattern scales to thousands of images: loop over the dataset, compute HOG features for each, stack them into a matrix, and fit the logistic regression. The resulting baseline will give you an initial accuracy that you can improve upon with more advanced techniques.
Illustrative Code Snippet: A Baseline Using Raw Pixels and a Support‑Vector Machine
For comparison, the following code shows how to build a baseline that uses raw pixel values as features and a linear support‑vector machine (SVM). This approach is often faster to prototype and can be surprisingly effective on small, well‑labelled datasets.
import cv2
import numpy as np
from sklearn.svm import LinearSVC
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
1. Load and resize to a fixed size
image = cv2.imread('leaf.jpg')
resized = cv2.resize(image, (128, 128)) # 128x128 pixels
2. Flatten to a 1‑D feature vector (raw RGB pixels)
features = resized.reshape(-1).astype(np.float32) / 255.0
3. Mock dataset (repeat for illustration)
X = np.array([features]) # feature matrix
y = np.array([0]) # 0 = healthy, 1 = diseased
4. Train‑test split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
5. Train linear SVM
clf = LinearSVC(max_iter=10000)
clf.fit(X_train, y_train)
6. Predict and evaluate
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred))
| Aspect | HOG + Logistic Regression | Raw Pixels + Linear SVM |
|---|---|---|
| Feature Engineering | Hand‑crafted HOG descriptors (edge orientation) | Zero‑engineering – raw RGB values |
| Model Complexity | Simple linear model (logistic regression) | Linear SVM with hinge loss |
| Training Time (small dataset) | Fast (seconds) | Fast (seconds) |
| Interpretability | Feature weights correspond to HOG bins | Weights correspond to individual pixel intensities |
| Robustness to Noise | Moderate – HOG aggregates local gradients | Low – sensitive to pixel‑level variations |
| Scalability to Large Images | Requires resizing or patch‑based extraction | Memory‑intensive for high‑resolution images |
| Typical Accuracy on Plant Disease Tasks | 70–80 % (depends on dataset) | 60–75 % (often lower without feature engineering) |
| Deployment Footprint | Lightweight (few KB) | Lightweight (few KB) |
Think about why a baseline crop disease classifier is a crucial first step in your machine learning pipeline. Consider how a simple model can guide feature selection, highlight data quality issues, and set realistic performance expectations for more complex approaches. Write a brief paragraph summarising your insights and how you might apply them to the next stages of your project.
Implementation/Code: Building a Simple CNN
In this section we walk through the construction of a minimal convolutional neural network (CNN) that can be trained to recognise crop diseases from leaf images. The network will be built using TensorFlow 2.x and Keras, the high‑level API that makes model definition straightforward while still offering fine‑grained control. By the end of this section you will understand how each layer contributes to feature extraction, how the model is compiled, and how to visualise training progress.
1. Importing the Essentials
Before any modelling can commence we must import the libraries that provide numerical support, data handling utilities, and the deep‑learning framework itself. Note that TensorFlow 2.x includes Keras as a sub‑package, so there is no need to install Keras separately.
import tensorflow as tf
from tensorflow.keras import layers, models
import matplotlib.pyplot as plt
import numpy as np
import os
Line 1 imports the core TensorFlow library, giving access to tensors, automatic differentiation, and GPU support if available. Line 2 pulls in the layers and models modules – these contain the building blocks for our CNN. Line 3 imports Matplotlib for visualising training curves. Line 4 brings in NumPy, which will be useful for any post‑processing. Line 5 imports the OS module to help with file path handling later on.
2. Preparing the Data
We assume that the dataset has already been split into training and validation directories, each containing sub‑folders named after the disease classes. Keras provides the convenient image_dataset_from_directory function, which handles loading, resizing, and batching automatically.
IMG_SIZE = (128, 128) # Standardise all images to 128×128
BATCH_SIZE = 32 # Batch size for efficient training
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
"data/train",
seed=123,
image_size=IMG_SIZE,
batch_size=BATCH_SIZE
)
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
"data/val",
seed=123,
image_size=IMG_SIZE,
batch_size=BATCH_SIZE
)
Line 1–2 declare constants for image dimensions and batch size. Line 4–10 create the training dataset. The seed parameter ensures reproducible shuffling. Line 12–18 do the same for validation data. Each dataset yields batches of tuples (images, labels) where images is a tensor of shape (BATCH_SIZE, 128, 128, 3) and labels is a one‑hot encoded vector corresponding to the disease class.
3. Normalising the Pixel Values
Deep learning models train faster and more stably when inputs are normalised. Keras can automatically rescale pixel values to the [0,1] range via a Rescaling layer. We insert this layer at the very start of the network so that every subsequent layer receives normalised data.
normalisation_layer = layers.Rescaling(1./255)
Line 1 creates a rescaling layer that divides each pixel value by 255. This transformation is applied lazily – it will only be executed when the model processes a batch during training or inference.
4. Defining the CNN Architecture
Our baseline CNN will consist of two convolutional blocks followed by a fully connected classifier. Each block contains a convolutional layer, a rectified linear unit (ReLU) activation, and a max‑pooling layer. The final dense layer uses softmax to produce class probabilities.
def build_simple_cnn(num_classes):
model = models.Sequential([
normalisation_layer, # Normalise inputs
# Block 1
layers.Conv2D(32, (3, 3), padding='same', activation='relu',
input_shape=IMG_SIZE + (3,)),
layers.MaxPooling2D((2, 2)),
# Block 2
layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dense(num_classes, activation='softmax')
])
return model
Line 1 declares a function that returns a compiled model once invoked. Line 2 starts a Keras Sequential model, which is appropriate for a linear stack of layers. Line 3 adds the normalisation layer defined earlier.
Block 1 comprises a Conv2D layer with 32 filters of size 3×3. The padding='same' argument ensures that the output spatial dimensions match the input. The activation='relu' applies the rectified linear unit non‑linearity. The input_shape parameter is only required on the first layer and is set to the image size plus three colour channels. Line 7 adds a 2×2 max‑pooling layer, halving the spatial dimensions.
Block 2 mirrors Block 1 but with 64 filters, allowing the network to learn more complex features. After the second pooling step the feature maps are flattened into a one‑dimensional vector. Line 13 introduces a fully connected layer with 128 units and ReLU activation, which mixes the extracted features. Line 14 adds the output layer; the number of units equals num_classes and the softmax activation converts logits into a probability distribution over classes.
5. Adding Dropout for Regularisation
To mitigate over‑fitting we can insert a Dropout layer after the flattening step. The following example shows a common placement that preserves the model’s simplicity while providing a small regularisation effect. The dropout probability is set to 0.5, meaning half of the activations are randomly set to zero during each training step.
def build_cnn_with_dropout(num_classes, dropout_rate=0.5):
model = models.Sequential([
normalisation_layer,
layers.Conv2D(32, (3, 3), padding='same', activation='relu',
input_shape=IMG_SIZE + (3,)),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dropout(dropout_rate), # New line
layers.Dense(128, activation='relu'),
layers.Dense(num_classes, activation='softmax')
])
return model
When comparing the baseline and dropout‑enhanced models, you may observe a slight increase in validation accuracy and a reduction in the training‑validation accuracy gap.
| Approach | Dropout Layer | Typical Impact | When to Use |
|---|---|---|---|
| Baseline (no dropout) | None | Higher training accuracy, risk of over‑fitting | Small datasets, early experiments |
| With dropout (0.5) | After Flatten | Improved generalisation, modest training accuracy drop | Moderate‑size datasets, when over‑fitting is evident |
| With dropout (0.3) | After Flatten | Balanced trade‑off, less regularisation | Large datasets, when model capacity is high |
6. Compiling the Model
Compilation specifies the optimisation strategy, the loss function and the metrics to monitor. For a multi‑class classification problem the categorical cross‑entropy loss is appropriate. We use the Adam optimiser, which adapts the learning rate during training.
model = build_simple_cnn(num_classes=len(train_ds.class_names))
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
Line 1 creates an instance of the model, passing the number of disease classes inferred from the training dataset. Line 3 compiles the model: Adam is instantiated with a learning rate of 0.001, a sensible default for many problems. Line 5 sets the loss to sparse_categorical_crossentropy, which is used when labels are integer‑encoded rather than one‑hot. Line 6 requests that the accuracy metric be computed during training.
7. Training the Network
Training is performed by calling model.fit. We provide the training and validation datasets, specify the number of epochs, and optionally add a callback to stop training early if the validation loss plateaus.
EPOCHS = 20
early_stop = tf.keras.callbacks.EarlyStopping(
monitor='val_loss',
patience=3,
restore_best_weights=True
)
history = model.fit(
train_ds,
validation_data=val_ds,
epochs=EPOCHS,
callbacks=[early_stop]
)
Line 1 sets the maximum number of epochs. Line 3–7 configure an early‑stopping callback that watches the validation loss; if it does not improve for three consecutive epochs, training halts and the weights from the best epoch are restored. Line 9–15 launch training. The history object returned by fit contains per‑epoch records of loss and accuracy for both training and validation sets.
8. Visualising Training Progress
Plotting the loss and accuracy curves is essential for diagnosing over‑fitting or under‑fitting. The following snippet uses Matplotlib to produce two sub‑plots side by side.
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs_range = range(len(acc))
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Accuracy over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Loss over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.tight_layout()
plt.show()
Lines 1–4 extract the relevant metrics from the history dictionary. Line 6 creates a range object that will be used as the x‑axis. Line 8 initialises a figure of width 12 inches and height 5 inches. Lines 10–15 plot training and validation accuracy on the left subplot, adding a legend and axis labels. Lines 17–22 produce the loss curves on the right subplot. Line 24 ensures that sub‑plots do not overlap, and Line 25 renders the figure. By inspecting these plots you can decide whether to increase model capacity, adjust the learning rate, or collect more data.
9. Saving and Loading the Model
After satisfactory performance, the model can be persisted to disk for later deployment. TensorFlow offers two convenient formats: the legacy H5 format and the TensorFlow SavedModel format, the latter being recommended for production.
# SavedModel format
model.save('models/simple_cnn', save_format='tf')
H5 format (optional)
model.save('models/simple_cnn.h5')
Line 2 saves the model in the default SavedModel format. Line 5 provides an alternative H5 file; this format is useful for compatibility with other frameworks but is less efficient for TensorFlow serving.
To reload the model later, simply call tf.keras.models.load_model and pass the path.
loaded_model = tf.keras.models.load_model('models/simple_cnn')
At this point the loaded_model instance is fully functional and can be used to predict on new leaf images, or further fine‑tuned if new disease categories are added.
10. Interpreting the Results
When evaluating the trained network, pay close attention to the following:
- Training Accuracy vs. Validation Accuracy: A large gap indicates over‑fitting. Consider adding dropout layers or collecting more data.
- Loss Curves: If the training loss continues to decrease while validation loss plateaus or increases, the model is memorising the training set.
- Confusion Matrix: Use scikit‑learn’s
confusion_matrixto visualise which disease classes are most frequently mis‑classified, providing insights into data imbalance or ambiguous imagery.
By systematically analysing these diagnostics you can iteratively refine the architecture, hyper‑parameters, and data pipeline, ultimately improving the baseline classifier’s robustness and generalisation.
- Which of the following layers is typically used to reduce spatial dimensions in a CNN?
- What is the primary purpose of the activation function in a convolutional layer?
- When compiling a Keras model for image classification, which loss function is most appropriate?
- In a fully connected layer, why might you choose a dropout rate of 0.5?
- Which optimizer is commonly used for training lightweight CNNs on limited hardware?
Worked Example: Applying the Baseline to a Wheat Disease Dataset
In this section we conduct a step‑by‑step walk‑through of a typical machine‑learning pipeline using the baseline classifier described earlier. The dataset we use comprises images of wheat leaves that are either healthy or infected with various fungal diseases such as rust, powdery mildew or leaf spot. The goal is to build a classifier that can recognise disease presence in a new leaf image, and to evaluate its performance against a simple baseline.
1. Loading the Dataset
We organise the data into two directories – healthy and diseased – each containing JPEG images. Using Python’s pathlib and skimage.io libraries we can read the images into memory and tag them with the appropriate class label. The following snippet demonstrates the essential steps.
import pathlib
from skimage import io
import numpy as np
Define the base directory where the two sub‑folders reside
BASE_DIR = pathlib.Path("data/wheat")
Initialise lists to hold image data and labels
images, labels = [], []
Iterate over the two classes
for label in ["healthy", "diseased"]:
class_dir = BASE_DIR / label
for img_path in class_dir.glob("*.jpg"):
# Read the image as a NumPy array
image = io.imread(img_path)
images.append(image)
# Encode the label as 0 (healthy) or 1 (diseased)
labels.append(0 if label == "healthy" else 1)
Convert to NumPy arrays for downstream processing
X = np.array(images)
y = np.array(labels)
In the code above,pathlib.Pathprovides a convenient, platform‑independent way to construct file paths.skimage.io.imreadloads the image into a 3‑dimensional NumPy array (height × width × channels). By appending the label after the image we create a parallel array that will be used for training and evaluation.
2. Pre‑processing the Images
Raw images vary in size and illumination. To feed them into a simple convolutional neural network (CNN) we normalise the pixel values and resize each image to a fixed resolution. This also reduces memory consumption.
from skimage.transform import resize
from sklearn.preprocessing import StandardScaler
Target resolution for all images
TARGET_SIZE = (128, 128)
Resize images
X_resized = np.array([resize(img, TARGET_SIZE, anti_aliasing=True) for img in X])
Flatten images for the baseline model (non‑CNN)
X_flat = X_resized.reshape((X_resized.shape[0], -1))
Normalise pixel values to zero‑mean, unit‑variance
scaler = StandardScaler()
X_normalised = scaler.fit_transform(X_flat)
Explanation of the code:
resizescales each image toTARGET_SIZEwhile preserving aspect ratio and applying anti‑aliasing to avoid aliasing artefacts.- We reshape the 3‑D array into a 2‑D array where each row is a flattened image, which is required for the simple baseline classifier (e.g. logistic regression).
- The
StandardScalercentres and scales each feature (pixel) across the training set, ensuring that all inputs contribute equally to the learning process.
3. Splitting the Data
It is crucial to evaluate the model on data it has never seen. We therefore split the dataset into training and test sets, maintaining the class balance using stratified sampling.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X_normalised, y, test_size=0.2, stratify=y, random_state=42
)
The random_state parameter guarantees reproducibility, while stratify=y ensures that the proportion of healthy and diseased samples is preserved in both splits.
4. Training the Baseline Classifier
For a baseline we employ a simple logistic regression model, which is fast to train and provides a clear benchmark. In practice, more sophisticated models (e.g. ResNet) would be used, but the baseline serves to highlight the added value of deeper architectures.
from sklearn.linear_model import LogisticRegression
Instantiate the model with a fixed random state
baseline = LogisticRegression(max_iter=1000, random_state=42)
Fit the model to the training data
baseline.fit(X_train, y_train)
Key points:
- Setting
max_iterto 1000 ensures convergence for small datasets. - The logistic regression likelihood is maximised via an interior‑point optimiser, producing a probabilistic output.
5. Evaluating Performance
After training we assess the model on the held‑out test set, computing several metrics that are standard in medical image analysis.
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, classification_report
Predict class labels on the test set
y_pred = baseline.predict(X_test)
Compute metrics
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.3f}")
print(f"Precision: {precision:.3f}")
print(f"Recall: {recall:.3f}")
print(f"F1‑Score: {f1:.3f}")
Display a confusion matrix
cm = confusion_matrix(y_test, y_pred)
print("Confusion Matrix:")
print(cm)
Detailed classification report
print("
Classification Report:")
print(classification_report(y_test, y_pred, target_names=["Healthy", "Diseased"]))
Interpretation of metrics:
- Accuracy indicates the proportion of correctly classified samples.
- Precision measures how many of the predicted diseased samples were truly diseased, thereby reflecting the rate of false positives.
- Recall (sensitivity) tells us how many of the actual diseased samples were identified, which is critical in a medical context where missing a disease is costly.
- F1‑Score is the harmonic mean of precision and recall, providing a single value that balances the two.
Suppose the output is:
Accuracy: 0.842
Precision: 0.815
Recall: 0.876
F1‑Score: 0.844
Confusion Matrix:
[[ 45 7]
[ 12 36]]
Classification Report:
precision recall f1-score support
Healthy 0.82 0.86 0.84 52
Diseased 0.81 0.88 0.84 48
accuracy 0.84 100
macro avg 0.82 0.87 0.84 100
weighted avg 0.82 0.84 0.84 100
This indicates that the baseline performs reasonably well, correctly classifying over 84 % of the samples. The confusion matrix reveals 12 false negatives (healthy samples mislabelled as diseased) and 7 false positives (diseased samples mislabelled as healthy). Depending on the operational requirements, a higher recall might be preferred to minimise missed disease cases.
6. Visualising Misclassifications
Understanding where the model fails is as important as knowing its accuracy. We can plot a few misclassified images to identify common failure modes such as subtle disease symptoms or lighting variations.
import matplotlib.pyplot as plt
Identify indices of misclassifications
mis_idx = np.where(y_test != y_pred)[0]
Plot the first five misclassified images
fig, axes = plt.subplots(1, 5, figsize=(15, 3))
for ax, idx in zip(axes, mis_idx[:5]):
# Retrieve the original image before flattening
img = X_resized[(X_train.shape[0] + idx)]
ax.imshow(img)
ax.set_title(f"True: {y_test[idx]}, Pred: {y_pred[idx]}")
ax.axis('off')
plt.tight_layout()
plt.show()
The visual inspection often reveals that the baseline struggles with images exhibiting low contrast or with diseases that present only as small, diffuse spots. These insights guide the next iteration: augmenting the dataset with more difficult examples, adding colour normalisation, or switching to a deeper CNN architecture.
7. Baseline vs. Advanced Models
While a logistic regression baseline offers a quick benchmark, it is limited by its linear decision boundary. In contrast, a convolutional neural network can capture spatial hierarchies and learn disease‑specific texture patterns. A simple transfer‑learning experiment using a pre‑trained ResNet‑18 could improve accuracy to 92 % or higher, at the cost of increased training time and GPU requirements.
| Model | Accuracy | Precision | Recall | Training Time |
|---|---|---|---|---|
| Logistic Regression (Baseline) | 84 % | 81 % | 88 % | ~1 min on CPU |
| ResNet‑18 (Transfer Learning) | 92 % | 90 % | 94 % | ~15 min on GPU |
When deciding which model to deploy, stakeholders must weigh the trade‑offs between performance, computational resources, and interpretability. The baseline serves as a transparent reference point, while more complex models provide higher accuracy at the expense of explainability.
8. Reproducibility Checklist
For rigorous scientific practice, the following steps should be documented:
- Set a global random seed (
np.random.seedandrandom_statein scikit‑learn functions). - Version‑control the dataset and pre‑processing scripts.
- Record the exact versions of libraries used (
scikit‑learn==1.3.0,skimage==0.19.3, etc.). - Save the trained model and scaler objects using
joblib.dumpfor future inference. - Log hyperparameters and evaluation metrics in a reproducible format (e.g. CSV or JSON).
Adhering to these practices ensures that the results can be replicated by others and that the model can be audited or updated as new data arrive.
9. Summary
In this worked example we:
- Loaded a wheat leaf image dataset and encoded labels.
- Pre‑processed images through resizing, flattening and normalisation.
- Split the data into stratified training and test sets.
- Trained a logistic regression baseline classifier.
- Computed key performance metrics and inspected misclassifications.
- Compared the baseline to a more advanced CNN approach.
- Outlined best practices for reproducibility.
These steps constitute a complete, end‑to‑end pipeline that can be adapted to other plant disease datasets or imaging modalities. By starting with a simple baseline, we establish a clear benchmark against which more sophisticated models can be objectively evaluated.
10. Data Augmentation Example
To improve robustness we can perform on‑the‑fly augmentation using TensorFlow’s ImageDataGenerator. This example applies random rotations, flips and brightness shifts while training a small CNN.
import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras import layers, models
Define augmentation strategy
datagen = ImageDataGenerator(
rotation_range=15,
width_shift_range=0.1,
height_shift_range=0.1,
horizontal_flip=True,
brightness_range=[0.8, 1.2],
fill_mode='nearest'
)
Prepare a simple CNN
model = models.Sequential([
layers.InputLayer(input_shape=(128, 128, 3)),
layers.Conv2D(32, (3, 3), activation='relu'),
layers.MaxPooling2D(),
layers.Conv2D(64, (3, 3), activation='relu'),
layers.MaxPooling2D(),
layers.Flatten(),
layers.Dense(64, activation='relu'),
layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
Fit with augmented data
model.fit(
datagen.flow(X_train.reshape(-1, 128, 128, 3), y_train, batch_size=32),
epochs=10,
validation_data=(X_test.reshape(-1, 128, 128, 3), y_test)
)
| Approach | Dataset Size | Training Time | Accuracy |
|---|---|---|---|
| No Augmentation (Baseline) | 1000 images | ~1 min | 84 % |
| With Augmentation (CNN) | ~8000 pseudo‑images | ~10 min | 88‑90 % |
You have just trained the baseline crop disease classifier on the Wheat Disease dataset. Now imagine you are a field technician who needs to test the model on a fresh batch of images collected from a different farm. Write a short plan (2-4 sentences) describing how you would: (1) prepare the new images for inference, (2) run the model, and (3) interpret the predicted disease labels to inform the farmer’s next steps.
Pitfalls: Common Errors and How to Avoid Them
When building a baseline crop disease classifier, the technical journey is often punctuated by subtle mistakes that can erode model performance. A solid understanding of these pitfalls and the strategies to sidestep them is essential for any practitioner aiming to deliver reliable, reproducible results. Below we dissect three of the most pervasive errors: overfitting, data leakage, and misinterpreting evaluation metrics. Each subsection provides concrete examples, explains why the mistake is harmful, and offers pragmatic solutions.
1. Overfitting: Learning the Noise Instead of the Signal
Overfitting occurs when a model captures idiosyncrasies of the training data that do not generalise to new images. This is particularly acute in image classification where high‑capacity architectures like ResNet or DenseNet can memorize even modest datasets.
- Symptoms: A training accuracy that climbs to 99 % while validation accuracy stagnates around 70 %. The model performs well on the training set but poorly on unseen data.
- Causes: 1) Insufficient data, 2) Lack of augmentation, 3) Excessive depth or width of the network, 4) Inadequate regularisation.
- Prevention:
- Use cross‑validation to gauge stability.
- Apply data augmentation: random flips, rotations, colour jitter, and random crops.
- Introduce dropout layers or weight decay (L2 regularisation).
- Employ early stopping based on validation loss.
- Practical tip: Monitor the ratio of training to validation loss. A widening gap is a red flag.
2. Data Leakage: Unintended Information Flow
Data leakage is the inadvertent introduction of test information into the training process. In crop disease classification, this can arise from several sources.
- Pre‑processing leakage: Standardising pixel values across the entire dataset before splitting. This shares mean and variance values from test images with the training set.
- Augmentation leakage: Applying augmentation that mixes training and test images (e.g., stitching patches).
- Metadata leakage: Using labels or attributes that are correlated with the target but not part of the image content (e.g., GPS coordinates that inadvertently correlate with disease prevalence).
Leakage inflates performance metrics during development, only to collapse when the model is deployed.
Detecting Leakage
One robust approach is to perform a sanity check: create a dummy model that predicts the mean of the training labels and compare its performance to the real model. Any significant advantage suggests leakage. Also, visualise the distribution of pixel intensities in training versus validation sets; large discrepancies may indicate leakage.
Mitigation Strategy
- Apply all transformations (normalisation, resizing) inside a pipeline that is fitted exclusively on the training data.
- Ensure that data augmentation is applied per‑sample and not across splits.
- Keep metadata separate and evaluate its influence independently.
3. Misinterpreting Evaluation Metrics: Accuracy Is Not Enough
In imbalanced datasets—common in disease detection where healthy examples vastly outnumber diseased ones—accuracy can be misleading. A model that always predicts “healthy” may achieve 90 % accuracy but zero recall for disease.
Key metrics to consider:
| Metric | What it measures |
|---|---|
| Accuracy | Overall correctness |
| Precision | Correct positive predictions / all positive predictions |
| Recall (Sensitivity) | Correct positive predictions / all actual positives |
| F1‑score | Harmonic mean of precision and recall |
| Area Under ROC (AUC‑ROC) | Trade‑off between true positive and false positive rates |
| Confusion Matrix | Breakdown of TP, FP, TN, FN |
For crop disease, recall is often prioritised: missing a disease can lead to crop loss. However, an overly high recall may inflate false positives, burdening farmers with unnecessary treatments.
Choosing the Right Threshold
Many classifiers output probabilities. Default threshold of 0.5 is arbitrary. Plotting the ROC curve or Precision‑Recall curve allows selection of a threshold that balances recall and precision to meet domain constraints.
4. Practical Implementation: Regularisation with Dropout
Below is a minimal example of adding dropout to a convolutional base to combat overfitting. The code uses PyTorch, a popular deep learning framework.
import torch
import torch.nn as nn
import torchvision.models as models
1. Load a pre‑trained ResNet‑18 model
model = models.resnet18(pretrained=True)
2. Replace the final fully‑connected layer to match our classes
num_features = model.fc.in_features
model.fc = nn.Sequential(
nn.Dropout(p=0.5), # 3. Dropout with 50% keep probability
nn.Linear(num_features, 2) # 4. Binary classification: healthy vs diseased
)
5. Define loss and optimiser
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
Explanation of the code:
- The
models.resnet18(pretrained=True)call downloads a ResNet‑18 model pre‑trained on ImageNet. This provides a robust feature extractor for our crop images. - We replace the original fully‑connected head with a new
nn.Sequentialblock. The original head is unsuitable because its output dimension (1000 classes) does not match our binary problem. - Dropout is inserted before the final linear layer. With a probability of 0.5, each neuron’s output is set to zero during training, forcing the network to learn redundant representations and reducing overfitting.
- The linear layer maps the extracted features to two output scores, one per class.
- We use cross‑entropy loss, appropriate for multi‑class classification, and the Adam optimiser, which adapts learning rates on a per‑parameter basis.
When training, monitor the training and validation losses. If the gap widens, consider increasing dropout or adding weight decay.
4.1 Alternative Example: Data Augmentation Pipeline in Keras
Below demonstrates a simple yet powerful augmentation strategy using TensorFlow/Keras. This approach is often overlooked but can significantly reduce overfitting.
import tensorflow as tf
datagen = tf.keras.preprocessing.image.ImageDataGenerator(
rescale=1./255,
rotation_range=30,
width_shift_range=0.1,
height_shift_range=0.1,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest'
)
train_generator = datagen.flow_from_directory(
'data/train',
target_size=(224, 224),
batch_size=32,
class_mode='binary',
subset='training'
)
This pipeline automatically resizes, normalises, and augments each image on the fly, ensuring that the model sees a diverse set of examples without the need for manual pre‑processing.
| Pitfall | Detection Method | Mitigation Technique |
|---|---|---|
| Overfitting | Training/validation loss gap, high training accuracy but low validation accuracy | Dropout, weight decay, data augmentation, early stopping, cross‑validation |
| Data Leakage | Dummy‑model sanity check, distribution mismatch, unexpected high metrics | Train‑only fitting of normalisation, per‑sample augmentation, metadata isolation |
| Metric Misinterpretation | High accuracy with low recall, unbalanced confusion matrix | Use precision/recall/F1, ROC/PR curves, threshold optimisation |
5. Checklist for Robust Model Development
Implement the following steps before publishing a model:
- Data hygiene: Verify that no test images appear in the training set.
- Balanced splits: Use stratified k‑fold cross‑validation to preserve class distribution.
- Augmentation strategy: Apply random transformations that reflect real‑world variability (e.g., lighting changes, occlusions).
- Metric selection: Report at least accuracy, precision, recall, F1‑score, and a confusion matrix.
- Threshold optimisation: Tune the decision threshold on a validation set using a grid search over recall‑precision trade‑offs.
- Model explainability: Visualise class activation maps to confirm that the model focuses on disease symptoms rather than background artefacts.
Adhering to this checklist mitigates the common pitfalls discussed and lays a solid foundation for a trustworthy crop disease classifier.
Reflect on the pitfalls highlighted in the “Pitfalls: Common Errors and How to Avoid Them” section. Identify a scenario where a baseline crop disease classifier might fail due to data imbalance, overfitting, or inadequate preprocessing. Share your thoughts on how you would detect and mitigate this issue in a real project.
Key Takeaways
- Explain the role of a baseline model in crop disease classification.
- Identify key steps in preparing a dataset for a CNN classifier.
- Implement a simple convolutional neural network using TensorFlow/Keras.
- Evaluate model performance using appropriate metrics and visualisations.
Further Reading
- Goodfellow, I., Bengio, Y., & Courville, A. (2016) — Deep Learning. A foundational text covering convolutional neural networks for image classification, essential for understanding crop disease detection pipelines.
- He, K., Zhang, X., Ren, S., & Sun, J. (2016) — Deep residual learning for image recognition. Presents ResNet architecture widely used in agricultural disease classifiers for improved accuracy.
- International Plant Protection Convention (IPPC) (2021) — Guidelines for Plant Health Risk Assessment. Provides standardised criteria for disease surveillance that inform the data curation stage of AI workflows.
- Chiu, T., Huang, Y., & Huang, J. (2020) — A survey of machine learning methods for plant disease detection. Reviews state‑of‑the‑art classifiers and dataset challenges relevant to baseline model building.
- Li, Y., Li, Z., & Wang, J. (2022) — Transfer learning for plant disease classification using limited data. Discusses practical transfer‑learning strategies that are often applied before training a baseline CNN.


Comments are closed.