№93 NL UI: Выбор координат нейронов для карты нейронов
Четверг, Апрель 23rd, 2009Нейронная карта/поле нейронов полезна для удобства отладки, для придания большей образности процессу разработки, для подготовки пояснительных схем. Как нейроны располагаются на этом поле? Доступно и ручное перетаскивание нейронов, чего достаточно для построения обучающих схем. В этой заметке рассматривается автоматическое расположение.
При создании окна карты, в ИНС уже может присутствовать большое количество нейронов. Например, ИНС может быть загружена из файла, либо это могут быть предварительно созданные сенсорные и эффекторные нейроны. Либо окно с картой нейронов может быть создано после некоторого времени работы ИНС.
По умолчанию, уже существующие нейроны располагаются квадратом, по порядку перечисления, который совпадает с порядком их создания.
Как располагать новые нейроны? Сколько их будет создано в процессе работы ИНС – окну заранее не известно. Желательно располагать нейроны так, чтобы обе координаты задействовались в примерно равной степени. Принята следующая схема: новые нейроны располагаются вправо от последних добавленных; при достижении некоторой внутренней границы происходит «перевод строки». После перевода «курсора» для вставки следующего нейрона, длина строки будет на единицу больше. Поэтому рост ИНС в карте будет напоминать треугольник (или трапецию – в зависимости от начальных условий). Со временем нижнее основание будет расти все больше. При этом, верхняя правая часть окна не используется, она остается пустой. Потери свободного пространства при таком способе расположения – около 50%.
Благодаря использованию Qt, карта поддерживает масштабирование.

Рис. 93-1 Автоматическое трапециевидное расширение ИНС при добавлении новых нейронов
На рис. 93-1 показана тестовая ИНС, у которой квадратом были расположены нейроны, существовавшие на момент инициализации карты. Остальные нейроны были добавлены позже.
За один такт работы ИНС может быть создано несколько нейронов. Особенно, если ИНС работает на основе кластеров. Неплохо было бы расположить их не по прямой линии, а как-то более компактно. Так и сделано: если в ИНС появляются новые нейроны, то карта группирует нейроны по циклу создания, и каждую такую группу размещает отдельно, в виде прямоугольника. Размещение начинается с той же позиции, где должен был расположиться один новый нейрон. Величина сторон прямоугольника выбирается на основе квадратного корня от числа нейронов в размещаемой группе. После размещения группы, текущий курсор смещается примерно так, как обрабатывается курсор в случае одного нейрона. Пример этого типа размещения показан на рис. 93-2. Для примера использую старые снимки экрана, так как сейчас этот режим нигде не задействован.

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

Рис. 93-3 Нечетное количество нейронов при прямоугольном размещении помогает визуально определять кластеры
Но хочется получить более точный контроль над размещением нейронов в кластере. Например, хочется, чтобы нейроны, работающие с признаками, размещались внизу кластера, а нейроны выхода – вверху. Решение-заплатка – подбирать очередность создания нейронов разных типов в кластере так, чтобы получить необходимое расположение.
При росте количества нейронов в кластере это решение становится совсем некрасивым. Кластеров может быть несколько типов – например, кластеры памяти, а также отдельные нейроны цепочек нечеткого распознавания. Поэтому сейчас в НЛ используется следующее решение. ИНС может переопределить метод для возврата объекта-«расположителя»:
-
//У объекта-«расположителя» длинное название. Ранее, карты нейронов
-
//располагали нейроны на основе целочисленных индексов стандартной сетки. Потом было
-
//принято решение перейти к расположению на основе индексов с числами с плавающей точкой.
-
//Поэтому и название содержит FloatIndexPlacer.
-
//(NeuroItem – имя класса нейрона в двухмерной карте)
-
virtual NeuroItemsFloatIndexPlacerAbstract* getNeuroItemsFloatIndexPlacer()=0;
Определение этого класса содержит единственную функцию:
-
virtual void placeByNeuron(IN bioId id, OUT QList<QPair<bioId, QPointF> > & listPlacedNeurons)=0;
Схема работы следующая. Если ИНС возвращает ненулевой расположитель, то для расположения используется именно он, иначе – схема с группировкой нейронов по циклам и прямоугольникам, описанная выше. При работе с расположителем, берется очередной нерасположенный нейрон и передается расположителю. Обычно, в качестве расположителя выступает сама ИНС (применяется множественное наследование ради одной функции). ИНС смотрит на тип нейрона. Если нейрон является частью кластера, то удобно расположить сразу весь кластер. Координаты всех нейронов данного кластера возвращаются через список. В качестве единицы координат выступает «стандартное расстояние между нейронами на карте», реально расстояние определяется настройками карты. Координаты трактуются как локальные для кластера. Этого достаточно. Карта, после получения списка нейронов с локальными координатами, прибавляет к локальным координатам позицию текущего курсора вставки, а расположенные нейроны удаляет из списка нерасположенных. Рост происходит все так же – по строкам и трапециевидно.
-
//QNLBrain – родительский класс для любой ИНС в Нейролаборатории
-
//в нем есть следующая функция, которая применяется только для краткости кода:
-
void QNLBrain::placeNeuron(CommonNeuron* pn, qreal x, qreal y, OUT QList<QPair<bioId, QPointF> > & listPlacedNeurons)
-
{
-
listPlacedNeurons.push_back( QPair<bioId, QPointF>(pn->id(), QPointF(x, y)));
-
}
-
-
//пример расположения нейронов кластера на карте:
-
void BrainB1::placeByNeuron(IN bioId id, OUT QList<QPair<bioId, QPointF> > & listPlacedNeurons)
-
{
-
neuron* pn = GetNeuronById(id);
-
ASSERT(pn);
-
//получить кластер по нейрону. В ИНС Б1 все нейроны принадлежат кластерам
-
TCC* tcc = getTcc(pn);
-
ASSERT(tcc);
-
placeNeuron(tcc->down , 0.0, 0.0, listPlacedNeurons);
-
placeNeuron(tcc->ninPrev, 0.0, 1.0, listPlacedNeurons);
-
placeNeuron(tcc->help , 0.5, 1.7, listPlacedNeurons);
-
placeNeuron(tcc->nout , 1.0, 0.0, listPlacedNeurons);
-
placeNeuron(tcc->nin , 1.0, 1.0, listPlacedNeurons);
-
}
Результат будет выглядеть так:

Рис. 93-4 Результат выполнения приведенного кода
Забегая вперед, вот как выглядит схема кластера ИНС Б2:

Рис. 93-5 Схема кластера ИНС Б2 в двухмерной карте нейронов
В ИНС Б2, нейроны цепочки нечеткого распознавания располагаются отдельно от основных кластеров: нейроны SL0 чуть ниже, посередине кластера, а SL1 – на одну позицию выше:

Рис. 93-6 Пример расположения нескольких видов нейронных цепочек на карте
Еще на рисунках можно встретить синие квадраты. Это группы, описанные ранее. В них можно добавлять любые нейроны, например, сенсорные. Расположение внутри групп происходит построчно.
Обновление: код уже переписан. В первой версии статьи, до переписывания, было следующее:
Подытоживая, можно добавить, что на текущий момент, код по выбору координат и смещений в самой карте – один из самых некрасивых во всей Нейролаборатории. В нем остались пережитки нескольких старых архитектурных решений, принятых в мою бытность начинающим разработчиком: работа с целочисленными индексами (можно оставить только плавающие), возможность устанавливать ограничения на рост (что было нужно при упаковке нейронов в другие контейнеры, но более правильное решение – разрешить рост контейнеров), возможность обмена осей x и y (правильно делать на основе прокси-объекта с тем же интерфейсом, а не на основе вездесущих проверок с перестановками, которые могут невзначай поменять оси лишний раз). Если выпадут пару свободных месяцев – может, исправлю. А сейчас, с учетом приоритетности, устраивает и текущий результат.








