Недавно здесь была новость про результаты исследований японских учёных из лаборатории 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 есть на их сайте).
А можно пояснить что вобще можно сделать этой сетью. На пальцах то я понимаю что делают нейронные сети, но не далее сети из 2-х нейронов и 1 выходом.
К сожалению ссылки на проект c++ уже не рабочие, можете пожалуйста залить на источник и скинуть? Очень хочется попробовать
Ссылка на архив проектов рабочая.