Обновить

Архитектура «Обратного Хэша»: Нейросети без умножения

Уровень сложностиСредний
Время на прочтение2 мин
Охват и читатели6.2K
Всего голосов 4: ↑3 и ↓1+2
Комментарии21

Комментарии 21

Без математического доказательства аналога Universal Approximation Theorem - это голословная демагогия. Вообще ниоткуда не следует, что ваш подход будет работать. Хоть бы тесты до публикации какие-нибудь погоняли. Хотя бы что-то тривиальное, вроде распознавания цифр.

Далее, с чего вы взяли, что это будет работать быстрее? У вас там индексация в массиве - это полный запрет на распараллеливание.

Ну и, вообще непонятно, как ваша модель работает с несколькими инпутами. Вон, в перцептронах идет суммирование по всем входам. У вас как? Типа, все входы - это разные биты input, а маска задает, какие входы инвертируются? Т.е. там фактически идет тупо сумма со множителями +-1 и потом берется пороговая функция активации. Т.е. у вас сильно урезанный перцептрон получается.

Обучение - вообще с потолка взято. Обратное распространение ошибки - это ведь градиентный спуск в пространстве параметров. У вас же какие-то счетчики. Даже не объяснено, как оно распространяется по сети. Плюс, если я правильно понял и ваша mask - это инвертация входов, то при распространении у каких-то нейронов надо наоборот счетчики уменьшать, если их выход берется инвертированным.

все будет) руки пока не дошли.
Индексация в массиве - только в процессе обучения. Потом - это операция (A >> dist) & 1 (массив предращается в 64 битное число)

Входы - разные биты input - не уверен что будет работать. для распознавания символов планируется ранговое кодирование. Для друих задач - "heatmap" - больше число - больше единиц в байте

 У нас массив A (LUT) позволяет задать любую нелинейную реакцию на расстояние (так что скорее продвинутый а не урезанный персептрон).

Для обучения планируем опираться на принципы Feedback Alignment и Target Propagation. В нашем Proof of Concept для MNIST планируем использовать логику близкую к Extreme Learning Machines (ELM): скрытые слои создают случайную проекцию признаков, а обучается только последний слой (статистически). Теоретически градиентный спуск тут не нужен, но покажут тесты..

Маски - это просто статические случайные "картинки" они не меняются в процессе обучения

Входы - разные биты input - не уверен что будет работать.

А как тогда? Что вообще у вас за input? В нейросети в один перцептрон идет несколько входов от разных нейронов. Они за счет этого и работают. У вас же формула от одного Input.

Зависит от задачи. В случае распознавания рукописных цифр думаю использовать ранговое кодирование -

Нейрону не важна абсолютная яркость. Ему важен перепад (градиент).
Мы берем 64 случайные пары точек изображения.

  • Если точка А ярче точки Б — пишем 1.

  • Иначе — 0.

Но это не значит что "тупо" брать случайные биты - не сработает. этого я не знаю

А в средних слоях инпуты - это аутпуты других нейронов. У нас у каждого нейрона 64 инпута, 1 аутпут (64 бита на вход, 1 на выход). Можно в принципе AVX512 использовать, тогда можно и больше бит на вход использовать, но 64 на современных процах наверно оптимум, батч (за такт сразу 4 нейрона считать например, заюзав 256 бит) при этом конечно можно использовать, но это уже тонкости

Выход нейронов первого слоя идет на рандомный вход нейронов второго слоя. сети не обязательно быть полносвязной

А в средних слоях инпуты - это аутпуты других нейронов.

Как вы эти несколько выходов других нейронов передаете в этот один? Они составляют биты input, ведь так? Это ровно то, что я написал: "Типа, все входы - это разные биты input".

 У нас массив A (LUT) позволяет задать любую нелинейную реакцию на расстояние (так что скорее продвинутый а не урезанный персептрон).

Сомнительное утверждение. У вас там все возможные функции: выдать 0 или 1 в зависимости от количества активных входов (некоторые фиксировано инвертированы). Вот это вот "количество активных входов" делает все входы равноправными и исключает любую возможность разделять сильные и слабые признаки.

Я вам сейчас вообще формально докажу, что ваша модель точно слабже обычных нейросетей.

Вообще, можно маски все у вас убрать, преобразовав вашу сеть: раздвоите каждый перцептрон, во второй копии инвертируйте таблицу A, соедините выход оригинального перцептрона с теми парами перцептронов, для которых в маске стоит 0. А выход инвертированной копии с теми, где в маске стоит 1. Получившаяся сеть может делать те же самые вычисления, что и изначальная, но может и больше, ведь в этой модели жесткая структура пар перцептронов, у которых инвертированны таблицы и входы и выходы аналогичны. Т.е. ваша модель точно не сильнее модели без масок.

Далее, можно и таблицу выкинуть, разбив каждый перцептрон на несколько новых. Первый будет тупо суммировать все входы и выдавать сумму/количество входов N. Эта сумма идет с весами +-1 (фиксированными от mask) в пару пороговых линейных перцептронов "сумма <= i" и "сумма >= i", которые выдают 0 или 1. Пара этих выходов с весами +1 идет в пороговый линейный перцептрон "input >= 2". Вот уже у нас есть N перцептронов, ровно один из которых активируется при заданной сумме входов. Это popcnt. Теперь осталось собрать из них выход, соответствующий таблице A[], так чтобы эти значения в A[] были весами. Ну, тупо ставим "input-0.5" перцептроны после каждого, потом все это с весами +-1 в зависимости от A[] соедняем с выходным перцептроном с пороговой активацией " сумма > -количество входов".

Итак, ваша изначальная сеть аналогична обычной нейросети, где абсолютно все веса -1|1 и все перцептроны пороговые, и очень фиксированная архитектура. Изменения в A[] в вашей модели приводят лишь к инвертации некоторых весов. Это сильно урезанный вариант нейросети. Очень большой вопрос, выполняется ли в нем теорема апроксимации. Без нее оно работать не будет.

у нейрона 64 битовых входа, соответственно он может принять 64 входа от 64 нейронов. Если нейнонов в слое до текущего больше чем 64, что всегда будет верно, то нейрон текущего слоя примет входы от рандомных нейронов предыдущего слоя (берем количеством)

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

Действительно, XOR + POPCNT + LUT можно представить как ансамбль пороговых перцептронов с весами +-1, Но дьявол, в деталях реализации и нелинейности.

Вы пишете, что модель точно не сильнее обычной нейросети. Возможно. Но классический пример: функция XOR. Один классический перцептрон не может решить XOR. Ему нужна сеть.
Наш один LUT-нейрон решает XOR от 64 входов мгновенно. Для этого достаточно заполнить таблицу A[] чередующимися нулями и единицами.

Таким образом, один наш «урезанный» нейрон на 64 входа способен реализовать функции, для которых классическому перцептрону (даже с float весами) понадобится скрытый слой. LUT дает нам произвольную нелинейность от расстояния. Да. это частный случай, но он - показательный

Про «можно выкинуть маски» :
Математически — да, маска это просто перенос начала координат. Но инженерно — это LSH (Locality Sensitive Hashing).XOR + POPCNT — это способ вычислить «похожесть» входа на эталон (маску) за 2 такта процессора. Если мы выкинем маски и будем пытаться собрать это из пороговых перцептронов, как вы предложили в доказательстве, мы получим чудовищную конструкцию, которая будет работать в 100 раз медленнее. Наша цель — максимальный КПД на такт процессора.

Про теорему аппроксимации: Существует теорема для WiSARD (Weightless Neural Networks), которая доказывает их аппроксимационную способность. Наша модель — это WiSARD «на стероидах», где мы добавили топологическую связность через расстояние Хэмминга.

Мы не пытаемся сделать модель, которая «умнее» классической нейронки в бесконечном пространстве. Мы делаем модель, которая эффективнее классической в пространстве команд современного CPU. Классический нейрон тратит 64 умножения, чтобы разделить пространство одной плоскостью. Наш нейрон за 4 инструкции разделяет пространство 64-мя нелинейными «сферами».

Тесты на MNIST уже готовятся — они и станут финальным аргументом в споре о «слабости».

Один классический перцептрон не может решить XOR. Ему нужна сеть.

Ну вот их не один, а несколько, отлично xor считают. Я указал как. Но от количества нейронов класс сети не меняется. Мы же обсуждаем возможности класса сетей, который вы вводите.

Наш один LUT-нейрон решает XOR от 64 входов мгновенно.

Да, ваша формула считается быстрее аналогичной нейросети. Но, это неважно. Потому что вообще считать такие сети может и смысла нет. Максимум 64 входа в каждый перцептрон? В ллмках их десятки тысяч. Тут только какие-нибудь очень узкие применения в крошечных сетях. А там они итак работают мгновенно. Плюс веса в такой нейросети +-1 и при этом многие веса вообще нельзя менять. Может оно вообще не сходится ни к чему в обучении.

Мы делаем модель, которая эффективнее классической в пространстве команд современного CPU

Давайте просто возьмем среднее арифметическое всех пикселей входной картинки. Это еще эффективнее ваших XORов и перцептронов. Сильно эффективнее! Но и возможности сильно меньше. Без возможностей эффективность бесполезна.

Потом, вы фокусируетесь на CPU. Для любого достаточно сложного применения у вас должны быть миллионы параметров, и там уже рулит GPU, TPU и прочие ускорители. А они уже под работу с float заточены.

Тесты на MNIST уже готовятся — они и станут финальным аргументом в споре о «слабости».

Ну вот с этого и надо было начинать еще до статьи. А то вы как некоторые чиновники, которые постоянно анонсируют то базу на луне, то еще что-то. Были бы хоть какие-то твердые данные, было бы гораздо меньше вопросов.

готово:
https://schoolscience.org/bit_nn/

не идеал, сделано "на коленке" но это-же proof of concept. Важем сам факт - работает)

На CPU - скорее можно попробовать, но сам концепт нейросети просто идеально ложится на асики, и проблем с использованием GPU тоже особо не видно

Что-то там точность весьма низкая. У меня оно только 1 нарисованное и отгадало правильно. Все остальные цифры - как попало:

Так, ваша формула получается ускорением 1-битных нейросеток, с пороговыми перцептронами и не более 64 входов для каждого нейрона. Какую-то точность оно даже даст. Право на жизнь идея имеет. Но я бы не называл это революцией. На собственно GPU эта формула ускоряется плохо, поэтому традиционные нейросетки все равно на практике будут и быстрее и точнее. Даже на мобильных процессорах, там везде встроенные iGPU же.

Еще, я так и не понял, как у вас идет обучение. Вот для выходного слоя понятно, какие нейроны должны быть активированны и "положительный" ли пример. Как оно распространяется назад?

Битность и входа в выхода может быть любой, скажем проблем сделать нейрон с 10000 входов - нет и масштабируется линейно, то есть нет такого что "большущий" нейрон будет сильно медленнее считаться. Размер нейрона подбирается под задачу и железо.

Да, действительно, я выложил недостаточно протестирова) подправлю. На тестовом датасете точность в среднем 98%, какой-то косяк при анализе нарисованной картинки(

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

Обучение в простейшем случае:

Когда мы показываем сети цифру «7»:

  1. Нейрон вычисляет dist = popcount(Input ^ Mask).

  2. Мы делаем A[dist]++ (если это правильный класс) или A[dist]-- (если неправильный).

Битность и входа в выхода может быть любой, скажем проблем сделать нейрон с 10000 входов

Ну так там индексация тогда будет от суммы битов. Это медленно. Ассимптотически, да, все такая же линейная сложность, конечно, ну так и ваша оптимизация с XOR-ами - не ассимптотическая. Вы лишь в несколько раз будете быстрее из-за архитектурных особенностей вычислений.

Обучение в простейшем случае:

Ну я понял, как для выходного нейрона это делать. Что происходит с нейронами в промежуточных слоях? У него нет класса, как ему понять, надо делать ++ или --?

Конечно планы могут измениться, но базовый сценарий - статистическими методами. Очень упрощенно - берем текст и строим табличку - такая-то маска встречатеся с такой-то вероятностью и после нее идут символы с такой-то вероятностью - это данные чтобы собрать первый слой. Потом берем тот-же текст и "сжимаем его" через эту табличку, прогоняем получившееся и строим новую такую-же табличку - получаем второй слой. *этот концепт сейчас в процессе разработки и это лишь базовое описание идеи

Стоп, у вас там однослойная нейросеть, а как обучать многослойную вы еще не придумали?

Да, но скорее не не придумал, а не проверил - будет ли работать. Сейчас работаю над этим.. Делаю мини чат бота на reverse hash нкйронах

Ну так расскажите же уже, наконец-то, как происходит обучение средних слоев? Как вы определяете, делать ++ или -- счетчика?

пока у меня нет хорошего ответа на этот вопрос. но скоро появится) как будет готов - напишу

пофиксил, теперь и ручками нарисованные символы нормально понимает) ссылка - не изменилась

+5070 от Куртки.

я попросил чат гпт 5.2 высказаться про вашу идею, вот его мнение

Ниже — разбор без «хайпа»: идея сама по себе не бессмысленная (это по сути нейрон радиальной базисной функции в пространстве Хэмминга), но в изложении есть как минимум несколько логических/технических дыр и несколько мест, где автор сильно перепродал скорость/универсальность.

1) Что это на самом деле за «нейрон»

Функция вида

y=A[popcount(xm)]

зависит только от расстояния Хэмминга до некоторого центра m. Это значит:

  • нейрон не различает, какие именно биты отличаются, важна лишь сколько отличается;

  • это «кольцевая» (по радиусу) функция вокруг маски m в 64‑мерном булевом кубе.

Это близко по духу к:

  • RBF/gaussian-like признакам, но в дискретном (Hamming) пространстве;

  • старым RAM-based моделям и «weightless neural networks» (WiSARD и т.п.), хотя там обычно LUT по адресу из битов, а не по расстоянию.

Вывод: это не замена классического нейрона вообще. Это один конкретный тип признака: “насколько похоже на шаблон”.

2) Главная концептуальная проблема обучения в статье: «копим только полезные примеры»

Автор пишет: “если пример полезный — A[dist]++”. Но если вы увеличиваете счётчики только для положительных/“полезных” примеров, то вы не учите нейрон избегать ложных срабатываний.

Типичная ситуация:

  • у вас есть класс “кот” и класс “не кот”;

  • и для “не кот” тоже будут встречаться некоторые расстояния до маски;

  • без учёта “не кот” вы получите bins, которые часто встречаются везде, и они пройдут порог.

Правильнее хотя бы хранить две гистограммы (positive/negative) или один массив, но обновлять по знаку:

  • вариант A (две гистограммы):

    • P[i]++ для positive

    • N[i]++ для negative затем решать по P[i]−N[i] или по отношению P[i]/(N[i]+1)

  • вариант B (одна “разностная”):

    • S[i]+=1 для positive

    • S[i]−=1 для negative затем пороговать S[i]

Иначе “компиляция порогом” превращается в очень шумную эвристику.

3) Огромная потеря информации: одинаковая реакция на все конфигурации с тем же dist

Для 64 бит число различных входов на фиксированном расстоянии d равно (d64​). Например, при d=32 это гигантское количество. Вся эта масса будет иметь одинаковый выход, потому что вы используете только d.

Это даёт:

  • сильную инвариантность (иногда полезно),

  • но часто катастрофическое недообучение: модель не может выучить “важные позиции битов”, только “сколько не совпало”.

Классический нейрон с весами различает вклад каждого бита, а здесь вклад “позиции” уничтожен.

4) «Сжимаем A в одно 64‑битное целое» — ошибка (off-by-one)

Для 64-битного входа:

popcount(⋅)∈{0,1,2,…,64}

То есть индексов 65, нужно 65 бит, а не 64.

Можно сделать:

  • два 64-битных слова (например, 128 бит хранения);

  • или хранить 64 бита для 0..63 и отдельный бит для случая 64;

  • или поменять постановку (но popcount всё равно даёт 64).

5) «SHIFT на величину дистанции» ломается при dist = 64 (и часто — даже формально)

Даже если закрыть глаза на 65-й бит, есть ещё хуже:

  • На x86 сдвиг на 64 для 64-битного регистра маскируется (берутся младшие 6 бит счётчика), т.е. shift by 64 эквивалентен shift by 0. Это даст неверный результат для случая dist=64.

  • В C/C++ сдвиг на ширину типа и больше — вообще Undefined Behavior.

То есть нужно отдельное ветвление/маскирование под dist=64, что ломает “4 инструкции безусловно”.

6) Заявление “никаких чтений из оперативной памяти” неверно для реальной сети

Для одного нейрона в вакууме можно представить, что Mask и A уже в регистрах.

Но любая реальная модель — это много нейронов:

  • у каждого свой Mask и своё A;

  • их нужно загружать из памяти (а это обычно главный лимит производительности).

Итог: на практике вы упрётесь не в ALU, а в memory bandwidth / cache misses. POPCNT действительно быстрый, но “без памяти” — маркетинг.

7) “Фиксированный случайный Mask для изображений” — очень спорно

Для изображений важна локальность и структура (окна/патчи). Если вы просто берёте 64 случайных бита из изображения (или случайную маску поверх 64 бит), вы часто:

  • уничтожаете устойчивые признаки,

  • получаете “рандомный отпечаток”, который плохо переносится при сдвигах/шуме/освещении.

Чтобы это работало, обычно делают:

  • много таких нейронов (как random features),

  • продуманную схему выбора битов (патчи, каналы, локальные рецептивные поля),

  • нормализацию/устойчивое бинаризующее кодирование.

8) Где это может быть полезно (и почему идея вообще “не мусор”)

Это похоже на очень быстрый, дискретный “похожесть на прототип”:

  • быстрый фильтр “похоже/не похоже” для 64-битных отпечатков;

  • дедупликация/near-duplicate для бинарных хэшей;

  • локальные признаки/аномалия, если маски — прототипы;

  • как часть ансамбля (много масок) — может стать рабочим классификатором.

Но это не универсальная замена нейронов и не “бесплатная магия”.

9) Минимальные правки, чтобы концепт стал технически корректнее

Если хочется довести до “рабочего” состояния:

Исправить 65-й случай:

  • хранить A как 65 бит (например, два uint64), и отдельно обрабатывать dist==64.

  • Учить с негативами:

  • хранить P/N или разностный массив и пороговать по разделяющей статистике, а не по “частоте в позитиве”.

  • Не обещать “без памяти”:

  • честно считать, что inference будет memory-bound при большом числе нейронов.

  • Признать ограничение модели:

  • выход зависит только от расстояния, а не от структуры различий.


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

На 65-й бит жалко тратить UINT64) я пока не знаю как решить эту проблему, но 1 бит надо удалить)

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

Но вот выход зависит от структуры различий а не отлько от расстояния. тут он не прав или невнимателен.. нейрон то не один, разные нейроны смотрят на разные биты

Насчет памяти - исправлю, но всеже Хочется надеяться что нейронки построенные по reverse hash будут менее прожорливыми по памяти.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации