Machine Learning for Drones
Machine learning transforms raw drone sensor data into actionable intelligence. This tutorial covers the core ML paradigms powering modern autonomous aerial systems.
The ML Pipeline for Drone Data
Every drone ML system follows a consistent pipeline from sensor input to intelligent output.
Supervised Learning for Drones
Supervised learning maps labeled sensor data to known outcomes—like training a model to classify terrain types from aerial images.
Real-world analogy: Think of it as teaching a student with flashcards. Each image shows terrain, and the label says "forest," "water," or "urban." After enough examples, the student recognizes patterns.
Key Applications
- Terrain classification for landing zone selection
- Obstacle detection from depth sensors
- Weather prediction from atmospheric sensors
- Path quality assessment for route planning
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.preprocessing import StandardScaler
# Simulate drone sensor data for terrain classification
np.random.seed(42)
n_samples = 1000
# Features: [altitude, temperature, humidity, vibration, visual_entropy, ndvi]
features = np.column_stack([
np.random.uniform(10, 500, n_samples), # altitude (m)
np.random.uniform(-10, 45, n_samples), # temperature (C)
np.random.uniform(20, 95, n_samples), # humidity (%)
np.random.uniform(0, 1, n_samples), # vibration level
np.random.uniform(0, 5, n_samples), # visual entropy
np.random.uniform(-1, 1, n_samples), # NDVI (vegetation index)
])
# Labels: 0=urban, 1=forest, 2=water, 3=desert
labels = np.random.randint(0, 4, n_samples)
# Scale features
scaler = StandardScaler()
features_scaled = scaler.fit_transform(features)
# Split data
X_train, X_test, y_train, y_test = train_test_split(
features_scaled, labels, test_size=0.2, random_state=42
)
# Train Random Forest classifier
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)
# Evaluate
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred,
target_names=['Urban', 'Forest', 'Water', 'Desert']))
# Feature importance analysis
feature_names = ['Altitude', 'Temperature', 'Humidity',
'Vibration', 'Visual Entropy', 'NDVI']
importances = clf.feature_importances_
for name, imp in sorted(zip(feature_names, importances),
key=lambda x: x[1], reverse=True):
print(f"{name}: {imp:.3f}")
Unsupervised Learning for Drones
Unsupervised learning finds hidden patterns in unlabeled drone data—perfect for discovering anomalies or grouping similar flight behaviors.
Real-world analogy: Like sorting a pile of mixed coins by size and color without knowing their denominations first. You naturally group similar items together.
Key Applications
- Anomaly detection in flight telemetry
- Cluster analysis of terrain types
- Dimensionality reduction for sensor fusion
- Behavioral clustering for fleet management
from sklearn.cluster import KMeans, DBSCAN
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# Simulate drone telemetry data (GPS, speed, altitude, battery)
np.random.seed(42)
n_flights = 500
# Normal flight patterns
normal_flights = np.column_stack([
np.random.normal(50, 5, n_flights), # speed (m/s)
np.random.normal(100, 20, n_flights), # altitude (m)
np.random.normal(0.7, 0.1, n_flights), # battery drain rate
np.random.normal(0.5, 0.1, n_flights), # vibration
])
# Anomalous flights (malfunctions, wind gusts)
anomalies = np.column_stack([
np.random.normal(50, 15, 20), # erratic speed
np.random.normal(200, 50, 20), # unusual altitude
np.random.normal(0.95, 0.05, 20), # high battery drain
np.random.normal(0.9, 0.1, 20), # high vibration
])
all_flights = np.vstack([normal_flights, anomalies])
labels_true = np.array([0]*n_flights + [1]*20)
# Apply PCA for visualization
scaler = StandardScaler()
flights_scaled = scaler.fit_transform(all_flights)
pca = PCA(n_components=2)
flights_2d = pca.fit_transform(flights_scaled)
# KMeans clustering
kmeans = KMeans(n_clusters=2, random_state=42, n_init=10)
kmeans_labels = kmeans.fit_predict(flights_scaled)
# DBSCAN for anomaly detection
dbscan = DBSCAN(eps=0.5, min_samples=5)
dbscan_labels = dbscan.fit_predict(flights_scaled)
print(f"KMeans found {len(np.unique(kmeans_labels))} clusters")
print(f"DBSCAN found {len(np.unique(dbscan_labels[dbscan_labels>=0]))} clusters")
print(f"DBSCAN flagged {np.sum(dbscan_labels == -1)} anomalies")
Reinforcement Learning for Drones
RL trains drones to make sequential decisions by maximizing rewards through trial-and-error interaction with their environment.
Real-world analogy: Like teaching a dog tricks. The dog tries actions, gets treats (rewards) for good behavior, and gradually learns which actions lead to rewards.
Key Applications
- Autonomous navigation through complex environments
- Landing optimization on moving platforms
- Formation flying for drone swarms
- Energy-efficient path planning
from collections import defaultdict
class DroneNavigationEnv:
"""Simplified 2D grid environment for drone navigation."""
def __init__(self, size=10):
self.size = size
self.drone_pos = [0, 0]
self.target_pos = [size-1, size-1]
self.obstacles = set()
self._generate_obstacles()
def _generate_obstacles(self):
"""Randomly place obstacles (15% of grid)."""
for _ in range(int(self.size**2 * 0.15)):
pos = (np.random.randint(0, self.size),
np.random.randint(0, self.size))
if pos != (0, 0) and pos != tuple(self.target_pos):
self.obstacles.add(pos)
def reset(self):
self.drone_pos = [0, 0]
return tuple(self.drone_pos)
def step(self, action):
# Actions: 0=up, 1=down, 2=left, 3=right
moves = {0: [0,1], 1: [0,-1], 2: [-1,0], 3: [1,0]}
new_pos = [self.drone_pos[i] + moves[action][i] for i in range(2)]
# Boundary check
new_pos = [max(0, min(self.size-1, p)) for p in new_pos]
# Obstacle check
if tuple(new_pos) in self.obstacles:
reward = -10
elif new_pos == self.target_pos:
reward = 100
else:
# Distance-based reward
old_dist = abs(self.drone_pos[0]-self.target_pos[0]) + \
abs(self.drone_pos[1]-self.target_pos[1])
new_dist = abs(new_pos[0]-self.target_pos[0]) + \
abs(new_pos[1]-self.target_pos[1])
reward = 1 if new_dist < old_dist else -1
self.drone_pos = new_pos
done = (self.drone_pos == self.target_pos)
return tuple(self.drone_pos), reward, done
class QLearningAgent:
"""Q-Learning agent for drone navigation."""
def __init__(self, n_actions=4, lr=0.1, gamma=0.99, epsilon=1.0):
self.q_table = defaultdict(lambda: np.zeros(n_actions))
self.lr = lr
self.gamma = gamma
self.epsilon = epsilon
self.epsilon_decay = 0.995
self.epsilon_min = 0.01
def choose_action(self, state):
if np.random.random() < self.epsilon:
return np.random.randint(4)
return np.argmax(self.q_table[state])
def update(self, state, action, reward, next_state, done):
current = self.q_table[state][action]
if done:
target = reward
else:
target = reward + self.gamma * np.max(self.q_table[next_state])
self.q_table[state][action] += self.lr * (target - current)
def decay_epsilon(self):
self.epsilon = max(self.epsilon_min,
self.epsilon * self.epsilon_decay)
# Training loop
env = DroneNavigationEnv(size=8)
agent = QLearningAgent()
episodes = 1000
rewards_history = []
for episode in range(episodes):
state = env.reset()
total_reward = 0
done = False
while not done:
action = agent.choose_action(state)
next_state, reward, done = env.step(action)
agent.update(state, action, reward, next_state, done)
state = next_state
total_reward += reward
agent.decay_epsilon()
rewards_history.append(total_reward)
if (episode + 1) % 100 == 0:
avg = np.mean(rewards_history[-100:])
print(f"Episode {episode+1}, Avg Reward: {avg:.1f}, "
f"Epsilon: {agent.epsilon:.3f}")
Feature Engineering from Sensor Data
Raw sensor data needs transformation into meaningful features for ML models.
from scipy import stats
from scipy.fft import fft
def extract_flight_features(sensor_data, window_size=100):
"""
Extract ML features from raw drone sensor data.
Parameters:
sensor_data: dict with keys 'accel', 'gyro', 'gps', 'battery'
window_size: number of samples per window
Returns:
dict of extracted features
"""
features = {}
# Accelerometer features
accel = sensor_data['accel']
features['accel_mean'] = np.mean(accel, axis=0)
features['accel_std'] = np.std(accel, axis=0)
features['accel_max'] = np.max(np.abs(accel), axis=0)
features['accel_rms'] = np.sqrt(np.mean(accel**2, axis=0))
features['accel_kurtosis'] = stats.kurtosis(accel, axis=0)
# Gyroscope features
gyro = sensor_data['gyro']
features['gyro_mean'] = np.mean(gyro, axis=0)
features['gyro_std'] = np.std(gyro, axis=0)
features['angular_rate'] = np.sqrt(np.sum(gyro**2, axis=1))
# Frequency domain features
accel_magnitude = np.sqrt(np.sum(accel**2, axis=1))
freq_data = np.abs(fft(accel_magnitude))
features['dominant_frequency'] = np.argmax(freq_data[1:len(freq_data)//2]) + 1
features['spectral_energy'] = np.sum(freq_data**2)
# GPS features
gps = sensor_data['gps']
features['speed'] = np.sqrt(np.sum(np.diff(gps, axis=0)**2, axis=1))
features['avg_speed'] = np.mean(features['speed'])
features['path_length'] = np.sum(features['speed'])
# Battery features
battery = sensor_data['battery']
features['battery_drain_rate'] = -np.polyfit(
range(len(battery)), battery, 1)[0]
features['remaining_flight_time'] = battery[-1] / features['battery_drain_rate']
# Statistical features
features['skewness'] = stats.skew(accel, axis=0)
return features
# Example usage
np.random.seed(42)
n_samples = 500
sensor_data = {
'accel': np.random.randn(n_samples, 3) * 0.5,
'gyro': np.random.randn(n_samples, 3) * 0.1,
'gps': np.cumsum(np.random.randn(n_samples, 2) * 0.5, axis=0),
'battery': np.linspace(100, 60, n_samples)
}
features = extract_flight_features(sensor_data)
for key, value in features.items():
if isinstance(value, np.ndarray):
print(f"{key}: {np.round(value, 3)}")
else:
print(f"{key}: {value:.3f}")
Model Selection Guide
| Task | Model | Pros | Cons |
|---|---|---|---|
| Terrain Classification | Random Forest | Fast, interpretable | Limited on images |
| Obstacle Detection | SVM | Good with small data | Slow on large datasets |
| Flight Prediction | LSTM | Captures time patterns | Needs lots of data |
| Anomaly Detection | Isolation Forest | No labels needed | Sensitive to parameters |
| Path Planning | DQN | Learns optimal policies | Training unstable |
Hands-On Project: Flight Anomaly Detector
Build a complete anomaly detection system for drone telemetry.
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
warnings.filterwarnings('ignore')
class DroneAnomalyDetector:
"""Complete anomaly detection system for drone telemetry."""
def __init__(self, contamination=0.05):
self.pipeline = Pipeline([
('scaler', StandardScaler()),
('detector', IsolationForest(
contamination=contamination,
random_state=42,
n_estimators=200
))
])
self.threshold = -0.5
def generate_training_data(self, n_samples=5000):
"""Generate synthetic flight data for training."""
np.random.seed(42)
# Normal flight patterns
normal = np.column_stack([
np.random.normal(15, 3, n_samples), # speed
np.random.normal(50, 10, n_samples), # altitude
np.random.normal(0, 0.5, n_samples), # pitch
np.random.normal(0, 0.5, n_samples), # roll
np.random.normal(0.7, 0.05, n_samples),# battery
np.random.normal(0.2, 0.05, n_samples),# vibration
np.random.normal(25, 2, n_samples), # temperature
])
return normal
def train(self, X):
"""Train the anomaly detection model."""
self.pipeline.fit(X)
print(f"Model trained on {len(X)} samples")
print(f"Anomaly threshold: {self.threshold}")
def predict(self, X):
"""Predict anomalies and return scores."""
predictions = self.pipeline.predict(X)
scores = self.pipeline.decision_function(X)
results = {
'is_anomaly': predictions == -1,
'anomaly_scores': scores,
'anomaly_count': np.sum(predictions == -1),
'anomaly_rate': np.mean(predictions == -1)
}
return results
def analyze_anomalies(self, X, feature_names):
"""Analyze detected anomalies by feature contribution."""
results = self.predict(X)
anomalies = X[results['is_anomaly']]
print(f"\nDetected {results['anomaly_count']} anomalies "
f"({results['anomaly_rate']:.1%} of data)")
if len(anomalies) > 0:
print("\nAnomaly Statistics (mean values):")
for i, name in enumerate(feature_names):
normal_mean = np.mean(X[~results['is_anomaly'], i])
anomaly_mean = np.mean(anomalies[:, i])
diff_pct = (anomaly_mean - normal_mean) / normal_mean * 100
print(f" {name}: {anomaly_mean:.2f} "
f"(normal: {normal_mean:.2f}, "
f"diff: {diff_pct:+.1f}%)")
return results, anomalies
# Run the project
detector = DroneAnomalyDetector(contamination=0.05)
# Generate and train
training_data = detector.generate_training_data(5000)
detector.train(training_data)
# Test with mixed normal and anomalous data
test_data = detector.generate_training_data(200)
# Inject anomalies
anomalies = np.column_stack([
np.random.uniform(30, 60, 10), # high speed
np.random.uniform(100, 300, 10), # high altitude
np.random.uniform(-3, 3, 10), # extreme pitch
np.random.uniform(-3, 3, 10), # extreme roll
np.random.uniform(0.1, 0.3, 10), # low battery
np.random.uniform(0.5, 1.0, 10), # high vibration
np.random.uniform(60, 90, 10), # high temperature
])
test_data = np.vstack([test_data, anomalies])
feature_names = ['Speed', 'Altitude', 'Pitch', 'Roll',
'Battery', 'Vibration', 'Temperature']
results, detected = detector.analyze_anomalies(test_data, feature_names)
print("\nProject Complete!")
print(f"Detection accuracy: "
f"{np.sum(results['is_anomaly'][-10:])/10*100:.0f}% on injected anomalies")
Key Takeaways
- Supervised learning excels when you have labeled sensor data
- Unsupervised learning discovers patterns without labels
- Reinforcement learning powers autonomous decision-making
- Feature engineering transforms raw sensors into ML-ready data
- Model selection depends on data size, latency needs, and accuracy requirements
Next, we'll explore deep learning techniques that can process raw sensor data directly.