Вам интересно узнать об искусственном интеллекте (ИИ) и машинном обучении (МО)? Вы хотите знать, как использовать его на микроконтроллерах, с которыми вы уже работаете? В этой статье мы познакомим вас с машинным обучением на микроконтроллерах. Эта тема также известна как крошечное машинное обучение (TinyML). Приготовьтесь проиграть ESP-EYE в камень, ножницы, бумагу. Вы узнаете о сборе и обработке данных, о том, как спроектировать и обучить ИИ и как запустить его на микроконтроллере. Этот пример предоставляет вам все необходимое для создания собственного проекта TinyML от начала до конца.

Зачем мне интересоваться TinyML?

Наверняка вы слышали о таких технологических компаниях, как DeepMind и OpenAI. Они доминируют в области машинного обучения благодаря экспертам и мощности графического процессора. Чтобы дать ощущение масштаба, лучшие ИИ, такие как те, которые используются в Google Translate, требуют месяцев обучения. Они используют сотни высокопроизводительных графических процессоров параллельно. TinyML несколько меняет ситуацию, становясь меньше. Из-за ограничений памяти большие модели ИИ не подходят для микроконтроллеров. На рисунке ниже показано несоответствие требований к оборудованию.

Какие преимущества предлагает машинное обучение на микроконтроллерах по сравнению с использованием сервисов ИИ в облаке? Мы находим семь основных сильных сторон.

Стоимость
Микроконтроллеры недороги в покупке и эксплуатации.

Экологичность
Запуск ИИ на микроконтроллере потребляет мало энергии.

Интеграция
Микроконтроллеры легко интегрируются в существующие среды, например, в производственные линии.

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

Быстрое прототипирование
TinyML позволяет разрабатывать проверенные решения за короткий период времени.

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

В режиме реального времени
Данные обрабатываются микроконтроллером без задержки. Единственным ограничением является скорость обработки MCU.

Камень, ножницы, бумага

Вы когда-нибудь проигрывали в камень, ножницы, бумага против ИИ? Или вы хотите произвести впечатление на своих друзей, победив ИИ? Вы будете играть против доски ESP-EYE, используя TinyML. Есть пять шагов, которые вам нужно сделать, чтобы сделать такой проект возможным. В следующих разделах представлен общий обзор необходимых шагов. Если вы хотите ознакомиться с ним поближе, см. документацию в нашем репозитории проекта. Это объясняет изящные детали.

Сбор данных

Сбор данных является важной частью ML. Чтобы все заработало, вам нужно сфотографировать свою руку, изображающую камень, ножницы, бумагу. Чем больше уникальных изображений, тем лучше. ИИ узнает, что ваша рука может быть под разными углами, в разных положениях или что освещение меняется. Набор данных содержит записанные изображения и для каждого изображения метку. Это известно как контролируемое обучение.

Лучше всего использовать те же датчики и среду для запуска вашего ИИ, которые также использовались для его обучения. Это гарантирует, что модель знакома с входящими данными. Например, подумайте о датчиках температуры, которые имеют разные выходные напряжения для одной и той же температуры из-за производственных различий. Для наших целей это означает, что запись изображений камерой ESP-EYE на однородном фоне идеальна. Во время развертывания ИИ лучше всего будет работать на аналогичном фоне. Вы также можете записывать изображения с помощью веб-камеры, но это может стоить некоторой точности. Из-за ограниченных возможностей MCU мы записываем и обрабатываем изображения в градациях серого размером 96×96 пикселей.

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

Вот несколько примеров изображений. Если вы не хотите собирать данные сейчас, вы можете скачать наш готовый датасет здесь.

Предварительно обработать данные

Распознавание закономерностей в данных сложно не только для людей. Чтобы упростить модель ИИ, обычно полагаются на алгоритмы предварительной обработки. В нашем наборе данных мы записали изображения с помощью ESP-EYE и веб-камеры. Поскольку ESP-EYE может захватывать изображения в градациях серого с разрешением 96×96, нам не нужна дополнительная обработка. Однако нам нужно было уменьшить и обрезать изображения с веб-камеры до 96×96 пикселей и преобразовать их из формата RGB в оттенки серого. Наконец, мы нормализуем все изображения. Ниже вы видите промежуточные этапы нашей обработки.

Разработка модели

Сконструировать модель довольно сложно! Подробное лечение выходит за рамки этой статьи. Мы опишем основные компоненты модели и то, как мы разработали нашу. Под капотом наш ИИ опирается на нейронную сеть. Вы можете думать о нейронной сети как о наборе нейронов, немного похожем на наш мозг. Вот почему в случае зомби-апокалипсиса ИИ тоже будет съеден зомби.

Когда все нейроны в сети связаны друг с другом, это называется полносвязной или плотной сетью. Это можно считать самым основным типом нейронной сети. Поскольку мы хотели бы, чтобы наш ИИ распознавал жесты рук на изображениях, мы используем что-то более продвинутое и подходящее для изображений, известное как сверточная нейронная сеть (CNN). Свертки уменьшают размерность изображений, извлекают важные шаблоны и сохраняют локальные отношения между пикселями. Для разработки модели мы используем библиотеку TensorFlow, которая предоставляет готовые компоненты нейронной сети, называемые слоями, которые упрощают создание нейронных сетей!

Создание модели означает наложение слоев. Их правильное сочетание имеет решающее значение для разработки надежной и высокоточной модели. На рисунке ниже показаны различные слои, которые мы используем. Conv2D представляет собой сверточный слой. Слой BatchNormalization применяет форму нормализации к выходным данным вышележащего слоя. Затем мы передаем данные на слой активации, который вносит нелинейность и отфильтровывает неважные точки данных. Затем максимальное объединение уменьшает размер изображения, аналогично свертке. Этот блок слоев повторяется несколько раз; нужное количество определяется опытом и экспериментами. После этого мы используем сглаживающий слой, чтобы преобразовать двумерные изображения в одномерный массив. Наконец, массив тесно связан с тремя нейронами, которые представляют классы «камень», «бумага» и «ножницы».

def make_model_simple_cnn(INPUT_IMG_SHAPE, num_classes=3):
    inputs = keras.Input(shape=INPUT_IMG_SHAPE)
    x = inputs

    x = layers.Rescaling(1.0 / 255)(x)
    x = layers.Conv2D(16, 3, strides=3, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)
    x = layers.MaxPooling2D()(x)
    x = layers.Conv2D(32, 3, strides=2, padding="same", activation="relu")(x)
    x = layers.MaxPooling2D()(x)
    x = layers.Conv2D(64, 3, padding="same", activation="relu")(x)
    x = layers.MaxPooling2D()(x)
    x = layers.Flatten()(x)
    x = layers.Dropout(0.5)(x)


    outputs = layers.Dense(units=num_classes, activation="softmax")(x)
    return keras.Model(inputs, outputs)

Обучение модели

После того, как мы разработали модель, мы готовы ее обучить. Первоначально модель ИИ будет делать случайные прогнозы. Предсказание — это вероятность, связанная с ярлыком, в нашем случае это камень, ножницы или бумага. Наш ИИ сообщает нам, насколько вероятно, что он считает изображение каждой меткой. Поскольку ИИ с самого начала угадывает метки, он часто ошибается. Обучение происходит после сравнения предсказанной метки с реальной меткой. Ошибки прогнозирования приводят к обновлениям между нейронами в сети. Такая форма обучения называется градиентный спуск. Поскольку мы построили нашу модель в TensorFlow, обучение стало простым как раз, два, три. Ниже вы видите, какие результаты были получены во время обучения — чем выше точность (обучающий набор) и точность проверки (тестовый набор), тем лучше!

Epoch 1/6
480/480 [==============================] - 17s 34ms/step - loss: 0.4738 - accuracy: 0.6579 - val_loss: 0.3744 - val_accuracy: 0.8718
Epoch 2/6
216/480 [============>.................] - ETA: 7s - loss: 0.2753 - accuracy: 0.8436

Во время обучения может возникнуть множество проблем. Самая распространенная проблема — переобучение. Поскольку модель снова и снова подвергается воздействию одних и тех же примеров, она начнет запоминать обучающие данные наизусть, а не изучать лежащие в их основе шаблоны. Наверняка, вы помните из школы, что понимание лучше заучивания! В какой-то момент точность данных поезда может продолжать расти, а точность тестового набора — нет. Это явный показатель переобучения.

Преобразование модели

После обучения получаем модель ИИ в формате TensorFlow. Поскольку ESP-EYE не может интерпретировать этот формат, мы изменяем модель в читаемый микропроцессором формат. Начнем с преобразования в модель TfLite. TfLite — это более компактный формат TensorFlow, в котором размер модели уменьшается с помощью квантования. TfLite широко используется в периферийных устройствах, таких как смартфоны или планшеты, по всему миру. Последним шагом является преобразование модели TfLite в массив C, поскольку микроконтроллер не может напрямую интерпретировать TfLite.

Развертывание модели

Теперь мы можем развернуть нашу модель на микропроцессоре. Единственное, что нам нужно сделать, это поместить новый массив C в предполагаемый файл. Замените содержимое массива C и не забудьте заменить переменную длины массива в конце файла. Мы предоставили скрипт, чтобы сделать это проще. Вот и все!

Встроенная среда

Давайте посмотрим, что происходит в MCU. Во время установки интерпретатор настраивается на форму наших изображений.

// initialize interpreter
static tflite::MicroInterpreter static_interpreter(
    model, resolver, tensor_arena, kTensorArenaSize, error_reporter);
interpreter = &static_interpreter;
model_input = interpreter->input(0);
model_output = interpreter->output(0);

// assert real input matches expect input
if ((model_input->dims->size != 4) || // tensor of shape (1, 96, 96, 1) has dim 4
    (model_input->dims->data[0] != 1) || // 1 img per batch
    (model_input->dims->data[1] != 96) || // 96 x pixels
    (model_input->dims->data[2] != 96) || // 96 y pixels
    (model_input->dims->data[3] != 1) || // 1 channel (grayscale)
    (model_input->type != kTfLiteFloat32)) { // type of a single data point, here a pixel
        error_reporter->Report("Bad input tensor parameters in model\n");
        return;
}

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

// read image from camera into a 1-dimensional array
uint8_t img[dim1*dim2*dim3]
if (kTfLiteOk != GetImage(error_reporter, dim1, dim2, dim3, img)) {
   TF_LITE_REPORT_ERROR(error_reporter, "Image capture failed.");
}

 // write image to model
 std::vector<uint8_t> img_vec(img, img + dim1*dim2*dim3);
 std::vector<float_t> img_float(img_vec.begin(), img_vec.end());
 std::copy(img_float.begin(), img_float.end(),  model_input->data.f);

 // apply inference
 TfLiteStatus invoke_status = interpreter->Invoke();
}

Затем модель возвращает вероятность для каждого жеста. Поскольку массив вероятностей представляет собой только ряд значений от 0 до 1, необходима некоторая интерпретация. Распознанный жест мы считаем наиболее вероятным. Теперь мы обрабатываем интерпретацию, сравнивая распознанный жест с движением ИИ и определяя, кто выиграл раунд. У тебя нет шансов!

// probability for each class
float paper = model_output->data.f[0];
float rock = model_output->data.f[1];
float scissors = model_output->data.f[2];

Милая диаграмма ниже иллюстрирует шаги на MCU. Для наших целей предварительная обработка на микроконтроллере не требуется.

Развернуть пример

Как насчет вызова? Новая цель в жизни? Хотите произвести впечатление на старых друзей или найти новых? Поднимите камень, ножницы, бумагу на новый уровень, добавив ящерицу и Спока. Ваш ИИ-друг станет на один навык ближе к мировому господству. Ну, сначала вы должны взглянуть на наш камень, ножницы, бумага репозиторий и быть в состоянии воспроизвести описанные выше шаги. Файлы README помогут вам с деталями. На рисунке ниже показано, как работает игра. Вам нужно добавить два дополнительных жеста и несколько новых условий выигрыша и проигрыша.

Начать собственный проект

Если вам понравилась эта статья и вы хотите начать свои собственные проекты, мы предоставим вам шаблон проекта, в котором используется тот же простой конвейер, что и в нашем проекте камень, ножницы, бумага. Шаблон вы можете найти здесь. Не стесняйтесь показывать нам свои проекты через социальные сети. Нам интересно посмотреть, что вы умеете создавать!

Узнать больше о TinyML можно здесь и там. Книга Пита Уордена — отличный ресурс.

Николас Ридер — студент Университета прикладных наук в Гамбурге. Он изучает мехатронику для получения степени бакалавра, а с февраля 2022 года проходит обязательную стажировку в Itemis AG. Он работает вместе со своим соавтором в области TinyML, где он сочетает свою страсть к ИИ со своим опытом во встроенных системах. Николас учится всю жизнь, и ему интересно узнать о технологиях будущего, которые улучшат повседневную жизнь.

Рафаэль Таппе Маэстро — работающий студент компании Itemis AG в Германии. Он получает степень магистра в области искусственного интеллекта в Гронингенском университете. Его исследовательские интересы в области ИИ связаны с энергетическими ограничениями, поиском нейронной архитектуры и локальными правилами обучения. Рафаэль пользуется широким спектром языков программирования, читает новости Phoronix Linux и отмечает проекты на GitHub.

Первоначально опубликовано на https://www.embedded.com 5 июля 2022 г.