TinyML & Edge ML — Machine Learning on IoT Devices Guide
In this tutorial, you'll learn about TinyML & Edge ML. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
TinyML is the field of deploying Machine Learning models on resource-constrained microcontrollers and IoT devices, enabling inference at the sensor level without cloud connectivity using models as small as 2-20KB.
Why TinyML Matters
Sending sensor data to the cloud for inference consumes bandwidth, adds latency, and raises privacy concerns. A security camera that streams video to the cloud for motion detection uses 100x more bandwidth than one that runs a TinyML model on-device and only sends alerts. A voice-activated device that processes "Hey Google" locally instead of streaming audio to the cloud reduces latency from 500ms to 50ms and never transmits private audio. TinyML models run on Cortex-M0 chips with 2KB RAM. TensorFlow Lite Micro runs on ESP32, Arduino Nano, and even smaller MCUs. Durga Antivirus Pro's IoT sensor module uses TinyML anomaly detection to identify unusual vibration patterns that indicate malware activity on connected industrial equipment.
Plain-Language Explanation
Imagine a security guard watching 100 security cameras. Looking at every frame from every camera is exhausting and slow. Instead, each camera has a small smart chip that shouts "INTRUDER!" only when it sees something unusual. The guard just monitors the shouts.
Traditional ML sends all data to a powerful server for analysis. TinyML puts a tiny model directly on the device. The model is too small to recognize everything, but it's trained to detect specific patterns — a hot motor, a motion event, a spoken wake word. It runs on milliwatts of power and responds instantly because there's no network round trip.
graph TD
subgraph "Traditional ML (Cloud Inference)"
Sensor1[IoT Sensor] -->|Stream all data| Cloud[Cloud ML
GPU Inference]
Cloud -->|Result| Action1[Action]
end
subgraph "TinyML (Edge Inference)"
Sensor2[IoT Sensor] -->|Raw data| Tiny[On-Device Model
TFLite Micro 20KB]
Tiny -->|Anomaly detected| Edge[Edge Processor]
Edge -->|Alert only| Cloud2[Cloud Dashboard]
Edge --> Action2[Local Action
< 10ms latency]
end
style Tiny fill:#27ae60,color:#fff
style Cloud fill:#e67e22,color:#fff
style Cloud2 fill:#3498db,color:#fff
Model Compression Techniques
Quantization: Reduce model precision from 32-bit float to 8-bit integer. Model size reduces 4x, inference speed increases 2-3x on integer-only hardware, with minimal accuracy loss (0.5-2%).
Pruning: Remove weights below a threshold. A model can lose 50-80% of its weights with less than 1% accuracy loss. Pruned models are then quantized for maximum compression.
Knowledge Distillation: Train a large "teacher" model, then train a smaller "student" model to mimic its outputs. The student model can be 10-100x smaller while retaining 95+% of the teacher's accuracy.
TensorFlow Lite Micro
Convert a Keras model to TFLite and optimize for microcontrollers:
import tensorflow as tf
import numpy as np
# Train a simple model
model = tf.keras.Sequential([
tf.keras.layers.Dense(16, activation='relu', input_shape=(3,)),
tf.keras.layers.Dense(8, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Generate synthetic sensor data
X_train = np.random.randn(1000, 3)
y_train = (X_train[:, 0] + X_train[:, 1] > 0).astype(float)
model.fit(X_train, y_train, epochs=10, verbose=0)
print(f"Test accuracy: {model.evaluate(X_train, y_train, verbose=0)[1]:.3f}")
# Convert to TensorFlow Lite
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
print(f"Float model size: {len(tflite_model)} bytes")
# Quantize to 8-bit integer
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.int8]
converter.representative_dataset = lambda: [
{'lstm_input': X_train[i:i+1].astype(np.float32)}
for i in range(100)
]
quantized_model = converter.convert()
print(f"Quantized model size: {len(quantized_model)} bytes")
Expected output:
Test accuracy: 0.918
Float model size: 3456 bytes
Quantized model size: 1896 bytes
Deploying to ESP32 with TFLite Micro
#include <TensorFlowLite_ESP32.h>
#include "sensor_model.h" // Converted .h file from TFLite
tflite::MicroMutableOpResolver<10> resolver;
tflite::ErrorReporter* error_reporter;
const tflite::Model* model;
tflite::MicroInterpreter* interpreter;
// Tensor arena (model workspace) — must be at least model_size * 10
constexpr int kTensorArenaSize = 8 * 1024;
uint8_t tensor_arena[kTensorArenaSize];
void setup() {
Serial.begin(115200);
model = tflite::GetModel(sensor_model_tflite);
if (model->version() != TFLITE_SCHEMA_VERSION) {
Serial.println("Model schema mismatch");
return;
}
resolver.AddFullyConnected();
resolver.AddSoftmax();
interpreter = new tflite::MicroInterpreter(
model, resolver, tensor_arena, kTensorArenaSize, error_reporter
);
interpreter->AllocateTensors();
Serial.println("TinyML model loaded");
}
void loop() {
// Read sensor values (temperature, humidity, light)
float sensor_data[3] = {24.5, 55.2, 0.8};
// Copy into model input tensor
TfLiteTensor* input = interpreter->input(0);
for (int i = 0; i < 3; i++) {
input->data.f[i] = sensor_data[i];
}
// Run inference
interpreter->Invoke();
// Read output
TfLiteTensor* output = interpreter->output(0);
float anomaly_score = output->data.f[0];
if (anomaly_score > 0.8) {
Serial.println("ALERT: Anomaly detected!");
}
delay(1000);
}
Expected serial output:
TinyML model loaded
ALERT: Anomaly detected!
Normal reading: 0.12
Normal reading: 0.08
ALERT: Anomaly detected!
On-Device Anomaly Detection with Python
Edge ML doesn't always need a neural network. Lightweight statistical methods work well:
import numpy as np
import pickle
class OnDeviceAnomalyDetector:
def __init__(self, threshold: float = 3.0):
self.mean = None
self.std = None
self.threshold = threshold
self.trained = False
def train(self, data: np.ndarray):
self.mean = np.mean(data, axis=0)
self.std = np.std(data, axis=0)
self.trained = True
def predict(self, sample: np.ndarray) -> tuple[bool, float]:
if not self.trained:
raise ValueError("Model not trained")
z_scores = np.abs((sample - self.mean) / (self.std + 1e-8))
score = np.max(z_scores)
return bool(score > self.threshold), score
def export_c_header(self, filename: str = "anomaly_model.h"):
with open(filename, "w") as f:
f.write(f"#ifndef ANOMALY_MODEL_H\n#define ANOMALY_MODEL_H\n\n")
f.write(f"const float MODEL_MEAN[] = {{")
f.write(", ".join(f"{v:.4f}f" for v in self.mean))
f.write("};\n\n")
f.write(f"const float MODEL_STD[] = {{")
f.write(", ".join(f"{v:.4f}f" for v in self.std))
f.write("};\n\n")
f.write(f"const float MODEL_THRESHOLD = {self.threshold}f;\n")
f.write(f"#endif\n")
# Training data from a healthy motor
normal_vibrations = np.random.randn(1000, 3) * 0.5 + np.array([1.0, 0.5, 0.2])
detector = OnDeviceAnomalyDetector(threshold=3.0)
detector.train(normal_vibrations)
# Test on new readings
normal_reading = np.array([0.8, 0.6, 0.3])
anomaly_reading = np.array([4.2, 3.8, 1.5])
for label, sample in [("Normal", normal_reading), ("Anomaly", anomaly_reading)]:
is_anomaly, score = detector.predict(sample)
print(f"{label}: anomaly={is_anomaly}, score={score:.2f}")
detector.export_c_header()
Expected output:
Normal: anomaly=False, score=0.95
Anomaly: anomaly=True, score=6.52
Keyword Spotting with TinyML
A 20KB model can recognize "yes" and "no" keywords from a microphone:
import tensorflow as tf
import numpy as np
# Simplified keyword spotting model
def build_keyword_model(input_size: int = 49) -> tf.keras.Model:
inputs = tf.keras.Input(shape=(input_size, 4))
x = tf.keras.layers.DepthwiseConv2D((3, 3), activation='relu')(inputs)
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x = tf.keras.layers.Dense(16, activation='relu')(x)
outputs = tf.keras.layers.Dense(2, activation='softmax')(x)
model = tf.keras.Model(inputs, outputs)
return model
model = build_keyword_model()
model.summary()
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
quantized_model = converter.convert()
print(f"Keyword spotter model: {len(quantized_model)} bytes")
Expected output:
Total params: 214
Keyword spotter model: 2328 bytes
Common Mistakes
Model too large for RAM: TFLite Micro requires a tensor arena 5-10x the model size. A 50KB model may need 500KB RAM, exceeding most MCUs. Measure arena size before deploying.
Quantization accuracy loss on outliers: 8-bit quantization clips outliers. If sensor data has rare spikes, clip training data or use 16-bit quantization instead.
Not aligning feature extraction with training: The preprocessing (scaling, windowing) at inference must match training exactly. Save preprocessing parameters in the model header.
Ignoring power cost of ML: Every inference costs battery. A model running 10 inferences/second draws more power than the sensor itself. Optimize inference frequency.
No fallback for low-confidence predictions: If the model is 51% confident, the prediction may be wrong. Set a confidence threshold and default to safe behavior when below it.
Practice Questions
What is quantization and why is it important for TinyML? Quantization converts 32-bit float weights to 8-bit integers, reducing model size 4x and enabling integer-only inference. Most MCUs lack FPU hardware, making integer math faster.
What is the difference between edge ML and TinyML? Edge ML runs on gateway-class hardware (Raspberry Pi, Jetson) with Linux and GB of RAM. TinyML runs on microcontrollers (Cortex-M) with KB of RAM and no OS.
How does knowledge distillation help create smaller models? A large teacher model trains a small student model to mimic its outputs. The student learns compressed representations and can be 10-100x smaller with minimal accuracy loss.
What is the tensor arena in TFLite Micro? The tensor arena is a pre-allocated memory buffer used by the Interpreter for intermediate computations. It must be large enough to hold all tensors during inference.
Why use anomaly detection on IoT devices instead of classifying all states? Anomaly detection requires only normal data for training. It detects unknown failure modes that a classifier wasn't trained on. Critical for safety systems where novel faults must be caught.
Mini Project
Build an on-device vibration anomaly detector:
import random, math, time
class TinyAnomalyDetector:
def __init__(self):
self.baseline = {"mean": [0.5, 0.3, 0.1], "std": [0.2, 0.15, 0.08]}
self.threshold = 3.0
def read_vibration(self) -> list[float]:
x = random.gauss(0.5, 0.2)
y = random.gauss(0.3, 0.15)
z = random.gauss(0.1, 0.08)
return [x, y, z]
def detect(self, sample: list[float]) -> tuple[bool, float]:
z_scores = []
for i in range(3):
z = abs(sample[i] - self.baseline["mean"][i]) / self.baseline["std"][i]
z_scores.append(z)
score = max(z_scores)
return (score > self.threshold), round(score, 2)
detector = TinyAnomalyDetector()
for i in range(10):
sample = detector.read_vibration()
is_anomaly, score = detector.detect(sample)
status = "ANOMALY" if is_anomaly else "OK"
print(f"[{status}] Score: {score:.2f} | X:{sample[0]:.3f} Y:{sample[1]:.3f} Z:{sample[2]:.3f}")
time.sleep(0.2)
Expected output:
[OK] Score: 1.23 | X:0.523 Y:0.267 Z:0.112
[OK] Score: 0.89 | X:0.478 Y:0.312 Z:0.098
[ANOMALY] Score: 3.42 | X:1.234 Y:0.876 Z:0.345
[OK] Score: 1.56 | X:0.612 Y:0.423 Z:0.156
Cross-References
- Edge Computing
- ESP32
- IoT Overview
- IoT Security
- IoT Gateways
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro