generated from microbit-foundation/micropython-cpp-module-example
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic ml MicroPython module and example ML4F model to run.
- Loading branch information
1 parent
0dcb8f7
commit ac6e522
Showing
12 changed files
with
2,135 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
################# | ||
# Project files # | ||
################# | ||
|
||
|
||
############ | ||
# OS files # | ||
############ | ||
# Windows thumbnail cache files | ||
Thumbs.db | ||
ehthumbs.db | ||
ehthumbs_vista.db | ||
|
||
# Windows Dump file | ||
*.stackdump | ||
|
||
# Windows Folder config file | ||
[Dd]esktop.ini | ||
|
||
# Windows Recycle Bin used on file shares | ||
$RECYCLE.BIN/ | ||
|
||
# Windows shortcuts | ||
*.lnk | ||
|
||
# macOS General | ||
.DS_Store | ||
.AppleDouble | ||
.LSOverride | ||
|
||
# macOS Thumbnails | ||
._* | ||
|
||
# macOS Files that might appear in the root of a volume | ||
.DocumentRevisions-V100 | ||
.fseventsd | ||
.Spotlight-V100 | ||
.TemporaryItems | ||
.Trashes | ||
.VolumeIcon.icns | ||
.com.apple.timemachine.donotpresent | ||
|
||
# macOS Directories potentially created on remote AFP share | ||
.AppleDB | ||
.AppleDesktop | ||
Network Trash Folder | ||
Temporary Items | ||
.apdisk | ||
|
||
# Linux temporary files which can be created if a process still has a handle open of a deleted file | ||
.fuse_hidden* | ||
|
||
# KDE directory preferences | ||
.directory |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
CPPEXAMPLE_MOD_DIR := $(USERMOD_DIR) | ||
|
||
SRC_USERMOD += \ | ||
$(CPPEXAMPLE_MOD_DIR)/model-example/model_example.c \ | ||
$(CPPEXAMPLE_MOD_DIR)/ml4f/ml4f.c \ | ||
$(CPPEXAMPLE_MOD_DIR)/src/mlmodule.c | ||
|
||
CFLAGS_USERMOD += \ | ||
-I$(CPPEXAMPLE_MOD_DIR)/ml4f \ | ||
-I$(CPPEXAMPLE_MOD_DIR)/model-example \ | ||
-I$(CPPEXAMPLE_MOD_DIR)/src |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
/** | ||
* @brief This file is based on the ML4F sample code: | ||
* https://github.com/microsoft/ml4f/blob/v1.9.1/sample/ml4f.h | ||
* | ||
* Sample code: Copyright (c) Microsoft Corporation. | ||
* Changes to the sample code: Copyright 2024 Micro:bit Educational Foundation. | ||
* | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
#include "ml4f.h" | ||
#include <string.h> | ||
|
||
int ml4f_is_valid_header(const ml4f_header_t *header) { | ||
if (!header || header->magic0 != ML4F_MAGIC0 || header->magic1 != ML4F_MAGIC1) | ||
return 0; | ||
if (header->input_type != ML4F_TYPE_FLOAT32 || header->output_type != ML4F_TYPE_FLOAT32) | ||
return 0; | ||
return 1; | ||
} | ||
|
||
typedef void (*model_fn_t)(const ml4f_header_t *model, uint8_t *arena); | ||
|
||
int ml4f_invoke(const ml4f_header_t *model, uint8_t *arena) { | ||
if (!ml4f_is_valid_header(model)) | ||
return -1; | ||
// +1 for Thumb mode | ||
model_fn_t fn = (model_fn_t)((const uint8_t *)model + model->header_size + 1); | ||
fn(model, arena); | ||
return 0; | ||
} | ||
|
||
#define EPS 0.00002f | ||
static int is_near(float a, float b) { | ||
float diff = a - b; | ||
if (diff < 0) | ||
diff = -diff; | ||
if (diff < EPS) | ||
return 1; | ||
if (a < 0) | ||
a = -a; | ||
if (b < 0) | ||
b = -b; | ||
if (diff / (a + b) < EPS) | ||
return 1; | ||
return 0; | ||
} | ||
|
||
int ml4f_test(const ml4f_header_t *model, uint8_t *arena) { | ||
if (!ml4f_is_valid_header(model)) | ||
return -1; | ||
|
||
if (!model->test_input_offset || !model->test_output_offset) | ||
return 0; // no tests | ||
|
||
memcpy(arena + model->input_offset, (uint8_t *)model + model->test_input_offset, | ||
ml4f_shape_size(ml4f_input_shape(model), model->input_type)); | ||
|
||
ml4f_invoke(model, arena); | ||
|
||
float *actual = (float *)(arena + model->output_offset); | ||
const float *expected = (const float *)((const uint8_t *)model + model->test_output_offset); | ||
int elts = ml4f_shape_elements(ml4f_output_shape(model)); | ||
for (int i = 0; i < elts; ++i) { | ||
if (!is_near(actual[i], expected[i])) | ||
return -2; | ||
} | ||
|
||
return 1; // tests OK | ||
} | ||
|
||
const uint32_t *ml4f_input_shape(const ml4f_header_t *model) { | ||
return model->input_shape; | ||
} | ||
|
||
const uint32_t *ml4f_output_shape(const ml4f_header_t *model) { | ||
const uint32_t *p = model->input_shape; | ||
while (*p) | ||
p++; | ||
p++; | ||
return p; | ||
} | ||
|
||
uint32_t ml4f_shape_elements(const uint32_t *shape) { | ||
uint32_t r = 1; | ||
while (*shape) | ||
r *= *shape++; | ||
return r; | ||
} | ||
|
||
uint32_t ml4f_shape_size(const uint32_t *shape, uint32_t type) { | ||
if (type != ML4F_TYPE_FLOAT32) | ||
return 0; | ||
return ml4f_shape_elements(shape) << 2; | ||
} | ||
|
||
int ml4f_argmax(float *data, uint32_t size) { | ||
if (size == 0) | ||
return -1; | ||
float max = data[0]; | ||
int maxidx = 0; | ||
for (unsigned i = 0; i < size; ++i) | ||
if (data[i] > max) { | ||
max = data[i]; | ||
maxidx = i; | ||
} | ||
return maxidx; | ||
} | ||
|
||
// This function is just an example - you'll likely have your own tensor formats and memory | ||
// allocation functions | ||
|
||
#include <stdlib.h> | ||
|
||
int ml4f_full_invoke(const ml4f_header_t *model, const float *input, float *output) { | ||
if (!ml4f_is_valid_header(model)) | ||
return -1; | ||
uint8_t *arena = malloc(model->arena_bytes); | ||
memcpy(arena + model->input_offset, input, | ||
ml4f_shape_size(ml4f_input_shape(model), model->input_type)); | ||
int r = ml4f_invoke(model, arena); | ||
memcpy(output, arena + model->output_offset, | ||
ml4f_shape_size(ml4f_output_shape(model), model->output_type)); | ||
free(arena); | ||
return r; | ||
} | ||
|
||
int ml4f_full_invoke_argmax(const ml4f_header_t *model, const float *input) { | ||
if (!ml4f_is_valid_header(model)) | ||
return -1; | ||
uint8_t *arena = malloc(model->arena_bytes); | ||
memcpy(arena + model->input_offset, input, | ||
ml4f_shape_size(ml4f_input_shape(model), model->input_type)); | ||
int r = ml4f_invoke(model, arena); | ||
if (r == 0) | ||
r = ml4f_argmax((float *)(arena + model->output_offset), | ||
ml4f_shape_size(ml4f_output_shape(model), model->output_type) >> 2); | ||
free(arena); | ||
return r; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/** | ||
* @brief This file is based on the ML4F sample code: | ||
* https://github.com/microsoft/ml4f/blob/v1.9.1/sample/ml4f.h | ||
* | ||
* Sample code: Copyright (c) Microsoft Corporation. | ||
* Changes to the sample code: Copyright 2024 Micro:bit Educational Foundation. | ||
* | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
#pragma once | ||
|
||
#include <stdint.h> | ||
|
||
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
|
||
#define ML4F_TYPE_FLOAT32 1 | ||
|
||
#define ML4F_MAGIC0 0x30470f62 | ||
#define ML4F_MAGIC1 0x46344c4d // "ML4F" | ||
|
||
// All values are little endian. | ||
// All offsets and sizes are in bytes. | ||
|
||
typedef struct ml4f_header { | ||
uint32_t magic0; | ||
uint32_t magic1; | ||
uint32_t header_size; | ||
uint32_t object_size; | ||
uint32_t weights_offset; | ||
uint32_t test_input_offset; | ||
uint32_t test_output_offset; | ||
uint32_t arena_bytes; | ||
uint32_t input_offset; | ||
uint32_t input_type; // always ML4F_TYPE_FLOAT32 | ||
uint32_t output_offset; | ||
uint32_t output_type; // always ML4F_TYPE_FLOAT32 | ||
uint32_t reserved[4]; | ||
// Shapes are 0-terminated, and are given in elements (not bytes). | ||
// Input shape is followed by output shape. | ||
uint32_t input_shape[0]; | ||
} ml4f_header_t; | ||
|
||
int ml4f_is_valid_header(const ml4f_header_t *header); | ||
int ml4f_invoke(const ml4f_header_t *model, uint8_t *arena); | ||
int ml4f_test(const ml4f_header_t *model, uint8_t *arena); | ||
const uint32_t *ml4f_input_shape(const ml4f_header_t *model); | ||
const uint32_t *ml4f_output_shape(const ml4f_header_t *model); | ||
uint32_t ml4f_shape_elements(const uint32_t *shape); | ||
uint32_t ml4f_shape_size(const uint32_t *shape, uint32_t type); | ||
int ml4f_argmax(float *data, uint32_t size); | ||
|
||
int ml4f_full_invoke(const ml4f_header_t *model, const float *input, float *output); | ||
int ml4f_full_invoke_argmax(const ml4f_header_t *model, const float *input); | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from microbit import * | ||
import time | ||
|
||
import ml | ||
|
||
SAMPLES_LEN = 250 | ||
TOTAL_SAMPLES = SAMPLES_LEN * 3 | ||
acc_x_y_z = [0] * TOTAL_SAMPLES | ||
|
||
ACTIVITIES = ( | ||
"Jumping", | ||
"Running", | ||
"Standing", | ||
"Walking", | ||
) | ||
|
||
i = 0 | ||
while True: | ||
acc_x_y_z[i + 0] = accelerometer.get_x() | ||
acc_x_y_z[i + 1] = accelerometer.get_y() | ||
acc_x_y_z[i + 2] = accelerometer.get_z() | ||
i += 3 | ||
if i >= TOTAL_SAMPLES: | ||
t = time.ticks_ms() | ||
result = ml.predict(acc_x_y_z) | ||
if result and len(result) == 2: | ||
print("t[{}] {:8s}".format(time.ticks_ms() - t, ACTIVITIES[result[0]]), end="") | ||
for i in range(4): | ||
print(" {}[{:.2f}]".format(ACTIVITIES[i][:1], result[1][i]), end="") | ||
print() | ||
else: | ||
print("t[{}] {}".format(time.ticks_ms() - t, result)) | ||
i = 0 | ||
sleep(20) |
Oops, something went wrong.