Skip to content

Commit

Permalink
Add basic ml MicroPython module and example ML4F model to run.
Browse files Browse the repository at this point in the history
  • Loading branch information
microbit-carlos committed May 10, 2024
1 parent 0dcb8f7 commit ac6e522
Show file tree
Hide file tree
Showing 12 changed files with 2,135 additions and 67 deletions.
54 changes: 54 additions & 0 deletions .gitignore
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
11 changes: 11 additions & 0 deletions ml-module/micropython.mk
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
139 changes: 139 additions & 0 deletions ml-module/ml4f/ml4f.c
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;
}
59 changes: 59 additions & 0 deletions ml-module/ml4f/ml4f.h
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
34 changes: 34 additions & 0 deletions ml-module/model-example/main.py
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)
Loading

0 comments on commit ac6e522

Please sign in to comment.