Skip to main content

Machine Learning и Edge AI — ИИ в кармане

Machine Learning и Edge AI — ИИ в кармане

Edge AI — это когда умные алгоритмы работают прямо на твоём устройстве, а не где-то далеко в облаке. Представь: раньше чтобы спросить у робота “Который час?”, нужно было отправить голос в интернет, подождать ответа от сервера и получить ответ. С Edge AI робот понимает тебя сразу, даже без интернета!

Зачем это нужно?

  1. Быстро — нет задержек на передачу данных
  2. Надёжно — работает без интернета
  3. Конфиденциально — твои данные никуда не уходят
  4. Дёшево — не нужно платить за облачные серверы

Три уровня сложности

Уровень 1: TinyML (ИИ на микроконтроллерах)

Для чего: Arduino, ESP32, STM32 Что может: Простые команды, базовое распознавание Пример: “Окей, робот” → робот просыпается

Уровень 2: Одноплатники (ИИ на Raspberry Pi)

Для чего: Raspberry Pi, Orange Pi Что может: Распознавание объектов, простое зрение Пример: Видит кошку и говорит “Мяу!”

Уровень 3: Ускорители (Серьёзный ИИ)

Для чего: NVIDIA Jetson, Google Coral Что может: Автономное вождение, сложное зрение Пример: Робот сам едет по коридору, объезжая препятствия


Проект 1: Робот, который понимает голос (TinyML на ESP32)

Что будет делать:

  • Слышит команды “вперёд”, “назад”, “стоп”
  • Выполняет их без интернета
  • Потребляет мало энергии (работает от батареек)

Код для ESP32 (Arduino IDE):

#include <TensorFlowLite.h>
#include <tensorflow/lite/micro/all_ops_resolver.h>
#include <tensorflow/lite/micro/micro_interpreter.h>
#include "model.h"  // Наша обученная модель

// Наша модель распознаёт 4 команды:
enum Command {
  SILENCE = 0,
  FORWARD = 1,
  BACKWARD = 2,
  STOP = 3
};

// Настройка микрофона (упрощённо)
const int micPin = 34;
const int sampleWindow = 50;  // 50 мс записи
const int sampleRate = 16000; // 16 кГц

// Загружаем модель
const tflite::Model* model = tflite::GetModel(g_model);
tflite::MicroInterpreter* interpreter;

void setup() {
  Serial.begin(115200);
  
  // Инициализируем TensorFlow Lite
  static tflite::AllOpsResolver resolver;
  static uint8_t tensor_arena[10 * 1024];  // 10 КБ памяти для модели
  
  static tflite::MicroInterpreter static_interpreter(
    model, resolver, tensor_arena, sizeof(tensor_arena));
  
  interpreter = &static_interpreter;
  interpreter->AllocateTensors();
  
  Serial.println("Робот слушает...");
  Serial.println("Скажи: 'вперёд', 'назад' или 'стоп'");
}

void loop() {
  // 1. Записываем звук
  float* audio_data = recordAudio();
  
  // 2. Подаём в нейросеть
  float* input = interpreter->input(0)->data.f;
  for (int i = 0; i < 16000 * 0.05; i++) {  // 50 мс аудио
    input[i] = audio_data[i];
  }
  
  // 3. Запускаем нейросеть
  interpreter->Invoke();
  
  // 4. Получаем результат
  float* output = interpreter->output(0)->data.f;
  int command = 0;
  float max_prob = 0;
  
  for (int i = 0; i < 4; i++) {
    if (output[i] > max_prob) {
      max_prob = output[i];
      command = i;
    }
  }
  
  // 5. Выполняем команду
  if (max_prob > 0.7) {  // Если уверенность > 70%
    switch(command) {
      case FORWARD:
        Serial.println("Команда: ВПЕРЁД!");
        moveForward();
        break;
      case BACKWARD:
        Serial.println("Команда: НАЗАД!");
        moveBackward();
        break;
      case STOP:
        Serial.println("Команда: СТОП!");
        stop();
        break;
      default:
        // Тишина или нераспознано
        break;
    }
  }
  
  delay(100);  // Проверяем каждые 100 мс
}

// Упрощённая запись звука
float* recordAudio() {
  static float samples[800];  // 16000 Гц * 0.05 с = 800 сэмплов
  
  unsigned long start = millis();
  int i = 0;
  
  while (millis() - start < sampleWindow && i < 800) {
    int sample = analogRead(micPin);
    samples[i] = (sample - 2048) / 2048.0;  // Нормализация -1..1
    i++;
    delayMicroseconds(62);  // ~16 кГц
  }
  
  return samples;
}

Как обучить модель (Google Colab):

# 1. Собираем данные
import tensorflow as tf
import numpy as np

# Создаём простой датасет (в реальности нужно записывать голос)
# 4 класса: тишина, вперёд, назад, стоп
def create_synthetic_dataset():
    samples = 1000
    audio_length = 800  # 50 мс при 16 кГц
    
    X = np.random.randn(samples, audio_length).astype(np.float32)
    y = np.random.randint(0, 4, samples)
    
    return X, y

X_train, y_train = create_synthetic_dataset()

# 2. Создаём простую модель
model = tf.keras.Sequential([
    tf.keras.layers.Input(shape=(800,)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(32, activation='relu'),
    tf.keras.layers.Dense(4, activation='softmax')  # 4 команды
])

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# 3. Обучаем
model.fit(X_train, y_train, epochs=10, batch_size=32)

# 4. Конвертируем для TinyML
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]  # Сжатие
converter.target_spec.supported_ops = [
    tf.lite.OpsSet.TFLITE_BUILTINS  # Только базовые операции
]

tflite_model = converter.convert()

# 5. Сохраняем как C-массив для Arduino
with open('model.h', 'w') as f:
    f.write('const unsigned char g_model[] = {')
    f.write(','.join(str(b) for b in tflite_model))
    f.write('};\n')
    f.write(f'const int g_model_len = {len(tflite_model)};')

Проект 2: Распознавание объектов на Raspberry Pi

Что будет делать:

  • По видео с камеры находит людей, машины, животных
  • Говорит, что видит
  • Работает в реальном времени (2-5 кадров в секунду)

Код для Raspberry Pi (Python):

import cv2
import numpy as np
from tflite_runtime.interpreter import Interpreter
import time

class ObjectDetector:
    def __init__(self, model_path='ssd_mobilenet_v2.tflite', labels_path='coco_labels.txt'):
        # Загружаем модель TensorFlow Lite
        self.interpreter = Interpreter(model_path=model_path)
        self.interpreter.allocate_tensors()
        
        # Получаем информацию о вводе/выводе
        self.input_details = self.interpreter.get_input_details()
        self.output_details = self.interpreter.get_output_details()
        
        # Размер входного изображения
        self.input_shape = self.input_details[0]['shape']
        self.height = self.input_shape[1]
        self.width = self.input_shape[2]
        
        # Загружаем названия классов
        with open(labels_path, 'r') as f:
            self.labels = [line.strip() for line in f.readlines()]
    
    def detect_objects(self, image):
        # Подготавливаем изображение
        img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        img_resized = cv2.resize(img_rgb, (self.width, self.height))
        input_data = np.expand_dims(img_resized, axis=0)
        
        # Нормализуем, если нужно
        if self.input_details[0]['dtype'] == np.float32:
            input_data = (np.float32(input_data) - 127.5) / 127.5
        
        # Запускаем модель
        self.interpreter.set_tensor(self.input_details[0]['index'], input_data)
        self.interpreter.invoke()
        
        # Получаем результаты
        boxes = self.interpreter.get_tensor(self.output_details[0]['index'])[0]
        classes = self.interpreter.get_tensor(self.output_details[1]['index'])[0]
        scores = self.interpreter.get_tensor(self.output_details[2]['index'])[0]
        
        # Фильтруем по уверенности
        min_score = 0.5
        detections = []
        
        for i in range(len(scores)):
            if scores[i] > min_score:
                ymin, xmin, ymax, xmax = boxes[i]
                
                # Переводим координаты в размер исходного изображения
                xmin = int(xmin * image.shape[1])
                xmax = int(xmax * image.shape[1])
                ymin = int(ymin * image.shape[0])
                ymax = int(ymax * image.shape[0])
                
                class_id = int(classes[i])
                label = self.labels[class_id]
                
                detections.append({
                    'box': (xmin, ymin, xmax, ymax),
                    'score': scores[i],
                    'label': label
                })
        
        return detections
    
    def draw_detections(self, image, detections):
        for det in detections:
            xmin, ymin, xmax, ymax = det['box']
            label = det['label']
            score = det['score']
            
            # Рисуем прямоугольник
            cv2.rectangle(image, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2)
            
            # Подписываем
            text = f"{label} {score:.2f}"
            cv2.putText(image, text, (xmin, ymin - 10),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
        
        return image

# Главная программа
def main():
    print("Запуск распознавания объектов на Raspberry Pi...")
    
    # Инициализируем детектор
    detector = ObjectDetector()
    
    # Открываем камеру
    cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)  # Уменьшаем для скорости
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
    
    fps_counter = 0
    fps_time = time.time()
    
    try:
        while True:
            # Читаем кадр
            ret, frame = cap.read()
            if not ret:
                break
            
            # Распознаём объекты
            detections = detector.detect_objects(frame)
            
            # Рисуем результаты
            frame_with_boxes = detector.draw_detections(frame.copy(), detections)
            
            # Считаем FPS
            fps_counter += 1
            if time.time() - fps_time >= 1.0:
                fps = fps_counter
                fps_counter = 0
                fps_time = time.time()
                print(f"FPS: {fps}, Объектов: {len(detections)}")
                
                # Говорим, что нашли
                if detections:
                    objects = ', '.join(set(d['label'] for d in detections))
                    print(f"Вижу: {objects}")
            
            # Показываем результат
            cv2.imshow('Raspberry Pi Object Detection', frame_with_boxes)
            
            # Выход по 'q'
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
    
    except KeyboardInterrupt:
        print("\nОстановка...")
    
    finally:
        cap.release()
        cv2.destroyAllWindows()
        print("Программа завершена")

if __name__ == "__main__":
    main()

Установка на Raspberry Pi:

# 1. Установка зависимостей
sudo apt-get update
sudo apt-get install python3-opencv python3-numpy

# 2. Установка TensorFlow Lite
pip3 install tflite-runtime

# 3. Скачивание модели и меток
wget https://storage.googleapis.com/download.tensorflow.org/models/tflite/coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip
unzip coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip

# 4. Создание файла с метками
echo "person" > coco_labels.txt
echo "bicycle" >> coco_labels.txt
echo "car" >> coco_labels.txt
# ... и так 80 классов из COCO датасета

Проект 3: Google Coral USB Accelerator — суперскорость

Google Coral — это USB-флешка с нейропроцессором (NPU). Она ускоряет нейросети в 10-100 раз!

Установка и использование:

from pycoral.utils.edgetpu import make_interpreter
from pycoral.adapters import common
from pycoral.adapters import detect
import cv2

# Загрузка модели для Coral
interpreter = make_interpreter('ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite')
interpreter.allocate_tensors()

# Обработка изображения (очень быстро!)
def detect_with_coral(image):
    # Подготовка
    _, scale = common.set_resized_input(
        interpreter, image.shape[:2],
        lambda size: cv2.resize(image, size))
    
    # Запуск (в 10 раз быстрее чем на CPU!)
    interpreter.invoke()
    
    # Получение результатов
    objs = detect.get_objects(interpreter, 0.5, scale)
    
    return objs

# Использование с веб-камерой
cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    # Детекция (60 FPS вместо 5 на Raspberry Pi!)
    objects = detect_with_coral(frame)
    
    for obj in objects:
        bbox = obj.bbox
        label = labels[obj.id]
        score = obj.score
        
        # Рисование результатов
        cv2.rectangle(frame, (bbox.xmin, bbox.ymin), 
                     (bbox.xmax, bbox.ymax), (0, 255, 0), 2)
        cv2.putText(frame, f"{label} {score:.2f}", 
                   (bbox.xmin, bbox.ymin-10),
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
    
    cv2.imshow('Coral Accelerator', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

Сравнение платформ

ПлатформаЦенаСкоростьЭнергияСложностьПример использования
ESP32$5МедленноОчень малоСредняяГолосовые команды
Raspberry Pi 4$35СреднеМалоНизкаяРаспознавание объектов (5 FPS)
Google Coral$60БыстроСреднеНизкаяРаспознавание объектов (60 FPS)
NVIDIA Jetson Nano$99Очень быстроМногоВысокаяАвтономный дрон

Практические задания

Для начинающих:

  1. Мигающий светодиод по голосу — скажи “включи” → загорается светодиод на ESP32
  2. Счётчик людей — Raspberry Pi считает, сколько людей прошло перед камерой
  3. Распознавание эмоций — определяет, улыбается человек или нет

Для продвинутых:

  1. Робот-сортировщик — распознаёт объекты и кладёт в разные корзины
  2. Система безопасности — распознаёт знакомые лица, чужих — предупреждает
  3. Ассистент для слабовидящих — описывает, что вокруг (человек, машина, дверь)

Готовые проекты для копирования:

  1. Google AIY Voice Kit — голосовой помощник на Raspberry Pi
  2. Edge Impulse — платформа для TinyML без кодирования
  3. TensorFlow Lite примеры — готовые модели для разных задач

Частые проблемы и решения

Проблема 1: Модель не помещается в память

# Решение: квантование (уменьшение размера)
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]  # Сжатие в 4 раза!
converter.target_spec.supported_types = [tf.float16]  # Половина точности
tflite_model = converter.convert()

Проблема 2: Работает слишком медленно

# Решение 1: Уменьшить разрешение
# Было: 320x240 -> Стало: 160x120 (в 4 раза быстрее!)

# Решение 2: Использовать готовые оптимизированные модели
# MobileNet, EfficientNet-Lite вместо ResNet

# Решение 3: Аппаратное ускорение (Coral, Jetson)

Проблема 3: Плохо распознаёт в реальных условиях

# Решение: Аугментация данных при обучении
datagen = tf.keras.preprocessing.image.ImageDataGenerator(
    rotation_range=20,      # Повороты
    width_shift_range=0.2,  # Сдвиги
    brightness_range=[0.8, 1.2],  # Яркость
    zoom_range=0.2,         # Зум
    horizontal_flip=True    # Отражение
)
# Модель учится на "разных" условиях

Полезные ресурсы

Данные для обучения:

  • Google Speech Commands — 65,000 записей голосовых команд
  • COCO — 330,000 изображений с объектами
  • MNIST — 70,000 рукописных цифр
  • CIFAR-10 — 60,000 маленьких цветных изображений

Готовые модели:

  • TensorFlow Hub — тысячи готовых моделей
  • Model Zoo от Google — оптимизированные для Edge
  • ONNX Model Zoo — модели в универсальном формате

Инструменты:

  • Edge Impulse — визуальное создание TinyML моделей
  • TensorFlow Lite Converter — конвертация моделей
  • Netron — просмотрщик моделей нейросетей

Советы от профессионалов:

  1. Начинай с готовых моделей — не учи с нуля
  2. Прототипируй на Colab — бесплатный GPU для обучения
  3. Тестируй на целевом железе — на компьютере всегда быстрее
  4. Оптимизируй постепенно — сначала работает, потом быстро работает
  5. Думай о энергии — от батареи или от розетки?

Edge AI — это не будущее, это настоящее. Уже сегодня умные устройства вокруг нас понимают наш мир без облаков. Теперь и ты можешь создавать такие устройства!