Random Forest (rf) Classifier
Data¶
We load training areas from
data/training_areas_ml.geojson.Spectral band values (Green, Red, NIR) are simulated from class-specific reflectance profiles.
The binary target is already encoded:
ml_label = 1(Corn/Maize) vsml_label = 0(Non-Corn).We normalize the features to [0, 1] and split into training and testing sets.
import json
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report, ConfusionMatrixDisplay
from sklearn.preprocessing import MinMaxScaler
from sklearn.ensemble import RandomForestClassifier
OUTPUT_DIR = 'outputs'
os.makedirs(OUTPUT_DIR, exist_ok=True)
# Load training areas
with open('../data/training_areas_ml.geojson') as f:
gj = json.load(f)
# Spectral profiles (Green, Red, NIR) per original land cover class
SPECTRAL_PROFILES = {
0: {'mean': [0.12, 0.08, 0.35], 'std': [0.02, 0.02, 0.04]}, # Rice Paddies
1: {'mean': [0.16, 0.07, 0.55], 'std': [0.03, 0.02, 0.05]}, # Corn / Maize
2: {'mean': [0.10, 0.05, 0.65], 'std': [0.02, 0.01, 0.05]}, # Other Vegetation / Trees
3: {'mean': [0.30, 0.35, 0.25], 'std': [0.04, 0.05, 0.04]}, # Built-up / Bare Land
4: {'mean': [0.08, 0.05, 0.04], 'std': [0.02, 0.01, 0.01]}, # Water / Fishponds
}
N_SAMPLES = 100
rng = np.random.default_rng(42)
records = []
for feat in gj['features']:
cl_id = feat['properties']['Cl_Id']
ml_label = feat['properties']['ml_label']
p = SPECTRAL_PROFILES[cl_id]
samples = rng.normal(loc=p['mean'], scale=p['std'], size=(N_SAMPLES, 3)).clip(0, 1)
for s in samples:
records.append({'Green': s[0], 'Red': s[1], 'NIR': s[2], 'Class': ml_label})
data = pd.DataFrame(records)
# Normalize the features (Green, Red, NIR)
scaler = MinMaxScaler()
data[['Green', 'Red', 'NIR']] = scaler.fit_transform(data[['Green', 'Red', 'NIR']])
# Split into training and test sets
X = data[['Green', 'Red', 'NIR']].values
y = data['Class'].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)
# Visualize the dataset in 2D (Green vs NIR)
plt.figure(figsize=(8, 6))
plt.scatter(X[:, 0], X[:, 2], c=y, cmap='coolwarm', edgecolors='k', alpha=0.7)
plt.xlabel('Green (Normalized)')
plt.ylabel('NIR (Normalized)')
plt.title('Corn/Maize vs Non-Corn Areas (Green vs NIR)')
plt.colorbar(label='Class (0 = Non-Corn, 1 = Corn/Maize)')
plt.savefig(os.path.join(OUTPUT_DIR, 'rf_scatter_green_nir.png'), dpi=150, bbox_inches='tight')
plt.show()
print(data.head())Function for decision boundaries¶
Next, we are going to define a helper function to plot the decision boundary for a 2D dataset.
# Define function
def plot_decision_boundary(model, X, y, title):
"""
Plots the decision boundary for a 2D dataset.
"""
x_min, x_max = X[:, 0].min() - 0.1, X[:, 0].max() + 0.1
y_min, y_max = X[:, 1].min() - 0.1, X[:, 1].max() + 0.1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
np.linspace(y_min, y_max, 200))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# Plot into the *current* axes
plt.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
plt.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k')
plt.xlabel('Green')
plt.ylabel('NIR')
plt.title(title)
plt.colorbar(label='Class (0 = Non-Corn, 1 = Corn/Maize)')Comparing RF Decision Boundaries¶
# Create two Random Forest classifiers with different parameters
rf_1 = RandomForestClassifier(n_estimators=100, max_depth=3, random_state=42)
rf_2 = RandomForestClassifier(n_estimators=500, max_depth=5, random_state=42)
# Train both models on the first two features
rf_1.fit(X_train[:, :2], y_train)
rf_2.fit(X_train[:, :2], y_train)
# Plot both decision boundaries side by side
plt.figure(figsize=(14, 6))
plt.subplot(1, 2, 1)
plot_decision_boundary(rf_1, X_train[:, :2], y_train,
'Random Forest (100 trees, max_depth=3)')
plt.subplot(1, 2, 2)
plot_decision_boundary(rf_2, X_train[:, :2], y_train,
'Random Forest (500 trees, max_depth=5)')
plt.tight_layout()
plt.savefig(os.path.join(OUTPUT_DIR, 'rf_decision_boundaries.png'), dpi=150, bbox_inches='tight')
plt.show()👩🏽💻 TEST IT OUT
How about if we want to check for 5 classes? Try it out with training_areas.geojson.