• - это CraftDuino - наш вариант полностью Arduino-совместимой платы.
  • CraftDuino - настоящий конструктор, для очень быстрого прототипирования и реализации идей.
  • Любая возможность автоматизировать что-то с лёгкостью реализуется с CraftDuino!
Просто добавьте CraftDuino!
подписаться на RSS-ленту

Пример работы самоорганизующейся инкрементной нейронной сети 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>

В стандартной сборке Visual Studio Express не хватает двух файлов winres.h и afxres.h. Их можно скачать и распаковать в C:\Program Files\Microsoft SDKs\Windows\v7.0A\Include — для экспресса 2010 или v6.0\Include — для 2008.

Под обычной версией 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
//
// http://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

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


Ссылки:
Робота научили решать задачи на основе базовых знаний
  • +2
  • 6 августа 2011, 12:49
  • noonv

Комментарии (2)

RSS свернуть / развернуть
+
0
Здравствуйте. Хотел бы спросить… Этот алгоритм можно использовать некоммерческим проектам?
avatar

AlanNein

  • 30 июля 2013, 17:30
+
0
в архиве никакой лицензии не было — поэтому вопрос следует задать японскому автору SOINN (e-mail есть на их сайте).
avatar

noonv

  • 31 июля 2013, 06:31

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.