Pace (CNN): activity recognition from IMU windows on a microcontroller

Version 0.1.0 · 2026-10-20 · status: experimental · topic: Condition monitoring · task: activity

Usage: Commercial use permitted · licence Apache-2.0.

Experimental. The output format may change in a minor version before 1.0.0.

Summary

A small int8 convolutional network that classifies 2.56-second windows of accelerometer and gyroscope signals into six activities (walking, walking upstairs, walking downstairs, sitting, standing, lying). It runs on the zoo's int8 engine on microcontrollers.

pace-cnn belongs to the topic Condition monitoring (condition-monitoring), task activity, variant cnn. Trained from scratch. Not for:

  • Monitoring, evaluating or profiling persons
  • Fall detection or other uses people rely on for their safety
  • Transport-mode or vehicle classes

Quality

Quality is measured against the labels of the datasets named below, on test data not used for training. Reference: labels of the uci-har dataset. Benchmark: uci-har-v1.

Metric Value What it measures Reference Benchmark Items Date
int8_macro_f1 0.9229 int8 macro-F1 over the six activities, test subjects labels of the uci-har dataset uci-har-v1 2947 2026-10-20
int8_accuracy 0.923 int8 accuracy, test subjects labels of the uci-har dataset uci-har-v1 2947 2026-10-20
int8_f1_min_class 0.81 lowest int8 F1 of the six activities labels of the uci-har dataset uci-har-v1 2947 2026-10-20
int8_f1_walking 0.9867 int8 F1 of activity walking labels of the uci-har dataset uci-har-v1 2947 2026-10-20
int8_f1_walking_upstairs 0.9333 int8 F1 of activity walking upstairs labels of the uci-har dataset uci-har-v1 2947 2026-10-20
int8_f1_walking_downstairs 0.9589 int8 F1 of activity walking downstairs labels of the uci-har dataset uci-har-v1 2947 2026-10-20
int8_f1_sitting 0.81 int8 F1 of activity sitting labels of the uci-har dataset uci-har-v1 2947 2026-10-20
int8_f1_standing 0.8636 int8 F1 of activity standing labels of the uci-har dataset uci-har-v1 2947 2026-10-20
int8_f1_laying 0.9849 int8 F1 of activity laying labels of the uci-har dataset uci-har-v1 2947 2026-10-20
int8_macro_f1_worst_subject 0.6671 int8 macro-F1 of the worst test subject labels of the uci-har dataset uci-har-v1 9 2026-10-20
int8_macro_f1_subject_range 0.3329 best minus worst test subject, int8 macro-F1 labels of the uci-har dataset uci-har-v1 9 2026-10-20
int8_macro_f1_seed_min 0.9156 lowest int8 macro-F1 over the five seeds labels of the uci-har dataset uci-har-v1 5 2026-10-20
int8_macro_f1_seed_mean 0.925 mean int8 macro-F1 over the five seeds labels of the uci-har dataset uci-har-v1 5 2026-10-20
int8_macro_f1_seed_max 0.93 highest int8 macro-F1 over the five seeds labels of the uci-har dataset uci-har-v1 5 2026-10-20
float_macro_f1 0.9224 float macro-F1 of the same model before quantisation (informative) labels of the uci-har dataset uci-har-v1 2947 2026-10-20
float_accuracy 0.9226 float accuracy before quantisation (informative) labels of the uci-har dataset uci-har-v1 2947 2026-10-20
tflite_interpreter_mismatches 0 test windows where the int8 host reference and the TFLite reference interpreter disagree (deterministic check) labels of the uci-har dataset uci-har-v1 2947 2026-10-20

Speed and memory

Budget: 32 KB RAM, 128 KB flash on ESP32.

Metric Value Unit What it measures Measured on Items Date
latency_us 55034 us Median latency per inference over 100 samples (100 inferences, device BENCH timer) ESP32-D0WDQ6-V3 rev3 cores=2, 240 MHz (target esp32, board esp32-1) (real board) 100 2026-10-21
latency_mean_us 5.503e+04 us Mean latency per inference over 100 samples (100 inferences, device BENCH timer) ESP32-D0WDQ6-V3 rev3 cores=2, 240 MHz (target esp32, board esp32-1) (real board) 100 2026-10-21
latency_p99_us 55041 us 99th percentile latency per inference over 100 samples (device BENCH timer) ESP32-D0WDQ6-V3 rev3 cores=2, 240 MHz (target esp32, board esp32-1) (real board) 100 2026-10-21
flash_kb 18 KiB Model footprint in flash from the ELF symbols: model constant data (17018 B: weights, bias, multipliers, shifts, layer table, self-test vectors) plus int8 engine code (1399 B) ESP32-D0WDQ6-V3 rev3 cores=2, 240 MHz (target esp32, board esp32-1) (real board) 2026-10-21
ram_kb 5.2 KiB Model footprint in RAM: tensor arena (4096 B), input (1152 B) and output (6 B) buffers, engine static data (0 B) ESP32-D0WDQ6-V3 rev3 cores=2, 240 MHz (target esp32, board esp32-1) (real board) 2026-10-21
arena_bytes 4096 bytes Tensor arena of the model (two ping-pong buffers, QModel.arena_size) ESP32-D0WDQ6-V3 rev3 cores=2, 240 MHz (target esp32, board esp32-1) (real board) 2026-10-21
device_bit_exact_vectors 1 share Share of the model's golden vectors whose device output equals the bit-exact host reference (device SELFTEST) ESP32-D0WDQ6-V3 rev3 cores=2, 240 MHz (target esp32, board esp32-1) (real board) 8 2026-10-21
flash_kb_image_esp32 312.1 KiB Flash used by the whole firmware image (bench app, inference engine and all bench models incl. this one), from the PlatformIO build ESP32-D0WDQ6-V3 rev3 cores=2, 240 MHz (target esp32, board esp32-1) (real board) 2026-10-21
ram_kb_image_esp32 28.81 KiB Static RAM used by the whole firmware image (data + bss, incl. the shared tensor arena), from the PlatformIO build ESP32-D0WDQ6-V3 rev3 cores=2, 240 MHz (target esp32, board esp32-1) (real board) 2026-10-21
latency_us_esp32_qemu 2690 us Median latency per inference over 100 samples (100 inferences, device BENCH timer) ESP32-D0WDQ6 rev0 cores=2, 240 MHz in QEMU (emulator, target esp32) (emulator) 100 2026-10-21
latency_mean_us_esp32_qemu 2705 us Mean latency per inference over 100 samples (100 inferences, device BENCH timer) ESP32-D0WDQ6 rev0 cores=2, 240 MHz in QEMU (emulator, target esp32) (emulator) 100 2026-10-21
latency_us_esp8266 159315 us Median latency per inference over 100 samples (100 inferences, device BENCH timer) ESP8266 flash=4096KB, 80 MHz (target esp8266, board esp8266-1) (real board) 100 2026-10-21
latency_mean_us_esp8266 1.593e+05 us Mean latency per inference over 100 samples (100 inferences, device BENCH timer) ESP8266 flash=4096KB, 80 MHz (target esp8266, board esp8266-1) (real board) 100 2026-10-21
latency_us_host 106 us Median latency per inference over 100 samples (100 inferences, device BENCH timer) host simulator (native build, Darwin arm64) (simulator) 100 2026-10-20
latency_mean_us_host 163.7 us Mean latency per inference over 100 samples (100 inferences, device BENCH timer) host simulator (native build, Darwin arm64) (simulator) 100 2026-10-20

Limitations and risks

Experimental: not ready for production use.

Not met yet:

  • S5 Transfer to mobility data tested: not tested: trained and measured on UCI HAR only

Known limits in short:

  • Waist-worn sensor only; no vehicle or transport-mode classes.
  • Sitting and standing are often confused (int8 F1 0.81 and 0.8636).
  • Results vary between persons: worst test subject macro-F1 0.6671.

All limitations:

  • Trained and measured on UCI HAR only: a smartphone worn at the waist by the volunteers of one study. Other placements, sensors, sample rates and populations are untested, and the model cannot detect a wrong input; it always returns six scores.
  • No vehicle or transport-mode classes. Whether waist-worn activity windows are a useful base for micromobility and manoeuvre detection (the task's riskiest assumption) is not tested, which keeps this release experimental.
  • Sitting and standing are the classes most often confused: int8 F1 0.81 for sitting and 0.8636 for standing.
  • Results vary between persons: the worst test subject reaches an int8 macro-F1 of 0.6671, against 0.9229 over all test subjects.
  • The input preparation values the dataset leaves open (median length, gravity filter order, causal filtering) were chosen by the zoo; windows prepared differently can change the results.
  • Energy use was not measured; emulator and host values in the speed table are informative only.

Intended use

A research and teaching baseline for classifying windows of a waist-worn accelerometer and gyroscope into six activities on a microcontroller, and a first step towards pedestrian, micromobility and manoeuvre detection with the same int8 pipeline. Use it to study motion classification on small devices, to test an IMU pipeline end to end, or as a measured reference for your own models.

Out-of-scope use

  • Monitoring or evaluating persons at work, or decisions about access to insurance, credit or services based on a person's movement.
  • Identifying or profiling persons from their movement.
  • Fall detection or any other use that people rely on for their safety; the model was not developed or tested for that.
  • Transport-mode or vehicle classes (car, bus, bicycle, e-scooter): the training data has none.
  • Sensors worn elsewhere than the waist, other sample rates or units without the preparation of the input contract.

How to run it

Requires Python 3.12 or newer.

pip install "mobility-model-zoo[edge] @ git+https://github.com/mhabedank/mobility-model-zoo@pace-cnn/v0.1.0"
import json

import numpy as np
from huggingface_hub import hf_hub_download

from mobility_model_zoo.edge.int8.model import QModel
from mobility_model_zoo.edge.int8.reference import run_model

repo, revision = "mobility-model-zoo/pace-cnn", "v0.1.0"
model = QModel.load(hf_hub_download(repo, "pace-cnn.npz", revision=revision))
example = json.load(open(hf_hub_download(repo, "examples/example-1.json", revision=revision)))
scores = run_model(model, np.asarray(example["input"], dtype=np.int8))
# The example input is synthetic: it checks the int8 arithmetic, not an activity.
print(scores.tolist(), scores.tolist() == example["expected"])

Output:

[-128, -107, 116, -77, -96, -21] True

On the device (C, int8 engine of the zoo):

/* Sources from mobility-model-zoo/pace-cnn at v0.1.0: c/pace_cnn_model.c and c/microinfer.c with their headers. */
#include "pace_cnn_model.h"

static int8_t arena[PACE_CNN_ARENA_SIZE];
int8_t window[PACE_CNN_IN_SIZE];  /* 128 x 9 int8, prepared as config.json describes */
int8_t scores[PACE_CNN_OUT_SIZE]; /* six int8 scores in label order */
mi_invoke(&mi_model_pace_cnn, window, scores, arena, sizeof arena);

The tag v0.1.0 always points to this version. For strict reproducibility, pin the commit hash of that tag instead (revision="<commit>").

Input and output

Input: one window of 2.56 s of a 6-axis IMU worn at the waist, sampled at 50 Hz: 128 samples x 9 channels in the order body_acc xyz, body_gyro xyz, total_acc xyz, as 1,152 int8 values (row-major, sample t then channel c). Acceleration is in g (1 g = 9.80665 m/s²), angular velocity in rad/s. Consecutive windows overlap by 50 % (step 64 samples).

Preparation from raw readings (host reference mobility_model_zoo.condition_monitoring.activity.prepare, coefficients in config.json):

  1. a causal median filter over 3 samples, per axis;
  2. a causal 3rd-order Butterworth low-pass at 20 Hz, per axis (noise filter, as the dataset documents);
  3. gravity separation of the acceleration with a causal 3rd-order Butterworth low-pass at 0.3 Hz; body_acc = total_acc - gravity;
  4. per-channel normalisation with the mean and std of the training subjects (config.json, normalisation);
  5. int8 quantisation with the model's input scale and zero point (config.json, quantisation).

The median length, the order of the gravity filter and causal filtering are not stated by the dataset; the zoo chose them. The model itself was trained on the dataset's own preparation.

Output: six int8 scores in the label order walking, walking upstairs, walking downstairs, sitting, standing, laying (lying); the highest score is the predicted activity.

Examples

Example 1: example-1.json

Source: synthetic. Input (1152 int8 values; the first 24 of 1,152 are shown; the full input is examples/example-1.json):

[2, 89, 107, 35, 87, 2, 61, -59, 104, -50, -67, -118, 112, -109, -89, -124, -47, -84, -6, 80, -100, 38, -39, 105]

Expected output (bit-exact on the host reference and on the device):

[-128, -107, 116, -77, -96, -21]

Example 2: example-2.json

Source: synthetic. Input (1152 int8 values; the first 24 of 1,152 are shown; the full input is examples/example-2.json):

[-65, 23, -27, 18, 83, 98, -68, -104, 124, -59, 1, -28, 31, 106, 48, -110, 35, -13, -109, -7, -95, -125, 69, -19]

Expected output (bit-exact on the host reference and on the device):

[-98, -69, 113, -94, -112, -50]

Example 3: example-3.json

Source: synthetic. Input (1152 int8 values; the first 24 of 1,152 are shown; the full input is examples/example-3.json):

[-41, -16, -21, 109, 55, 127, -127, -22, 84, -29, -117, -30, -90, -37, 109, 28, -9, 35, -77, 42, -2, 71, 98, 41]

Expected output (bit-exact on the host reference and on the device):

[-128, -75, 127, -95, -128, -56]

Training data and attribution

pace-cnn 0.1.0 was trained on 1 published dataset, credited below as its licence requires.

Dataset Creators Source DOI Licence
Human Activity Recognition Using Smartphones J. Reyes-Ortiz, D. Anguita, A. Ghio, L. Oneto, X. Parra https://archive.ics.uci.edu/dataset/240/human+activity+recognition+using+smartphones 10.24432/C54S4K CC BY 4.0
  • Attribution: Human Activity Recognition Using Smartphones by J. Reyes-Ortiz, D. Anguita, A. Ghio, L. Oneto, X. Parra, UCI Machine Learning Repository, doi:10.24432/C54S4K (CC BY 4.0) Modifications: Converted to the common frame or feature format for training; not redistributed. The datasets are not published or redistributed with the model; download them from their providers. Their licences and attribution apply to them (project constitution, Principle VI); see the copyright policy.

Teacher and labeling models

None. The model learned only from the labels of its training dataset; no teacher or labeling model was used.

Privacy and personal data

The model was trained on motion signals (accelerometer and gyroscope) of 30 volunteers that the dataset authors published under numbers only. No data, no window and no subject number is part of this release, and per-subject results are reported by rank only, never under a subject number. The model returns scores for the inputs you give it and contains no training data. How the training data were processed and how to object: privacy notice. AI Act classification: pace-cnn 0.1.0. Contact: privacy@miskatonic-analytics.com.

Training recipe

Version history

Version Date Status Change Changes int8_macro_f1 int8_accuracy int8_f1_min_class
0.1.0 2026-10-20 experimental initial First release (feature 021): trained on the frozen benchmark uci-har-v1 with pre-registered criteria and five seeds; int8 results on the bit-exact host reference; experimental because the transfer to mobility data is not tested. 0.9229 0.923 0.81

License

Apache-2.0 Commercial use permitted · licence Apache-2.0.

Citation

@misc{mobility-model-zoo-pace-cnn,
  title  = {Pace (CNN): activity recognition from IMU windows on a microcontroller, mobility-model-zoo},
  author = {Habedank, Martin},
  year   = {2026},
  url    = {https://huggingface.co/mobility-model-zoo/pace-cnn}
}

About the zoo

Part of mobility-model-zoo, a collection of small, fast mobility models grouped by topic. Topic collection: Condition monitoring. Source code, recipes and release records: https://github.com/mhabedank/mobility-model-zoo.

Downloads last month
5
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Collection including mobility-model-zoo/pace-cnn

Evaluation results

  • int8 macro-F1 over the six activities, test subjects on uci-har-v1 (frozen)
    self-reported
    0.923
  • int8 accuracy, test subjects on uci-har-v1 (frozen)
    self-reported
    0.923
  • lowest int8 F1 of the six activities on uci-har-v1 (frozen)
    self-reported
    0.810
  • int8 F1 of activity walking on uci-har-v1 (frozen)
    self-reported
    0.987
  • int8 F1 of activity walking upstairs on uci-har-v1 (frozen)
    self-reported
    0.933
  • int8 F1 of activity walking downstairs on uci-har-v1 (frozen)
    self-reported
    0.959
  • int8 F1 of activity sitting on uci-har-v1 (frozen)
    self-reported
    0.810
  • int8 F1 of activity standing on uci-har-v1 (frozen)
    self-reported
    0.864