Multi-Format Export — C, C++, Arduino, MicroPython¶
The same trained model can be deployed to radically different platforms. This notebook exports a single classifier to all four supported output formats and compares their structure and size.
In [1]:
Copied!
# !pip install blackbox2c -q # Uncomment on Colab
# !pip install blackbox2c -q # Uncomment on Colab
In [2]:
Copied!
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from blackbox2c import convert, ConversionConfig
from blackbox2c.exporters import CppExporter, ArduinoExporter, MicroPythonExporter, create_exporter
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.3, random_state=42, stratify=iris.target
)
model = DecisionTreeClassifier(max_depth=4, random_state=42)
model.fit(X_train, y_train)
print(f"Model accuracy: {model.score(X_test, y_test):.4f}")
feature_names = ["sepal_length", "sepal_width", "petal_length", "petal_width"]
class_names = ["setosa", "versicolor", "virginica"]
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from blackbox2c import convert, ConversionConfig
from blackbox2c.exporters import CppExporter, ArduinoExporter, MicroPythonExporter, create_exporter
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.3, random_state=42, stratify=iris.target
)
model = DecisionTreeClassifier(max_depth=4, random_state=42)
model.fit(X_train, y_train)
print(f"Model accuracy: {model.score(X_test, y_test):.4f}")
feature_names = ["sepal_length", "sepal_width", "petal_length", "petal_width"]
class_names = ["setosa", "versicolor", "virginica"]
Model accuracy: 0.8889
1. Plain C (via convert())¶
In [3]:
Copied!
cfg_c = ConversionConfig(function_name="classify_iris", max_depth=4)
code_c = convert(model, X_train, X_test=X_test,
feature_names=feature_names, class_names=class_names, config=cfg_c)
print(code_c)
cfg_c = ConversionConfig(function_name="classify_iris", max_depth=4)
code_c = convert(model, X_train, X_test=X_test,
feature_names=feature_names, class_names=class_names, config=cfg_c)
print(code_c)
Starting conversion for model: DecisionTreeClassifier
Task: Classification, Features: 4, Classes: 3, Max depth: 4
[1/4] Extracting surrogate decision tree...
Model is already a decision tree, using directly.
[2/4] Optimizing decision rules...
Nodes: 13, Leaves: 7, Depth: 4
[3/4] Generating C code...
[4/4] Estimating code size...
Estimated FLASH: 150 bytes, RAM: 32 bytes
[OK] Conversion complete!
/*
* Auto-generated C code by BlackBox2C
*
* Model Information:
* - Input features: 4
* * - Output classes: 3
* - Precision: 8-bit
* - Fixed-point: No
*
* This code is optimized for embedded systems with limited resources.
*/
#include <stdint.h>
/* Class labels */
#define SETOSA 0
#define VERSICOLOR 1
#define VIRGINICA 2
/* Prediction function */
uint8_t classify_iris(float features[4]) {
if (features[2] <= 2.450000f) {
return 0;
} else {
if (features[3] <= 1.550000f) {
if (features[2] <= 4.950000f) {
return 1;
} else {
return 2;
}
} else {
if (features[3] <= 1.700000f) {
if (features[1] <= 2.850000f) {
return 1;
} else {
return 2;
}
} else {
if (features[2] <= 4.850000f) {
return 1;
} else {
return 2;
}
}
}
}
}
/*
* Usage Example:
*
* float input[4] = {...}; // Your feature values
* uint8_t result = classify_iris(input);
*
* Input features: sepal_length, sepal_width, petal_length, petal_width
* Output classes: setosa, versicolor, virginica
*/
2. C++ with class and namespace¶
In [4]:
Copied!
exporter_cpp = CppExporter(
function_name="predict",
class_name="IrisClassifier",
use_namespace=True,
namespace="ml",
)
code_cpp = exporter_cpp.generate(model, feature_names=feature_names, class_names=class_names)
print(code_cpp)
exporter_cpp = CppExporter(
function_name="predict",
class_name="IrisClassifier",
use_namespace=True,
namespace="ml",
)
code_cpp = exporter_cpp.generate(model, feature_names=feature_names, class_names=class_names)
print(code_cpp)
/*
* Auto-generated C++ code by BlackBox2C
*
* Model Information:
* - Input features: 4
* - Task: Classification
* - Language: C++11
* - Fixed-point: No
*
* This code uses modern C++ features for better type safety and performance.
*/
#include <cstdint>
#include <array>
#include <stdexcept>
#include <string>
namespace ml {
class IrisClassifier {
public:
// Feature names
static constexpr std::array<const char*, 4> FEATURE_NAMES = {
"sepal_length", "sepal_width", "petal_length", "petal_width"
};
// Class names
static constexpr std::array<const char*, 3> CLASS_NAMES = {
"setosa", "versicolor", "virginica"
};
// Prediction method
static uint8_t predict(const std::array<float, 4>& features) {
if (features[2] <= 2.450000f) {
return 0;
} else {
if (features[3] <= 1.550000f) {
if (features[2] <= 4.950000f) {
return 1;
} else {
return 2;
}
} else {
if (features[3] <= 1.700000f) {
if (features[1] <= 2.850000f) {
return 1;
} else {
return 2;
}
} else {
if (features[2] <= 4.850000f) {
return 1;
} else {
return 2;
}
}
}
}
}
// Get class name from prediction
static const char* get_class_name(uint8_t prediction) {
if (prediction >= 3) {
throw std::out_of_range("Invalid class index");
}
return CLASS_NAMES[prediction];
}
};
} // namespace ml
/*
* Usage Example:
*
* #include "predictor.hpp"
*
* int main() {
* std::array<float, 4> features = {...};
* auto result = ml::IrisClassifier::predict(features);
*
* std::cout << ml::IrisClassifier::get_class_name(result) << std::endl;
*
* return 0;
* }
*/
3. Arduino (.ino) — with PROGMEM¶
In [5]:
Copied!
exporter_ard = ArduinoExporter(function_name="classify_iris", use_progmem=True)
code_ard = exporter_ard.generate(model, feature_names=feature_names, class_names=class_names)
print(code_ard)
exporter_ard = ArduinoExporter(function_name="classify_iris", use_progmem=True)
code_ard = exporter_ard.generate(model, feature_names=feature_names, class_names=class_names)
print(code_ard)
/*
* Auto-generated Arduino code by BlackBox2C
*
* Model Information:
* - Input features: 4
* - Task: Classification
* - PROGMEM: Yes
* - Fixed-point: No
*
* Compatible with: Arduino Uno, Nano, Mega, ESP8266, ESP32, etc.
*/
#include <Arduino.h>
// Feature names
const char* const PROGMEM FEATURE_NAMES[] = {
"sepal_length",
"sepal_width",
"petal_length",
"petal_width",
};
// Class names
const char* const PROGMEM CLASS_NAMES[] = {
"setosa",
"versicolor",
"virginica",
};
// Prediction function
uint8_t classify_iris(float features[4]) {
if (features[2] <= 2.450000f) {
return 0;
} else {
if (features[3] <= 1.550000f) {
if (features[2] <= 4.950000f) {
return 1;
} else {
return 2;
}
} else {
if (features[3] <= 1.700000f) {
if (features[1] <= 2.850000f) {
return 1;
} else {
return 2;
}
} else {
if (features[2] <= 4.850000f) {
return 1;
} else {
return 2;
}
}
}
}
}
// Get class name from prediction
const char* get_class_name(uint8_t prediction) {
return CLASS_NAMES[prediction];
}
/*
* Arduino Sketch Example:
*
* void setup() {
* Serial.begin(9600);
* }
*
* void loop() {
* float features[4];
*
* // Read sensor values
* features[0] = analogRead(A0) / 1023.0;
* features[1] = analogRead(A1) / 1023.0;
* // ... fill other features
*
* // Make prediction
* uint8_t result = classify_iris(features);
*
* // Print result
* Serial.print("Prediction: ");
* Serial.println(get_class_name(result));
*
* delay(1000);
* }
*/
4. MicroPython¶
In [6]:
Copied!
exporter_mp = MicroPythonExporter(
function_name="predict",
class_name="IrisClassifier",
use_const=True,
)
code_mp = exporter_mp.generate(model, feature_names=feature_names, class_names=class_names)
print(code_mp)
exporter_mp = MicroPythonExporter(
function_name="predict",
class_name="IrisClassifier",
use_const=True,
)
code_mp = exporter_mp.generate(model, feature_names=feature_names, class_names=class_names)
print(code_mp)
"""
Auto-generated MicroPython code by BlackBox2C
Model Information:
- Input features: 4
- Task: Classification
- Memory optimization: Yes
Compatible with: ESP32, ESP8266, Raspberry Pi Pico, PyBoard, etc.
"""
from micropython import const
class IrisClassifier:
"""Decision tree predictor for classification."""
# Feature names
FEATURE_NAMES = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
# Class names
CLASS_NAMES = ['setosa', 'versicolor', 'virginica']
@staticmethod
def predict(features):
"""
Make a prediction.
Args:
features: List or tuple of 4 feature values
Returns:
Class index (int)
"""
if len(features) != 4:
raise ValueError(f"Expected 4 features, got {len(features)}")
if features[2] <= 2.450000:
return 0
else:
if features[3] <= 1.550000:
if features[2] <= 4.950000:
return 1
else:
return 2
else:
if features[3] <= 1.700000:
if features[1] <= 2.850000:
return 1
else:
return 2
else:
if features[2] <= 4.850000:
return 1
else:
return 2
@staticmethod
def get_class_name(prediction):
"""Get class name from prediction index."""
return Predictor.CLASS_NAMES[prediction]
"""
Usage Example:
from predictor import IrisClassifier
# Prepare features
features = [...] # 4 values
# Make prediction
result = IrisClassifier.predict(features)
# Print result
print("Predicted class:", IrisClassifier.get_class_name(result))
# Example with sensor readings
from machine import ADC
adc0 = ADC(0)
adc1 = ADC(1)
while True:
features = [
adc0.read() / 1023.0,
adc1.read() / 1023.0,
# ... other features
]
result = IrisClassifier.predict(features)
print("Prediction:", result)
time.sleep(1)
"""
5. Size comparison¶
In [7]:
Copied!
data = [
{"Format": "C (plain)", "Bytes": len(code_c), "Lines": code_c.count('\n'), "Platforms": "Any MCU with C compiler"},
{"Format": "C++ (class)", "Bytes": len(code_cpp), "Lines": code_cpp.count('\n'), "Platforms": "ARM Cortex, ESP32, Desktop"},
{"Format": "Arduino", "Bytes": len(code_ard), "Lines": code_ard.count('\n'), "Platforms": "Uno, Nano, ESP8266, ESP32"},
{"Format": "MicroPython", "Bytes": len(code_mp), "Lines": code_mp.count('\n'), "Platforms": "ESP32, Pi Pico, PyBoard"},
]
pd.DataFrame(data).set_index("Format")
data = [
{"Format": "C (plain)", "Bytes": len(code_c), "Lines": code_c.count('\n'), "Platforms": "Any MCU with C compiler"},
{"Format": "C++ (class)", "Bytes": len(code_cpp), "Lines": code_cpp.count('\n'), "Platforms": "ARM Cortex, ESP32, Desktop"},
{"Format": "Arduino", "Bytes": len(code_ard), "Lines": code_ard.count('\n'), "Platforms": "Uno, Nano, ESP8266, ESP32"},
{"Format": "MicroPython", "Bytes": len(code_mp), "Lines": code_mp.count('\n'), "Platforms": "ESP32, Pi Pico, PyBoard"},
]
pd.DataFrame(data).set_index("Format")
Out[7]:
| Bytes | Lines | Platforms | |
|---|---|---|---|
| Format | |||
| C (plain) | 1324 | 60 | Any MCU with C compiler |
| C++ (class) | 2115 | 85 | ARM Cortex, ESP32, Desktop |
| Arduino | 1900 | 92 | Uno, Nano, ESP8266, ESP32 |
| MicroPython | 2296 | 94 | ESP32, Pi Pico, PyBoard |
6. Factory pattern — export to all formats in a loop¶
In [8]:
Copied!
for fmt in ["cpp", "arduino", "micropython"]:
exp = create_exporter(fmt)
code = exp.generate(model, feature_names=feature_names, class_names=class_names)
print(f"{fmt.upper():<15} {len(code):>5} bytes {code.count(chr(10)):>4} lines")
for fmt in ["cpp", "arduino", "micropython"]:
exp = create_exporter(fmt)
code = exp.generate(model, feature_names=feature_names, class_names=class_names)
print(f"{fmt.upper():<15} {len(code):>5} bytes {code.count(chr(10)):>4} lines")
CPP 2100 bytes 85 lines ARDUINO 1888 bytes 92 lines MICROPYTHON 2271 bytes 94 lines
Platform selection guide¶
| Platform | Recommended format | Why |
|---|---|---|
| Arduino Uno / Nano (AVR) | Arduino | PROGMEM saves RAM; C++ classes add overhead |
| ESP32 / STM32 / Cortex-M | C or C++ | FPU available; C++ classes have negligible cost |
| Raspberry Pi Pico / ESP32+MicroPython | MicroPython | Native interpreter; fastest iteration |
| Linux / desktop validation | C or C++ | Easiest to integrate in CI test |
Using the C++ class in a sketch¶
#include "iris_classifier.hpp"
ml::IrisClassifier clf;
void loop() {
float features[4] = {readSepalL(), readSepalW(), readPetalL(), readPetalW()};
int label = clf.predict(features);
Serial.println(label); // 0=setosa, 1=versicolor, 2=virginica
}
Using the MicroPython class on an ESP32¶
from iris_classifier import IrisClassifier
clf = IrisClassifier()
features = [5.1, 3.5, 1.4, 0.2]
print(clf.predict(features)) # 'setosa'