W tym dokumencie wyjaśniono, jak trenować model i uruchamiać wnioskowanie za pomocą mikrokontrolera.
Przykład Hello World
Przykład Hello World ma na celu zademonstrowanie absolutnych podstaw używania TensorFlow Lite dla mikrokontrolerów. Uczymy i uruchamiamy model, który replikuje funkcję sinus, tj. przyjmuje na wejściu pojedynczą liczbę i wyprowadza wartość sinusową tej liczby. Po wdrożeniu w mikrokontrolerze jego przewidywania służą do migania diod LED lub sterowania animacją.
Kompleksowy przepływ pracy obejmuje następujące kroki:
- Trenuj model (w Pythonie): plik Pythona do uczenia, konwertowania i optymalizowania modelu do użytku na urządzeniu.
- Uruchom wnioskowanie (w C++ 17): kompleksowy test jednostkowy, który uruchamia wnioskowanie na modelu przy użyciu biblioteki C++ .
Uzyskaj obsługiwane urządzenie
Przykładowa aplikacja, z której będziemy korzystać, została przetestowana na następujących urządzeniach:
- Arduino Nano 33 BLE Sense (przy użyciu Arduino IDE)
- SparkFun Edge (budowa bezpośrednio ze źródła)
- Zestaw Discovery STM32F746 (przy użyciu Mbed)
- Adafruit EdgeBadge (przy użyciu Arduino IDE)
- Zestaw Adafruit TensorFlow Lite dla mikrokontrolerów (przy użyciu Arduino IDE)
- Plac zabaw Adafruit Circuit Bluefruit (przy użyciu Arduino IDE)
- Espressif ESP32-DevKitC (przy użyciu ESP IDF)
- Espressif ESP-EYE (przy użyciu ESP IDF)
Dowiedz się więcej o obsługiwanych platformach w TensorFlow Lite dla mikrokontrolerów .
Trenuj modelkę
Użyj train.py do szkolenia modelu Hello World w celu rozpoznawania fali sinusoidalnej
Uruchom: bazel build tensorflow/lite/micro/examples/hello_world:train
bazel-bin/tensorflow/lite/micro/examples/hello_world/train --save_tf_model --save_dir=/tmp/model_created/
Uruchom wnioskowanie
Aby uruchomić model na swoim urządzeniu, przejdziemy przez instrukcje zawarte w pliku README.md
:
W poniższych sekcjach omówiono przykładowy test jednostkowy evaluate_test.cc
, który demonstruje, jak uruchamiać wnioskowanie przy użyciu TensorFlow Lite dla mikrokontrolerów. Ładuje model i kilkakrotnie uruchamia wnioskowanie.
1. Dołącz nagłówki biblioteki
Aby skorzystać z biblioteki TensorFlow Lite for Microcontrollers, musimy dołączyć następujące pliki nagłówkowe:
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
-
micro_mutable_op_resolver.h
udostępnia operacje używane przez interpreter do uruchomienia modelu. -
micro_error_reporter.h
wyświetla informacje debugowania. -
micro_interpreter.h
zawiera kod do ładowania i uruchamiania modeli. -
schema_generated.h
zawiera schemat dla formatu pliku modelu TensorFlow LiteFlatBuffer
. -
version.h
zawiera informacje o wersji schematu TensorFlow Lite.
2. Dołącz nagłówek modelu
Interpreter TensorFlow Lite dla mikrokontrolerów oczekuje, że model zostanie dostarczony jako tablica C++. Model jest zdefiniowany w plikach model.h
i model.cc
. Nagłówek jest dołączony do następującej linii:
#include "tensorflow/lite/micro/examples/hello_world/model.h"
3. Dołącz nagłówek struktury testów jednostkowych
Aby utworzyć test jednostkowy, dołączamy platformę testów jednostkowych TensorFlow Lite dla mikrokontrolerów, dołączając następujący wiersz:
#include "tensorflow/lite/micro/testing/micro_test.h"
Test definiuje się za pomocą następujących makr:
TF_LITE_MICRO_TESTS_BEGIN
TF_LITE_MICRO_TEST(LoadModelAndPerformInference) {
. // add code here
.
}
TF_LITE_MICRO_TESTS_END
Omówimy teraz kod zawarty w powyższym makrze.
4. Skonfiguruj rejestrowanie
Aby skonfigurować rejestrowanie, tworzony jest wskaźnik tflite::ErrorReporter
przy użyciu wskaźnika do instancji tflite::MicroErrorReporter
:
tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = µ_error_reporter;
Zmienna ta zostanie przekazana do interpretera, który umożliwi mu zapisywanie logów. Ponieważ mikrokontrolery często mają różne mechanizmy rejestrowania, implementacja tflite::MicroErrorReporter
została zaprojektowana tak, aby można ją było dostosować do konkretnego urządzenia.
5. Załaduj model
W poniższym kodzie tworzona jest instancja modelu przy użyciu danych z tablicy char
g_model
, która jest zadeklarowana w model.h
. Następnie sprawdzamy model, aby upewnić się, że jego wersja schematu jest zgodna z wersją, której używamy:
const tflite::Model* model = ::tflite::GetModel(g_model);
if (model->version() != TFLITE_SCHEMA_VERSION) {
TF_LITE_REPORT_ERROR(error_reporter,
"Model provided is schema version %d not equal "
"to supported version %d.\n",
model->version(), TFLITE_SCHEMA_VERSION);
}
6. Narzędzie do rozpoznawania operacji instancji
Zadeklarowano instancję MicroMutableOpResolver
. Będzie to wykorzystane przez interpreter do zarejestrowania i uzyskania dostępu do operacji używanych przez model:
using HelloWorldOpResolver = tflite::MicroMutableOpResolver<1>;
TfLiteStatus RegisterOps(HelloWorldOpResolver& op_resolver) {
TF_LITE_ENSURE_STATUS(op_resolver.AddFullyConnected());
return kTfLiteOk;
MicroMutableOpResolver
wymaga parametru szablonu wskazującego liczbę operacji, które zostaną zarejestrowane. Funkcja RegisterOps
rejestruje operacje w programie rozpoznawania nazw.
HelloWorldOpResolver op_resolver;
TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver));
7. Przydziel pamięć
Musimy wstępnie przydzielić pewną ilość pamięci dla tablic wejściowych, wyjściowych i pośrednich. Jest to dostarczane jako tablica uint8_t
o rozmiarze tensor_arena_size
:
const int tensor_arena_size = 2 * 1024;
uint8_t tensor_arena[tensor_arena_size];
Wymagany rozmiar będzie zależał od używanego modelu i może wymagać ustalenia na podstawie eksperymentów.
8. Tłumacz instancji
Tworzymy instancję tflite::MicroInterpreter
, przekazując utworzone wcześniej zmienne:
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
tensor_arena_size, error_reporter);
9. Przydzielaj tensory
Mówimy interpreterowi, aby przydzielił pamięć z tensor_arena
dla tensorów modelu:
interpreter.AllocateTensors();
10. Sprawdź kształt wejściowy
Instancja MicroInterpreter
może dostarczyć nam wskaźnik do tensora wejściowego modelu, wywołując funkcję .input(0)
, gdzie 0
reprezentuje pierwszy (i jedyny) tensor wejściowy:
// Obtain a pointer to the model's input tensor
TfLiteTensor* input = interpreter.input(0);
Następnie sprawdzamy tensor, aby potwierdzić, że jego kształt i typ są takie, jakich oczekujemy:
// Make sure the input has the properties we expect
TF_LITE_MICRO_EXPECT_NE(nullptr, input);
// The property "dims" tells us the tensor's shape. It has one element for
// each dimension. Our input is a 2D tensor containing 1 element, so "dims"
// should have size 2.
TF_LITE_MICRO_EXPECT_EQ(2, input->dims->size);
// The value of each element gives the length of the corresponding tensor.
// We should expect two single element tensors (one is contained within the
// other).
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[1]);
// The input is a 32 bit floating point value
TF_LITE_MICRO_EXPECT_EQ(kTfLiteFloat32, input->type);
Wartość wyliczeniowa kTfLiteFloat32
jest odniesieniem do jednego z typów danych TensorFlow Lite i jest zdefiniowana w common.h
.
11. Podaj wartość wejściową
Aby zapewnić dane wejściowe do modelu, ustawiamy zawartość tensora wejściowego w następujący sposób:
input->data.f[0] = 0.;
W tym przypadku wprowadzamy wartość zmiennoprzecinkową reprezentującą 0
.
12. Uruchom model
Aby uruchomić model, możemy wywołać Invoke()
w naszej instancji tflite::MicroInterpreter
:
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n");
}
Możemy sprawdzić wartość zwracaną, TfLiteStatus
, aby określić, czy uruchomienie się powiodło. Możliwe wartości TfLiteStatus
zdefiniowane w common.h
to kTfLiteOk
i kTfLiteError
.
Poniższy kod potwierdza, że wartość to kTfLiteOk
, co oznacza, że wnioskowanie zostało pomyślnie przeprowadzone.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);
13. Uzyskaj wynik
Tensor wyjściowy modelu można uzyskać, wywołując output(0)
w interfejsie tflite::MicroInterpreter
, gdzie 0
reprezentuje pierwszy (i jedyny) tensor wyjściowy.
W przykładzie wyjściem modelu jest pojedyncza wartość zmiennoprzecinkowa zawarta w tensorze 2D:
TfLiteTensor* output = interpreter.output(0);
TF_LITE_MICRO_EXPECT_EQ(2, output->dims->size);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[1]);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteFloat32, output->type);
Możemy odczytać wartość bezpośrednio z tensora wyjściowego i stwierdzić, że jest to, czego oczekujemy:
// Obtain the output value from the tensor
float value = output->data.f[0];
// Check that the output value is within 0.05 of the expected value
TF_LITE_MICRO_EXPECT_NEAR(0., value, 0.05);
14. Uruchom ponownie wnioskowanie
Pozostała część kodu uruchamia wnioskowanie jeszcze kilka razy. W każdym przypadku przypisujemy wartość tensorowi wejściowemu, wywołujemy interpreter i odczytujemy wynik z tensora wyjściowego:
input->data.f[0] = 1.;
interpreter.Invoke();
value = output->data.f[0];
TF_LITE_MICRO_EXPECT_NEAR(0.841, value, 0.05);
input->data.f[0] = 3.;
interpreter.Invoke();
value = output->data.f[0];
TF_LITE_MICRO_EXPECT_NEAR(0.141, value, 0.05);
input->data.f[0] = 5.;
interpreter.Invoke();
value = output->data.f[0];
TF_LITE_MICRO_EXPECT_NEAR(-0.959, value, 0.05);