Как стать автором
Обновить

Структурная адаптация, brand-new самоорганизующаяся сеть на палочках и кружочках

Время на прочтение11 мин
Количество просмотров4.6K

Речь в статье пойдет о принципиально новой ИИ методологии, основанной на распространении потока в адаптивной многомерной структуре (фильтре). Ранее подход описан нигде не был, знакомьтесь. Говорят, еще надо предупреждать о картинках - предупреждаю, картинки будут. Код тоже будет.

Замечание по терминологии

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

К примеру - синапс; под "синапсом" мы будем понимать просто "место контакта" двух нейронов, невзирая на природу синапса и его "тактико-технические характеристики". Говоря "нейрон" мы иногда будем подразумевать "узел графа", а иногда "рецептор", если хотим абстрагировать рецептор до уровня нейрона.

Почему мы можем позволять себе такое? Описываемая модель самоорганизующейся сети использует самые абстрактные уровни поведения реальных объектов живой (да и неживой) природы. Всё действительно просто, почти как колесо, смысла в усложнении попросту нет. Контекст при этом важен, старайтесь не терять нить повествования. Я, в свою очередь, постараюсь вызвать у вас визуальную картину происходящего вместо скучного разбора терминов.

Поток

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

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

В моем Java коде поток это просто enum класс. Бинарный выбор - поток либо есть, либо его нет. Все нюансы взаимодействия реализуются уже исходя из свойств среды, работающей с потоком.

enum Flow {
    RUN, STILL
}

Но зачем мы ввели понятие потока? Можно и дальше было пользоваться каким-нибудь Потенциалом действия! Или, возможно, Информацией, Сигналом? Импульсом?

Я исхожу из того, что до конкретных нейронов добраться нельзя. Что придётся работать с системой целиком, как с чёрным ящиком, имея только вход и выход потоков. Поток - это попытка полного абстрагирования от природы перемещающегося вещества. Это описываемое универсальным образом воздействие одного объекта на другой.

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

Пример пассивного фильтр потока - фильтрующая насадка на водопроводный смеситель
Пример пассивного фильтр потока - фильтрующая насадка на водопроводный смеситель

Модель синапса

Перечислим свойства синапса.

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

Синапсы будем различать по характеру воздействия на целевой нейрон:

  • тормозные - понижающие вероятность активации целевого нейрона

  • возбуждающие - повышающие вероятность активации целевого нейрона

Два синапса - тормозный и возбуждающий
Два синапса - тормозный и возбуждающий

Модель нейрона

Нейрон - узел для локализации потока в пространстве. Выполняет функции обобщения (суммирования, абстрагирования) потоков из других локализаций пространства сети. Нейрон не обрабатывает импульс в вычислительном смысле, а просто обобщает (конвергирует) потоки. Почти как водопроводный смеситель.

Модель нейрона не учитывает пространственное расположение синапсов на дендритном дереве нейрона. Также модель не учитывает очередность прихода импульсов по синапсам.

Видим два тормозных и три возбуждающих синапса на дендритном дереве
Видим два тормозных и три возбуждающих синапса на дендритном дереве

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

Нейрон заторможен, хотя активны два возбуждающих синапса
Нейрон заторможен, хотя активны два возбуждающих синапса

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

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

Целевой нейрон возбуждён от единственного активного возбуждающего синапса
Целевой нейрон возбуждён от единственного активного возбуждающего синапса

Долговременная память

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

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

Какие свойства паттерна можно выделить особо?

Паттерн - это множество субпаттернов, или составных частей паттерна. Модель нейрона говорит нам, что достаточно хотя бы одного активного субпаттерна, чтобы паттерн стал активным. Мы говорим об элементарной операции ИЛИ.

Затормозить созданный паттерн может поток с активного тормозного синапса. Но как можно проинтерпретировать это торможение? Желательно, с примером. Посмотрим, как наш нейрон-паттерн будет узнавать всем известную бабушку. И, что важнее, как он её узнавать точно не будет.

Вот она - среднестатистическая бабушка с "командирскими" часами на правой руке
Вот она - среднестатистическая бабушка с "командирскими" часами на правой руке

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

Седые волосы, очки, пирожки, кот (довольный и спящий одновременно), ну... и достаточно
Седые волосы, очки, пирожки, кот (довольный и спящий одновременно), ну... и достаточно

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


"Злой кот" это точно не про бабушку на картинке
"Злой кот" это точно не про бабушку на картинке

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

Надеюсь, все понимают, что на картинках не более чем булева алгебра? Заниматься вручную подобными упражнениями может быстро наскучить. Нам хотелось бы как-то автоматизировать процесс.

Построение сети

Введем еще пару-тройку понятий. Тупиковым (deadend, D) нейроном будем называть нейрон, не передавший пришедший поток далее по цепочке своих исходящих связей. Поток оказался запертым в текущем нейроне, иначе говоря, в тупике.

Обходными (sideway, S) нейронами будем называть все прямые нейроны-предки тупиковых нейронов, соединенные с тупиковыми нейронами возбуждающими синапсами, причем по этим синапсам потока в текущем такте не было. "Прямой" предок означает, что нейрон-предок находится ровно через один синапс от своего потомка. Замечание: термин "обходной нейрон" мне искренне не нравится, надо будет заменить при случае.

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

При построении сети необходимо держать в голове правило: вошедший в сеть поток должен выйти из сети.

Представим ситуацию, что пришедший в сеть поток оказался запертым в каком-то количестве тупиковых нейронов. Нужно следовать только что введённому правилу и ответить на вопрос: "Куда направлять поток с тупиковых нейронов?"

Если поток оказался запертым в каком-то количестве тупиковых нейронов, мы создаём новый нейрон-паттерн (pattern, P), а аксоны тупиковых нейронов соединяем возбуждающими синапсами с дендритом созданного нейрона. Все обходные нейроны мы соединяем тормозными синапсами с новым нейроном.

Новые связи показаны пунктирными линиями
Новые связи показаны пунктирными линиями

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

Учёт времени

Необходимость учёта времени появляется из простого факта, что система, не учитывающая время, не может выявлять зависимости во времени, строить причинно-следственные связи. Такая система может лишь выявлять повторные комбинации символов, но прогнозировать физически не способна.

Несомненно, с временем можно работать как с потоком символов, но... можете ли вы себе представить "рецептор времени" в реальном биологическом мире?!

Решение, на самом деле, весьма простое. Время может учтено (а, значит, и с прогнозированием вопрос может быть решён), если реализовать stateful систему. На каждую единицу времени, сеть будет изменять свое внутреннее состояние, чем и будет достигаться различие откликов на идентичную стимуляцию.

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

Где формулы?!

Формул нет, друзья! Параметров нет! Порогов нет! А все потому, что сеть не занимается символьными вычислениями. Сеть представляет собой результат полной абстракции от предметов реального мира, формальную систему. Разберитесь с природой символьных вычислений, и ваше желание искать формулы в биологически правдоподобных сетях пропадёт.

Пример построения сети по шагам

Я покажу в картинках пошаговое разворачивание сети с тремя рецепторами (receptor, R). На старте ничего кроме рецепторов в сети нет. Поток, который мы будем подавать на сеть показан на картинке чуть ниже рецепторов:

Спойлер - в конце получится вот так
Такт 1
Активны рецепторы №2 и №3. Плюс, в них "застрял" поток. 
Бросаем возбуждающие связи на новый нейрон-паттерн №1.
Активны рецепторы №2 и №3. Плюс, в них "застрял" поток. Бросаем возбуждающие связи на новый нейрон-паттерн №1.
Такт 2
R3 не участвует в создании паттерна №2, но потенциально способен возбудить его через исходящую цепочку связей. Блокируем такую возможность, создавая тормозную связь.
R3 не участвует в создании паттерна №2, но потенциально способен возбудить его через исходящую цепочку связей. Блокируем такую возможность, создавая тормозную связь.
Такт 3
Бывают ситуации, когда связи между паттернами в последовательных тактах не создаются. №2 и №3 без взаимной связи.
Бывают ситуации, когда связи между паттернами в последовательных тактах не создаются. №2 и №3 без взаимной связи.
Такт 4
Тут ничего интересного, продолжаем...
Тут ничего интересного, продолжаем...
Такт 5
Внимание на возбуждающую связь между нейронами №1 и №2. Мы должны сохранить её состояние и перенести на следующий такт.
Внимание на возбуждающую связь между нейронами №1 и №2. Мы должны сохранить её состояние и перенести на следующий такт.
Такт 6
Видим, что активен возбуждающий синапс на дендрите нейрона №2. Он перенёс своё состояние из предыдущего такта.
Видим, что активен возбуждающий синапс на дендрите нейрона №2. Он перенёс своё состояние из предыдущего такта.
Все 6 тактов на одной картинке
Все шаги разом на одной картинке для удобства
Все шаги разом на одной картинке для удобства

Ниже на видео рост трёхрецепторной сети при потоке случайных воздействий. В каждом такте активен только один рецептор. Задача видео - просто демонстрация роста сети, выводы делать не будем. Случайный поток на входе - случайная структура в результате.

Практическое применение

Разумеется, технологию проще понять и принять, если она что-то умеет делать. Например, решить с её помощью какую-то общеизвестную проблему. Как это ни печально, но шансов у сети побороть narrow AI очень мало. А использовать сеть для решения задач general AI за вменяемое время в одиночку не представляется возможным. Остается надеяться на хоть какое-то решение хоть какой-то задачи, а умные люди с ресурсами смогут генерализовать подход и развить светлое начинание.

Детектор аномалий

Для тех, кто не знаком с предметом, вот ссылка на статью в вики.

Превратим сеть в детектор аномалий. Встроим её в реализацию интерфейса детектора.

Вот интерфейс
public interface OutlierDetector {
    void addChannel(DoubleSupplier ds, Bounds bounds, int bucketCount);
    void addAdaptiveChannel(Supplier<Object> supplier, Set<Object> dictionary);
    double next();
}
А вот реализация с сетью внутри
public class NeuralOutlierDetector implements OutlierDetector {

    private final Network network;
    private double outlierEstimation = 0;

    public NeuralOutlierDetector(int maxNeuronSize) {
        network = new Network(Collections.singletonList(new NetworkEventsListener() {
            @Override
            public void onDeadendNodesDetected(List<Node<?, ?>> deadendNodes, int maxDeadendNeuronCount) {
                double average = deadendNodes.stream().mapToInt(Node::getId).summaryStatistics().getAverage();
                outlierEstimation = 1. - average / (double) maxDeadendNeuronCount;
            }
        }), maxNeuronSize);
    }

    @Override
    public void addChannel(DoubleSupplier ds, Bounds bounds, int bucketCount) {
        network.addDoubleReception(ds, bounds, bucketCount);
    }

    @Override
    public void addAdaptiveChannel(Supplier<Object> supplier, Set<Object> dictionary) {
        network.addAdaptiveDictReceptor(supplier, dictionary);
    }

    @Override
    public double next() {
        network.tick();
        return outlierEstimation;
    }
}

Из реализации нас интересует только конструктор. Видно, что значение "аномальности происходящего" рассчитывается как отношение усреднённого значения id тупиковых нейронов к текущему числу нейронов в сети.

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

В первом эксперименте будем подавать на вход сети двухканальные данные ЭКГ, а сеть нам должна каким-то образом сообщить, является ситуация на текущем такте аномальной или нет.

Признаться, я не силён в ЭКГ, и до конца не знаю физического и, тем более, физиологического смысла этих данных. Но это и не важно для нас сейчас. Нам необходимо увидеть, способна ли сеть детектировать аномалии, которые видны невооружённым глазом.

Исходные двухканальные данные ЭКГ (15 тыс. точек). Видны ли вам "аномалии"?
Исходные двухканальные данные ЭКГ (15 тыс. точек). Видны ли вам "аномалии"?

Будем работать с тремя независимыми детекторами. Первый детектор будет двухканальный, остальные два - одноканальные. Максимальное число нейронов в сети сделаем 300. То есть первые 300 точек будут своего рода нормальным потоком, сеть будет учиться, "привыкать" на протяжении 300 точек к потоку данных, искать в нем внутренние зависимости.

Кусок Java кода с объявлением и настройкой детекторов
OutlierDetector detector = new NeuralOutlierDetector(maxNeuronSize);
detector.addChannel(() -> data[idx][0], bounds, bucketCount);
detector.addChannel(() -> data[idx][1], bounds, bucketCount);

OutlierDetector detector1 = new NeuralOutlierDetector(maxNeuronSize);
detector1.addChannel(() -> data[idx][0], bounds, bucketCount);

OutlierDetector detector2 = new NeuralOutlierDetector(maxNeuronSize);
detector2.addChannel(() -> data[idx][1], bounds, bucketCount);

Настраиваем каналы детекторов. Работаем с числовыми данными, а значит: разбиваем область возможных значений каждого канала на корзины, каждой корзине ставим в соответствие рецептор.

Читаем данные в массив, подаем на детекторы, получаем следующее:

Зелёный график - сигнал с двухканального детектора.
Два нижних синих графика - сигналы с одноканальных детекторов.
Видим, что детекторы действительно неравнодушны к аномальным участкам.
Зелёный график - сигнал с двухканального детектора. Два нижних синих графика - сигналы с одноканальных детекторов. Видим, что детекторы действительно неравнодушны к аномальным участкам.

Во втором эксперименте подадим на вход детектора текст Lorem ipsum.

Сверху - результат работы детектора аномалий для текста Lorem ipsum dolor sit amet... (5 тыс. знаков)
Нижний график - это приближенная часть в черном прямоугольнике.
Видим резкое падение коэффициента аномалий для знакомого куска текста.
Сверху - результат работы детектора аномалий для текста Lorem ipsum dolor sit amet... (5 тыс. знаков) Нижний график - это приближенная часть в черном прямоугольнике. Видим резкое падение коэффициента аномалий для знакомого куска текста.

Что важно. Работа с числами абсолютно не свойственна сети! Сеть естественным образом работает с объектами, а не с числами. Чтобы заставить сеть работать с числами в первом эксперименте, мы сделали преобразование, которое стёрло информацию об исходной природе объекта - бывшего числа. Если в прочих моделях (работающих с символами) нужно ломать голову как преобразовать объект в число, то здесь совершенно противоположная картина.

Что примечательно. Мы увидели, что сеть работает абсолютно без настройки, с ходу. Мы не тренируем сеть бесчисленное множество эпох, не делаем кросс-валидаций на трёх выборках. Акт обращения к сети является, одновременно, операцией чтения и операцией изменения, их физически невозможно разделить.

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

Почему нет других экспериментов, помимо жалкого детектирования аномалий? Ответ очень прост: к сожалению, текущая стадия исследований не позволяет показать ничего больше. В режиме детектора аномалий от сети не требуется воздействовать на среду, а также принимать от неё поощрение или наказание. Требуется лишь умение накапливать опыт, а сеть уже это умеет. Небольшой костыль, и вот перед нами уже целый интеллектуальный прибор по распознаванию аномалий на зашумлённых многомерных данных во времени.

Обучение с подкреплением

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

Код

Да, код есть, написан на Java, лежит тут https://github.com/sturex/sonn. Качество кода - исключительно proof-of-concept, который в силах поднять один разработчик в режиме хобби. Кстати, код дает существенно более широкие возможности, нежели чем описаны в статье, экспериментируйте! В целом код пока не вышел из-под контроля, но очередное желание все переписать появляется все отчётливее, особенно с учетом нескольких костылей, влияющих на производительность.

Быстрые ссылки:

Заключение

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

Структура самоорганизующейся сети является физическим воплощением входящего в неё потока-опыта. Вам тоже кажется, что не хватает небольшого механизма подстройки структуры по сигналам от внешней среды, чтобы сеть начала осознанно принимать решения?

Спасибо

Александру Коневу за ценную переписку, часть которой я самым наглым образом скопипастил.

Теги:
Хабы:
Всего голосов 9: ↑8 и ↓1+7
Комментарии15

Публикации