Skip to content

Commit 2edc733

Browse files
committed
add support to produce C code
1 parent 6c6110d commit 2edc733

13 files changed

+178
-72
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11

22
# EmbML
33

4-
EmbML is a tool written in Python to automatically convert off-board-trained models into C++ source code files that can be compiled and executed in low-power microcontrollers. The main goal of EmbML is to produce classifier source codes that will run specifically in unresourceful hardware systems, using bare metal programming.
4+
EmbML is a tool written in Python to automatically convert off-board-trained models into C++ or C (C++ is the default option) source code files that can be compiled and executed in low-power microcontrollers. The main goal of EmbML is to produce classifier source codes that will run specifically in resource-constrained hardware systems, using bare metal programming.
55

6-
This tool takes as input a classification model that was trained in a desktop or server computer using WEKA or scikit-learn libraries. EmbML is responsible for converting the input model into a carefully crafted C++ code with support for embedded hardware, such as the avoidance of unnecessary use of main memory and implementation of fixed-point operations for non-integer numbers.
6+
This tool takes as input a classification model that was trained in a desktop or server computer using WEKA or scikit-learn libraries. EmbML is responsible for converting the input model into a carefully crafted code in C++ or C with support for embedded hardware, such as the avoidance of unnecessary use of SRAM memory and implementation of fixed-point operations for non-integer numbers.
77

88
## Input Models
99

@@ -35,7 +35,7 @@ You can install `embml` from [PyPi](https://pypi.org/project/embml/):
3535
pip install embml
3636
```
3737

38-
This tool is supported on Python 2.7 and Python 3.5 versions, and depends on the `javaobj` library.
38+
This tool is supported on Python 2.7 and Python 3.5 versions, and depends on the `javaobj` library (<https://pypi.org/project/javaobj-py3/>).
3939

4040
## How To Use
4141

@@ -58,6 +58,10 @@ embml.wekaModel(inputModel, outputFile, opts)
5858
embml.wekaModel(inputDecisionTreeModel, outputFile, opts='-rules')
5959
embml.sklearnModel(inputDecisionTreeModel, outputFile, opts='-rules')
6060

61+
# Examples of generating classifier codes in C programming language.
62+
embml.wekaModel(inputModel, outputFile, opts='-c')
63+
embml.sklearnModel(inputModel, outputFile, opts='-c')
64+
6165
# Examples of generating classifier codes using fixed-point formats.
6266
embml.wekaModel(inputModel, outputFile, opts='-fxp 21 10') # Q21.10
6367
embml.sklearnModel(inputModel, outputFile, opts='-fxp 21 10') # Q21.10

embml/embml.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def processOptions(opt):
2525
exit(1)
2626

2727
opts['rules'] = ('-rules' in opt.split())
28+
29+
opts['C'] = ('-c' in opt.split())
2830

2931
opts['sigApprox'] = ('-sigApprox' in opt.split())
3032
opts['pwl'] = ('-sigPwl' in opt.split())

embml/sklearnModels/embml_sklearn_LinearSVC.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,15 @@ def write_output(classifier, opts):
2626
# Classify function
2727
funcs += utils.write_func_init("int", "classify") + \
2828
utils.write_dec("int", "indMax", initValue="0", tabs=1) + \
29-
utils.write_dec(decType, "scores[NUM_CLASSES]", tabs=1) + \
30-
utils.write_for("i = 0", "i < NUM_CLASSES", "i++", tabs=1) + \
29+
utils.write_dec(decType, "scores[NUM_CLASSES]", tabs=1)
30+
31+
if opts['C']:
32+
funcs += utils.write_dec("int", "i", tabs=1) + \
33+
utils.write_dec("int", "j", tabs=1)
34+
35+
funcs += utils.write_for("i = 0", "i < NUM_CLASSES", "i++", tabs=1, inC=opts['C']) + \
3136
utils.write_attribution("scores[i]", "intercept[i]", tabs=2) + \
32-
utils.write_for("j = 0", "j < INPUT_SIZE", "j++", tabs=2) + \
37+
utils.write_for("j = 0", "j < INPUT_SIZE", "j++", tabs=2, inC=opts['C']) + \
3338
utils.write_attribution("scores[i]", \
3439
("fxp_sum(scores[i], fxp_mul(coef[i][j], instance[j]))" \
3540
if opts['useFxp'] else \

embml/sklearnModels/embml_sklearn_LogisticRegression.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,15 @@ def write_output(classifier, opts):
2626
# Classify function
2727
funcs += utils.write_func_init("int", "classify") + \
2828
utils.write_dec("int", "indMax", initValue="0", tabs=1) + \
29-
utils.write_dec(decType, "scores[NUM_CLASSES]", tabs=1) + \
30-
utils.write_for("i = 0", "i < NUM_CLASSES", "i++", tabs=1) + \
29+
utils.write_dec(decType, "scores[NUM_CLASSES]", tabs=1)
30+
31+
if opts['C']:
32+
funcs += utils.write_dec("int", "i", tabs=1) + \
33+
utils.write_dec("int", "j", tabs=1)
34+
35+
funcs += utils.write_for("i = 0", "i < NUM_CLASSES", "i++", tabs=1, inC=opts['C']) + \
3136
utils.write_attribution("scores[i]", "intercept[i]", tabs=2) + \
32-
utils.write_for("j = 0", "j < INPUT_SIZE", "j++", tabs=2) + \
37+
utils.write_for("j = 0", "j < INPUT_SIZE", "j++", tabs=2, inC=opts['C']) + \
3338
utils.write_attribution("scores[i]", \
3439
("fxp_sum(scores[i], fxp_mul(coef[i][j], instance[j]))" \
3540
if opts['useFxp'] else \

embml/sklearnModels/embml_sklearn_MLPClassifier.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,13 @@ def write_output(classifier, opts):
153153
"const " + decType + " *coef, " + \
154154
"const " + decType + " *intercept, " + \
155155
"const int inputSize, const int outputSize") + \
156-
utils.write_dec("int", "i", initValue="0", tabs=1) + \
157-
utils.write_for("j = 0", "j < outputSize", "j++", tabs=1) + \
156+
utils.write_dec("int", "i", initValue="0", tabs=1)
157+
158+
if opts['C']:
159+
funcs += utils.write_dec("int", "j", tabs=1) + \
160+
utils.write_dec("int", "k", tabs=1)
161+
162+
funcs += utils.write_for("j = 0", "j < outputSize", "j++", tabs=1, inC=opts['C']) + \
158163
utils.write_dec(decType, "acc", initValue=(utils.toFxp(0.0, opts) \
159164
if opts['useFxp'] else \
160165
"0.0"), tabs=2) + \
@@ -177,14 +182,19 @@ def write_output(classifier, opts):
177182
# Classify function
178183
funcs += utils.write_func_init("int", "classify") + \
179184
utils.write_dec(decType, "*input", initValue="buffer1", tabs=1) + \
180-
utils.write_dec(decType, "*output", initValue="buffer2", tabs=1) + \
181-
utils.write_for("i = 0", "i < INPUT_SIZE", "i++", tabs=1) + \
185+
utils.write_dec(decType, "*output", initValue="buffer2", tabs=1)
186+
187+
if opts['C']:
188+
funcs += utils.write_dec("int", "i", tabs=1) + \
189+
utils.write_dec("int", "j", tabs=1)
190+
191+
funcs += utils.write_for("i = 0", "i < INPUT_SIZE", "i++", tabs=1, inC=opts['C']) + \
182192
utils.write_attribution("input[i]", "instance[i]", tabs=2) + \
183193
utils.write_end(tabs=1) + \
184-
utils.write_for("i = 0", "i < N_LAYERS - 1", "i++", tabs=1) + \
194+
utils.write_for("i = 0", "i < N_LAYERS - 1", "i++", tabs=1, inC=opts['C']) + \
185195
utils.write_call("forward_pass(input, output, coefs[i], intercepts[i], sizes[i], sizes[i + 1])", tabs=2) + \
186196
utils.write_if("(i + 1) != (N_LAYERS - 1)", tabs=2) + \
187-
utils.write_for("j = 0", "j < sizes[i + 1]", "j++", tabs=3) + \
197+
utils.write_for("j = 0", "j < sizes[i + 1]", "j++", tabs=3, inC=opts['C']) + \
188198
utils.write_attribution("output[j]", "activation_hidden(output[j])", tabs=4) + \
189199
utils.write_end(tabs=3) + \
190200
utils.write_dec(decType, "*tmp", initValue="input", tabs=3) + \
@@ -193,12 +203,12 @@ def write_output(classifier, opts):
193203
utils.write_end(tabs=2) + \
194204
utils.write_else(tabs=2) + \
195205
utils.write_dec(decType, "max_output", initValue="output[0]", tabs=3) + \
196-
utils.write_for("j = 1", "j < sizes[i + 1]", "j++", tabs=3) + \
206+
utils.write_for("j = 1", "j < sizes[i + 1]", "j++", tabs=3, inC=opts['C']) + \
197207
utils.write_if("output[j] > max_output", tabs=4) + \
198208
utils.write_attribution("max_output", "output[j]", tabs=5) + \
199209
utils.write_end(tabs=4) + \
200210
utils.write_end(tabs=3) + \
201-
utils.write_for("j = 0", "j < sizes[i + 1]", "j++", tabs=3) + \
211+
utils.write_for("j = 0", "j < sizes[i + 1]", "j++", tabs=3, inC=opts['C']) + \
202212
utils.write_attribution("output[j]", "activation_output(output[j], max_output)", tabs=4) + \
203213
utils.write_end(tabs=3) + \
204214
utils.write_end(tabs=2) + \
@@ -211,7 +221,7 @@ def write_output(classifier, opts):
211221
else:
212222
funcs += utils.write_dec("int", "indMax", "0", tabs=1) + \
213223
utils.write_for("i = 0", \
214-
"i < sizes[N_LAYERS - 1]", "i++", tabs=1) + \
224+
"i < sizes[N_LAYERS - 1]", "i++", tabs=1, inC=opts['C']) + \
215225
utils.write_if("output[i] > output[indMax]", tabs=2) + \
216226
utils.write_attribution("indMax", "i", tabs=3) + \
217227
utils.write_end(tabs=2) + \

embml/sklearnModels/embml_sklearn_SVC_Kernel.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,12 @@ def process(self, model, opts):
6464
def kernelFunction(kernel, decType, opts):
6565
funcCode = utils.write_dec(decType, "sum", (utils.toFxp(0, opts)\
6666
if opts['useFxp'] else \
67-
"0.0"), tabs=1) + \
68-
utils.write_for("i = 0", "i < INPUT_SIZE", "i++", tabs=1)
67+
"0.0"), tabs=1)
68+
69+
if opts['C']:
70+
funcCode += utils.write_dec("int", "i", tabs=1)
71+
72+
funcCode += utils.write_for("i = 0", "i < INPUT_SIZE", "i++", tabs=1, inC=opts['C'])
6973

7074
if kernel == 'linear':
7175
return funcCode + \
@@ -135,8 +139,14 @@ def write_output(classifier, opts):
135139

136140
# Classify function
137141
funcs += utils.write_func_init("int", "classify") + \
138-
utils.write_dec(decType, "k_value[MODEL_L]", tabs=1) + \
139-
utils.write_for("i = 0", "i < MODEL_L", "i++", tabs=1) + \
142+
utils.write_dec(decType, "k_value[MODEL_L]", tabs=1)
143+
144+
if opts['C']:
145+
funcs += utils.write_dec("int", "i", tabs=1) + \
146+
utils.write_dec("int", "j", tabs=1) + \
147+
utils.write_dec("int", "k", tabs=1)
148+
149+
funcs += utils.write_for("i = 0", "i < MODEL_L", "i++", tabs=1, inC=opts['C']) + \
140150
utils.write_attribution("k_value[i]",
141151
"k_function(support_vectors[i])",
142152
tabs=2) + \
@@ -146,13 +156,13 @@ def write_output(classifier, opts):
146156
"{0}", tabs=1) + \
147157
utils.write_dec(utils.chooseDataType(classifier.n_class),
148158
"p", "0", tabs=1) + \
149-
utils.write_for("i = 0", "i < NR_CLASS - 1", "i++", tabs=1) + \
150-
utils.write_for("j = i + 1", "j < NR_CLASS", "j++", tabs=2) + \
159+
utils.write_for("i = 0", "i < NR_CLASS - 1", "i++", tabs=1, inC=opts['C']) + \
160+
utils.write_for("j = i + 1", "j < NR_CLASS", "j++", tabs=2, inC=opts['C']) + \
151161
utils.write_dec(decType, "sum", (utils.toFxp(0, opts)\
152162
if opts['useFxp'] else \
153163
"0.0"), tabs=3) + \
154164
utils.write_for("k = end[j - 1][i]", \
155-
"k < end[j - 1][i + 1]", "k++", tabs=3) + \
165+
"k < end[j - 1][i + 1]", "k++", tabs=3, inC=opts['C']) + \
156166
utils.write_attribution("sum",
157167
("fxp_sum(sum, fxp_mul(dual_coef[j - 1][k], k_value[index_sv[j - 1][k]]))"\
158168
if opts['useFxp'] else\
@@ -161,7 +171,7 @@ def write_output(classifier, opts):
161171
tabs=4) + \
162172
utils.write_end(tabs=3) + \
163173
utils.write_for("k = end[i][j]", \
164-
"k < end[i][j + 1]", "k++", tabs=3) + \
174+
"k < end[i][j + 1]", "k++", tabs=3, inC=opts['C']) + \
165175
utils.write_attribution("sum",
166176
("fxp_sum(sum, fxp_mul(dual_coef[i][k], k_value[index_sv[i][k]]))"\
167177
if opts['useFxp'] else\
@@ -184,7 +194,7 @@ def write_output(classifier, opts):
184194
utils.write_end(tabs=2) + \
185195
utils.write_end(tabs=1) + \
186196
utils.write_dec("int", "indMax", "0", tabs=1) + \
187-
utils.write_for("i = 1", "i < NR_CLASS", "i++", tabs=1) + \
197+
utils.write_for("i = 1", "i < NR_CLASS", "i++", tabs=1, inC=opts['C']) + \
188198
utils.write_if("vote[i] > vote[indMax]", tabs=2) + \
189199
utils.write_attribution("indMax", "i", tabs=3) + \
190200
utils.write_end(tabs=2) + \

embml/utils/utils.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@ def write_end(tabs=0):
1616
def write_while(condition, tabs=0):
1717
return ('\t' * tabs) + 'while (' + condition + '){\n'
1818

19-
def write_for(var, condition, inc, tabs=0):
20-
return ('\t' * tabs) + 'for (int ' + var + \
21-
'; ' + condition + '; ' + inc + '){\n'
19+
def write_for(var, condition, inc, tabs=0, inC=False):
20+
if inC:
21+
return ('\t' * tabs) + 'for (' + var + \
22+
'; ' + condition + '; ' + inc + '){\n'
23+
else:
24+
return ('\t' * tabs) + 'for (int ' + var + \
25+
'; ' + condition + '; ' + inc + '){\n'
2226

2327
def write_if(condition, tabs=0):
2428
return ('\t' * tabs) + 'if (' + condition + '){\n'

embml/wekaModels/embml_weka_Logistic.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,17 @@ def write_output(classifier, opts):
7272
funcs += utils.write_output_classes(classifier.classes) + '\n' + \
7373
utils.write_func_init("int", "classify") + \
7474
utils.write_dec(decType, "prob[NUM_CLASSES]", tabs=1) + \
75-
utils.write_dec(decType, "newInstance[NUM_PREDICTORS + 1]", tabs=1) + \
76-
utils.write_attribution("newInstance[0]", \
75+
utils.write_dec(decType, "newInstance[NUM_PREDICTORS + 1]", tabs=1)
76+
77+
if opts['C']:
78+
funcs += utils.write_dec("int", "i", tabs=1) + \
79+
utils.write_dec("int", "j", tabs=1)
80+
81+
funcs += utils.write_attribution("newInstance[0]", \
7782
(utils.toFxp(1.0, opts) \
7883
if opts['useFxp'] else \
7984
"1.0"), tabs=1) + \
80-
utils.write_for("i = 1", "i <= SELECTED_ATT_SIZE", "i++", tabs=1) + \
85+
utils.write_for("i = 1", "i <= SELECTED_ATT_SIZE", "i++", tabs=1, inC=opts['C']) + \
8186
utils.write_if("m_SelectedAttributes[i] <= CLASS_INDEX", tabs=2) + \
8287
utils.write_attribution("newInstance[i]", \
8388
"instance[m_SelectedAttributes[i - 1]]", \
@@ -93,8 +98,8 @@ def write_output(classifier, opts):
9398
"v[NUM_CLASSES]", \
9499
initValue="{0}", \
95100
tabs=1) + \
96-
utils.write_for("i = 0", "i < NUM_CLASSES - 1", "i++", tabs=1) + \
97-
utils.write_for("j = 0", "j <= NUM_PREDICTORS", "j++", tabs=2) + \
101+
utils.write_for("i = 0", "i < NUM_CLASSES - 1", "i++", tabs=1, inC=opts['C']) + \
102+
utils.write_for("j = 0", "j <= NUM_PREDICTORS", "j++", tabs=2, inC=opts['C']) + \
98103
utils.write_attribution("v[i]", \
99104
("fxp_sum(v[i], fxp_mul(m_Par[(i * (NUM_PREDICTORS + 1)) + j], newInstance[j]))" \
100105
if opts['useFxp'] else \
@@ -103,10 +108,10 @@ def write_output(classifier, opts):
103108
utils.write_end(tabs=2) + \
104109
utils.write_end(tabs=1) + \
105110
utils.write_attribution("v[NUM_CLASSES - 1]", "0", tabs=1) + \
106-
utils.write_for("i = 0", "i < NUM_CLASSES", "i++", tabs=1) + \
111+
utils.write_for("i = 0", "i < NUM_CLASSES", "i++", tabs=1, inC=opts['C']) + \
107112
utils.write_dec(decType, "acc", \
108113
initValue=("0" if opts['useFxp'] else "0.0"), tabs=2) + \
109-
utils.write_for("j = 0", "j < NUM_CLASSES - 1", "j++", tabs=2) + \
114+
utils.write_for("j = 0", "j < NUM_CLASSES - 1", "j++", tabs=2, inC=opts['C']) + \
110115
utils.write_attribution("acc", \
111116
("fxp_sum(acc, fxp_exp(fxp_diff(v[j], v[i])))" \
112117
if opts['useFxp'] else \
@@ -121,7 +126,7 @@ def write_output(classifier, opts):
121126
"1.0 / (acc + expf(-v[i]))"), tabs=2) + \
122127
utils.write_end(tabs=1) + \
123128
utils.write_dec("int", "indexMax", initValue="0", tabs=1) + \
124-
utils.write_for("i = 1", "i < NUM_CLASSES", "i++", tabs=1) + \
129+
utils.write_for("i = 1", "i < NUM_CLASSES", "i++", tabs=1, inC=opts['C']) + \
125130
utils.write_if("prob[i] > prob[indexMax]", tabs=2) + \
126131
utils.write_attribution("indexMax", "i", tabs=3) + \
127132
utils.write_end(tabs=2) + \

embml/wekaModels/embml_weka_MultilayerPerceptron.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,17 @@ def write_output(classifier, opts):
196196
"const " + decType + " *coef, " + \
197197
"const " + decType + " *intercept, " + \
198198
"const int inputSize, const int outputSize") + \
199-
utils.write_dec("int", "i", initValue="0", tabs=1) + \
200-
utils.write_for("j = 0", "j < outputSize", "j++", tabs=1) + \
199+
utils.write_dec("int", "i", initValue="0", tabs=1)
200+
201+
if opts['C']:
202+
funcs += utils.write_dec("int", "j", tabs=1) + \
203+
utils.write_dec("int", "k", tabs=1)
204+
205+
funcs += utils.write_for("j = 0", "j < outputSize", "j++", tabs=1, inC=opts['C']) + \
201206
utils.write_dec(decType, "acc", initValue=(utils.toFxp(0.0, opts) \
202207
if opts['useFxp'] else \
203208
"0.0"), tabs=2) + \
204-
utils.write_for("k = 0", "k < inputSize", "k++", tabs=2) + \
209+
utils.write_for("k = 0", "k < inputSize", "k++", tabs=2, inC=opts['C']) + \
205210
utils.write_attribution("acc", \
206211
"fxp_sum(acc, fxp_mul(coef[i++], input[k]))" \
207212
if opts['useFxp'] else \
@@ -219,8 +224,13 @@ def write_output(classifier, opts):
219224

220225
# Classify function
221226
funcs += utils.write_output_classes(classifier.classes) + '\n' + \
222-
utils.write_func_init("int", "classify") + \
223-
utils.write_for("i = 0", "i < INPUT_SIZE", "i++", tabs=1) + \
227+
utils.write_func_init("int", "classify")
228+
229+
if opts['C']:
230+
funcs += utils.write_dec("int", "i", tabs=1) + \
231+
utils.write_dec("int", "j", tabs=1)
232+
233+
funcs += utils.write_for("i = 0", "i < INPUT_SIZE", "i++", tabs=1, inC=opts['C']) + \
224234
utils.write_if("m_attributeRanges[i] != " + ("0"\
225235
if opts['useFxp'] else \
226236
"0.0"), tabs=2) + \
@@ -240,12 +250,12 @@ def write_output(classifier, opts):
240250
utils.write_end(tabs=1) + \
241251
utils.write_dec(decType, "*input", initValue="buffer1", tabs=1) + \
242252
utils.write_dec(decType, "*output", initValue="buffer2", tabs=1) + \
243-
utils.write_for("i = 0", "i < INPUT_SIZE", "i++", tabs=1) + \
253+
utils.write_for("i = 0", "i < INPUT_SIZE", "i++", tabs=1, inC=opts['C']) + \
244254
utils.write_attribution("input[i]", "instance[i]", tabs=2) + \
245255
utils.write_end(tabs=1) + \
246-
utils.write_for("i = 0", "i < N_LAYERS - 1", "i++", tabs=1) + \
256+
utils.write_for("i = 0", "i < N_LAYERS - 1", "i++", tabs=1, inC=opts['C']) + \
247257
utils.write_call("forward_pass(input, output, coefs[i], intercepts[i], sizes[i], sizes[i + 1])", tabs=2) + \
248-
utils.write_for("j = 0", "j < sizes[i + 1]", "j++", tabs=2) + \
258+
utils.write_for("j = 0", "j < sizes[i + 1]", "j++", tabs=2, inC=opts['C']) + \
249259
utils.write_attribution("output[j]", "activation_function(output[j])", tabs=3) + \
250260
utils.write_end(tabs=2) + \
251261
utils.write_if("(i + 1) != (N_LAYERS - 1)", tabs=2) + \
@@ -256,7 +266,7 @@ def write_output(classifier, opts):
256266
utils.write_end(tabs=1) + \
257267
utils.write_dec("int", "indMax", "0", tabs=1) + \
258268
utils.write_for("i = 0", \
259-
"i < sizes[N_LAYERS - 1]", "i++", tabs=1) + \
269+
"i < sizes[N_LAYERS - 1]", "i++", tabs=1, inC=opts['C']) + \
260270
utils.write_if("output[i] > output[indMax]", tabs=2) + \
261271
utils.write_attribution("indMax", "i", tabs=3) + \
262272
utils.write_end(tabs=2) + \

0 commit comments

Comments
 (0)