Дойде време и аз да допринеса към форума с една статийка, дано щатните автори да нямат нищо против.
С развиването на технологията, роботите изискват все по-сложни програми. До голяма степен тези програми са подобни на изкуствения интелект използван в компютърни програми и игри (особено по-старите) и се свежда до проверка на входни данни и генерирането на изходни такива. Тоест if-else statements казано на "програмен език". Целта на тази статия е да представи една алтернатива на този вид програмиране. Ще започна с обяснение на една добре позната система - мозъка, и ще завърша с полуготов код за нашите роботчета. Темата я пускам в раздел "Компютри" защото по-голямата част от програмирането се извършва на компютъра и едва в най-крайния етап ще отиде в робота.
В много статии на сайта създаването на робот се сравнява със създаването на живот. След като сме създали живот е редно да му дадем и разум, тоест мозък! Но откъде да започнем? Какво е мозъка?
Човешкият мозък (както и всички други) представлява огромно количество неврони, които са свързани и комуникират с до 10000 други неврони. Ето сравнение между човешки мозък и компютър:
Мозък КомпютърБрой елементи: 10^8 синапса 10^8 транзистораСкорост: 100 херца от порядъка на 10^9 херцаТип изчисляване/мислене: Паралелен, разпределен Последователен, централизиранТолериращ грешки: Да НеЗа мен лично мозъка, със способността си да се "препрограмира" и да забелязва последователности (pattern recognition) за сега е по-добър от един компютър, въпреки бавната скорост на изчисление. Как постига това мозъка? За да разберем отговора трябва да видим най-простия елемент в мозъка - неврона. Няма да ви занимавам с обяснения как работи биологическия неврон, тъй като ще се отдалеча от темата и ще трябва да ровя за превод на научни термини, които няма да срещнем никъде другаде. Ако искате може да прочетете в Wikipedia. Ще ви покажа директно опростен модел на неврон, който ще използваме:
В случая неврона работи по следния начин:
1. Взема всеки "претеглен" елемент (тоест Вход [n] * Тежест [n])
2. Събира ги
3. Ако резултата е по-голям от определно число (праг на неврона) пуска определн сигнал като Изход
Събирайки няколко такива неврона и свързвайки ги, се получава малък мозък!
Този мозък е до определена степен по-ефективен от нормална програма. Ето един пример за едно-слойна невронна мрежа (neural network):
Еднослойна защото на практика единия слой служи само за въвеждане на данни и се слага за яснота. Засега ще се занимаваме само с такива, защото многослойните мрежи са прекалено сложни и надхвърлят нуждите ни.
Ако ви изглежда сложно - спокойно. Нормално е.
Да обясним какво става:
Невронната мрежа взима 4 входа (inputs). Първия слой само ги предава, а втория всъщност ги обработва и дава 5 изхода. За какво може да се използва ли? Да разгледаме следния робот: Line tracer с 4 сензора, 2 мотора и светодиод (примера го измислих след като начертах и качих схемата). Мрежата взима входа от сензорите. Например 0 1 1 0 ако е в центъра на линията, 0 0 1 1 ако е леко в дясно на линията и т.н. Като изход очакваме да ни каже как да завъртим моторчетата (за всяко моторче ни трябват по 2 изхода, за да определим посока на въртене) и ако робота "вижда" линията ще светне светодиода. Например в първия случай (0 1 1 0) ще очакваме изход [1 0] [1 0] 1. Въртим и двата мотора напред и светодиода свети. Във втория пък ще очакваме да завием на ляво значи може да спрем левия мотор и получаваме [0 0] [1 0] 1.
За да определим как да се държи невронната мрежа, трябва да настроим тежестите. За това си има алгоритми. Ето един за еднослойни мрежи.
Ако имаме N входа, то те са част от вектора I и се бележат I1, I2, ... In
По подобен начин изхода е вектора O - O1, O2, ... Om
Тежестите ще са матрица W с размери M x N
За да научим мрежата да разпознава комбинацията 0 1 1 0 примерно и да реагира с 1 0 1 0 1 (горния пример) трябва да умножим вектора с входове по вектора с изходи и получената матрица да добавим към W. Единствената уловка е че алгоритъма работи не с 0 и 1, а с -1 и 1. Иначе се получават неточности. Ето и какво се получава
Тъй като това е първия ни пример, то това са и началните стойности на вектора ни с тежести W. Да добавим и следващия пример 0 0 1 1 - 0 0 1 0 1
Използвам 1 и 0 само за прегледност иначе ще стане мешавица. Събираме двете матрици (този път с -1 и 1) като събираме всеки елемент от едната със съответния от другата.
-1 1 1 -1 1 1 -1 -1 0 2 0 -2
1 -1 -1 1 1 1 -1 -1 2 0 -2 0
-1 1 1 -1 + -1 -1 1 1 = -2 0 2 0
1 -1 -1 1 1 1 -1 -1 2 0 -2 0
-1 1 1 -1 -1 -1 1 1 -2 0 2 0
И това са ни тежестите! На практика това ни е достатъчно. Стига ни програмата която отива в робота да знае тези тежести, за да сметне резултат. А това става по следния начин. Умножаваме всеки вход със съответната тежест към съответния изход. Ще разгледам отново примера 0 1 1 0 (помнете че нулите са -1):
Изход 1 (първи ред)
-1 * 0 = 0
1 * 2 = 2
1 * 0 = 0
-1 * -2 = 2
Общо 4. Ако си спомняте горе споменах праг на неврона. Ами това е най-просто нула. Тоест ако полученото число е > 0, значи неврона пуска към изхода 1 (в изключително редки случай ще срещате алгоритми които ще променят прага на невроните. това в случай че ще се ровите и след тази статия
) Сега да продължим
Изход 2 (втори ред)
-1 * 2 = -2
1 * 0 = 0
1 * -2 = -2
-1 * 0 = 0
Общо -4 => Пускаме 0 към изхода
Изход 3
-1 * -2 = 2
1 * 0 = 0
1 * 2 = 2
-1 * 0 = 0
Общо 4 => 1
Изход 4
-1 * 2 = -2
1 * 0 = 0
1 * -2 = -2
-1 * 0 = 0
Общо -4 => 0
Изход 5
-1 * -2 = 2
1 * 0 = 0
1 * 2 = 2
-1 * 0 = 0
Общо 4 => 1
От петте изхода получихме 1 0 1 0 1. А именно това бяхме въвели и в началото! Целта на цялото упражнение бе следната - да имаме алгоритъм който се справя с произволно количество входни данни и изважда от тях произволно количество изходни данни. При наличие на достатъчно много примери, робота ще започне сам да решава какво да прави дори в ситуации, които не са били зададени като примери
. По този начин вече само трябва да решим при какви условия какво да става и да въведем данните. Тоест ние казваме какво да стане, а компютъра измисля как да стане.
Добре, сега като разяснихме основите и първия (и основен) алгоритъм за учене ще покажа и как се пренася това в код. Ако сте чели внимателно и имате поне основни познания по езика С няма да имате проблеми с кода. Разбира се ще отговарям на въпроси и ще доразвивам темата ако видя че има интерес.
Ето го и кода:
#include <iostream>
using namespace std;
#define INPUTNUM 2 //Брой на входни данни (примерно сензори)
#define OUTPUTNUM 2 //Брой изходни данни (примерно мотори)
short num[2] = {-1,1}; //Алгоритъмът не работи с 0/1, а с -1/1. така се спестяват преобразувания
void learn(bool inputs[INPUTNUM], bool outputs[OUTPUTNUM], short weights[OUTPUTNUM][INPUTNUM]);//учещата функция - виж дефиницията долу
void test (bool inputs[INPUTNUM], bool outputs[OUTPUTNUM], short weights[OUTPUTNUM][INPUTNUM]);//тестова функция - виж дефиницията долу
int main()
{
bool inputs[INPUTNUM]; //масив за входните данни
bool outputs[OUTPUTNUM]; //масив за изходните данни
short weights[OUTPUTNUM][INPUTNUM]; //"тежестта" на връзките между невроните
for (int o=0;o<OUTPUTNUM;o++) //
for (int i=0;i<INPUTNUM;i++) //В началото всички тежести са 0
weights[o][i] = 0; //
int examples;
//Започва обучението
cout << "Enter number of examples: ";
cin >> examples;
while (examples > 0)
{
cout << "Enter inputs: ";
for (int i=0;i<INPUTNUM;i++) cin >> inputs[i]; //Входни данни
cout << "Enter outputs: ";
for (int o=0;o<OUTPUTNUM;o++) cin >> outputs[o]; //Очакван резултат при въведените входни данни
learn(inputs,outputs,weights); //Виж дефиницията долу
examples--;
}
for (int o=0;o<OUTPUTNUM;o++) //Печата пресметнатите тежести
{
cout << "Output " << o+1 << ": ";
for (int i=0;i<INPUTNUM;i++)
cout << weights[o][i] << " ";
cout << endl;
}
//С кода от тук до return 0; се проверяват резултатите от обучението
cout << "Enter number of test cases: ";
cin >> examples;
while (examples > 0)
{
cout << "Enter inputs: ";
for (int i=0;i<INPUTNUM;i++)
cin >> inputs[i];
test(inputs,outputs,weights); //Виж дефиницията долу
cout << "Outputs: ";
for (int o=0;o<OUTPUTNUM;o++)
cout << outputs[o] << " ";
cout << endl;
examples--;
}
return 0;
}
/* Учещата функция всъшност извършва едно просто действие - умножение на вектори,
при което се получава матрица, чиито стойности се ползват като тежести */
void learn(bool inputs[INPUTNUM], bool outputs[OUTPUTNUM], short weights[OUTPUTNUM][INPUTNUM])
{
for (int o=0;o<OUTPUTNUM;o++)
for (int i=0;i<INPUTNUM;i++)
weights[o][i] += num[inputs[i]] * num[outputs[o]];
}
/* Тестовата функция взима входни данни и пресмята резултата според тежестите.
Резултата се записва в масива outputs */
void test(bool inputs[INPUTNUM], bool outputs[OUTPUTNUM], short weights[OUTPUTNUM][INPUTNUM])
{
short total;
for (int o=0;o<OUTPUTNUM;o++)
{
total = 0;
for (int i=0;i<INPUTNUM;i++)
total += num[inputs[i]]*weights[o][i];
outputs[o] = (total > 0);
}
}
А ето и накратко какво прави:
Броя на входове и изходи се задава с двата define в началото на кода. Нататък може да покажа как става и динамични с вход от потребителя. След това пита за входове и изходи съответния брой пъти и ги добавя към тежестите чрез обяснения алгоритъм. След това изкарва сметнатите тежести за всеки изход. След това пита колко теста иска да направите. По време на теста вие въвеждате входни данни а функцията test смята резултата. На робота ще му трябва само тази функция, за да работи (освен ако не се обучава сам, но до там не сме стигнали).
Знам че стана адкси дълго но няма как. Областта е толкова обширна, че и 10 пъти по толкова няма да стигнат да се обясни добре. Следващия пост (ако го има) ще покаже как да пренесем получените тежести на робота. Нататък ще се опитам да разширявам темата с цялостно усложняване на материяла. Надявам се че съм бил от полза и ви е било поне интересно.
С това се занимавам от около половин година. Статията може да се нарече авторска защото не съм преписвал или превеждал нищо директно, но откъде съм го чел първия път не знам. Само таблицата в началото беше от
този сайт.