В настоящее время мы все, так или иначе, пользуемся последними достижениями в сфере так называемого «искусственного интеллекта», который на самом деле представляет собой зачастую просто интеллектуальные алгоритмы на базе нейросетей.

Тем не менее подобные интеллектуальные решения всё сильнее входят в жизнь и начинают захватывать всё новые и новые сферы.

Одним из достаточно жарких направлений в современной микроэлектронике и интеллектуальных системах является тема встраивания подобных алгоритмов в маленькие неэнергозатратные системы (потребление которых при работе этих алгоритмов измеряется милливаттами). Подобный подход называется TinyML — алгоритмы машинного обучения на микроконтроллерах. Об этом и поговорим в статье ниже.

Неоспоримыми преимуществами подобных решений являются:
  • Улучшенная защищенность данных, так как обработка происходит прямо на месте и они не передаются на удалённый сервер.
  • Система, которая становится более независимой от качества соединения с сетью.
  • Возможность подобных решений привнести долю интеллекта во множество простых на первый взгляд вещей, которые мы не привыкли наделять какими-либо «мыслительными способностями».
Далее будет некоторый обзор информации по этой теме, которую мне удалось найти и систематизировать для себя самого, так как я испытываю весьма горячий интерес к теме, потому что достаточно плотно занимаюсь микроконтроллерами. Возможно, эта информация поможет и вам в продвижении в этом направлении! Материал не претендует на исключительную полноту знаний.

Достаточно интересной системой для машинного обучения является TensorFlow, которая благодаря своему большому количеству библиотек, сообществу, множеству инструментов и кода позволяет применять самые новые решения из этой области.

Система поддерживает достаточно большое количество платформ и требует 32-битной архитектуры. Кстати сказать, что радует, моя любимая esp-32 среди них тоже присутствует :-)

Как правило, работа с этой системой состоит из 4 шагов:

1. Непосредственное обучение модели.

2. Уменьшение модели в размерах, чтобы она поместилась в микроконтроллер (для чего обычно используется конвертер TensorFlow Lite). Для ещё большего уменьшения её размера можно использовать последующее квантование модели. Например, применяется квантование до целых чисел с 8-битной точностью:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()

3. После чего модель встраивается в микроконтроллер с помощью преобразования в массив char:

unsigned char converted_model_tflite[] = {
  0x18, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x0e, 0x00,
  // <Lines omitted>
};
unsigned int converted_model_tflite_len = 18200;

4. Вывод на устройстве и обработка результатов.

Работа с этой системой содержит на гитхабе множество примеров и расписанных readme-файлов, позволяющих реализовать разнообразные решения, в том числе захват речи, захват жестов, обнаружение человека на изображении с камеры.
Ознакомление с этой системой можно начать прямо с этой страницы.

Кстати говоря, помимо официального ресурса, имеется и интересный учебник с пошаговым обучением TensorFlow Lite, который можно найти здесь.

Однако не так давно меня привлекла альтернативная система — MicroML, которая, в отличие от TensorFlow, предлагает более компактные решения из области нейронных сетей, позволяющие производить обработку на 8-битных микроконтроллерах. Так как она может выполняться даже на системах с ОЗУ менее чем в 2 килобайта, в отличие от того же TensorFlow (так его позиционирует разработчик, и в блоге у TensorFlow озвучивается цифра в 16 КБ. Возможно, она может быть развёрнута и на более «мелких» платформах).

Разработчик этой системы даёт подробный алгоритм для использования её на микроконтроллерах, который состоит из 4 шагов:

1. Сохранение данных образцов для каждого класса в отдельной строке файла .csv и загрузка этих данных в программу:

import numpy as np
from glob import glob
from os.path import basename

def load_features(folder):
    dataset = None
    classmap = {}
    for class_idx, filename in enumerate(glob('%s/*.csv' % folder)):
        class_name = basename(filename)[:-4]
        classmap[class_idx] = class_name
        samples = np.loadtxt(filename, dtype=float, delimiter=',')
        labels = np.ones((len(samples), 1)) * class_idx
        samples = np.hstack((samples, labels))
        dataset = samples if dataset is None else np.vstack((dataset, samples))

    return dataset, classmap

2. Обучение модели использованием ряда подходов:


Например, так выглядит применение Random Forest:

from sklearn.ensemble import RandomForestClassifier

def get_classifier(features):
    X, y = features[:, :-1], features[:, -1]

    return RandomForestClassifier(20, max_depth=10).fit(X, y)

3. Экспортирование обученной модели в код C. Делается это с помощью пакета micromlgen.

4. Использование полученного кода в рамках вашего Arduino-проекта:

// put the code you got in Step 3 into this file
#include "model.h"

// this class will be different if you used another type of classifier, just check the model.h file
Eloquent::ML::Port::RandomForest classifier;

void classify() {
    float x_sample[] = { /* fill this vector with sample values */ };

    Serial.print("Predicted class: ");
    Serial.println(classifier.predictLabel(x_sample));
}

Чем мне нравится такая система: автор практически постоянно подчёркивает тот момент, что многие мануалы в интернете, посвящённые теме машинного обучения, являются достаточным многословными и запутанными, что существенно осложняет вход в эту тему для новичков. В противовес этому, его система и инструкции позиционируются как весьма лаконичные и понятные.

В блоге этой системы содержится много примеров. Рассмотрим вкратце некоторые наиболее интересные из них.

Обнаружение человека на видео


Например, как вам возможность обнаружения человека на картинке с камеры за 3 строчки кода?

Мне самому весьма понравился этот подход, потому что я сейчас обратил своё внимание на плату esp32 cam, с которой как раз начинаю серию экспериментов.

Для этого потребуется пройти всего лишь 3 шага:

1. Установить библиотеку EloquentTinyML прямо из стандартного менеджера библиотек Arduino:



2. Ввести настройки для камеры (например, esp32):

// file ESP32Camera.h

#define CAMERA_MODEL_M5STACK_WIDE
#include <EloquentVision.h>

Eloquent::Vision::ESP32Camera camera;
camera_fb_t *frame;

/**
 * Configure camera
 */
void initCamera() {
  camera.begin(FRAMESIZE_QVGA, PIXFORMAT_GRAYSCALE, 20000000);
}

/**
 * Capture frame from ESP32 camera
 */
uint8_t* captureFrame() {
  frame = camera.capture();

  return frame->buf;
}

3. Задействовать нейронную сеть:

Пример
#include <EloquentTinyML.h>
#include <eloquent_tinyml/tensorflow/person_detection.h>

#if defined(ESP32)
#include "ESP32Camera.h"
#else
#include "PortentaVision.h"
#endif

const uint16_t imageWidth = 320;
const uint16_t imageHeight = 240;

Eloquent::TinyML::TensorFlow::PersonDetection<imageWidth, imageHeight> detector;

void setup() {
    Serial.begin(115200);
    delay(5000);
    initCamera();

    // configure a threshold for "robust" person detection
    // if no threshold is set, "person" would be detected everytime
    // person_score > not_person_score, even if just by 1
    // by trial and error, considering that scores range from 0 to 255,
    // a threshold of 190-200 dramatically reduces the number of false positives
    detector.setDetectionAbsoluteThreshold(190);
    detector.begin();

    // abort if an error occurred
    if (!detector.isOk()) {
        Serial.print("Setup error: ");
        Serial.println(detector.getErrorMessage());

        while (true) delay(1000);
    }
}

void loop() {
    uint8_t *frame = captureFrame();
    bool isPersonInFrame = detector.detectPerson(frame);

    if (!detector.isOk()) {
        Serial.print("Loop error: ");
        Serial.println(detector.getErrorMessage());

        delay(10000);
        return;
    }

    Serial.println(isPersonInFrame ? "Person detected" : "No person detected");
    Serial.print("\t > It took ");
    Serial.print(detector.getElapsedTime());
    Serial.println("ms to detect");
    Serial.print("\t > Person score: ");
    Serial.println(detector.getPersonScore());
    Serial.print("\t > Not person score: ");
    Serial.println(detector.getNotPersonScore());
    delay(1000);
}

По этой теме есть весьма подробный мануал по вот этому адресу, который позволяет настроить распознавание движения на камерах, подключённых к Arduino и esp32 (в данном случае мы говорим о «esp32 cam»).

Подход, который демонстрируется в этом мануале, в двух словах заключается в том, что камера сравнивает каждый последующий кадр с предыдущим, и если они отличаются на слишком много пикселей, то запускается «событие движения». Однако, для ускорения работы системы, так как сравнение каждого пикселя заняло бы слишком большое время, в качестве сравниваемых образцов используются уменьшенные варианты кадра, в данном случае 32 на 24:

#define THUMB_WIDTH 32
#define THUMB_HEIGHT 24

Eloquent::Vision::Motion::Naive<THUMB_WIDTH, THUMB_HEIGHT> detector;

Благодаря такому упрощённому подходу, система позволяет работать в реальном времени и обнаруживать движение!

Распознавание звука


Для этой задачи можно использовать Arduino и подключённый к ней микрофон. Сама процедура выглядит так: запись фонового шума, запись произносимых фраз, быстрое преобразование Фурье (которое даёт нам информацию о частоте с привязкой ко времени), после чего всё так же, как и в других примерах, обучается модель, экспортируется и вставляется в ваш код Arduino, после чего можно запускать вывод и смотреть результаты работы модели:

#include "model.h"

void loop() {
    if (!soundDetected()) {
        delay(10);
        return;
    }

    captureWord();
    Serial.print("You said ");
    Serial.println(classIdxToName(predict(features)));

    delay(1000);
}

Затея весьма интересная, так как позволяет внедрить голосовое управление в бытовую автоматизацию (привет идее умного дома!).

Позиционирование


Ещё одним весьма любопытным применением машинного обучения на микроконтроллерах является позиционирование внутри помещения, которое представляет собой определение местоположения с задействованием анализа силы wi-fi излучения каждой точки доступа, сеть из которых размещена внутри помещения (и в качестве которых может выступать массив esp32! – это к слову), эта система является альтернативой в тех случаях, когда GPS сигнал недоступен (что, как правило, происходит всегда, если дело касается помещений).

Для работы этого подхода требуется сначала создать карту помещения, что подразумевает под собой проход по как можно большему кол-ву мест в помещении, подключаясь к точке доступа и получая данные точки и сигнала в каждой конкретной точке. Для этого используется скетч wi-fi сканера:
Скетч WiFi сканера
/**
 * WiFi scanner example
 * Print WiFi hotspots with their RSSI in Json format
 *
 * Enter the name of the location to start scanning
 * Enter 'stop' to stop the scanning
 */

// replace with WiFiNINA.h if you use a different board
#include "WiFi.h"

#include "eloquent.h"
#include "eloquent/networking/wifi/WifiScanner.h"


String location;


void setup() {
    Serial.begin(115200);
    delay(2000);
    Serial.println("Instructions:");
    Serial.println("\tEnter the name of the location to start scanning");
    Serial.println("\tEnter 'stop' to stop scanning");
}

void loop() {
    // await user to input his current location or "stop" to abort scanning
    if (Serial.available()) {
        location = Serial.readStringUntil('\n');
    }

    // if a location is set, perform scan
    if (location != "" && location != "stop") {
        Serial.print(location);
        Serial.print(": ");
        wifiScanner.printAsJson(Serial);
        delay(2000);
    }
}

Подход позволяет реализовать более дешёвую систему, чем профессиональные решения. Единственным минусом является задействование обучение модели в облаке, ограниченное тремя запросами в минуту (максимум).

ML — на Attiny85?


Как ни странно, алгоритмы машинного обучения могут работать даже на таком маленьком микроконтроллере! Рассмотренный пример позволяет с достаточно высокой степенью точности анализировать цвет, который видит RGB датчик цвета TCS3200:



При этом задействуется всего лишь 3434 байта памяти под скетч и 21 байт ОЗУ!

ПО от Espressif для esp


Компания Espressif тоже не осталась в стороне и разработала своё решение в рамках среды разработки программного обеспечения для интернета вещей ESP-IDF (Expressif IoT Development Framework). Кстати сказать, это среда может быть встроена и в стандартную Arduino IDE. И тогда вам станет доступен весь набор функционала API ESP-IDF, подробно описанный с примерами здесь.

В рамках этой среды компания предложила набор компонентов ESP-ADF для расширения её функционала:
  • обнаружение в звуке слов пробуждения,
  • опознание голосовых команд,
  • аудиоинтерфейс,
  • преобразование текста в речь.
Всю необходимую информацию по работе с этими направлениями вы можете найти на гитхабе вот здесь.

Кроме того, вот по этой ссылке есть подробная русскоязычная инструкция по ESP-ADF.

Завершая рассказ, хочется сказать, что сфера TinyML является весьма новой, а IT-специалистов в принципе не хватает и среди них ещё меньше обладающих квалификацией в области искусственного интеллекта. Здесь же требуется не просто работа в области алгоритмов, необходимо ещё и учитывать аппаратные особенности систем! То есть специалист в области TinyML должен разбираться не только в алгоритмах, но и понимать аппаратную сторону, что является весьма непростой и творческой задачей, так как таким образом требуется оптимизировать алгоритмы, чтобы они могли работать на достаточно скромных по своим возможностям аппаратных платформах микроконтроллеров. Это сложная творческая работа на стыке науки, техники, и, если хотите — технического искусства.

Компания ABI Research в своём прогнозе обещает, что уже к 2030 году объём встраиваемых решений TinyML будет только расти и достигнет цифры в 2,5 млрд. Впечатляющая цифра и интересный тренд, который заставляет более внимательно присмотреться к подобной ветке развития технологий и попытаться оседлать эту волну (прокачать свои скиллы), пока она ещё не набрала свою силу.

Для генерации идей, в каком качестве можно было бы применить машинное обучение, можно посмотреть вот эту ссылку, где собрано просто огромное количество разнообразных вариантов применения технологий машинного обучения (в целом) на разные случаи жизни. Кроме того, существует большой русскоязычный ресурс по машинному обучению, ознакомление с которым может быть достаточно интересным.

Если у вас есть свои примеры — делитесь в комментариях.


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Комментарии (10)


  1. cosmo68
    25.05.2022 11:30
    +1

     (потребление которых при работе этих алгоритмов измеряется милливольтами).

    Может быть милливаттами?


    1. kot_review Автор
      25.05.2022 11:57
      +1

      Вы правы, очепятался — поправил.


  1. Affdey
    25.05.2022 13:45

    Очень интересно, но не всё понятно. А именно "3. Экспортирование обученной модели в код C ". Это имеется ввиду сама нейросеть с её весами? Она хранится в виде кода или в виде коэффициентов (весов)? Что делает МК при "распознавании" таким образом?

    Вопрос 2. const uint16_t imageWidth = 320; const uint16_t imageHeight = 240; А разве памяти в ESP32 хватит на такой кадр? особенно если ещё нейросеть в ОЗУ висит?


    1. pvvv
      25.05.2022 16:13

      там под 512кБ памяти внутренней, без учёта мегабайтов внешней psram на qspi.

      75кб (150 для 16ти битного цвета) можно под буфер кадра и выделить


    1. peacemakerv
      25.05.2022 16:22

      The ESP32 chip contains 520KB of RAM. Похоже, только на такой размер видеобуфера и хватает, не больше.


      1. pvvv
        25.05.2022 18:20

        есть возможноть то ли 8 то ли 16МБайт внешнего psram если так уж хочется мегапикселями ворочать, медленно и печально.


    1. kot_review Автор
      25.05.2022 16:52

      В разных мануалах обычно подчёркивается что «экспортированная модель представляет собой простой массив» (и даже в этом примере, в статье) — поэтому, я так полагаю, этот массив в виде кода C, — является набором поправочных коэффициентов для предсказания результатов, при получении входных данных (с микрофона, камеры и откуда либо ещё) — но, могу ошибаться.

      По поводу размера кадра: так как везде в дальнейшем идёт речь только о работе с уменьшенной версией кадра — видимо это ошибка автора этого конкретного решения (уменьшил кадр и добился приемлемой производительности, а в коде поправить забыл).


  1. lgorSL
    25.05.2022 16:18

    Для ML моделей очень важны вычислительная производительность и количество памяти. Для того, чтобы уместить модель в 2кб или добиться приличной производительности, надо хорошо понимать всё, что происходит "под капотом". Специально под ограничения МК подобрать архитектуру модели и потом обучить ее.


    1. tigreavecdesailes
      26.05.2022 13:22

      Просто в статье формально говориться в целом о машинном обучении, и приведённые алгоритмы (и нейросети), действительно, являются ML. Но во многих моментах создаётся впечатление, что речь идёт о именно нейросетях (в 2 кб, вах!).

      Вот только там где говорится о ATtiny85 или о 2-х килобайтах, там приводятся примеры машинного обучения без нейросетей. Ну а в таком разрезе, чему удивляться, к примеру, дереву решений? Оно вообще может из трёх узлов состоять. А в сотню байт можно засунуть настоящее дерево для расчёта ОСАГО:)

      Реально полезная сеть содержала бы тысячи/десятки тысяч параметров (можно 1 к 1 перевести в байты чтобы получить требования к оперативе).


  1. ZlodeiBaal
    27.05.2022 13:34

    Привет! Да, за TinyML будущее. Но статья немного сумбурно написала, особенно со стороны ML-специалиста.
    1) У вас идет путаница TensorFlow/TensorFlow lite/TensorFlow Micro и системами их инференса (не все из того что вы приводите использует /требует TensorFlow)
    2) Вы приводите лишь несколько вариантов запуска на ESP32. В реальности из больше. И у того же Eloquent много проблем (например у него нет моделей исходных для CV, он конвертирует рандомно найденные в инете). Если хотите, посмотрите, я тут обзор делал про другие способы — youtu.be/w_sCTPDuutQ
    3) Сегодня есть платы примерно в цену ESP32, но с куда большей производительностью. Про k210 и Alwinner'овский процессор я тут рассказываю — youtu.be/Hf4Ra59_DCA

    И, может быть любопытно, вот тут я показываю минималистический End-to-end пример с обучением для ESP32 — youtu.be/ms6uoZr-4dc