Skip to content
This repository has been archived by the owner on Jan 15, 2020. It is now read-only.

Commit

Permalink
fix readme
Browse files Browse the repository at this point in the history
  • Loading branch information
agrimagsrl committed Dec 7, 2019
0 parents commit 98de2d0
Show file tree
Hide file tree
Showing 21 changed files with 370 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
venv
.idea
17 changes: 17 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
MIT License
Copyright (c) 2018 YOUR NAME
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
16 changes: 16 additions & 0 deletions MANIFEST
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# file GENERATED by distutils, do NOT edit
setup.cfg
setup.py
micromlgen/__init__.py
micromlgen/micromlgen.py
micromlgen/micromlgen_test.py
micromlgen/templates/binary_classification.jinja
micromlgen/templates/classmap.jinja
micromlgen/templates/compute_class.jinja
micromlgen/templates/compute_decisions.jinja
micromlgen/templates/compute_kernels.bck.jinja
micromlgen/templates/compute_kernels.jinja
micromlgen/templates/compute_votes.jinja
micromlgen/templates/kernel_function.jinja
micromlgen/templates/self_test.jinja
micromlgen/templates/svm.jinja
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Introducing MicroML

MicroML is an attempt to bring Machine Learning algorithms to microcontrollers.
Please refer to [this blog post](https://agrimagsrl.github.io/EloquentArduino/2019/11/introducing-microml/)
to an introduction to the topic.

## Install

`pip install micromlgen`

## Use

```python
from micromlgen import port
from sklearn.svm import SVC
from sklearn.datasets import load_iris


if __name__ == '__main__':
iris = load_iris()
X = iris.data
y = iris.target
clf = SVC(kernel='linear').fit(X, y)
print(port(clf))
```

You may pass a classmap to get readable class names in the ported code

```python
from micromlgen import port
from sklearn.svm import SVC
from sklearn.datasets import load_iris


if __name__ == '__main__':
iris = load_iris()
X = iris.data
y = iris.target
clf = SVC(kernel='linear').fit(X, y)
print(port(clf, classmap={
0: 'setosa',
1: 'virginica',
2: 'versicolor'
}))
```

You can pass a test set to generate self test code

```python
from micromlgen import port
from sklearn.svm import SVC
from sklearn.datasets import load_iris


if __name__ == '__main__':
iris = load_iris()
X_train, X_test = iris.data[:-10, :], iris.data[-10:, :]
y_train, y_test = iris.target[:-10], iris.target[-10:]
clf = SVC(kernel='linear').fit(X_train, y_train)
print(port(clf, test_set=(X_test, y_test)))
```
Binary file added dist/micromlgen-0.5.tar.gz
Binary file not shown.
1 change: 1 addition & 0 deletions micromlgen/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from micromlgen.micromlgen import port
37 changes: 37 additions & 0 deletions micromlgen/micromlgen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os
import re
from math import factorial
from jinja2 import FileSystemLoader, Environment


def port(clf, test_set=None, classmap=None, **kwargs):
assert type(clf).__name__ == 'SVC', 'Only sklearn.svm.SVC is supported for now'
support_v = clf.support_vectors_
template_data = {
'KERNEL_TYPE': clf.kernel,
'KERNEL_GAMMA': clf.gamma,
'KERNEL_COEF': clf.coef0,
'KERNEL_DEGREE': clf.degree,
'FEATURES_DIM': len(support_v[0]),
'VECTORS_COUNT': len(support_v),
'CLASSES_COUNT': len(clf.n_support_),
'DECISIONS_COUNT': factorial(len(clf.n_support_)),
'support_v': support_v,
'n_support': clf.n_support_,
'intercepts': clf.intercept_,
'coefs': clf.dual_coef_,
'X': test_set[0] if test_set else None,
'y': test_set[1] if test_set else None,
'classmap': classmap,
'F': {
'enumerate': enumerate,
}
}
dir_path = os.path.dirname(os.path.realpath(__file__))
print(dir_path)
loader = FileSystemLoader(dir_path + '/templates')
template = Environment(loader=loader).get_template('svm.jinja')
code = template.render(template_data)
code = re.sub(r'\n\s*\n', '\n', code)

return code
22 changes: 22 additions & 0 deletions micromlgen/micromlgen_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import pickle
from micromlgen import port
from sklearn.svm import SVC
from sklearn.datasets import load_iris


if __name__ == '__main__':
test_iris = True
if test_iris:
iris = load_iris()
X = iris.data
y = iris.target
clf = SVC(kernel='linear').fit(X, y)
print(port(clf))
else:
with open('../svmporter/datasets/svm.clf', 'rb') as file:
payload = pickle.load(file)
clf = payload['clf']
classmap = payload['classmap']
# test_set = (payload['X_test'], payload['y_test'])
print(port(clf, classmap=classmap))

6 changes: 6 additions & 0 deletions micromlgen/templates/binary_classification.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
double decision = 0;

decision = decision - ({% for i in range(0, n_support[0]) %} + kernels[{{ i }}] * {{ coefs[0][i] }} {% endfor %});
decision = decision - ({% for i in range(n_support[0], n_support[0] + n_support[1]) %} + kernels[{{ i }}] * {{ coefs[0][i] }} {% endfor %});

return decision > 0 ? 0 : 1;
16 changes: 16 additions & 0 deletions micromlgen/templates/classmap.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% if classmap is not none %}

/**
* Convert class idx to readable name
*/
const char* classIdxToName(uint8_t classIdx) {
switch (classIdx) {
{% for idx, name in classmap.items() %}
case {{ idx }}:
return "{{ name }}";
{% endfor %}
default:
return "UNKNOWN";
}
}
{% endif %}
11 changes: 11 additions & 0 deletions micromlgen/templates/compute_class.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
int classVal = -1;
int classIdx = -1;

for (int i = 0; i < {{ CLASSES_COUNT }}; i++) {
if (votes[i] > classVal) {
classVal = votes[i];
classIdx = i;
}
}

return classIdx;
32 changes: 32 additions & 0 deletions micromlgen/templates/compute_decisions.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{% set helpers = {'ii': 0} %}

{% for i in range(0, CLASSES_COUNT) %}
{% for j in range(i + 1, CLASSES_COUNT) %}
{% set start_i = n_support[:i].sum() %}
{% set start_j = n_support[:j].sum() %}
decisions[{{ helpers.ii }}] = {{ intercepts[helpers.ii] }}
{% for k in range(start_i, start_i + n_support[i]) %}
{% with coef=coefs[j-1][k] %}
{% if coef == 1 %}
+ kernels[{{ k }}]
{% elif coef == -1 %}
- kernels[{{ k }}]
{% elif coef %}
+ kernels[{{ k }}] * {{ coef }}
{% endif %}
{% endwith %}
{% endfor %}
{% for k in range(start_j, start_j + n_support[j]) %}
{% with coef=coefs[i][k] %}
{% if coef == 1 %}
+ kernels[{{ k }}]
{% elif coef == -1 %}
- kernels[{{ k }}]
{% elif coef %}
+ kernels[{{ k }}] * {{ coef }}
{% endif %}
{% endwith %}
{% endfor %};
{% if helpers.update({'ii': helpers.ii + 1}) %}{% endif %}
{% endfor %}
{% endfor %}
10 changes: 10 additions & 0 deletions micromlgen/templates/compute_kernels.bck.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% for i, v in F.enumerate(support_v) %}
{% for j in range(0, FEATURES_DIM) %}
chunkedSupportVectors[{{ i % CHUNK_SIZE }}][{{ j }}] = {{ v[j] }};
{% endfor %}

{% if (i + 1) % CHUNK_SIZE == 0 %}
compute_kernels(kernels, {{ i // CHUNK_SIZE * CHUNK_SIZE }}, x, chunkedSupportVectors);
{% endif %}

{% endfor %}
3 changes: 3 additions & 0 deletions micromlgen/templates/compute_kernels.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% for i, w in F.enumerate(support_v) %}
kernels[{{ i }}] = compute_kernel(x, {% for j, wj in F.enumerate(w) %} {% if j > 0 %},{% endif %} {{ wj }} {% endfor %});
{% endfor %}
8 changes: 8 additions & 0 deletions micromlgen/templates/compute_votes.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% set helpers = {'ii': 0} %}

{% for i in range(0, CLASSES_COUNT) %}
{% for j in range(i + 1, CLASSES_COUNT) %}
votes[decisions[{{ helpers.ii }}] > 0 ? {{ i }} : {{ j }}] += 1;
{% if helpers.update({'ii': helpers.ii + 1}) %}{% endif %}
{% endfor %}
{% endfor %}
29 changes: 29 additions & 0 deletions micromlgen/templates/kernel_function.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Compute kernel between feature vector and support vector.
* Kernel type: {{ KERNEL_TYPE }}
*/
double compute_kernel(double x[{{ FEATURES_DIM }}], ...) {
va_list w;
double kernel = 0.0;

va_start(w, {{ FEATURES_DIM }});

for (uint16_t i = 0; i < {{ FEATURES_DIM }}; i++)
{% if KERNEL_TYPE in ['linear', 'poly', 'sigmoid'] %}
kernel += x[i] * va_arg(w, double);
{% elif KERNEL_TYPE == 'rbf' %}
kernel += pow(x[i] - va_arg(w, double), 2);
{% else %}
#error "UNKNOWN KERNEL {{ kernel }}";
{% endif %}

{% if KERNEL_TYPE == 'poly' %}
kernel = pow((KERNEL_GAMMA * kernel) + KERNEL_COEF, KERNEL_DEGREE);
{% elif KERNEL_TYPE == 'rbf' %}
kernel = exp(-{{ KERNEL_GAMMA }} * kernel);
{% elif KERNEL_TYPE == 'sigmoid' %}
kernel = sigmoid((KERNEL_GAMMA * kernel) + KERNEL_COEF);
{% endif %}

return kernel;
}
37 changes: 37 additions & 0 deletions micromlgen/templates/self_test.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% if X is not none %}

/**
* Test the classifier performances on the test set
*/
void self_test() {
int correct = 0;
double X[{{ X|length }}][{{ FEATURES_DIM }}] = {
{% for x in X %}
{% if loop.index > 1 %},{% endif %} { {% for xi in x %}{% if loop.index > 1 %},{% endif %} {{ xi }} {% endfor %} }
{% endfor %}
};

int y[{{ X|length }}] = { {% for yi in y %}{% if loop.index > 1 %},{% endif %} {{ yi }} {% endfor %} };

for (int i = 0; i < {{ X|length }}; i++) {
int predicted = predict(X[i]);

Serial.print('#');
Serial.print(i);
Serial.print("\t Expected ");
Serial.print(y[i]);
Serial.print("\tGot ");
Serial.print(predicted);
Serial.print('\t');
Serial.print(predicted == y[i] ? "OK\n" : "ERR\n");

correct += (predicted == y[i]) ? 1 : 0;
}

Serial.print("Run {{ X|length }} predictions. ");
Serial.print(correct);
Serial.print(" were OK (");
Serial.print(100 * correct / {{ X|length }});
Serial.print("%)");
}
{% endif %}
25 changes: 25 additions & 0 deletions micromlgen/templates/svm.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

{% include 'kernel_function.jinja' %}

/**
* Predict class for features vector
*/
int predict(double *x) {
double kernels[{{ VECTORS_COUNT }}] = { 0 };
double decisions[{{ DECISIONS_COUNT }}] = { 0 };
int votes[{{ CLASSES_COUNT }}] = { 0 };

{% include 'compute_kernels.jinja' %}

{% if CLASSES_COUNT == 2 %}
{% include 'binary_classification.jinja' %}
{% else %}
{% include 'compute_decisions.jinja' %}
{% include 'compute_votes.jinja' %}
{% include 'compute_class.jinja' %}
{% endif %}
}

{% include 'self_test.jinja' %}
{% include 'classmap.jinja' %}
6 changes: 6 additions & 0 deletions publish
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

git push origin master -f
rm -rf dist/*
python setup.py sdist
twine upload dist/*
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[metadata]
description-file = README.md
Loading

0 comments on commit 98de2d0

Please sign in to comment.