Pradžia / Programavimas / Kaip veikia neuroniniai tinklai

Kaip veikia neuroniniai tinklai

Prisimenu, kai prieš kelerius metus pirmą kartą bandžiau suprasti, kas iš tikrųjų vyksta viduje, kai kompiuteris „atpažįsta” katę nuotraukoje. Skaičiau straipsnį po straipsnio, ir visur buvo tos pačios abstrakcijos – neuronai, sinapsės, aktyvacijos. Skambėjo gražiai, bet nieko iš esmės nepaaiškino. Tik kai pradėjau patį algoritmą programuoti nuo nulio, viskas atsistojo į savo vietas. Šiame straipsnyje pabandysiu paaiškinti taip, kaip norėjau, kad man kažkas paaiškintų tada.

Kas iš tikrųjų yra tas „neuronas”

Pirmiausia reikia atsikratyti biologinio bagažo. Taip, terminas kilęs iš smegenų neuronų analogijos, bet dirbtinis neuronas yra kur kas paprastesnis dalykas – tai tiesiog matematinė funkcija. Įsivaizduokite, kad turite kelis skaičius įėjime, kiekvienam iš jų priskiriamas svoris (weight), viskas sudauginama, sudedama ir dar pridedamas vienas papildomas skaičius, vadinamas poslinkiu (bias). Gautas rezultatas praeina per vadinamąją aktyvacijos funkciją, kuri nusprendžia, ar tas „neuronas” apskritai „suveiks”.

Praktiškai tai atrodo maždaug taip: jei turite tris įėjimus x1, x2, x3, ir jiems atitinkamai priskirtus svorius w1, w2, w3, neuronas suskaičiuoja sumą w1*x1 + w2*x2 + w3*x3 + b. Tas b – tas pats poslinkis – leidžia funkcijai pasislinkti, kad neuronas galėtų „suaktyvuotis” net kai visi įėjimai lygūs nuliui. Be aktyvacijos funkcijos visas tinklas būtų tiesiog didelė tiesinė lygtis, ir kad ir kiek sluoksnių pridėtum, jis vis tiek galėtų atpažinti tik tiesines priklausomybes. Realiame pasaulyje beveik niekas nėra tiesiška, todėl netiesiškumas yra būtinas.

Sluoksniai, per kuriuos keliauja informacija

Vienas neuronas pats vienas nieko protingo padaryti negali. Galia atsiranda, kai tokių neuronų sujungiama tūkstančiai, sudėliojant juos sluoksniais. Klasikinė struktūra susideda iš trijų dalių: įėjimo sluoksnio, kuris tiesiog priima duomenis (pavyzdžiui, kiekvieno pikselio ryškumo reikšmę), paslėptų sluoksnių, kuriuose ir vyksta visa „mąstymo” magija, bei išėjimo sluoksnio, kuris duoda galutinį atsakymą.

Įdomiausia dalis – paslėpti sluoksniai. Kiekvienas jų sluoksnis mokosi vis abstraktesnių dalykų. Jei kalbame apie vaizdų atpažinimą, pirmasis sluoksnis dažniausiai išmoksta atpažinti paprasčiausius kraštus ir kontūrus. Antrasis sluoksnis iš tų kraštų sudėlioja formas – akis, nosį, ratą. Trečiasis – iš tų formų jau sudėlioja visą objektą. Tai vadinama hierarchiniu mokymusi, ir tai yra viena iš priežasčių, kodėl gilieji tinklai (deep learning) tapo tokie galingi – jie patys, be žmogaus pagalbos, sugeba atrasti tuos požymius, kuriuos anksčiau reikėdavo programuoti rankomis.

Kaip tinklas iš tikrųjų mokosi

Čia dažniausiai ir pasimeta žmonės, kurie tik pradeda domėtis šia tema. Tinklas nemokosi taip, kaip mokosi žmogus – jokio „supratimo” ten nėra. Iš esmės viskas, ką tinklas daro, yra bandymas rasti tokius svorių ir poslinkių derinius, kurie sumažina klaidą tarp prognozuojamo ir teisingo atsakymo.

Procesas prasideda nuo atsitiktinių svorių – tinklas iš pradžių tiesiog spėlioja, ir spėlioja blogai. Tada apskaičiuojama klaida, dažniausiai vadinama nuostolio funkcija (loss function). Tai skaičius, parodantis, kiek tinklas suklydo. Kuo didesnis skaičius, tuo prasčiau tinklas atspėjo.

Toliau prasideda tikroji magija – vadinamasis atgalinio sklidimo (backpropagation) algoritmas. Jis skaičiuoja, kaip kiekvienas svoris tinkle prisidėjo prie tos klaidos, ir šiek tiek pakoreguoja jį priešinga kryptimi, kad klaida sumažėtų. Matematiškai tai daroma naudojant dalinę išvestinę (gradientą) – iš esmės nustatoma, kuria kryptimi ir kiek reikia pajudinti kiekvieną svorį. Šis procesas kartojasi tūkstančius, kartais milijonus kartų, kol klaida sumažėja iki priimtino lygio.

Praktiškai tai atrodo taip: paimamas nedidelis duomenų paketas (batch), padaromas spėjimas, suskaičiuojama klaida, atliekamas atgalinis sklidimas, atnaujinami svoriai. Tada imamas kitas paketas, ir viskas kartojasi iš naujo. Vienas pilnas perėjimas per visą treniravimo duomenų rinkinį vadinamas epocha (epoch), ir dažniausiai reikia dešimčių ar šimtų epochų, kol tinklas pasiekia patenkinamą tikslumą.

Gradiento nusileidimas – paprastais žodžiais

Man labiausiai patiko analogija su žmogumi, kuris naktį, be žibinto, bando nusileisti nuo kalno į slėnį. Jis nemato viso vaizdo, bet gali pajusti, kuria kryptimi žemė krypsta žemyn po kojomis, ir žengia mažą žingsnelį ta kryptimi. Tada vėl pajaučia, vėl žengia. Būtent taip veikia gradiento nusileidimo (gradient descent) algoritmas – jis nemato viso „nuostolio kraštovaizdžio”, bet gradientas parodo lokalią kryptį, kur klaida mažėja greičiausiai.

Čia svarbus parametras yra vadinamasis mokymosi greitis (learning rate). Jei žingsniai per dideli, tinklas gali „peršokti” per patį geriausią sprendimą ir niekada jo nepasiekti – tarsi žmogus, žengiantis milžiniškais šuoliais, praleistų patį slėnio dugną. Jei žingsniai per maži, mokymasis užtruks amžinybę, o tinklas gali įstrigti nedideliame duobutėje, manydamas, kad tai ir yra geriausias sprendimas (tai vadinama lokaliu minimumu).

Šiuolaikiniai optimizatoriai, tokie kaip Adam ar RMSprop, iš esmės yra šio paprasto principo patobulinimai – jie automatiškai koreguoja žingsnio dydį priklausomai nuo situacijos, todėl mokymasis vyksta greičiau ir stabiliau nei naudojant paprasčiausią gradiento nusileidimą.

Aktyvacijos funkcijos – kodėl jos tokios svarbios

Grįžtant prie netiesiškumo temos – būtent aktyvacijos funkcijos suteikia tinklui galimybę modeliuoti sudėtingus, ne tiesinius ryšius. Anksčiau populiariausia buvo sigmoidė, kuri suspaudžia bet kokį skaičių į intervalą tarp 0 ir 1. Skambėjo logiškai, bet praktikoje sukeldavo problemų – kai tinklas turi daug sluoksnių, gradientas, keliaudamas atgal per tinklą, ima vis mažėti ir mažėti, kol galiausiai tiesiog „išnyksta”. Ši problema vadinama nykstančio gradiento (vanishing gradient) problema, ir dėl jos gilūs tinklai ilgą laiką praktiškai nebuvo treniruojami.

Situacija pasikeitė, kai populiari tapo ReLU (Rectified Linear Unit) funkcija – iš esmės ji tiesiog paverčia visus neigiamus skaičius nuliu, o teigiamus palieka nepakitusius. Skamba primityviai, bet būtent šis paprastumas ir yra jos privalumas – gradientas per ją keliauja daug lengviau, todėl galima treniruoti kur kas gilesnius tinklus. Šiandien egzistuoja dešimtys ReLU variantų (Leaky ReLU, GELU, Swish ir panašiai), kiekvienas su savo mažais niuansais, bet esmė ta pati – leisti informacijai laisviau tekėti per sluoksnius.

Kodėl duomenys svarbesni už algoritmą

Vienas dalykas, kurio nesuprasdavau ilgą laiką – kad pati architektūra ne visada yra svarbiausia. Galima turėti puikiausią tinklo struktūrą, bet jei duomenys prasti, netvarkingi ar jų tiesiog per mažai, rezultatas bus prastas. Tinklas mokosi tiksliai to, ką jam parodai, ne daugiau ir ne mažiau. Jei treniravimo duomenyse katės visada fotografuojamos ant sofos, tinklas gali pradėti tapatinti sofą su kate, o ne pačią gyvūno anatomiją.

Būtent dėl to praktikoje tiek daug dėmesio skiriama duomenų paruošimui – normalizavimui, papildymui (data augmentation), triukšmo pašalinimui. Taip pat labai svarbu duomenis padalinti į tris dalis: treniravimo, validavimo ir testavimo rinkinius. Treniravimo duomenys naudojami svoriams koreguoti, validavimo – stebėti, ar tinklas nepersimoko (overfitting), o testavimo rinkinys naudojamas tik pačioje pabaigoje, kad būtų galima objektyviai įvertinti, kaip tinklas elgsis su realiais, dar nematytais duomenimis.

Persimokymas – dar viena tema, kurią verta paminėti. Tai atsitinka, kai tinklas tiesiog „įsimena” treniravimo duomenis vietoj to, kad išmoktų bendrus dėsningumus. Tokiu atveju tinklas puikiai veikia su treniravimo duomenimis, bet visiškai sugriūva susidūręs su naujais pavyzdžiais. Kovojama su tuo įvairiais būdais – reguliarizacija, dropout metodu (kai treniravimo metu atsitiktinai „išjungiama” dalis neuronų), arba tiesiog surenkant daugiau duomenų.

Praktinis pavyzdys – ką iš tikrųjų daryti, jei norite pabandyti patys

Teorija teorija, bet geriausiai suprasti pavyksta, kai pats pabandai. Jei norite pradėti, nesileiskite iš karto į sudėtingas architektūras – PyTorch ar TensorFlow bibliotekos turi puikiai paruoštus pavyzdžius su MNIST duomenų rinkiniu (rankomis rašytų skaitmenų atpažinimas). Tai idealus startas, nes duomenų rinkinys nedidelis, o modelis apmokomas per kelias minutes net ir be galingos vaizdo plokštės.

Keletas konkrečių patarimų, kuriuos norėčiau būti girdėjęs anksčiau:

  • Pradėkite nuo mažo tinklo – vieno ar dviejų paslėptų sluoksnių dažnai užtenka, kad suprastum principą. Didelius, gilius tinklus paliksite vėlesniam etapui.
  • Stebėkite nuostolio funkcijos grafiką kiekvienos epochos metu – jei jis nemažėja, dažniausiai problema yra per didelis arba per mažas mokymosi greitis.
  • Nebijokite eksperimentuoti su hiperparametrais – sluoksnių skaičiumi, neuronų kiekiu, batch dydžiu. Nėra universalios formulės, kuri tiktų visoms užduotims.
  • Naudokite jau paruoštus modelius (transfer learning), jei dirbate su vaizdais ar tekstu – dažnai nereikia mokytis nuo nulio, galima pasiimti jau apmokytą modelį ir pritaikyti savo užduočiai.
  • Vizualizuokite tai, ką tinklas „mato” – ypač vaizdų atpažinimo srityje galima peržiūrėti, ką konkretus sluoksnis aktyvuoja, ir tai daug pasako apie tai, kaip tinklas iš tikrųjų priima sprendimus.

Vietoj apibendrinimo – keletas minčių pabaigai

Kai žmonės klausia, ar neuroniniai tinklai „galvoja” taip, kaip mes, atsakymas visada bus neigiamas. Tai statistinės mašinos, ieškančios dėsningumų duomenyse ir bandančios juos apibendrinti. Bet tame ir slypi jų grožis – jiems nereikia suprasti pasaulio taip, kaip suprantame mes, kad būtų naudingos. Užtenka, kad jos rastų pakankamai gerą matematinę funkciją, sujungiančią įėjimą su norimu išėjimu.

Jei ką nors iš viso šio straipsnio verta įsiminti, tai būtų šitai: neuroninis tinklas yra tiesiog labai daug paprastų aritmetinių operacijų, sudėliotų sluoksniais, kurios kartu, per iteratyvų klaidos mažinimo procesą, sugeba atrasti sudėtingus dėsningumus. Nėra jokios magijos, nėra sąmonės, nėra „supratimo” prasme, kokį mes tam suteikiame. Yra tik svoriai, gradientai ir kantrybė – milijonai kartojimų, per kuriuos mašina pamažu tampa vis geresnė spėti teisingą atsakymą.

Norintiems eiti gilyn – kitas logiškas žingsnis būtų pasidomėti konvoliuciniais tinklais (CNN), skirtais vaizdams, arba transformerio architektūra, ant kurios pastatyti visi šiuolaikiniai kalbos modeliai. Bet be pagrindų, kuriuos aptarėme čia, tie dalykai atrodys dar labiau kaip juoda dėžė. Suprask pagrindus – ir viskas kita ims dėliotis daug lengviau, žingsnis po žingsnio, lygiai taip pat, kaip mokosi ir patys tinklai.