Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio. ai.. ai. machine learning.. ai. machine learning. machinelearning.. ai. machine learning. machinelearning. ml.. ai. machine learning. machinelearning. ml. python.. ai. machine learning. machinelearning. ml. python. машинное+обучение.. ai. machine learning. machinelearning. ml. python. машинное+обучение. нейросети.. ai. machine learning. machinelearning. ml. python. машинное+обучение. нейросети. нейросеть.. ai. machine learning. machinelearning. ml. python. машинное+обучение. нейросети. нейросеть. перцептрон.

Всем привет, это моя первая статья на Хабре и я решил посвятить ее своему недавнему мини‑проекту, сутью которого является обучение небольшого перцептрона 2-5-1 с помощью Python без сторонних библиотек (типа NumPy), и его последующий инференс на непрограммируемом инженерном калькуляторе Casio‑Fx-82-Es Plus (2nd edition).

В качестве задачи для перцептрона я выбрал определение того, находится ли точка в пределах графика следующей лемнискаты Бернулли: (x² + y²)² — 2a²(x² — y²) = 0 (с a = sqrt(0.5), то есть вообще без коэффициента 2a²), с минимально приемлемой вероятностью (70–85%)

 Лемниската Бернулли

Лемниската Бернулли

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

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

Что я подразумеваю под запуском/инференсом

По моему мнению задача будет выполнена, если в конечном итоге я смогу записать значения в 2 переменных, выполнить 1–2 выражения и получить ответ. Важный момент: выражения должны влезть в буфер последних выполненных выражений (должно остаться место под запись переменных) и должна остаться возможность повторно их использовать.

Технические ограничения

Калькулятор принимает только математические выражения. Построчное программирование, циклы и условные операторы отсутствуют. Для записи пользовательских значений и использования в выражениях доступно 9 переменных. Память для одного выражения всего 99 байт, всего под выражения можно использовать около ~128-150 байт, чтобы хватило места под запись входных данных во входные переменные, без потери выражений.

Важное признание

Я не являюсь специалистом в области машинного обучения и не считаю себя хорошим программистом. Я скорее энтузиаст. Обучение перцептрона я представляю относительно поверхностно и обладаю лишь базовыми знаниями в матанализе, в частности имею лишь общее представление о градиентах и градиентном спуске. Поэтому для написания кода для обучения модели я использовал LLM (DeepSeek), а затем во время попыток обучения модели, я занимался лишь тем, что мне интересно и что я могу нормально сделать, а именно редактировал гипер‑параметры и параметры для генерации датасета, адаптировал выражения, вручную квантовал и оптимизировал их.

Коротко о задаче и архитектуре

Почему именно лемниската Бернулли?

По моему мнению лемниската Бернулли одновременно имеет достаточно интересную и сложную границу, но при этом является достаточно наглядной и поддается отоносительно простой логике, доступной для сети всего с 5 скрытыми нейронами.

Ограничения архитектуры

— 2 входа для координат X и Y
— Скрытый слой на 5 нейронов (были эксперименты на 3 нейрона, но сеть не достаточно хорошо справлялась с задачей)
— 1 выход (вероятность принадлежности точки к лемнискате)
— Функция активации — логистическая сигмоида (1/(1+e‑x))
Архитектура выбиралась по принципу «Чем больше — тем лучше», но запихнуть в скрытый слой еще больше нейронов или добавить еще один слой было бы проблематично, так как памяти и так хватило в упор. Логистическая сигмоида была выбрана, так как неплохо подходит к такой архитектуре, не требует большого количества знаков для написания и не нуждается в прямом использовании условных переходов.

Коротко о генерации данных и обучении

Датасет

Является набором случайных точек в квадрате [-1.5, 1.5] x [-1.5, 1.5]. Метка точки вычисляется по уравнению лемнискаты:

val = (x**2 + y**2)**2 - 1.0 * (x**2 - y**2)
label = 1 if val <= 0 else 0

Я выбрал количество обучающих примеров n=5000 и 500 для тестовых:

train_data = generate_dataset(n=5000)
test_data = generate_dataset(n=500, bias_right=0.5)

Обучение

Чистый Python без библиотек, бинарная кросс‑энтропия, обратное распространение ошибки.

train(train_prepared, w, b, v, b_out, lr=0.01, epochs=10000)

Конечная точность неквантованной модели 96% на обучающих данных и 96.6 на тестовых.
Конечные выражения выглядят так:

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 2

Если отразить полученную модель на трехмерном графике, используя входы X и Y в качестве соответствующих осей, а выход в качестве оси Z, то получится следующее:

 График перцептрона и лемнискаты. Ссылка на график: https://www.desmos.com/3d/lifmbtkz06
График перцептрона и лемнискаты. Ссылка на график: https://www.desmos.com/3d/lifmbtkz06

Считаю, что точность не плохая для такой небольшой модели.

Адаптация к калькулятору

Проблема

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

Упрощение выражений и удаление лишних знаков / битва за байты

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

Начальные выражения

Начальные выражения

Для начала перемножаем все минусы, переносим все слагаемые так, чтобы у нас не возникало лишних минусов (то есть ‑a+b → b‑a), удаляем знаки умножения и лишние скобки:

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 5

Округление значений, уменьшение количества знаков / впихивание невпихуемого

Заметим смещения или значения с коэффициентами близкими к нулю:

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 6
Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 7
Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 8

Попробуем удалить их:

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 9

Ничего страшного не произошло.

Далее в каждом коэффициенте по‑очереди стараемся уменьшить число знаков до минимума, стараясь терять минимум точности, пробуем округлять в большую или в меньшую сторону. Если что‑то не влияет на точность или влияет минимально — пытаемся удалить. Небольшие двузначные значения(например ~12.87) пробуем заменить на 9, чтобы сэкономить дополнительный знак. Все оставшиеся значения более чем с 1 знаком записываем в свободные переменные, и используем в выражении их.

Получаем следующее:

 Ссылка на конечный график: https://www.desmos.com/3d/81uiflhf10

Ссылка на конечный график: https://www.desmos.com/3d/81uiflhf10

Запуск на калькуляторе (насилие)

1) По очереди задаем значения всем переменным A‑E: [Значение] [SHIFT] [RCL(STO)] [Клавиша с буквой переменной]; 2) Затем вводим само выражение после «a=» и нажимаем «=». Не обращаем внимание на вывод калькулятора, вне зависимости от того какой он; 3) Далее вводим второе выражение после «z=» заменяя переменную «a» на Ans и снова нажимем «=»

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 11

Готово! Для использования перцептрона необходимо задать значения X и Y тем же методом, что и первые переменные, а затем, последовательно выполнить первое и второе выражение, возвращаясь к ним стрелками вверх и вниз, как в терминале.

Для теста возьму несколько точек.

1) (1;1):

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 12

Видим что по мнению перцептрона вероятность нахождения точки в пределах лемнискаты близка к 0, что правда:

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 13

2) (0.5;0):

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 14

Значение почти совпадает с 1, значит вероятность попадания точки в лемнискату почти 100%. Результат верный:

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 15

Ну и возьмем точку (0.6;0.25):

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 16

Значение ~= 0.8. На точках близких к границе точность определения падает, но все же определено верно:

Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio - 17

Заключение

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

Здесь еще есть что доработать и улучшить. Думаю, что при желании расчёт скрытого слоя и выходного нейрона можно попробовать уместить в одно выражение, можно заняться файнтюнингом модели и дообучить ее, улучшив определения точки по краям и в центре лемнискаты. Возможно получится уместить в выражение дополнительные нейроны, заменить функцию активации или как‑то по другому улучшить архитектуру модели, но я доволен текущем результатом, который показывает, что модель в принципе работает. Надеюсь, кто‑то захочет это повторить или улучшить. Github с полезными файлами.

Автор: NoName12332112

Источник