Pozdrav svima, nakon dugo vremena (točno 4 godine) evo nas u novom vodiču kroz umjetnu inteligenciju! Ovoga puta obrađujemo umjetne neuronske mreže (bar nekakvi početak). Svi znamo kako se u posljednje vrijeme mnogo spominje umjetna inteligencija koja je sve bolja i bolja u zadacima koji su donedavno bili rezervirani samo za čovjeka jer je bilo nemoguće razviti eksplicitni algoritam koji bi mogao obraditi te zadatke. Neki primjeri takvih zadataka su: raspoznavanje objekata na slikama (računalni vid), strojno prevođenje teksta, automatska analiza teksta (npr. izvlačenje sentimenta iz komentara, generiranje sažetka iz velikog komada teksta i sl.) ili razumijevanje govora. U svakom od navedenih primjera dimenzionalnost podataka je iznimno velika i zapravo je nemoguće osmisliti nekakav eksplicitni algoritam koji bi radio bilo što od navedenog samo na temelju nekakvih pravila.
Stoga, da bi riješili ovaj problem, okrećemo se jednom drugačijem pristupu. Umjesto da kodiramo eksplicitan algoritam, mi ćemo osmisliti algoritam koji će imati sposobnost iz gomile podataka sam naučiti tzv. hipotezu. Struktura tih podataka može biti jako kompleksna, no to nas ne brine jer teret učenja hipoteze prebacujemo na sam algoritam. Takav postupak gdje imamo podatke na temelju kojih se algoritam uči naziva se nadzirano učenje.
Postupcima nadziranog učenja mogu se rješavati dvije vrste problema: klasifikacija i regresija. Kod klasifikacije određujemo kojoj klasi (razredu) pripada neki novi primjer podatka koji smo dobili, a kod regresije primjeru pridružujemo neku kontinuiranu vrijednost (npr. aproksimacija funkcije). Razlika je dakle u tome je li ciljana varijabla diskretna (klasifikacija) ili je kontinuirana (regresija).
U prošlom tutorijalu (na ovoj poveznici) bavili smo se jednom drugom granom umjetne inteligencije - genetskim algoritmima i tamo smo rješavali problem regresije (aproksimirali smo funkciju). U ovom tutorijalu/vodiču bavit ćemo se granom umjetne inteligencije koja je danas iznimno vruća i aktivna - dubokim učenjem (točnije nekom osnovom svega). Pokušat ću čim jednostavnije opisati što su to neuronske mreže, kako rade i kako ih se uči. Genetski algoritmi koje smo obrađivali prošli put inspirirani su evolucijom, jednostavni za shvatiti i bez matematike. Neuronske mreže također su inspirirane prirodom tj. mozgom i načinom na koji on radi, možda su mrvicu kompleksnije za shvatiti kako rade, ali za rigorozno objašnjenje rada imaju matematike i to dosta teške matematike koju ću pokušati svesti na minimum.
Kao što sam već spomenuo, prošlog smo se puta bavili problemom regresije, a sada ćemo se baviti problemom klasifikacije. Ukratko, radi se o tome da imamo označene podatke gdje svaki podatak ima pridruženu klasu. Na temelju tih podataka učimo neuronsku mrežu. Zatim dobijemo novi, ovaj puta neoznačeni podatak i ubacimo ga u neuronsku mrežu. Njezin je zadatak tom neoznačenom podatku dodijeliti neku klasu za koju mreža smatra da joj taj podatak pripada. Dakle, zadaća je klasifikacijskog algoritma (u ovom slučaju neuronske mreže) naučiti hipotezu h: X -> {0, 1} koja određuje pripada li neki primjer x klasi C ili ne:
Primjetimo da je ovdje riječ o K međusobno *isključivih* stanja, dakle podatak u ovom slučaju može pripadati isključivo *jednoj* klasi. Ovakav slučaj ne mora biti i u praksi: npr. novinski članak može pripadati u dvije ili tri rubrike po svom sadržaju, tj. u više klasa. To je općeniti slučaj i naziva se klasifikacija s višestrukim oznakama (engl. multilabel classification) ili klasifikacija tipa jedan-na-više. Međutim, takav slučaj nije potrebno posebno razmatrati budući da se može izvesti kao klasifikacija tipa jedan-na-jedan koju smo do sada opisali.
Sada kada smo pokrili neku osnovnu terminologiju, možemo krenuti sa samim neuronskim mrežama. Kao što sam već rekao, neuronske su mreže inspirirane mozgom. Razvoj neuroračunarstva i umjetnih neuronskih mreža započeo je 40-tih godina prošlog stoljeća potaknut nizom bioloških spoznaja. Još 1943. godine McCulloh i Pitts su u svom radu predstavili prvi koncept pojednostavljenog umjetnog neurona i pokazali da se njime mogu računati logičke funkcije I, ILI i NE. Međutim, važno je napomenuti da se ovdje radilo o rješavanju problema konstrukcijom, a ne učenjem. Oni su zapravo izgradili (ručno konstruirali) umjetnu neuronsku mrežu na način da dobiju proizvoljnu Booleovu funkciju. Kasnije, 1949. godine britanski biolog Donald Hebb u svojoj knjizi objavljuje spoznaje o radu bioloških neurona. On je zapazio da ako dva neurona često zajedno pale, tada dolazi do metaboličkih promjena kojima efikasnost kojom jedan neuron pobuđuje drugoga s vremenom raste. Drugim riječima, njegova je ideja da učiti znači mijenjati jakost veza između neurona. Na temelju te ideje osmišljen je prvi algoritam učenja McCullohove i Pittsove mreže (tzv. TLU-perceptrona) kojeg je osmislio Frank Rosenblatt 1962. godine i to je algoritam s kojim ćemo prvo krenuti i kojeg ćemo prvo implementirati. Kao što možete primjetiti, ideja umjetnih neuronskih mreža seže daleko u prošlost, ali tek 2010-ih godina (koja godina prije) postaju konkretno upotrebljive. Razlog tome je razvoj Interneta i posljedično dostupnost abnormalnih količina podataka (pogotovo društvene mreže i sve ostale "besplatne" usluge koje zapravo nisu besplatne jer poklanjamo svoje privatne podatke) te razvoj računalnog sklopovlja, naročito paralelnog sklopovlja kao što je npr. tehnologija CUDA ili tehnologija OpenCL.
Kao i u prošlom tutorijalu i u ovom ćemo za implementaciju koristiti programski jezik Python. Jedan od razloga je dakako to što je Python vrlo jednostavan i čitljiv jezik kojeg lako mogu razumijeti i oni koji ga ne znaju (vrlo je sličan pseudokodu). Svu sintaksu koja je specifična za Python dodatno ću pojasniti. Drugi razlog je taj što je Python kao jezik izrazito popularan u znanstvenoj zajednici, pa tako i u području računarske znanosti i za njega postoji mnogo biblioteka koje se koriste za kojekakve proračune kao npr. biblioteka za "drobljenje" brojeva NumPy koju ćemo koristiti i mi.
Dodatne biblioteke koje ćemo koristiti su već spomenuti NumPy za numeričke proračune, matplotlib za iscrtavanje rezultata te biblioteka pandas za rad s podatkovnim strukturama. Operacijski sustav na kojem je sve napravljeno je Linux Ubuntu 16.04 jer i inače na tom sustavu programiram/radim/igram, a i instalirati sve potrebne alate na Linuxu je prejednostavno. Python je već instaliran, a za instalirati numpy, matplotlib i pandas dovoljno je u naredbenom retku ukucati (ako više volite grafički način, potražite te pakete u Synapticu):
sudo apt install python3-numpy python3-matplotlib python3-pandas
ili ako preferirate PyPI:
pip3 install numpy
pip3 install matplotlib
pip3 install pandas
NAPOMENA: imajte na umu, kao što se može i vidjeti iz ovih naredbi za instalaciju, da je inačica Pythona koja se koristi 3, a ne 2. Dakle, koristimo Python 3, a ne Python 2 koji, ako se ne varam, i dalje dolazi kao zadana instalacija u praktički svim distribucijama Linuxa. Tako da ukoliko nemate instaliran Python 3, instalirajte ga jednostavnim:
sudo apt install python3.5 # ili noviji od 3.5 ako postoji u repozitorijima
Sada kada su svi potrebni alati instalirani, možemo krenuti s implementacijom našeg prvog jednostavnog umjetnog neurona - TLU-perceptrona (od engl. threshold logic unit). Da bismo mogli implementirati naš umjetni neuron, moramo se zapitati kako izgleda biološki neuron.
Ukupnu pobudu net računamo prema izrazu:pri čemu zadnji član -θ predstavlja prag paljenja neurona. Kako se u matematičkim izrazima iznos praga θ ne bi morao posebno tretirati, najčešće definiramo da neuron ima još jedan dodatni ulaz x0 koji uvijek ima vrijednost 1, a prag -θ se zamjenjuje težinom w0 čime se dobije konačan izraz:Nadam se da je do sada sve jasno, ovdje ne radimo ništa posebno, samo množimo svaki ulaz x s njegovom težinom w i sve te umnoške zbrojimo u jedan konačni rezultat. Ono gdje stvari postaju zanimljive je u prijenosnoj funkciji kojoj predajemo izračunan net. Prijenosna funkcija može biti proizvoljno definirana, no postoje određene funkcije koje imaju dobra svojstva i koje se koriste u današnjim mrežama. Prijenosna funkcija koju ćemo mi koristiti obična je funkcija praga ili funkcija skoka (još znana i kao Heavisideova funkcija praga/skoka) koja je definirana na sljedeći način:Kao što vidimo iz izraza, ukoliko će net biti negativan ili jednak nuli funkcija će na izlazu dati nulu, tj. neuron neće okinuti. Ukoliko će net biti pozitivan, na izlazu će biti jedinica, tj. neuron će opaliti. Ako ste više vizualan tip, ta funkcija izgleda ovako nekako:Ovdje ima još jedna sitnica koju je pažljiviji čitatelj možda primjetio. Problematična točka ove funkcije je vrijednost u nuli. Iz slike se vrlo lijepo vidi da u nuli ova funkcija ima više iznosa (loše!). Ova se funkcija koristi i u drugim granama znanosti i problematična je zbog deriviranja (problem je vrijednost u nuli). Sama se funkcija može definirati na više načina. Mi smo ju definirali da ukoliko je net jednak nuli i izlaz će biti nula, ali jednako tako mogli smo ju definirati i da izlaz bude jedinica ukoliko je net nula. Taj dio nije toliko bitan i ostavljen je da proizvoljno odaberemo vrijednost koju želimo imati u točki nula. Sad smo definirali gotovo sve vezano uz naš neuron.
Ostao nam je još jedan, najbitniji dio, a to je sam proces učenja. Učenje kod neuronskih mreža svodi se na podešavanje težina w0 do wn (sjetimo se Hebbove ideje od ranije da učiti znači mijenjati jakost veza) na takav način da se neuroni pobuđuju (pale) kad nam to treba, a isto tako i da ostaju neaktivni kad nam treba neaktivnost. Za perceptron je osmišljeno sljedeće pravilo:
- ukoliko se primjer klasificira ispravno tada nemoj raditi nikakvu korekciju težina
- ukoliko se primjer klasificira neispravno tada primjeni korekciju na težine
- ciklički uzimaj sve primjere redom, a postupak zaustavi kada su svi primjeri klasificirani ispravno
Korekcija težina vrši se na sljedeći način: