Пример работы самоорганизующейся инкрементной нейронной сети SOINN


Недавно здесь была новость про результаты исследований японских учёных из лаборатории Hasegawa Lab.
Робота научили решать задачи на основе базовых знаний

Учёные использовали «самоорганизующуюся инкрементную нейронную сеть» (Self-Organizing Incremental Neural Network — SOINN), для управления роботом HIRO (Kawada Industries) с целью решения задач на основе базовых знаний ( т.е. алгоритм ИИ делает предположения и принимает решения на основе своего предыдущего опыта).

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

И всё бы ничего — в новостных лентах часто появляются новости про очередные мега-достижения в науке и технике, но зайдя на сайт Hasegawa Lab можно обнаружить статьи в формате PDF про их исследования и, что ещё более важно, проект на C++:
SOINN (C++) — Microsoft Visual Studio 2005 solution
с пояснительной PDF-запиской How to Use the SOINN Software

Разумеется, я сразу же скачал проект и попробовал его собрать.
Как видно, проект создавался под Visual Studio 2005 и поэтому при попытке открыть его в более старших версиях, появляется мастер, предлагающий конвертацию, которая проходит без всяких проблем.

Если попробовать собрать проект под Visual Studio 2008 express edition, то сразу не получится — выпадает ошибка:

fatal error RC1015: cannot open include file 'afxres.h'

— ошибка в отсутствии afxres.h (в файле Application\resource.rc)):
Причина — нет MFC и чтобы решить эту проблему нужно просто отредактировать resource.rc и заменить «afxres.h» на <windows.h> <blockquote=»»>В стандартной сборке Visual Studio Express не хватает двух файлов winres.h и afxres.h. Их можно скачать и распаковать в C:\Program Files\Microsoft SDKs\Windows\v7.0A\Include — для экспресса 2010 или v6.0\Include — для 2008.</windows.h>>

Под обычной версией Visual Studio 2008 всё собирается с одного клика и в директории
soinn-project-vs2005\Application\Release\
появляется файл Application.exe

Сразу же бросается в глаза, что код написан очень хорошо и понятно. Собственно сам SOINN содержится в одноимённом проекте и состоит всего из 6 файлов (soinn-project-vs2005\SOINN\):

CEdge.cpp
CEdge.h
CNode.cpp
CNode.h
CSOINN.cpp
CSOINN.h

CSOINN — реализует алгоритм SOINN
CNode — узел (нейрон) в сети
CEdge — связь между двумя узлами

SOINN API:

CSOINN::InputSignal(const double *signal)

— реализует алгоритм SOINN для входных данных (signal).

CSOINN::Classify(void)

— присваивает идентификатор класса для всех узлов в соответствии с текущей структурой сети.

CSOINN::Reset(const int dimension=NO_CHANGE, const int lambda=NO_CHANGE, const int ageMax=NO_CHANGE)

— сбрасывает параметры SOINN (размер, счётчики удаления узлов). В текущей реализации, все узлы и связи сети, также удаляются.

CSOINN::GetNodeNum(const bool ignoreAcnode=false)

— возвращает текущее количество узлов в сети.

CSOINN::GetClassNum()

— возвращает текущее число классов в сети.

CSOINN::GetNode(const int node)

— возвращает экземпляр CNode с id, переданным в качестве аргумента функции.

CSOINN::GetClassFromNode(const int node)

— возвращает идентификатор класса из CNode с id, переданным в качестве аргумента функции.

CSOINN::SaveNetworkData(const char *fileName)

— сохраняет текущее состояние сети в заданный файл.

CSOINN::LoadNetworkData(const char *fileName)

— загружает сеть из заданного файла.

CNode::GetSignal() 

— возвращает вес вектора экземпляра CNode.

CNode::GetClass()

— возвращает идентификатор класса экземпляра CNode.

Алгоритм работы SOINN

при добавлении нового сигнала через
CSOINN::InputSignal(const double *signal)
проверяется существующее количество узлов в сети и если их меньше двух, то сигнал просто добавляется как новый узел сети, во всех остальных случаях — сначала выявляются два узла сети, ближайших к поступающему сигналу — метод
CSOINN::FindWinnerAndSecondWinner(int &winner, int &secondWinner, const double *signal)
Затем, производится проверка относится ли сигнал к новым данным с помощью метода
CSOINN::IsWithinThreshold(const int winner, const int secondWinner, const double *signal)
— внутри него просто вычисляется расстояние от двух ближайших узлов до заданного сигнала и если оно больше, чем порог подобия ( CSOINN::GetSimilarityThreshold(const int node) ), то сигнал добавляется в качестве нового узла сети, в противном случае формируется связь между двумя полученными узлами.

классификация — CSOINN::Classify(void)
— это рекурсивный обход сети и назначение id-ка класса (CSOINN::SetClassID(const int node, const int classID)) всем узлам, которые связанны между собой.

Попробуем запустить этот алгоритм 🙂

создадим проект, в настройках проекта укажем:

С/C++ - Code Generation - Runtime Library - /MT

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

LINK : warning LNK4098: defaultlib 'LIBCMT' conflicts with use of other libs; use /NODEFAULTLIB:library

пропишем путь до заголовочных файлов и путь до готового SOINN.lib

мой тестовый проект на основе примера из руководства:

//
// тестовый пример работы с SOINN
// http://haselab.info/soinn-e.html
//
// https://robocraft.ru
//

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "CSOINN.h"

#define DIMENSION			2
#define REMOVE_NODE_TIME	200
#define DEAD_AGE			100

// показать информацию о SOINN-сети
void show_SOINN_info(CSOINN* pSOINN);

// расстояние между двумя сигналами
double Distance(const double *signal1, const double *signal2, int dimension=DIMENSION);

int main(int argc, char* argv[])
{
	printf("[i] Start...\n");

	CSOINN* pSOINN = 0;

	// создание объекта SOINN
	pSOINN = new CSOINN ( DIMENSION , REMOVE_NODE_TIME , DEAD_AGE );

	if(!pSOINN){
		printf("[!] Error: cant allocate memory!\n");
		return -1;
	}

	// для хранения входных данных
	double signal[DIMENSION];

	int i=0;

	//-----------------------------------------
	for(i=0; i<10; i++){
		signal[0] = 0;
		signal[1] = i;

		// добавляем данные в сеть
		pSOINN -> InputSignal ( signal );
	}

	// классификация данных
	pSOINN -> Classify ();

	// поcмотрим информацию о SOINN-сети
	show_SOINN_info(pSOINN);
	//-----------------------------------------
	double delta = 0.9;

#if 1
	for(i=0; i<10; i++){
		signal[0] = i;
		signal[1] = i;

		// добавляем данные в сеть
		pSOINN -> InputSignal ( signal );
	}

	// классификация данных
	pSOINN -> Classify ();

	// поcмотрим информацию о SOINN-сети
	show_SOINN_info(pSOINN);
#endif
	//-----------------------------------------

	// 1-NN algorithm .
	double minDist = CSOINN :: INFINITY ;
	int nearestID = CSOINN :: NOT_FOUND ;

	// тестовый сигнал
	double targetSignal[DIMENSION];
	targetSignal[0] = 1+delta;
	targetSignal[1] = 1+delta;

	for(i=0; i < pSOINN->GetNodeNum(); i++ ){

		double* nodeSignal = pSOINN -> GetNode ( i )-> GetSignal ();
		double dist = Distance ( targetSignal , nodeSignal );

		if ( minDist > dist ) {
			minDist = dist ;
			nearestID = i;
		}
	}

	int nearestClassID = pSOINN -> GetNode ( nearestID )-> GetClass ();
	printf ("[i] SOINN: Nearest Node ID : %d, Class ID : %d.\n", nearestID , nearestClassID );

	if(pSOINN){
		delete pSOINN;
		pSOINN = 0;
	}

	printf("[i] End.\n");
	return 0;
}

// показать информацию о SOINN-сети
void show_SOINN_info(CSOINN* pSOINN)
{
	if(!pSOINN){
		return;
	}

	// покажем информацию о сети
	printf("[ ] SOINN info: \n");
	printf("[i] Dimension: %d\n", pSOINN->GetDimension());
	printf("[i] NodeNum: %d\n", pSOINN->GetNodeNum());
	printf("[i] EdgeNum: %d\n", pSOINN->GetEdgeNum());
	printf("[i] ClassNum: %d\n", pSOINN->GetClassNum());
	printf("----------------------------\n");

}

// расстояние между двумя сигналами
double Distance(const double *signal1, const double *signal2, int dimension)
{
	int i;
	double sum;

	if (signal1 == NULL || signal2 == NULL || dimension<=0) return 0.0;
	
	sum = 0.0;
	for (i=0; i<dimension; i++){
		sum += (signal1[i]-signal2[i])*(signal1[i]-signal2[i]);
	}
	
	return sqrt(sum)/(double)dimension;
}

вывод:

[i] Start...
[ ] SOINN info:
[i] Dimension: 2
[i] NodeNum: 10
[i] EdgeNum: 0
[i] ClassNum: 10
----------------------------
[ ] SOINN info:
[i] Dimension: 2
[i] NodeNum: 19
[i] EdgeNum: 1
[i] ClassNum: 18
----------------------------
[i] SOINN: Nearest Node ID : 11, Class ID : 10.
[i] End.

угу — как-то работает 🙂

Попробуем как-нибудь применить эту сетку 🙂
Например, подружим её с OpenCV 🙂

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

при попытке её приладить возникли трудности с доступом к переменным класса CEdge и CNode, которые в программе японцев решается friend-доступом, но я решил пока просто перетащить исходники SOINN в свой проект и дать public-доступ закрытым переменным (m_from и m_to из CEdge, m_signal из CNode), которые нужны для отрисовки 🙁

На одной картинке мы не увидим почти ничего интересного , но если обернуть процедуру обработки сигнала в цикл, то всё будет намного интереснее (и это не удивительно — жизнь это движение 😉

основа:

        CSOINN* pSOINN = 0;

	// создание объекта SOINN
	pSOINN = new CSOINN ( DIMENSION , REMOVE_NODE_TIME , DEAD_AGE );

	if(!pSOINN){
		printf("[!] Error: cant allocate memory!\n");
		return -1;
	}

	unsigned int round = 0;

	while(cvWaitKey(33)!=27){
		// для хранения входных данных
		double signal[DIMENSION];

		// пробегаемся по всем пикселям изображения
		for( int y=0; y<bin->height; y++ ) {
			uchar* ptr = (uchar*)(bin->imageData + y * bin->widthStep);
			for( int x=0; x<bin->width; x++ ) {
				// если пиксель белый
				if(ptr[x]>0){
					signal[0] = x;
					signal[1] = y;
					// добавляем данные в сеть
					pSOINN -> InputSignal ( signal );
				}
			}
		}


		// классификация данных
		pSOINN -> Classify ();

		// покажем номер итерации
		printf("[i] round: %d \n", round++);
		// поcмотрим информацию о SOINN-сети
		show_SOINN_info(pSOINN);

		show_SOINN(pSOINN, soinn);

		cvShowImage("SOINN", soinn);

	}

функция отображения сети (адаптированый вариант функции COutputWindow::Refresh()):

// показать сеть SOINN на картинке :)
void show_SOINN(CSOINN* pSOINN, IplImage* img)
{
	if(!pSOINN || !img){
		return;
	}

	int i, f, t, nodeNum, edgeNum;
	double x, y, x0, y0, x1, y1;
	CNode* node;
	CEdge* edge;

	// очистим картинку
	cvZero(img);

	edgeNum = pSOINN->GetEdgeNum();
	for (i=0; i<edgeNum; i++){
		edge = pSOINN->GetEdge(i);
		f = edge->m_from;
		node = pSOINN->GetNode(f);
		x0 = node->m_signal[0];
		y0 = node->m_signal[1];

		t = edge->m_to;
		node = pSOINN->GetNode(t);
		x1 = node->m_signal[0];
		y1 = node->m_signal[1];

		cvLine(img, cvPoint((int)x0, (int)y0), cvPoint((int)x1, (int)y1), CV_RGB(250,250,250), 1, 8);
	}

	nodeNum = pSOINN->GetNodeNum();
	for (i=0; i<nodeNum; i++){
		node = pSOINN->GetNode(i);
		
		x = node->m_signal[0];
		y = node->m_signal[1];

		cvCircle(img,cvPoint((int)x, (int)y), 2, m_color[node->m_classID%MAX_COLOR], -1, 8);
	}
}

1-й раунд:

132-й:

а вот какую gif-ку (3.3Mb) у меня получилось замутить:

забавно — прямо возникло чувство сопричастности к передовым разработкам в области ИИ для роботов :)))

SOINN.zip — архив проектов (с exe-ками):

SOINN\soinn-project-vs2005
SOINN\test
SOINN\soinn2cv

UPD
добавил в архив проект SOINN\soinn2cv2 , в котором в качестве входного сигнала в сеть подаются координаты клика мышкой

обратите внимание, что для программы soinn2cv требуется OpenCV версии 2.1

Автор: Vladimir (noonv), 2010-2011

Эксклюзивно для robocraft.ru
копирование на другие ресурсы и публикация
без разрешения автора запрещены.

По теме
Робота научили решать задачи на основе базовых знаний
Нейронная сеть — введение
Обучение ИНС с помощью алгоритма обратного распространения


0 комментариев на «“Пример работы самоорганизующейся инкрементной нейронной сети SOINN”»

    • в архиве никакой лицензии не было — поэтому вопрос следует задать японскому автору SOINN (e-mail есть на их сайте).

  1. А можно пояснить что вобще можно сделать этой сетью. На пальцах то я понимаю что делают нейронные сети, но не далее сети из 2-х нейронов и 1 выходом.

Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Arduino Bluetooth CraftDuino DIY Google IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение