Skip to content

Commit

Permalink
Add header with labels on top of the ML4F model.
Browse files Browse the repository at this point in the history
  • Loading branch information
microbit-carlos committed May 15, 2024
1 parent 6f2c37e commit 5f04f34
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 70 deletions.
10 changes: 10 additions & 0 deletions ml-module/model-example/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
TOTAL_SAMPLES = SAMPLES_LEN * 3
acc_x_y_z = [0] * TOTAL_SAMPLES

print("Model labels: {}".format(ml.get_labels()))

i = 0
while True:
acc_x_y_z[i + 0] = accelerometer.get_x()
Expand All @@ -24,4 +26,12 @@
else:
print("t[{}] {}".format(time.ticks_ms() - t, result))
i = 0
if button_a.is_pressed():
print("Use build-in model")
ml.internal_model(True)
sleep(500)
if button_b.is_pressed():
print("Use flash model")
ml.internal_model(False)
sleep(500)
sleep(20)
29 changes: 28 additions & 1 deletion ml-module/model-example/model_example.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
#include "model_example.h"

const unsigned int ml4f_model_example[13852] = {
#define ml4f_model_example_header_len 52
#define ml4f_model_example_size 13852
#define ml4f_full_model_size (ml4f_model_example_header_len + ml4f_model_example_size)


// This is a struct representation of the header included at the beginning of model_example
/* const ml_model_header_t ml4f_model_example_header = {
.magic0 = MODEL_LABELS_MAGIC0,
.header_size = 0x31, // 49
.model_offset = 0x34, // 52
.number_of_labels = 0x04,
.reserved = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
// 33 bytes
.labels = {
"Jumping\0"
"Running\0"
"Standing\0"
"Walking\0\0\0"
}
}; */

const unsigned int model_example[ml4f_full_model_size] = {
// Manually converted ml4f_model_example_header
0x4D444C42, 0x00340031, 0x00000004, 0x00000000,
0x706D754A, 0x00676E69, 0x6E6E7552, 0x00676E69,
0x6E617453, 0x676E6964, 0x6C615700, 0x676E696B,
0x00000000,
// Original ML4F model from this point forward
0x30470f62, 0x46344c4d, 0x00000054, 0x0000d864, 0x00001874, 0x00000000, 0x00000000, 0x00002ec8,
0x00000008, 0x00000001, 0x00000008, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x000000fa, 0x00000003, 0x00000000, 0x00000004, 0x00000000, 0x5ff0e92d, 0x6901460f, 0x60391809,
Expand Down
6 changes: 4 additions & 2 deletions ml-module/model-example/model_example.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <mlmodel.h>

#define ml4f_model_example_input_num_elements 750
#define ml4f_model_example_num_labels 4
extern const unsigned int ml4f_model_example[13852];

extern const unsigned int model_example[];
208 changes: 165 additions & 43 deletions ml-module/src/mlmodel.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/**
* @brief Functions to interact with the ML model.
*
* @copyright
* Copyright 2024 Micro:bit Educational Foundation.
* SPDX-License-Identifier: MIT
*
* @details
* The ML4F model has its own header, but does not include the labels.
* So an extra header with the labels is added on top.
* We call the "full model" the labels header + the ML4F model.
*/
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
Expand All @@ -14,7 +26,18 @@ static bool USE_BUILT_IN = true;
/*****************************************************************************/
/* Private API */
/*****************************************************************************/
static int get_model_start_address() {
/**
* @brief Get the start address of the full model (header + ML4F model).
*
* This would also be the start address of the model header with the labels.
* This function does not check if the data in flash is a valid model.
*
* @return The start address to where the full model is stored in flash.
*/
static uint32_t get_full_model_start_address() {
if (USE_BUILT_IN) {
return (uint32_t)model_example;
}
// The last section in FLASH is meant to be text, but data section contents
// are placed immediately after it (to be copied to RAM), but there isn't
// a symbol to indicate its end in FLASH, so we calculate how long data is
Expand All @@ -30,26 +53,137 @@ static int get_model_start_address() {
return (end_of_flash_data + flash_page_size - 1) & ~(flash_page_size - 1);
}

static ml4f_header_t* get_model_header() {
return (ml4f_header_t *)get_model_start_address();
/**
* @brief Get a pointer to the full model header.
*
* @return The model header or NULL if the model is not present or invalid.
*/
static ml_model_header_t* get_model_header() {
ml_model_header_t *model_header = (ml_model_header_t *)get_full_model_start_address();
if (model_header->magic0 != MODEL_LABELS_MAGIC0) {
return NULL;
}
// We should have at least one label
if (model_header->number_of_labels == 0) {
return NULL;
}
// Also check the ML4F header magic values to ensure it's there too
ml4f_header_t *ml4f_model = (ml4f_header_t *)((uint32_t)model_header + model_header->model_offset);
if (ml4f_model->magic0 != ML4F_MAGIC0 || ml4f_model->magic1 != ML4F_MAGIC1) {
return NULL;
}
return model_header;
}

/**
* @brief Get a pointer to the ML4F model.
*
* @return The ML4F model or NULL if the model is not present or invalid.
*/
static ml4f_header_t* get_ml4f_model() {
// get_model_header() already checks the ML4F header magic values
ml_model_header_t *model_header = get_model_header();
if (model_header == NULL) {
return NULL;
}
return (ml4f_header_t *)((uint32_t)model_header + model_header->model_offset);
}

/*****************************************************************************/
/* Public API */
/*****************************************************************************/
bool use_built_in_model(bool use) {
USE_BUILT_IN = use;
bool get_use_built_in_model(void) {
return USE_BUILT_IN;
}

void set_use_built_in_model(bool use) {
USE_BUILT_IN = use;
}

bool is_model_present(void) {
// TODO: Implement built-in module
return USE_BUILT_IN ? true : false;
ml_model_header_t *model_header = get_model_header();
return model_header != NULL;
}

size_t get_model_label_num(void) {
// TODO: Implement built-in module
return USE_BUILT_IN ? ml4f_model_example_num_labels : 0;
ml_model_header_t *model_header = get_model_header();
return (model_header != NULL) ? model_header->number_of_labels : 0;
}

ml_labels_t* get_model_labels(void) {
static ml_labels_t labels = {
.num_labels = 0,
.labels = NULL
};

const ml_model_header_t* const model_header = get_model_header();
if (model_header == NULL) {
labels.num_labels = 0;
if (labels.labels != NULL) {
free(labels.labels);
labels.labels = NULL;
}
return NULL;
}

// Workout the addresses in flash from each label, there are as many strings
// as indicated by model_header->number_of_labels, they start from address
// model_header->labels and are null-terminated.
uint32_t header_end = (uint32_t)model_header + model_header->header_size;
const char* flash_labels[model_header->number_of_labels];
flash_labels[0] = &model_header->labels[0];
for (int i = 1; i < model_header->number_of_labels; i++) {
// Find the end of the previous string by looking for the null terminator
flash_labels[i] = flash_labels[i - 1];
while (*flash_labels[i] != '\0' && (uint32_t)flash_labels[i] < header_end) {
flash_labels[i]++;
}
if ((uint32_t)flash_labels[i] >= header_end) {
// We reached the end of the header without finding the null terminator
free(flash_labels);
return NULL;
}
// Currently pointing to the null terminator, so point to the following string
flash_labels[i]++;
}
// Check the last string is null terminated at the end of header
if (*(char *)(header_end - 1) != '\0') {
free(flash_labels);
return NULL;
}

// First check if the labels are the same, if not we need to set them again
bool set_labels = false;
if (labels.num_labels == 0 || labels.labels == NULL) {
set_labels = true;
} else if (labels.num_labels != model_header->number_of_labels) {
set_labels = true;
} else {
for (int i = 0; i < labels.num_labels; i++) {
if (labels.labels[i] != flash_labels[i]) {
set_labels = true;
break;
}
}
}
if (set_labels) {
// First clear them out if needed
labels.num_labels = 0;
if (labels.labels != NULL) {
free(labels.labels);
}
// Then set them to point to the strings in flash
labels.labels = (const char **)malloc(model_header->number_of_labels * sizeof(char *));
if (labels.labels == NULL) {
return NULL;
}
labels.num_labels = model_header->number_of_labels;
for (int i = 0; i < labels.num_labels; i++) {
labels.labels[i] = flash_labels[i];
}
}

return &labels;
}

size_t get_model_input_num() {
Expand All @@ -58,52 +192,40 @@ size_t get_model_input_num() {
}

ml_prediction_t* model_predict(const float *input) {
if (!USE_BUILT_IN) {
(void)get_model_header();
return NULL;
}

typedef struct out_s {
size_t len;
float* values;
} out_t;

static out_t out = {
.len = 0,
.values = NULL
};
static ml_prediction_t predictions = {
.max_index = 0,
.num_labels = ml4f_model_example_num_labels,
.predictions = {
{ .prediction = 0.0, .label = "Jumping" },
{ .prediction = 0.0, .label = "Running" },
{ .prediction = 0.0, .label = "Standing" },
{ .prediction = 0.0, .label = "Walking" },
}
.num_labels = 0,
.labels = NULL,
.predictions = NULL,
};

size_t label_num = get_model_label_num();
ml_labels_t* labels = get_model_labels();
if (labels == NULL) {
return NULL;
}

// The model shouldn't really change (only during testing while we built-in
// one), so this should be a one-time allocation.
if (out.len != label_num || out.values == NULL) {
if (out.values != NULL) {
free(out.values);
// Check if we need to resize the predictions array
if (predictions.num_labels != labels->num_labels) {
if (predictions.predictions != NULL) {
free(predictions.predictions);
}
predictions.num_labels = labels->num_labels;
predictions.predictions = (float *)malloc(predictions.num_labels * sizeof(float));
if (predictions.predictions == NULL) {
predictions.num_labels = 0;
return NULL;
}
out.len = label_num;
out.values = (float *)malloc(out.len * sizeof(float));
}
// Always update the labels in case they changed
predictions.labels = labels->labels;

int r = ml4f_full_invoke((ml4f_header_t *)ml4f_model_example, input, out.values);
ml4f_header_t* ml4f_model = get_ml4f_model();
int r = ml4f_full_invoke(ml4f_model, input, predictions.predictions);
if (r != 0) {
return NULL;
}

for (int i = 0; i < out.len; i++) {
predictions.predictions[i].prediction = out.values[i];
}
predictions.max_index = ml4f_argmax(out.values, out.len);
predictions.max_index = ml4f_argmax(predictions.predictions, predictions.num_labels);

return &predictions;
}
15 changes: 9 additions & 6 deletions ml-module/src/mlmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,22 @@ typedef __PACKED_STRUCT ml_model_header_t {
char labels[]; // Mutiple null-terminated strings, as many as number_of_labels
} ml_model_header_t;

typedef struct ml_label_prediction_s {
float prediction;
char* label;
} ml_label_prediction_t;
typedef struct ml_labels_s {
size_t num_labels;
const char **labels;
} ml_labels_t;

typedef struct ml_prediction_s {
size_t max_index;
size_t num_labels;
ml_label_prediction_t predictions[];
const char **labels;
float *predictions;
} ml_prediction_t;

bool use_built_in_model(bool use);
bool get_use_built_in_model(void);
void set_use_built_in_model(bool use);
bool is_model_present(void);
size_t get_model_label_num(void);
ml_labels_t* get_model_labels(void);
size_t get_model_input_num(void);
ml_prediction_t* model_predict(const float *input);
Loading

0 comments on commit 5f04f34

Please sign in to comment.