Machine learning on Wi-Fi CSI is less about clever models and more about disciplined data. The classifier at the end is usually the easy part. The work that decides success is capturing clean CSI, cleaning it honestly, and turning it into features that actually describe what is happening in the room.
01The CSI machine-learning pipeline
Every CSI project, whether it recognises gestures or counts people, follows the same four steps: capture raw Channel State Information, clean it, extract features, and train a model. The diagram above is not decoration; it is the order you should spend your time in. Beginners reach for the deep network first and wonder why it fails. Experienced builders spend most of their effort on the first two boxes.
If you have not captured CSI before, read how to capture Wi-Fi CSI on an ESP32 first, and keep cleaning CSI data open for the second box. This article is about the whole toolchain and the choices that connect those steps into a working model.
02Tools to capture CSI
You can either capture your own CSI or use someone else's. For a project you control, capturing your own is almost always better, because you know the hardware, the room and the labels. A few families of tools exist:
- ESP32 CSI on Espressif chips. The modern, low-cost route. Espressif exposes CSI through a callback on the ESP32, ESP32-S3 and ESP32-C series, and community firmware such as the widely used ESP32 CSI tooling wraps that into something you can log. This is the easiest way to start on your own hardware.
- Nexmon on Broadcom chips. A firmware-patching framework historically used to extract CSI from certain Broadcom Wi-Fi chips, including those in some single-board computers. Powerful but hardware-specific and finicky to set up.
- Historical PC tools. Much published research used the Intel 5300 CSI Tool and the Atheros CSI Tool on older Wi-Fi cards and Linux drivers. They are worth knowing because so many datasets came from them, but the hardware is dated and these are reference points more than a recommended new setup.
03Public dataset types
Public CSI datasets exist and can save weeks, but their names, hosts and licences change over time, so this section describes categories rather than specific files. Do not trust a dataset name or URL quoted in any article, including this one — search current academic repositories and dataset indexes, then verify the link, licence and format yourself before you build on it.
The common categories you will find are:
- Activity recognition — labelled CSI for actions such as walking, sitting, standing and falling. Useful for activity recognition work.
- Gesture recognition — short hand or arm gestures captured close to the link, often with many repetitions per gesture.
- Occupancy and people counting — CSI labelled with how many people are present, for presence and counting models.
Two cautions. First, a dataset captured on an Intel 5300 card will not match your ESP32 capture, so mixing sources needs care. Second, published accuracy figures are tied to the exact dataset, split and hardware they were measured on; treat any single number as specific to its paper, not a promise for your room.
04Feature engineering
Raw CSI is complex-valued: a real and imaginary part per subcarrier, per packet. The first job is to turn that into stable, meaningful numbers. Amplitude (the magnitude of each subcarrier) is the workhorse and is far easier to use than raw phase, which is corrupted by timing and frequency offsets and usually needs sanitisation first — see cleaning CSI data for why.
A simple, effective starting point is to compute amplitude per subcarrier and then summarise a short time window with basic statistics:
# Minimal CSI feature extraction. # csi: int8 array per packet, interleaved [imag, real] per subcarrier, # shaped (packets, 2 * subcarriers) to match a common ESP32 layout. import numpy as np def amplitude(csi): imag = csi[:, 0::2] # even bytes = imaginary real = csi[:, 1::2] # odd bytes = real return np.sqrt(real.astype("float32")**2 + imag.astype("float32")**2) def window_features(csi_window): amp = amplitude(csi_window) # (packets, subcarriers) return np.concatenate([ amp.mean(axis=0), # mean per subcarrier amp.std(axis=0), # variability = motion energy [amp.std()], # one overall motion number ])
From there, useful additions include the variance over time (a strong motion cue), a spectrogram of amplitude over a window (which exposes the slow breathing band and the faster rhythm of walking), and selecting the most informative subcarriers while dropping dead ones. Keep features interpretable at first; you will debug far faster when you can see what each number means.
05Models to try, in order
Resist the urge to start deep. A sensible ladder:
- Baselines first. Train a support vector machine or a random forest on your hand-built features. These are fast, run on a CPU, and give an honest floor. If they cannot tell two classes apart, a deep net probably will not rescue bad data.
- Convolutional networks (CNN). Treat a window of CSI, or its spectrogram, as an image and let a CNN learn spatial-frequency patterns. A good step up once features plateau and you have enough varied data.
- Recurrent networks (LSTM/GRU). CSI is a time series, so recurrent layers, or a CNN feeding an LSTM, capture how a gesture or activity evolves over time. Powerful, but hungrier for data and compute.
Always split your data so the test set contains people, rooms or days the model never trained on. A random split that mixes the same session into train and test will flatter you badly, because the model learns the room, not the activity.
06Pitfalls that fool you
- Leaky splits. The single most common mistake. If frames from one recording land in both train and test, your accuracy is fiction. Split by session, subject or room.
- One-room overfitting. A model trained in a single room with one person often collapses elsewhere. Variety of conditions beats volume of near-identical data.
- Phase used raw. Unsanitised phase carries hardware offsets that have nothing to do with the room. Clean it or stick to amplitude.
- Mismatched hardware. Combining CSI from different chips or tools without aligning formats quietly poisons the dataset.
- Trusting a quoted benchmark. Accuracy numbers belong to a specific dataset and split. Reproduce them on your own data before you believe them.
Get the data right and modest models work well. Get it wrong and no architecture saves you. That is the honest heart of CSI machine learning: the pipeline, not the network, is where projects are won or lost.
07FAQ
Do I need a GPU to train on Wi-Fi CSI?
Not for the baselines. Classical models such as SVM and random forest on hand-built features train fine on a normal CPU. You only really want a GPU once you move to deep models like CNNs or LSTMs on raw or spectrogram inputs, where it saves a lot of time.
How much CSI data do I need to train a model?
More varied than large. A few hundred to a few thousand labelled examples per class can get a baseline working, but the bigger factor is variety: different people, rooms and days. A model trained in one room often fails in another, so breadth of conditions matters more than raw count.
Where can I find public Wi-Fi CSI datasets?
Several research groups and code repositories publish CSI datasets for activity, gesture and occupancy tasks, but names and links change over time. Search current academic repositories and dataset indexes, and verify any link, licence and format yourself before you rely on it, rather than trusting a URL quoted in an article.
Which model should I start with for CSI?
Start simple. Build clean features, then train an SVM or a random forest as a baseline. Only move to a CNN or LSTM once that baseline is honest and you have enough varied data, because a deep model on a tiny single-room dataset usually just memorises the room.