Часть 30: Нейронная сеть на ATmega328P (2 КБ RAM, 32 КБ Flash)

Да, мы можем запустить нейросеть на 8-битном контроллере! Используем квантование весов до 8 бит и активационную функцию ReLU (быстрее, чем сигмоида).

Архитектура: 3 слоя (8-16-8 нейронов)

#include <avr/pgmspace.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define INPUT_SIZE 8
#define HIDDEN_SIZE 16
#define OUTPUT_SIZE 8

// Квантованные веса (8 бит, со знаком)
int8_t weights_ih[INPUT_SIZE * HIDDEN_SIZE];
int8_t weights_ho[HIDDEN_SIZE * OUTPUT_SIZE];

// Смещения
int8_t bias_h[HIDDEN_SIZE];
int8_t bias_o[OUTPUT_SIZE];

// Буферы
int16_t hidden[HIDDEN_SIZE];
int16_t output[OUTPUT_SIZE];

// Функция активации ReLU (быстрая, без делений)
inline int16_t relu(int16_t x) {
    return (x > 0) ? x : 0;
}

// Прямой проход (оптимизирован под 8-битные умножения)
void forward(int8_t* input) {
    // Скрытый слой
    for (uint8_t i = 0; i < HIDDEN_SIZE; i++) {
        int32_t sum = 0;
        for (uint8_t j = 0; j < INPUT_SIZE; j++) {
            sum += (int16_t)input[j] * (int16_t)weights_ih[i * INPUT_SIZE + j];
        }
        hidden[i] = relu(sum / 128 + bias_h[i]);
    }
    
    // Выходной слой
    for (uint8_t i = 0; i < OUTPUT_SIZE; i++) {
        int32_t sum = 0;
        for (uint8_t j = 0; j < HIDDEN_SIZE; j++) {
            sum += (int16_t)hidden[j] * (int16_t)weights_ho[i * HIDDEN_SIZE + j];
        }
        output[i] = relu(sum / 128 + bias_o[i]);
    }
}

// Функция обучения (градиентный спуск, 1 эпоха)
void train(uint8_t* input, uint8_t* target, float learning_rate) {
    // Прямой проход
    forward(input);
    
    // Вычисляем ошибку
    int16_t error_o[OUTPUT_SIZE];
    for (uint8_t i = 0; i < OUTPUT_SIZE; i++) {
        error_o[i] = (int16_t)target[i] - output[i];
    }
    
    // Обратное распространение (упрощенное для 8 бит)
    // ... (код для обучения)
}

// Загрузка весов из Flash (экономия RAM)
void loadWeights() {
    // Веса хранятся в PROGMEM как массивы
    memcpy_P(weights_ih, weights_ih_progmem, sizeof(weights_ih));
    memcpy_P(weights_ho, weights_ho_progmem, sizeof(weights_ho));
    memcpy_P(bias_h, bias_h_progmem, sizeof(bias_h));
    memcpy_P(bias_o, bias_o_progmem, sizeof(bias_o));
}

// Пример: распознавание рукописных цифр (MNIST)
void setup() {
    Serial.begin(115200);
    loadWeights();
    
    // Тестовый вход (цифра 5)
    int8_t test_input[8] = {0, 1, 1, 0, 1, 1, 1, 0};
    forward(test_input);
    
    Serial.print("Распознанная цифра: ");
    uint8_t max_idx = 0;
    for (uint8_t i = 1; i < OUTPUT_SIZE; i++) {
        if (output[i] > output[max_idx]) {
            max_idx = i;
        }
    }
    Serial.println(max_idx);
}

void loop() {}

Обучение нейросети на компьютере и экспорт в C:

# train.py - обучение на Python, экспорт весов для Arduino
import numpy as np
from keras.models import Sequential
from keras.layers import Dense

# Создаем модель
model = Sequential([
    Dense(16, activation='relu', input_shape=(8,)),
    Dense(8, activation='relu')
])

# Обучаем (пример)
X_train = np.random.rand(1000, 8)
y_train = np.random.randint(0, 2, (1000, 8))

model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=10)

# Экспортируем веса в 8-битный формат
weights = model.get_weights()
for i, w in enumerate(weights):
    w_quant = np.round(w * 128).astype(np.int8)
    print(f"// Веса слоя {i}")
    print(f"const int8_t weights_{i}[] PROGMEM = {{")
    for val in w_quant.flatten():
        print(f"    {val},")
    print("};")

Вам также может понравиться

About the Author: ардуинчиков

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *