Pull to refresh

Comments 29

А для каких задач подходят такие сети?
В данном виде эти сети интересны скорее с исследовательской точки зрения, чем с практической. В теории, можно попробовать применить их к распознаванию речи, визуальных образов, навигации. К любому свойственному человеку виду деятельности.
Нумента в данный момент продвигает платформу Grok, например. Тут можно прочитать об отличии алгоритмов grok от приведенных в статье и схожести с скрытой марковской моделью.
Хочу заострить внимание читателей на весьма интересной заметке и выразить благодарность автору. Это действительно весьма перспективная технология.

Идея «разделяй и властвуй» используется в нейромоделировании уже очень давно. Сверточные нейросети, неокогнитроны, ассоциативные машины ME и HME и т.д. — все эти виды сетей показали впечатляющие результаты в свое время именно за счет разделения пространства признаков на подпространства. Такие сети используются если простой многослойный персептрон (классика) не справляется с поставленной задачей.

Важно (!), что каждая из этих сетей в принципе может быть представлена в виде архитектуры HTM. Ключевым же отличием HTM является время (обработка последовательностей) что расширяет сферу применения кардинально.

Простой пример: обычная сеть способна распознать картинку, HTM — видеоряд. Сфера применения HTM поэтому просто огромна, все что могут обычные сети умеет и HTM + способность обрабатывать информацию в динамике.

В закладки непременно.
Жаль, что ява, я уже практически завершаю реализацию на C#…
В функции getSegmentActiveSynapses у вас такой код:

> activeSynapses.add(new Synapse(idx[0], idx[1], initialPerm));

т.е. как я понимаю вы создаете новые синапсы. В то время как в документе сказано:

> Такие синапсы случайно выбираются из числа клеток, у которых learnState равно 1 в момент времени t.

кажется тут ошибка
Интересный вопрос. Опишу, как это видится мне.
Это не ошибка, это суть оптимизации.
idx[0], idx[1] — индексы рандомной клетки с learnState = true в текущий момент времени.
Синапсы добавляются по мере необходимости. Каждый дендритный сегмент имеет свой набор потенциальных синапсов, который может пересекаться с наборами других дендритных сегментов.
На вопрос, почему он может пересекаться, приведу цитату о проксимальных сегментах:
>Each column in a region is connected to a unique subset of the input bits (usually overlapping with other columns but never exactly the same subset of input bits) (Страница 21)
Если же смотреть логически, то вы правы. Каждая клетка имеет лишь один аксон, следовательно и сформировать она может лишь один синапс.
Что мы получим таким образом? Ровно то же, что имеем сейчас — каждый дендритный сегмент по-прежнему имеет возможность сформировать с каждым аксоном каждой клетки синапс и мы опять вернемся к механизму потенциальных синапсов. С той лишь разницей, что придется отслеживать одну вещь — если данный аксон уже сформировал рабочий синапс, то он не может сформировать его с другой клеткой. Подобного механизма, насколько я помню, в документе нет.
У нас с вами несколько отличаются реализации (чуть более, чем совсем :) ). Хотелось бы понять основные точки расхождения. Поэтому давайте начнем с более простых вещей.

> idx[0], idx[1] — индексы рандомной клетки

Что это за индексы? У меня получается несколько по другому: колонка может задана двумя числами (индексами) по X и по Y, но чтобы задать клетку — нужен еще один индекс — индекс положения клетки в колонке.

Далее Ваши же индексы также продублированы в синапсе — что это за индексы?

У меня в синапсе существуют совсем другие по смыслу индексы:
/// X индекс подключения синапса к входным данным
/// Y индекс подключения синапса к входным данным
(Один тип дендритных сегментов клеток формирует синапсы с входными битами. Активность синапсов из такого сегмента линейно суммируется для определения прямой активации всей колонки.)
Т.е. по сути это синапсы колонки, соединенные с входными данными случайным образом.

в классе Synapse два поля:
>public Integer c;
>public Integer i;
Класс используется как для синапсов на проксимальных дендритах (которые ко входу подключены), так и на дистальных, с той разницей, что для проксимальных 'c' и 'i' это на самом деле ваши же X и Y, а для дистальных это c — номер колонки и i — номер клетки, как в пдфе. Неочевидное место, конечно.
Таким образом, idx[0] и idx[1], так как берутся из списка learnState (аналогично определению в пдфе: learnState(c, i, t) — A boolean indicating whether cell i in column c is chosen as the cell to learn on, с разницей в написании: learnState.get(t).get(j).get(k), где t — время, j — колонка, k — клетка), представляют собой номер колонки и номер клетки соотетственно.
Ок,

Уточню для для дистальных
> c — номер колонки

Т.е. если регион имеет 3х3 (=9) колонок — у вас номируется:

123
456
789

Так?

в то время как у меня двумя индексами по X, Y (в документе это не так, но так удобнее потом работать)

yx yx yx
11 12 13
21 22 23
31 32 33

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

> А если newSynapses равно true, тогда число синапсов, равное newSynapseCount — count(activeSynapses),
добавляется к активным синапсам activeSynapses. Такие синапсы случайно выбираются из числа клеток, у которых learnState равно 1 в момент времени t.

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

Вы это решили, так что у вас тут же они и создаются, а не выбираются. Но кажется, после начального состояния это будет не правильно (после создания синапсов, они начинают обучаться, а у вас будут создаваться все новые и новые — или я чего-то не понимаю в вашем коде?).
Вы все правильно понимаете. Только в некоторых моментах неточно.
>В начальном состоянии, еще нет даже дендритных сегментов
Сомнительно. Если у нас в начальном состоянии вообще не будет сегментов, то как минимум две начальные фазы темпорального группировщика бесполезны на первом временном шаге и вся система алгоритмов может загнуться. Хотя проще проэкспериментировать и посмотреть на результат.
Насчет создания синапсов, вот отрывок с 43 страницы:

In the temporal pooler, each segment can have its own (possibly large) list of potential synapses. In practice maintaining a long list for each segment is computationally expensive and memory intensive. Therefore in the
temporal pooler, we randomly add active synapses to each segment during learning (controlled by the parameter newSynapseCount). This optimization has a similar effect to maintaining the full list of potential synapses, but the list per segment is far smaller while still maintaining the possibility of learning new temporal patterns.
> добавляем активные синапсы к каждому сегменту во время обучения

Так в том то и дело — добавляем, а не создаем новые! Причем добавляем активные (!), т.е. они уже существовали, и активировались.

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

> Такие синапсы случайно выбираются из числа клеток, у которых learnState равно 1 в момент времени t.

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

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

Но также и есть. Как раз на первой фазе вызывается getSegmentActiveSynapses с s= -1, и комментарий

> Если же текущий прямой вход снизу не был предсказан, тогда все клетки становятся активными (строки 32-34) и кроме того, клетка, лучше всего соответствующая входным данным, выбирается для обучения (строки 36-41), причем ей добавляется новый (!!!) латеральный дендритный сегмент.

А раз он создается, на первой фазе — но инициализировать случайно нет смысла, он добавится там, где это будет нужно.
На первой фазе создается только структура segmentUpdate, фактический сегмент появляется только в конце третье фазы. Хотя, я не отрицаю, что можно попробовать не создавать начальные дистальные сегменты.
Чтобы понимать где чисто ваша реализация, а что исходит из документа:

При инициализации у вас есть такой код:

for(int k = 0; k < 2; k++) {
                    dendriteSegments.get(c).get(i).add(new Segment());
                    for(int s=0; s < newSynapseCount; s++) {
                        Integer col = rnd.nextInt(xDimension*yDimension);
                        Integer cell = rnd.nextInt(cellsPerColumn);
                        dendriteSegments.get(c).get(i).get(k).synapses.add(new Synapse(col, cell,
                                connectedPerm + connectedPerm / 2.0 - (rnd.nextDouble()/10.0)));
                    }



Т.е. вы создаете 2 сегмента с полным набором синапсов. Если инициализация синапсов колонок (при работе пространственного групировщика) описана явно, то для латеральных дендритных сегментов — это не описано, или я где-то просмотрел? И соответственно эта вот инициализация — есть сугубо ваше решение?
Все верно, это отсебятина.
Для проверки вашего предположения я подставил условие k < 0, тем самым пропустив этот кусок кода — приложение действительно работает без этой части инициализации, а картина добавления новых сегментов практически идентична той, что получается с инициализацией. Можно эту часть кода удалить, спасибо.
Синапсы на проксимальных дендритах образуются между аксонами клеток-входов и дендритными сегментами колонок.
Синапсы на дистальных образуются между аксонами клеток внутри региона и дендритами других клеток внутри региона.
Еще кусок из того же абзаца той же страницы, прочтите её, там все изложено подробно и складно:
The implementation of potential synapses is different from the implementation in the spatial pooler. In the spatial pooler, the complete list of potential synapses is represented as an explicit list. In the temporal pooler, each egment can have its own (possibly large) list of potential synapses.
И насчет выбора:
These synapses are randomly chosen from the set of cells that have learnState output = 1 at time step t.
Ваш вариант больше подходит под глагол taken, да и в общем не имеет практического смысла. Весь этот механизм по добавлению/созданию новых синапсов направлен всего лишь на увеличение быстродействия, что видно из цитаты комментарием выше.
Суть действия в том, что при недостаточной степени латеральной (внутрирегиональной, межклеточной) активности сегментов клетки не входят в предсказательное состояние, ввиду чего им добавляются новые синапсы-латеральные связи. В документе, в описании второй фазы темпорального группировщика, можно посмотреть подробнее.
Видимо тут действительно какие трудности перевода, т.к. есть еще такая фраза

> Но только одна из этих клеток (которая лучше всего соответствует данному входу) включает свое состояние обучения learnState. И мы добавляем синапсы только клеткам с таким включенным параметром

что противоречит

> Такие синапсы случайно выбираются из числа клеток, у которых learnState равно 1 в момент времени t.

И похоже действительно нужно СОЗДАВАТЬ синапсы, но только не как у вас при инициализации, а только в getSegmentActiveSynapses.

И далее не так как у вас:

idx = learningCells.get(r.nextInt(learningCells.size()));
activeSynapses.add(new Synapse(idx[0], idx[1], initialPerm));

т.е. соединяя синапсы не с learningCells которых еще может не быть, а наоборот присоединяя видимо случайные (как у вас на инициализации ?) к тем клеткам которые в данный момент проставлена как learnState равно 1

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

Вполне вероятно, что массив learningCells окажется нулевого размера и на строке

idx = learningCells.get(r.nextInt(learningCells.size()));

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

Вот для проксимальных дендритов (которые ко входу подключены) — можно сказать когда синапс активный, это у вас вычисляется в функции SOverlap(). Там правда косвенная оценка, но если не умножать boost, то overlap — это будет «Число действующих синапсов подключенных к активным входным битам»

Что в этом месте для дистальных синапсов? Я не нахожу функции, где генерируется активность активность этого синапса. Есть только оценка этого после в функции segmentActive например. Как я понимаю там идет подсчет «числа подключенных синапсов сегмента s, которые активны благодаря заданным состояниям в момент t» — но когда (где это просчитывается) они становятся активными?
>This routine returns true if the number of connected synapses on segment 's' that are active due to the given state at time 't' is greater than activationThreshold.
Т.е. функция возвращает true, если количество соединенных синапсов (permanence > connectedPerm), аткивных вследствие заданного состояния (active/learn state) больше параметра activationThreshold.
В реализации функции нужно еще добавить условие, чтобы было:
>if(list.get(syn.c).get(syn.i) && syn.permanence > connectedPerm) {
Что такое

> if(list.get(syn.c).get(syn.i)

не понимаю эту запись.

> активных вследствие заданного состояния (active/learn state)

— так в том то и дело — ГДЕ происходит само задание этого состояния? Прямо в этой же функции?
Строка кода из функции segmentActive.
>ГДЕ происходит само задание этого состояния
В перовой фазе темпорального группировщика.
> Строка кода из функции segmentActive.

Я извиняюсь, наверное я плохо понимаю код на яве — поясните что делает эта строка

> В перовой фазе темпорального группировщика.

Там такой код

for c in activeColumns(t)
2.
3. buPredicted = false
4. for i = 0 to cellsPerColumn — 1
5. if predictiveState(c, i, t-1) == true then
6. s = getActiveSegment(c, i, t-1, activeState)
7. if s.sequenceSegment == true then
8. buPredicted = true
9. activeState(c, i, t) = 1
10.
11. if buPredicted == false then
12. for i = 0 to cellsPerColumn — 1
13. activeState(c, i, t) = 1

Где? Тут я вижу установление активности клетки, а не синапса…
причем активность клетки устанавливается после вызова getActiveSegment. В то время как к моменту вызова getActiveSegment уже должны активироваться синапсы — но где?
В том месте getSegmentActiveSynapses и прочие связанные с активностью сегментов функции, как вы могли заметить, вызываются от предыдущего шага времени.
Активность синапса = подключенность синапса + активность клетки, на которую он указывает.
Другими словами синапс активен когда подключен к активной клетки? И if(list.get(syn.c).get(syn.i) — проверяет как раз это?

похоже начинает доходить :) спасибо…
Sign up to leave a comment.

Articles