Skip to content
This repository was archived by the owner on May 25, 2024. It is now read-only.

Commit e0cbb27

Browse files
committed
fix
1 parent 0bb6d6a commit e0cbb27

15 files changed

+197
-20
lines changed

dist/micromlgen-0.7.tar.gz

-3.38 KB
Binary file not shown.

dist/micromlgen-0.8.tar.gz

3.4 KB
Binary file not shown.

micromlgen/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from micromlgen.micromlgen import port
1+
from micromlgen.micromlgen import port, port_pca
100 Bytes
Binary file not shown.

micromlgen/micromlgen.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,61 @@
44
from jinja2 import FileSystemLoader, Environment
55

66

7-
def port(clf, test_set=None, classmap=None, platform='arduino', **kwargs):
7+
def jinja(template_file, data):
8+
dir_path = os.path.dirname(os.path.realpath(__file__))
9+
loader = FileSystemLoader(dir_path + '/templates')
10+
template = Environment(loader=loader).get_template(template_file)
11+
code = template.render(data)
12+
code = re.sub(r'\n\s*\n', '\n', code)
13+
return code
14+
15+
16+
def port_pca(eigen_vectors, test_set=None, test_tolerance=0.01):
17+
return jinja('pca/pca.jinja', {
18+
'X_DIM': len(eigen_vectors[0]),
19+
'PCA_DIM': len(eigen_vectors),
20+
'F': {
21+
'round': round
22+
},
23+
'eigen_vectors': eigen_vectors,
24+
'X_test': test_set[0] if test_set is not None else None,
25+
'y_test': test_set[1] if test_set is not None else None,
26+
'TEST_TOLERANCE': test_tolerance
27+
})
28+
29+
30+
def port(clf,
31+
pca=None,
32+
test_set=None,
33+
classmap=None,
34+
platform='arduino',
35+
**kwargs):
836
assert type(clf).__name__ == 'SVC', 'Only sklearn.svm.SVC is supported for now'
937
support_v = clf.support_vectors_
38+
n_classes = len(clf.n_support_)
39+
pca_code = port_pca(pca) if pca is not None else ''
1040
template_data = {
1141
'KERNEL_TYPE': clf.kernel,
1242
'KERNEL_GAMMA': clf.gamma,
1343
'KERNEL_COEF': clf.coef0,
1444
'KERNEL_DEGREE': clf.degree,
1545
'FEATURES_DIM': len(support_v[0]),
1646
'VECTORS_COUNT': len(support_v),
17-
'CLASSES_COUNT': len(clf.n_support_),
18-
'DECISIONS_COUNT': factorial(len(clf.n_support_)),
47+
'CLASSES_COUNT': n_classes,
48+
'DECISIONS_COUNT': n_classes * (n_classes - 1) / 2,
49+
'ORIGINAL_FEATURES_DIM': len(support_v[0]) if pca is None else len(pca[0]),
1950
'support_v': support_v,
2051
'n_support': clf.n_support_,
2152
'intercepts': clf.intercept_,
2253
'coefs': clf.dual_coef_,
2354
'X': test_set[0] if test_set else None,
2455
'y': test_set[1] if test_set else None,
2556
'classmap': classmap,
57+
'pca_code': pca_code,
2658
'F': {
2759
'enumerate': enumerate,
60+
'round': round
2861
},
2962
'isAttiny': platform == 'attiny',
3063
}
31-
dir_path = os.path.dirname(os.path.realpath(__file__))
32-
print(dir_path)
33-
loader = FileSystemLoader(dir_path + '/templates')
34-
template = Environment(loader=loader).get_template('svm.jinja')
35-
code = template.render(template_data)
36-
code = re.sub(r'\n\s*\n', '\n', code)
37-
38-
return code
64+
return jinja('svm.jinja', template_data)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Scalar product of vectors
3+
*/
4+
double scalar_product(double *x, const int length, ...) {
5+
va_list w;
6+
double product = 0.0;
7+
8+
va_start(w, length);
9+
10+
for (uint16_t i = 0; i < length; i++)
11+
product += x[i] * va_arg(w, double);
12+
13+
return product;
14+
}

micromlgen/templates/compute_decisions.jinja

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
{% for j in range(i + 1, CLASSES_COUNT) %}
55
{% set start_i = n_support[:i].sum() %}
66
{% set start_j = n_support[:j].sum() %}
7-
decisions[{{ helpers.ii }}] = {{ intercepts[helpers.ii] }}
7+
decisions[{{ helpers.ii }}] = {{ F.round(intercepts[helpers.ii], 9) }}
88
{% for k in range(start_i, start_i + n_support[i]) %}
99
{% with coef=coefs[j-1][k] %}
1010
{% if coef == 1 %}
1111
+ kernels[{{ k }}]
1212
{% elif coef == -1 %}
1313
- kernels[{{ k }}]
1414
{% elif coef %}
15-
+ kernels[{{ k }}] * {{ coef }}
15+
+ kernels[{{ k }}] * {{ F.round(coef, 9) }}
1616
{% endif %}
1717
{% endwith %}
1818
{% endfor %}
@@ -23,7 +23,7 @@
2323
{% elif coef == -1 %}
2424
- kernels[{{ k }}]
2525
{% elif coef %}
26-
+ kernels[{{ k }}] * {{ coef }}
26+
+ kernels[{{ k }}] * {{ F.round(coef, 9) }}
2727
{% endif %}
2828
{% endwith %}
2929
{% endfor %};

micromlgen/templates/compute_kernels.jinja

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
{% for i, w in F.enumerate(support_v) %}
66
{% if isAttiny %}
7-
{% for j, wj in F.enumerate(w) %} w[{{ j }}] = {{ wj }}; {% endfor %}
7+
{% for j, wj in F.enumerate(w) %} w[{{ j }}] = {{ F.round(wj, 9) }}; {% endfor %}
88
kernels[{{ i }}] = compute_kernel(x, w);
99
{% else %}
10-
kernels[{{ i }}] = compute_kernel(x, {% for j, wj in F.enumerate(w) %} {% if j > 0 %},{% endif %} {{ wj }} {% endfor %});
10+
kernels[{{ i }}] = compute_kernel(x, {% for j, wj in F.enumerate(w) %} {% if j > 0 %},{% endif %} {{ F.round(wj, 9) }} {% endfor %});
1111
{% endif %}
1212
{% endfor %}

micromlgen/templates/pca/pca.jinja

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{% include '_scalar_product.jinja' %}
2+
3+
/**
4+
* This one overrides the original vector
5+
*/
6+
void pca(double x[{{ X_DIM }}]) {
7+
double u[{{ PCA_DIM }}];
8+
9+
{% for vector in eigen_vectors %}
10+
u[{{ loop.index - 1 }}] = scalar_product(x, {{ X_DIM }}, {% for vi in vector %} {% if loop.index > 1 %}, {% endif %} {{ F.round(vi, 9) }} {% endfor %});
11+
{% endfor %}
12+
13+
{% for vector in eigen_vectors %}
14+
x[{{ loop.index - 1 }}] = u[{{ loop.index - 1 }}];
15+
{% endfor %}
16+
17+
{% for i in range(eigen_vectors|length, X_DIM) %}
18+
x[{{ i }}] = 0;
19+
{% endfor %}
20+
}
21+
22+
{% include 'pca/self_test.jinja' %}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
double scalar_product(double x[{{ ORIGINAL_FEATURES_DIM }}], ...) {
2+
va_list w;
3+
double pca = 0.0;
4+
va_start(w, {{ ORIGINAL_FEATURES_DIM }});
5+
for (uint16_t i = 0; i < {{ ORIGINAL_FEATURES_DIM }}; i++)
6+
pca += x[i] * va_arg(w, double);
7+
return pca;
8+
}
9+
10+
/**
11+
* This one overrides the original vector
12+
*/
13+
void apply_pca(double x[{{ ORIGINAL_FEATURES_DIM }}]) {
14+
double u[{{ FEATURES_DIM }}];
15+
16+
{% for components in pca %}
17+
u[{{ loop.index - 1 }}] = scalar_product(x, {% for c in components %} {% if loop.index > 1 %}, {% endif %} {{ F.round(c, 7) }} {% endfor %});
18+
{% endfor %}
19+
20+
{% for components in pca %}
21+
x[{{ loop.index - 1 }}] = u[{{ loop.index - 1 }}];
22+
{% endfor %}
23+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{% if X_test is not none %}
2+
3+
/**
4+
*
5+
*/
6+
bool vector_equals(double actual[{{ X_DIM }}], double expected[{{ PCA_DIM }}], double tolerance) {
7+
for (uint16_t i = 0; i < {{ PCA_DIM }}; i++)
8+
if (abs(actual[i] - expected[i]) > tolerance)
9+
return false;
10+
11+
return true;
12+
}
13+
14+
/**
15+
* Test the pca reduction
16+
*/
17+
void pca_self_test() {
18+
uint16_t correct = 0;
19+
20+
double X[{{ X_test|length }}][{{ X_DIM }}] = {
21+
{% for x in X_test %}
22+
{% if loop.index > 1 %},{% endif %} { {% for xi in x %}{% if loop.index > 1 %},{% endif %} {{ xi }} {% endfor %} }
23+
{% endfor %}
24+
};
25+
26+
double y[{{ y_test|length }}][{{ PCA_DIM }}] = {
27+
{% for y in y_test %}
28+
{% if loop.index > 1 %},{% endif %} { {% for yi in y %}{% if loop.index > 1 %},{% endif %} {{ yi }} {% endfor %} }
29+
{% endfor %}
30+
};
31+
32+
for (int i = 0; i < {{ X_test|length }}; i++) {
33+
pca(X[i]);
34+
35+
bool equals = vector_equals(X[i], y[i], {{ TEST_TOLERANCE }});
36+
37+
Serial.print('#');
38+
Serial.print(i);
39+
Serial.print("\tStatus\t");
40+
Serial.print(equals ? "OK" : "ERR");
41+
Serial.println();
42+
43+
correct += equals ? 1 : 0;
44+
}
45+
46+
Serial.print("Run {{ X_test|length }} predictions. ");
47+
Serial.print(correct);
48+
Serial.print(" were OK (");
49+
Serial.print(100 * correct / {{ X_test|length }});
50+
Serial.print("%)");
51+
}
52+
53+
54+
void setup() {
55+
Serial.begin(115200);
56+
pca_self_test();
57+
}
58+
59+
void loop() {
60+
}
61+
62+
{% endif %}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
double scalar_product(double x[{{ ORIGINAL_FEATURES_DIM }}], ...) {
2+
va_list w;
3+
double pca = 0.0;
4+
va_start(w, {{ ORIGINAL_FEATURES_DIM }});
5+
for (uint16_t i = 0; i < {{ ORIGINAL_FEATURES_DIM }}; i++)
6+
pca += x[i] * va_arg(w, double);
7+
return pca;
8+
}
9+
10+
/**
11+
* This one overrides the original vector
12+
*/
13+
void apply_pca(double x[{{ ORIGINAL_FEATURES_DIM }}]) {
14+
double u[{{ FEATURES_DIM }}];
15+
16+
{% for components in pca %}
17+
u[{{ loop.index - 1 }}] = scalar_product(x, {% for c in components %} {% if loop.index > 1 %}, {% endif %} {{ c }} {% endfor %});
18+
{% endfor %}
19+
20+
{% for components in pca %}
21+
x[{{ loop.index - 1 }}] = u[{{ loop.index - 1 }}];
22+
{% endfor %}
23+
}

micromlgen/templates/self_test.jinja

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66
void self_test() {
77
int correct = 0;
8-
double X[{{ X|length }}][{{ FEATURES_DIM }}] = {
8+
double X[{{ X|length }}][{{ ORIGINAL_FEATURES_DIM }}] = {
99
{% for x in X %}
1010
{% if loop.index > 1 %},{% endif %} { {% for xi in x %}{% if loop.index > 1 %},{% endif %} {{ xi }} {% endfor %} }
1111
{% endfor %}

micromlgen/templates/svm.jinja

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma once
22

3+
{{ pca_code }}
4+
35
{% include 'kernel_function.jinja' %}
46

57
/**
@@ -10,6 +12,11 @@ int predict(double *x) {
1012
double decisions[{{ DECISIONS_COUNT }}] = { 0 };
1113
int votes[{{ CLASSES_COUNT }}] = { 0 };
1214

15+
{% if pca_code %}
16+
pca(x);
17+
{% endif %}
18+
19+
1320
{% include 'compute_kernels.jinja' %}
1421

1522
{% if CLASSES_COUNT == 2 %}

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
setup(
33
name = 'micromlgen',
44
packages = ['micromlgen'],
5-
version = '0.7',
5+
version = '0.8',
66
license='MIT',
77
description = 'Generate C code for microcontrollers from Python\'s sklearn classifiers',
88
author = 'Simone Salerno',
99
author_email = 'web@agrimag.it',
1010
url = 'https://github.com/agrimagsrl/micromlgen',
11-
download_url = 'https://github.com/agrimagsrl/micromlgen/archive/v_07.tar.gz',
11+
download_url = 'https://github.com/agrimagsrl/micromlgen/archive/v_08.tar.gz',
1212
keywords = ['ML', 'microcontrollers', 'sklearn', 'machine learning'],
1313
install_requires=[
1414
'jinja2',

0 commit comments

Comments
 (0)