Доброго времени суток!
В последнее время на хабре довольно часто появляются статьи, в которых авторы описывают современные теории и подходы к построению искусственного интеллекта и нейронных сетей. Однако примеров конкретной реализации приводится довольно скудное количество. Попробуем восполнить этот пробел. В данной статье я опишу только основные теоретические и практические моменты, использованные при написании рабочего макета алгоритмов, предоставленных Numenta Inc.
Основой для реализации послужил документ, доступный здесь. Там же можно найти и русский перевод, но для полноты картины лично я порекомендовал бы использовать оригинал.
Архитектура HTM
Архитектура сети, как уверяет автор, базируется на строении коры головного
Картинка с официального сайта дает общее представление о строении участков:
Строение клетки, по аналогии с естественным нейроном, предусматривает наличие дендритных сегментов (состоящих из некоторого множества дендритов) и синапсов — мест соединения между аксоном другой клетки и собственным дендритом. Каждый синапс в HTM фактически представлен «прочностью» (permanence) — вещественным числом от 0 до 1. Если прочность синапса меньше задаваемого значения, то синапс считается «несуществующим», в противоположном случае — действующим. В этом заключается понятие потенциального синапса (potential synapse).
Дендритные сегменты одной клетки делятся на дистальные и проксимальные. Проксимальные используются для получения входной информации, дистальные — для связи клеток между собой, внутри данного участка. В предлагаемой Numenta архитектуре каждая колонка клеток имеет один общий проксимальный сегмент, что способствует увеличению быстродействия. Каждый дендритный сегмент (как дистальный, так и проксимальный) содержит некоторое множество потенциальных синапсов.
Входные биты информации представляются в виде слоя клеток, способных формировать синапсы с проксимальными дендритами.
На базе данной архитектуры работают алгоритмы обучения, состоящие из двух фаз: пространственного (spatial) и временного (temporal, темпорального?) группировщиков.
Пространственный группировщик
Пространственный группировщик включает в себя три фазы: перекрытия (overlap), подавления (inhibition), обучения (learning).
В фазе перекрытия для каждой колонки вычисляется величина, отражающая «охват» синапсами проксимального дендритного сегмента данной колонки «единичных» битов входной информации входной информации. Активным считается синапс, прочность которого больше установленного значения и бит информации, к которому он ведет, содержит 1.
- public void SOverlap() {
- for(int i=0;i<xDimension*yDimension;i++) {
- overlap[i] = 0.0;
- for(Synapse synapse: connectedSynapses(i)) {
- overlap[i] += input(time, synapse.c, synapse.i);
- }
- if (overlap[i] < minOverlap)
- overlap[i] = 0.0;
- else
- overlap[i] *= boost[i];
- }
- }
В фазе подавления вычисляется список активных колонок. Колонки с максимальным «охватом» подавляют своих соседей, количество активных колонок в регионе регулируется задаваемым пользователем параметром.
- public void SInhibition() {
- activeColumns.add(new ArrayList<Integer>());
- for(int i=0;i<xDimension*yDimension;i++) {
- Double minLocalActivity = kthScore(neighbours(i), desiredLocalActivity);
- if (overlap[i] > 0.0 && overlap[i] >= minLocalActivity) {
- activeColumns.get(time).add(i);
- }
- }
- }
В фазе обучения прочность активных синапсов проксимального дендритного сегмента каждой активной колонки повышается, неактивных, соответственно понижается. Затем применяется механизм, обеспечивающий регулярное срабатывание всех колонок в регионе. В общих словах, он состоит в том, что мы ведем историю активации каждой из колонок (в виде скользящего среднего) и усиливаем «охват» колонки в случае её долгого простоя.
- public void SLearning() {
- for(Integer c: activeColumns.get(time)) {
- for(Synapse s: potentialSynapses.get(c)) {
- if (input(time, s.c, s.i) > 0) {
- s.permanence += permanenceInc;
- s.permanence = Math.min(s.permanence, 1.0);
- } else {
- s.permanence -= permanenceDec;
- s.permanence = Math.max(s.permanence, 0.0);
- }
- }
- }
- for(int i=0;i<xDimension*yDimension;i++) {
- minDutyCycle[i] = 0.01 * maxDutyCycle(neighbours(i));
- activeDutyCycle[i] = updateActiveDutyCycle(i);
- boost[i] = boostFunction(activeDutyCycle[i], minDutyCycle[i]);
- overlapDutyCycle[i] = updateOverlapDutyCycle(i);
- if (overlapDutyCycle[i] < minDutyCycle[i]) {
- increasePermanences(i, 0.1*connectedPerm);
- }
- }
- inhibitionRadius = averageReceptiveFieldSize();
- }
На этом этап пространственного группировщика заканчивается. В качестве результата мы имеем список активных колонок, который нам еще понадобится.
Временной группировщик
Временной группировщик, как и пространственный, состоит из трех фаз: вычисления состояний активности (active) и обучения (learn) каждой из клеток, вычисления состояний предсказания (prediction) и фазы применения предпологаемых на предыдущих шагах изменений в структуре сети — добавления новых синапсов/дистальных дендритных сегментов.
В первой фазе для каждой активной колонки из списка, доставшегося от пространственного группировщика, вычисляется — была ли эта активность предсказана какой-либо из клеток колонки. В случае нахождения такой клетки, она одна становится активной, в противном — все клетки в колонке становятся активными. По определенному правилу выбирается клетка для обучения, при невыполнении правила обучается наиболее подходящая клетка из колонки.
В следущей фазе на основе активности сегментов вычисляются клетки в предсказывающем состоянии и усиливаются сегменты, активность которых привела к удачному предсказанию на прошлом временном шаге.
На последнем этапе для обучающихся клеток применяются все изменения, которые были предложены в двух предыдущих фазах. Добавляются новые сегменты или потенциальные синапсы к списку уже существующих для данного сегмента.
- public void TLearning() {
- for(int c = 0; c < xDimension*yDimension; c++) {
- for(int i = 0; i < cellsPerColumn; i++) {
- if(learnState.get(time).get(c).get(i)) {
- adaptSegments(segmentUpdateList.get(c).get(i), true);
- // segmentUpdateList.get©.get(i).clear();
- } else if (!predictiveState.get(time).get(c).get(i) && predictiveState.get(time-1 > 0 ? time-1 : 0).get(c).get(i)) {
- adaptSegments(segmentUpdateList.get(c).get(i), false);
- // segmentUpdateList.get©.get(i).clear();
- }
- segmentUpdateList.get(c).get(i).clear();
- }
- }
- }
Заключение
Будем считать, что общее представление об алгоритмах обучения, представленное в статье, достаточно для понимания базовых принципов. Всех интересующихся конкретной реализацией могу пригласить на github. Представленный там проект далек от идеала в плане соответствия архитектуре HTM, но, практически, в точности соответсвует представленным в нументовском пдф'е отрывкам псевдокода и описанию функций и данных.
Автор: specialclean