Да, мы можем запустить нейросеть на 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("};")