All skills
Skillintermediate
Machine Learning for Geospatial Data
Guide to ML and deep learning applications for remote sensing and spatial analysis.
Claude Code Knowledge Pack7/10/2026
Overview
Machine Learning for Geospatial Data
Guide to ML and deep learning applications for remote sensing and spatial analysis.
Traditional Machine Learning
Random Forest for Land Cover
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix
from rasterio.features import rasterize
def train_random_forest_classifier(raster_path, training_gdf):
"""Train Random Forest for image classification."""
# Load imagery
with rasterio.open(raster_path) as src:
image = src.read()
profile = src.profile
transform = src.transform
# Extract training data
X, y = [], []
for _, row in training_gdf.iterrows():
mask = rasterize(
[(row.geometry, 1)],
out_shape=(profile['height'], profile['width']),
transform=transform,
fill=0,
dtype=np.uint8
)
pixels = image[:, mask > 0].T
X.extend(pixels)
y.extend([row['class_id']] * len(pixels))
X = np.array(X)
y = np.array(y)
# Split data
X_train, X_val, y_train, y_val = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Train model
rf = RandomForestClassifier(
n_estimators=100,
max_depth=20,
min_samples_split=10,
min_samples_leaf=4,
class_weight='balanced',
n_jobs=-1,
random_state=42
)
rf.fit(X_train, y_train)
# Validate
y_pred = rf.predict(X_val)
print("Classification Report:")
print(classification_report(y_val, y_pred))
# Feature importance
feature_names = [f'Band_{i}' for i in range(X.shape[1])]
importances = pd.DataFrame({
'feature': feature_names,
'importance': rf.feature_importances_
}).sort_values('importance', ascending=False)
print("\
Feature Importance:")
print(importances)
return rf
# Classify full image
def classify_image(model, image_path, output_path):
with rasterio.open(image_path) as src:
image = src.read()
profile = src.profile
image_reshaped = image.reshape(image.shape[0], -1).T
prediction = model.predict(image_reshaped)
prediction = prediction.reshape(image.shape[1], image.shape[2])
profile.update(dtype=rasterio.uint8, count=1)
with rasterio.open(output_path, 'w', **profile) as dst:
dst.write(prediction.astype(rasterio.uint8), 1)
Support Vector Machine
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
def svm_classifier(X_train, y_train):
"""SVM classifier for remote sensing."""
# Scale features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
# Train SVM
svm = SVC(
kernel='rbf',
C=100,
gamma='scale',
class_weight='balanced',
probability=True
)
svm.fit(X_train_scaled, y_train)
return svm, scaler
# Multi-class classification
def multiclass_svm(X_train, y_train):
from sklearn.multiclass import OneVsRestClassifier
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
svm_ovr = OneVsRestClassifier(
SVC(kernel='rbf', C=10, probability=True),
n_jobs=-1
)
svm_ovr.fit(X_train_scaled, y_train)
return svm_ovr, scaler
Deep Learning
CNN with TorchGeo
from torch.utils.data import DataLoader
# Define CNN
class LandCoverCNN(nn.Module):
def __init__(self, in_channels=12, num_classes=10):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv2d(in_channels, 64, 3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(64, 128, 3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(128, 256, 3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2),
)
self.decoder = nn.Sequential(
nn.ConvTranspose2d(256, 128, 2, stride=2),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.ConvTranspose2d(128, 64, 2, stride=2),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.ConvTranspose2d(64, num_classes, 2, stride=2),
)
def forward(self, x):
x = self.encoder(x)
x = self.decoder(x)
return x
# Training
def train_model(train_loader, val_loader, num_epochs=50):
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = LandCoverCNN().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
for epoch in range(num_epochs):
model.train()
train_loss = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()
# Validation
model.eval()
val_loss = 0
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
val_loss += loss.item()
print(f'Epoch {epoch+1}/{num_epochs}, Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}')
return model
U-Net for Semantic Segmentation
class UNet(nn.Module):
def __init__(self, in_channels=4, num_classes=5):
super().__init__()
# Encoder
self.enc1 = self.conv_block(in_channels, 64)
self.enc2 = self.conv_block(64, 128)
self.enc3 = self.conv_block(128, 256)
self.enc4 = self.conv_block(256, 512)
# Bottleneck
self.bottleneck = self.conv_block(512, 1024)
# Decoder
self.up1 = nn.ConvTranspose2d(1024, 512, 2, stride=2)
self.dec1 = self.conv_block(1024, 512)
self.up2 = nn.ConvTranspose2d(512, 256, 2, stride=2)
self.dec2 = self.conv_block(512, 256)
self.up3 = nn.ConvTranspose2d(256, 128, 2, stride=2)
self.dec3 = self.conv_block(256, 128)
self.up4 = nn.ConvTranspose2d(128, 64, 2, stride=2)
self.dec4 = self.conv_block(128, 64)
# Final layer
self.final = nn.Conv2d(64, num_classes, 1)
def conv_block(self, in_ch, out_ch):
return nn.Sequential(
nn.Conv2d(in_ch, out_ch, 3, padding=1),
nn.BatchNorm2d(out_ch),
nn.ReLU(inplace=True),
nn.Conv2d(out_ch, out_ch, 3, padding=1),
nn.BatchNorm2d(out_ch),
nn.ReLU(inplace=True)
)
def forward(self, x):
# Encoder
e1 = self.enc1(x)
e2 = self.enc2(F.max_pool2d(e1, 2))
e3 = self.enc3(F.max_pool2d(e2, 2))
e4 = self.enc4(F.max_pool2d(e3, 2))
# Bottleneck
b = self.bottleneck(F.max_pool2d(e4, 2))
# Decoder with skip connections
d1 = self.dec1(torch.cat([self.up1(b), e4], dim=1))
d2 = self.dec2(torch.cat([self.up2(d1), e3], dim=1))
d3 = self.dec3(torch.cat([self.up3(d2), e2], dim=1))
d4 = self.dec4(torch.cat([self.up4(d3), e1], dim=1))
return self.final(d4)
Change Detection with Siamese Network
class SiameseNetwork(nn.Module):
"""Siamese network for change detection."""
def __init__(self):
super().__init__()
self.feature_extractor = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(64, 128, 3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
)
self.classifier = nn.Sequential(
nn.Conv2d(256, 128, 3, padding=1),
nn.ReLU(),
nn.Conv2d(128, 64, 3, padding=1),
nn.ReLU(),
nn.Conv2d(64, 2, 1), # Binary: change / no change
)
def forward(self, x1, x2):
f1 = self.feature_extractor(x1)
f2 = self.feature_extractor(x2)
# Concatenate features
diff = torch.abs(f1 - f2)
combined = torch.cat([f1, f2, diff], dim=1)
return self.classifier(combined)
Graph Neural Networks
PyTorch Geometric for Spatial Data
from torch_geometric.data import Data
from torch_geometric.nn import GCNConv
# Create spatial graph
def create_spatial_graph(points_gdf, k_neighbors=5):
"""Create graph from point data using k-NN."""
from sklearn.neighbors import NearestNeighbors
coords = np.array([[p.x, p.y] for p in points_gdf.geometry])
# Find k-nearest neighbors
nbrs = NearestNeighbors(n_neighbors=k_neighbors).fit(coords)
distances, indices = nbrs.kneighbors(coords)
# Create edge index
edge_index = []
for i, neighbors in enumerate(indices):
for j in neighbors:
edge_index.append([i, j])
edge_index = torch.tensor(edge_index, dtype=torch.long).t().contiguous()
# Node features
features = points_gdf.drop('geometry', axis=1).values
x = torch.tensor(features, dtype=torch.float)
return Data(x=x, edge_index=edge_index)
# GCN for spatial prediction
class SpatialGCN(torch.nn.Module):
def __init__(self, num_features, hidden_channels=64):
super().__init__()
self.conv1 = GCNConv(num_features, hidden_channels)
self.conv2 = GCNConv(hidden_channels, hidden_channels)
self.conv3 = GCNConv(hidden_channels, 1)
def forward(self, data):
x, edge_index = data.x, data.edge_index
x = self.conv1(x, edge_index).relu()
x = F.dropout(x, p=0.5, training=self.training)
x = self.conv2(x, edge_index).relu()
x = self.conv3(x, edge_index)
return x
Explainable AI (XAI) for Geospatial
SHAP for Model Interpretation
def explain_model(model, X, feature_names):
"""Explain model predictions using SHAP."""
# Create explainer
explainer = shap.Explainer(model, X)
# Calculate SHAP values
shap_values = explainer(X)
# Summary plot
shap.summary_plot(shap_values, X, feature_names=feature_names)
# Dependence plot for important features
for i in range(X.shape[1]):
shap.dependence_plot(i, shap_values, X, feature_names=feature_names)
return shap_values
# Spatial SHAP (accounting for spatial autocorrelation)
def spatial_shap(model, X, coordinates):
"""Spatial explanation considering neighborhood effects."""
# Compute SHAP values
explainer = shap.Explainer(model, X)
shap_values = explainer(X)
# Spatial aggregation
shap_spatial = {}
for i, coord in enumerate(coordinates):
# Find neighbors
neighbors = find_neighbors(coord, coordinates, radius=1000)
# Aggregate SHAP values for neighborhood
neighbor_shap = shap_values.values[neighbors]
shap_spatial[i] = np.mean(neighbor_shap, axis=0)
return shap_spatial
Attention Maps for CNNs
def generate_attention_map(model, image_tensor, target_layer):
"""Generate attention map using Grad-CAM."""
# Forward pass
model.eval()
output = model(image_tensor)
# Backward pass
model.zero_grad()
output[0, torch.argmax(output)].backward()
# Get gradients
gradients = model.get_gradient(target_layer)
# Global average pooling
weights = torch.mean(gradients, axis=(2, 3), keepdim=True)
# Weighted combination of activation maps
activations = model.get_activation(target_layer)
attention = torch.sum(weights * activations, axis=1, keepdim=True)
# ReLU and normalize
attention = F.relu(attention)
attention = F.interpolate(attention, size=image_tensor.shape[2:],
mode='bilinear', align_corners=False)
attention = (attention - attention.min()) / (attention.max() - attention.min())
return attention.squeeze().cpu().numpy()
For more ML examples, see code-examples.md.