JSON-configured model training, quantization, and export pipeline for ESP32 with ESP-DL and TFLite Micro
Project description
esp-easy-ai
English | 中文
esp-easy-ai is a JSON-driven workflow for building 1D AI models that run on ESP devices:
- dataset validation
- training
- PTQ INT8 quantization
- export as an ESP-IDF component + project template
The current release targets 1D only. 2D classification and YOLO detection are not in scope.
Supported Matrix
| Task | Dataset formats | Backends |
|---|---|---|
| 1D classification | windowed_per_class_csv, continuous_per_class_csv |
esp-dl, tflite-micro |
| 1D regression | windowed_regression_csv |
esp-dl, tflite-micro |
esp-dl: PyTorch + ESP-PPQtflite-micro: Keras + TFLite converter- Quantization is currently PTQ + INT8 only
- For
esp-dl, quantizationtargetcan beesp32s3(default) oresp32p4 - For
tflite-micro,quantization.targetis accepted but not chip-limited
Dataset Formats
- windowed_per_class_csv — one row = one full sample; one CSV per class, the file stem is the class name.
<root>/<train_dir>/<class>.csv <root>/<val_dir>/<class>.csv - windowed_regression_csv — one row = one full sample; feature columns and label columns share the row. Any CSV under
train_dir/val_diris picked up. - continuous_per_class_csv — one row = one timestamped point; one folder per class, each folder may hold multiple continuous-sequence CSVs. Training windows are cut with
window_size/window_stride.<root>/<train_dir>/<class>/seq_*.csv
The 1D input semantic is always [C, L]: C = num_features, L = sequence length.
Two Authoring Styles
Same top-level JSON keys, two ways of filling them in.
Simple mode
A preset model plus a three-field dataset block; everything else is auto-filled:
{
"backend": "esp-dl",
"task": { "input_dim": 1, "type": "classification" },
"dataset": {
"format": "windowed_per_class_csv",
"root": "/abs/path/to/dataset",
"num_features": 3
},
"model": { "preset": "tiny_conv_1d" },
"training": { "epochs": 200 },
"quantization": { "enable": true, "checkpoint": "best" }
}
Auto-filled in simple mode: project_name, output_dir, seed, input_shape, class_names, num_classes, default transforms / calibration / batch_size / num_workers, a full training block (Adam + CosineAnnealingLR + cross_entropy / mse), and a full quantization block.
Current presets: tiny_conv_1d, tiny_mlp_1d.
Advanced mode
Write the full layer stack under model.layers and a complete training block (optimizer / scheduler / loss / checkpoint).
{
"backend": "esp-dl",
"task": { "input_dim": 1, "type": "classification" },
"project_name": "my_1d_classifier",
"output_dir": "./outputs/my_1d_classifier",
"seed": 42,
"dataset": {
"format": "windowed_per_class_csv",
"root": "/abs/path/to/dataset",
"train_dir": "train",
"val_dir": "test",
"num_features": 3,
"feature_layout": "interleaved",
"has_header": false,
"input_shape": [3, 200],
"num_classes": 3,
"class_names": ["class_a", "class_b", "class_c"],
"transforms": { "train": "none", "val": "none" },
"batch_size": 32,
"num_workers": 4,
"calibration": { "source": "reuse_val", "num_samples": 100 }
},
"model": {
"layers": [
{ "type": "Conv1d", "in_channels": 3, "out_channels": 16, "kernel_size": 5, "padding": 2 },
{ "type": "ReLU" },
{ "type": "MaxPool1d", "kernel_size": 2 },
{ "type": "Conv1d", "in_channels": 16, "out_channels": 32, "kernel_size": 5, "padding": 2 },
{ "type": "ReLU" },
{ "type": "GlobalAvgPool1d" },
{ "type": "Linear", "in_features": 32, "out_features": 3 }
]
},
"training": {
"epochs": 200,
"optimizer": { "type": "Adam", "lr": 0.001, "weight_decay": 0.0001 },
"lr_scheduler": { "type": "CosineAnnealingLR", "T_max": 30 },
"loss": "cross_entropy",
"metrics": ["accuracy"],
"checkpoint": { "save_best": true, "monitor": "val_accuracy" },
"device": "auto"
},
"quantization": {
"enable": true,
"method": "ptq",
"dtype": "int8",
"target": "esp32s3",
"checkpoint": "best"
}
}
Field reference
Values accepted by each type / string field in the example above.
dataset
train_dir/val_dir— subdirectory names underroot. Defaults:"train"/"test".feature_layout—"interleaved"(default) or"grouped". Describes how multi-feature columns are arranged inside one windowed row.has_header— whether the first CSV row is a header. Defaultfalse.delimiter—","/"whitespace". Auto-detected per file when omitted.batch_size,num_workers— dataloader knobs. Defaults64/4.transforms— see below.calibration.source—"reuse_val"(default) or"reuse_train".
transforms
dataset.transforms takes per-split lists of transform items:
"transforms": {
"train": [{ "type": "Normalize" }],
"val": [{ "type": "Normalize" }]
}
Shorthand to disable a split: "none", null, or [].
Supported type values:
Normalize— ifmean/stdare omitted, stats are computed from the train split and reused for train / val / calibration / quantization. Manual{ "mean": ..., "std": ... }is also accepted.RescaleIdentity
training
optimizer.type—AdamorSGD. Any extra keys (e.g.lr,weight_decay,momentum) are forwarded to the backend's optimizer constructor.lr_scheduler.type—CosineAnnealingLRorStepLR. Extra keys (e.g.T_max,step_size,gamma) are forwarded.loss—cross_entropy,mse,l1, orhuber.metrics— any combination ofaccuracy,mae,rmse.checkpoint.monitor— a metric key prefixed withval_, e.g.val_accuracy,val_mae.device—auto(default),cuda, orcpu.
quantization
method—ptq(only supported value).dtype—int8(only supported value).target— whenbackend="esp-dl", must beesp32s3oresp32p4. Fortflite-micro, this field is accepted but not chip-limited.checkpoint—best(default) orlast. Selects which training checkpoint is fed into quantization.
model.layers
Supported type values:
Conv1d, Linear, BatchNorm1d, MaxPool1d, AvgPool1d, GlobalAvgPool1d, Flatten, Dropout, ReLU, ReLU6, LeakyReLU, Sigmoid, Tanh, Softmax.
Extra keys on each layer are forwarded as constructor kwargs (e.g. in_channels, out_channels, kernel_size, padding, stride).
Commands
uv run esp-easy-ai validate <config.json>
uv run esp-easy-ai train <config.json>
uv run esp-easy-ai quantize <config.json>
uv run esp-easy-ai export <config.json>
train/quantize/exportall run a fullvalidatefirst.quantizedoes not retrain; it reads existing checkpoints.exportneither retrains nor requantizes; it reads existing quantization artifacts.
Outputs
Training artifacts (<output_dir>/):
best_model.*(selected bytraining.checkpoint.monitor)last_model.*(final epoch)report.json
Quantization artifacts:
esp-dl:model_int8.espdl,espdl_test_report.jsontflite-micro:model_int8.tflite,tflite_test_report.json
Calibration uses the sample source configured in dataset.calibration with shuffle=false. For esp-dl, one calibration sample is randomly picked as the reference sample and recorded as test_input_index; the later export step reuses the same index.
Export
esp-easy-ai export generates a reference ESP-IDF component plus a companion ESP-IDF project template under <output_dir>/export/:
Component directory (<output_dir>/export/<component_name>/):
predict.h/predict.cpptest_data.h/test_data.cppCMakeLists.txt,idf_component.ymlexport_report.jsonesp-dl:model.espdl(embedded by the component's CMakeLists)tflite-micro:model.h/model.cpp(C arrays)
Project template (<output_dir>/export/):
CMakeLists.txt,sdkconfig.defaultsmain/CMakeLists.txt,main/idf_component.yml,main/main.cppesp-dlonly:partitions.csv(8 MB factory partition; assumes 16 MB flash + PSRAM, matching typical S3 / P4 modules)tflite-micro: nopartitions.csv, no flash-size / PSRAM defaults — IDF picks its own 2 MB-safe defaults so the template builds on C3 / C6 / H2 / S2 / S3 / P4 alike
predict.cpp runs on-device: Normalize → INT8-quantize or pass-through FLOAT based on the deployed tensor's real dtype → reorder layout when needed.
Input Layout Notes
ESP-DL (NCL → NLC)
Training / config semantics stay [N, C, L]; the esp-dl quantized deployed tensor may be [N, L, C]. The current 1D export template treats the deployed layout as NLC by default.
The JSON config does not change because of this. Layout decisions should follow the runtime tensor in the quantization artifacts, not input_shape from the JSON.
In addition, quantization.dtype = "int8" describes the quantization target, not the runtime input dtype of the deployed model. Always read the actual input dtype from .info / runtime tensors on the device side — the template branches automatically: INT8 input → Normalize + quantize; FLOAT input → Normalize + write FLOAT.
TFLite Micro ([C, L] → [L, C])
Training / config semantics stay [C, L]; the deployed TFLite Micro model is fed [L, C]. The exported test_input keeps the raw [C, L] semantics and the transpose happens inside predict.cpp.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file esp_easy_ai-0.1.0.dev1.tar.gz.
File metadata
- Download URL: esp_easy_ai-0.1.0.dev1.tar.gz
- Upload date:
- Size: 44.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.20 {"installer":{"name":"uv","version":"0.9.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1444f397c621828dfaca96869ce1b6133c22e94d7055d5a16679cf6edb1989cb
|
|
| MD5 |
f36270164194e3040aae9dc5f2658e19
|
|
| BLAKE2b-256 |
7e45977effd56a356285ea0b4a63316c4caa7bb2052c6ea354fafbaadd35f29e
|
File details
Details for the file esp_easy_ai-0.1.0.dev1-py3-none-any.whl.
File metadata
- Download URL: esp_easy_ai-0.1.0.dev1-py3-none-any.whl
- Upload date:
- Size: 61.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.20 {"installer":{"name":"uv","version":"0.9.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9816e8d4772eaeb92290303d86eb89c588eb207b54f7f28a55ffa4a06bd7bb3e
|
|
| MD5 |
313bc4051f80da2a2e2116e38b285278
|
|
| BLAKE2b-256 |
29a960af7e0c28d0173cf2bf5bf1d7ef5b19a0507025b14dd3958c413632a2bc
|