SAMSUNG_022024A Advertisement SAMSUNG_022024A Advertisement SAMSUNG_022024A Advertisement

ML v Pythone 9 – trénovanie neurónu

0

predchádzajúcej časti sme ukázali rozpoznávanie obrazu pomocou už natrénovanej neurónovej siete ResNet-50. V deviatej časti ukážeme ako sa trénujú neuróny umelej neurónovej siete. Bude to trochu dlhšie, nechceli sme túto problematiku rozdeľovať do viacerých dielov

Postup vytvorenia príkladov je v krátkom videu

Neurónové siete

Simulujú štruktúru ľudského mozgu, ktorý sa od detstva postupne učí, získava poznatky z množiny podnetov z okolitého sveta.  Podobne aj neurónové siete získavajú poznatky z množiny vstupov a na základe nich dolaďujú svoje parametre modelu vzhľadom na nové znalostí. Spracovanie pomocou neurónových sietí funguje  podobne ako ľudský mozog na princípe rozpoznávania vzorov a minimalizácie chýb. Tento proces si môžete predstaviť ako prijímanie informácií a poučenie sa z každej skúsenosti tak aby boli nájdené v údajoch určité schémy. Neurónovú sieť tvoria uzly usporiadané do vrstiev. Umelý neurón je funkcia, ktorá prijíma vstupy od ostatných neurónov, tieto vstupy sú vynásobené takzvanými váhami, takto získané hodnoty sa sčítajú a výsledok je odovzdaný aktivačnej funkcii. Výstup aktivačnej funkcie je výstupom neurónu.

Perceptrón

Je neurón určený na zaradenie prípadov do dvoch tried. Napríklad či na obrázku je, alebo nie je mačka. Je to algoritmus, ktorý poskytuje klasifikovaný výstup, čiže rozhoduje či niečo určené množinou vstupov je pravdivé alebo nepravdivé. Zložitejšie povedané je to model neurónu, ktorý prijíma vstupné signály nie priamo, ale cez synaptické váhy tvoriace váhový vektor. Zložky vstupného vektora môžu nadobúdať reálne alebo binárne hodnoty, napríklad obrázok zakódovaný ako pole, či viacrozmerný vektor, čiže tenzor. Zložky váhového vektora, ktoré definujú dôležitosť údajov z jednotlivých vstupov sú reálne čísla. Na vstup perceptronu sa v konečnom dôsledku dostane takzvaná vážená suma, čo je súčet hodnôt jednotlivých vstupov vynásobený váhovým vektorom. Táto hodnota sa porovná s takzvanou prahovou hodnotou. Ak ju vážená suma prekročí, výsledok rozhodovania bude kladný, v opačnom prípade záporný. Napríklad ak vážená suma, trebárs pravdepodobnosť toho, že na obrázku je konkrétny objekt, napríklad mačka  je 0.4 a predpojatosť modelu (bias) v tomto príklade sme ju nazvali prahová hodnota je 0.5, tak na výstupe perceptrónu bude nula, čiže na obrázku zrejme mačka nie je. To samozrejme neznamená že tam naozaj nie je, môže byť napríklad  v neobvyklej polohe.

Tak ako rôzni ľudia reagujú na rôzne podnety rôzne, tak aj rovnaké podnety privedené na vstup dvoch rôznych perceptrónov môžu byť a aj budú rôzne. Analógiou môžu byť dve veľmi malé deti, ktoré sa ešte len učia poznávať svet okolo seba. Jedno môže povedať, že na obrázku je mačka, a druhé, že to mačka nie je. Technokraticky povedané výsledkom je v prvom prípade pozitívna a v druhom prípade negatívna reakcia na rovnaký podnet. Takže aj dva perceptróny, ktoré majú na vstupe rovnaké podnety môžu dôjsť k rôznym záverom. Jeden môže vyhodnotiť, že na obrázku je pes, druhý, že je to mačka.

Ukážeme to na jednoduchom príklade kde budú vstupy a váhy zadané ako konštanty. Mohol by to byť perceptrón, ktorého úlohou je podieľať sa na rozhodnutí, či zviera na obrázku je, alebo nie je mačka. Nám je to okamžite jasné, ale perceptrón to musí zistiť. Príklad môžete realizovať buď v online prostredí Google Colaboratory, alebo lokálne v prostredí Jupyter Notebook, ktoré je súčasťou platformy Anaconda.

Aby bol príklad jednoduchý zrozumiteľný a trochu aj reálny, použijeme pre každý prípad tri vstupy, ktoré budú udávať, či objekt na obrázku má:

  • sklopené uši
  • výrazné fúzy
  • elipsovité oči

Vstupy, ktoré by inak pochádzali z ďalších uzlov neurónovej siete budeme zadávať ručne, podľa toho ako na stupnici od 0 po 1 objekt spĺňa príslušné kritérium.

Empiricky sú definované aj váhové koeficienty

  •  sklopené uši (má máloktorá možno žiadna mačka) – 0.02
  • výrazné fúzy (charakteristický znak mačky) – 0,8
  • elipsovité oči (charakteristický znak mačky ) – 0.6

Váhový koeficient môže byť aj záporné číslo, my sme dali veľmi malé kladné. Uvidíme ako sa tieto koeficienty neskôr v procese optimalizácie zmenia. Zdôrazňujeme, že váhové koeficienty sme určili odhadom.

Prvý vstup nášho príkladu udáva s akou pravdepodobnosťou bolo rozpoznané, že zviera na obrázku má sklopené uši. Yorkšír na prvom obrázku sklopené uši má, takže vstupná hodnota by mohla byť 0.9.

Druhý vstup udáva, či zviera na obrázku má fúzy. Nič také však na prvom obrázku nebolo rozpoznané takže vstup má hodnotu 0.1,

Psík na obrázku má síce oči skôr okrúhle, avšak nie je ostrihaný, takže vyzerajú ako podlhovasté. Vstup má hodnotu 0.6

# sklopené uši, výrazné fúzy, podlhovasté oči...
vahy = [0.05, 0.8, 0.6]
 
vstupy1 = [0.9, 0.1, 0.6] # obr1 yorkšír
vstupy2 = [0.1, 0.6, 0.3] # obr2 psík kríženec
vstupy3 = [0.1, 0.9, 0.8] # obr3 mačka
bias = 0.5
 
def aktivacna_funkcia(vazena_suma):
    return 1 if vazena_suma > bias else 0
 
def perceptron(vstupy):
   vazena_suma = 0
   for i,w in zip(vstupy, vahy):
      vazena_suma += i*w
   return vazena_suma, aktivacna_funkcia(vazena_suma)
 
print("Vážená suma, výstup:",  perceptron(vstupy1),"obr1-yorkšír")
print("Vážená suma, výstup:", perceptron(vstupy2),"obr2-psík kríženec")
print("Vážená suma, výstup:", perceptron(vstupy3),"obr3-mačka")
 
Vážená suma, výstup: (0.485, 0) obr1-yorkšír
Vážená suma, výstup: (0.665, 1) obr2-psík kríženec
Vážená suma, výstup: (1.205, 1) obr3-mačka

Výstup 0 v prvom prípade je správny, o tom že to nie je mačka, rozhodla skutočnosť že na obrázku boli rozpoznané sklopené uši a zviera nemalo rozpoznateľné fúzy. Zároveň výsledok je tesný, a to kvôli neostrihaným očiam, ktoré vyzerajú ako podlhovasté.

V druhom prípade je výsledok nesprávny výstup 1 indikuje, že na obrázku by mala byť mačka. Tento výstup je nesprávny na obrázku je tiež psík – kríženec so špicatými ušami, rozpoznateľnými fúzami a tentoraz  s okrúhlymi (ostrihanými)  očami

Tretia množina vstupov je pre obrázok mačky so všetkými charakteristickými vlastnosťami a výstup je 1, čiže správny

V našom príklade bol jeden omyl, keď perceptrón nesprávne určil, že na obrázku je mačka, aj keď tam mačka nebola. Takýto mechanizmus nás samozrejme nemôže uspokojiť. Aby perceptrón fungoval s vyššou presnosťou a dopúšťal sa čo najmenšieho počtu chýb, je potrebné upraviť váhové koeficienty.

Doteraz získané výsledky sme zhrnuli do tabuľky a pridali sme aj hodnotu cieľa, čiže očakávaného výstupu korešpondujúceho so skutočnosťou.

objekt
V1
V2
V3
V_suma
Výstup
Očakávaný výstup
yorkšír
0.9
0.1
0.6
0,485
0
0
psík kríženec
0.9
0.6
0.3
0.665
1
0
mačka
0.1
0.9
0.8
1,205
1
1

Výstup je preto potrebné upraviť, alebo lepšie povedané upravovať dovtedy kým sa výstupy nezhodujú s cieľom. Najskôr potrebujeme určiť ako ďaleko sme od stanoveného cieľa. Na tento účel sa používa stratová funkcia, nazývaná aj chybová, v originálnej terminológii Loss Error Function.

Predpokladajme situáciu že máme definované vstupy, váhy a stanovené ciele, pričom predpoveď, čiže výstup perceptronu sa v niektorých prípadoch s predpokladaným cieľom zhoduje a v niektorých sa nezhoduje.

Chyba = – (vystup * log(vazena_suma) + (1 - vystup) * log(1- vazena_suma))

Kde výstup z perceptrónu môže nadobúdať hodnoty 0, alebo 1. Ak je výstup 0 tak prvá časť rovnice bude tiež 0. Ak je výstup 1, bude 0 aj druhý člen súčtu.

Pre vstupy so správne rozpoznaným obrázkom na ktorom nie je mačka

Vážená suma: 0.485
Výstup:0

Chyba = – (0 * log(0.485) + (1 - 0) * log(1 - 0.485)) = - log(0,515) = -( -0,29) = 0.29

Pre ďalšie vstupy (psík kríženec) a správny výstup

Vážená suma: 0.665
Výstup:1

Chyba = – (1 * log(0.665) + (1 - 1) * log(1- 0.665)) = -log(0.335)  = 0,47

Pre ďalšie vstupy (mačka) a správny výstup

Vážená suma: 1.205
Výstup:1

Chyba = – log(1,205)  = 0.08

Samozrejme tieto hodnoty by nám spočítal kód v Pythone na niekoľko riadkov, no sme chceli ukázať ako sa to počíta. Napokon o nič neprídete, implementácia vzorca pre výpočet chyby bude v ďalšom príklade.

Ak sa predpoveď zhoduje s cieľom, chybová hodnota bude nízka.  Takže doplňme našu tabuľku o výsledky

objekt
V1
V2
V3
V_suma
Výstup
Očakávaný výstup
chyba
yorkšír
0.9
0.1
0.6
0,485
0
0
0.29
psík kríženec
0.9
0.6
0.3
0.665
1
0
0.47
mačka
0.1
0.9
0.8
1,205
1
1
0.08

Priemerná chyba 0.28

Priemerná chyba, vyjadrujúca chybovosť modelu, v našom prípade 0.28 nám určuje ako ďaleko sú naše výstupy od vytýčeného cieľa, čiže vzdialenosť medzi výstupom a jeho cieľom.

Mimochodom mohli sme hneď na začiatku uviesť tento pre matematika jednoduchý vzorec

Radšej sme zvolili popis výpočtu krok za krokom aby sme vzorcom neodradili ľudí, ktorí si potrebujú problematiku zopakovať.

Gradientný zostup

Za týmto názvom sa v podstate skrýva aktualizácia váhových koeficientov modelu tak aby predpovede optimalizovaného modelu boli najpresnejšie, alebo inak povedané aby mali čo najmenšiu chybovosť. V skriptách by teraz nasledovali zložité vzorce s mnohými gréckymi písmenami a matematickými symbolmi. Princíp gradientného zostupu skúsime vysvetliť jednoduchšie a čo najnázornejšie.

Perceptrón ako ho zatiaľ poznáme poskytuje binárny výstup, čiže pravdepodobnosť predpovede, je buď 0, alebo 1. Laicky povedané, v predchádzajúcom prípade na obrázku buď je mačka, alebo to mačka nie je.  S takýmto „hrubým“ výstupom by sme však nedokázali model aktualizovať. Preto potrebujeme predpoveď v intenciách klasickej pravdepodobnosti. Čiže v našom prípade s akou pravdepodobnosťou je na obrázku mačka. Váženú sumu sme porovnávali s  prahovou hodnotou označenou bias. Toto anglické slovo by sme mohli preložiť ako predpojatosť, alebo zaujatosť

def aktivacna_funkcia(vazena_suma):
   if vazena_suma > bias:
      return 1
   else:
      return 0

Preto musíme aktivačnú funkciu zmeniť tak aby na výstupe nevracala binárne hodnoty 0 a 1, ale pravdepodobnosť v rozsahu 0.0 – 1, napríklad 0.35, čo znamená 35 %. Na tento účel využijeme funkciu sigmoid , ktorej vzorec je

kde e je základ prirodzených logaritmov 2.71828...  Priebeh funkcie sigmoid je krivka v tvare S. Parameter x bude v našom prípade vážená suma.

def sigmoid(x):
   return 1/(1 + np.exp(-x))

Na ilustráciu si môžeme si nechať vykresliť graf tejto funkcie pre zadaný rozsah hodnôt

import numpy as np
import matplotlib.pyplot as plt
 
def sigmoid(x):
 return 1/(1 + np.exp(-x))
 
x = np.linspace(-10, 10, 50)  
p = sigmoid(x)
plt.xlabel("x")
plt.ylabel("Sigmoid(x)") 
plt.plot(x, p)
plt.show()

Vážené sumy vypočítame sčítaním výsledkov násobenia na čo nám vynikajúco poslúži funkcia numpy.dot()

# Σ (vstupy * vahy)
def vazene_sumy (vstupy, vahy):
    return np.dot(vstupy, vahy)

Do funkcie na výpočet predpovede potrebujeme zahrnúť aj bias (predpojatosť), ktorý jednoducho pripočítame k váženým sumám

def predpoved(vstupy, vahy, prah):
    return f_sigmoid(vazene_sumy(vstupy, vahy) + bias)

Predpovede už teda nebudú len vstupy vynásobené váhami, ale pripočíta sa k ním aj predpojatosť.

V procese optimalizácie nazývanom gradientný zostup postupne vo viacerých krokoch vypočítame nové, lepšie váhové koeficienty pre jednotlivé vstupy.

Prepísané do zrozumiteľnejšej podoby

Nová_váha = pôvodná_váha + rýchlost_ucenia *(ciel - predpoved) * vstup

Aktualizovaný bias vypočítame podľa vzorca

Čiže

Nový_bias = pôvodný_bia + rýchlost_ucenia *(ciel - predpoved)

Rýchlosť učenia je potrebné nastaviť na malú hodnotu, napríklad 0,1, 0,01 prípadne aj menšiu aby sa jednotlivé váhy aktualizovali nie skokovo, ale postupne, pretože v každom kroku ich smenu ovplyvňuje viac faktorov. Pri veľkej zmene by mohol náš model ľudovo povedané niečo dôležité nepostrehnúť.

Kompletný kód pre optimalizáciu v zadanom počtu krokov bude
import numpy as np
 
vstupy = np.array(([0.9, 0.1, 0.6],[0.1, 0.6, 0.3],[0.1, 0.9, 0.8]))
vahy = np.array([0.05, 0.8, 0.6])
ciele = np.array([0,0,1])
bias = 0.5
iteracie = 10
rychlost_ucenia = 0.1
 
 
# Σ (vstupy * vahy)
def vazene_sumy (vstupy, vahy):
    return np.dot(vstupy, vahy)
   
# aktivačná funkcia signmoid pre "S krivku" 1/1+(e **(-x))
def f_sigmoid(x):
    return 1/(1+ np.exp(-x))
 
# predpoveď
def predpoved(vstupy, vahy, bias):
    return f_sigmoid(vazene_sumy(vstupy, vahy) + bias)
 
# chybová funkcia
def chybova_funkcia(ciele, pred):
    return -(ciele*np.log10(pred)+(1-ciele)*(np.log10(1-pred)))
 
# úprava váhových koeficientov
def korekcia_gradientov(x, y, vahy, bias, rychlost_ucenia, pred):
    akt_vahy = []
    bias += rychlost_ucenia*(y-pred)
    for w,xi in zip(vahy,x):
        akt_vaha = w + rychlost_ucenia*(y-pred)*xi
        akt_vahy.append(akt_vaha)
    return akt_vahy, bias
 
print("Počiatočné váhy:",[ "{:0.2f}".format(x) for x in vahy ], "BIAS:", '%.1f' % bias)
print("=========================================================")
for e in range(iteracie):
    for x, y in zip(vstupy, ciele):
        predp = predpoved(x, vahy, bias)
        vahy, bias = korekcia_gradientov(x, y, vahy, bias, rychlost_ucenia, predp)
    vystup = predpoved(vstupy, vahy, bias)
    chyby  = chybova_funkcia(ciele, vystup)
    priemer_chyba = np.mean(chybova_funkcia(ciele, vystup))
    print("Iterácia", e,"> Váhy:",[ '%.2f' %x for x in vahy ], "bias:", '%.3f' % bias, "Loss:", '%.3f' % priemer_chyba)
 
 
Počiatočné váhy: ['0.05', '0.80', '0.60'] BIAS: 0.5
=========================================================
Iterácia 0 > Váhy: ['-0.02', '0.77', '0.55'] Bias: 0.372 Loss: 0.384
Iterácia 1 > Váhy: ['-0.09', '0.74', '0.50'] Bias: 0.255 Loss: 0.354
Iterácia 2 > Váhy: ['-0.15', '0.71', '0.47'] Bias: 0.148 Loss: 0.329
Iterácia 3 > Váhy: ['-0.21', '0.69', '0.43'] Bias: 0.053 Loss: 0.309
Iterácia 4 > Váhy: ['-0.26', '0.68', '0.41'] Bias: -0.034 Loss: 0.292
Iterácia 5 > Váhy: ['-0.31', '0.67', '0.39'] Bias: -0.111 Loss: 0.279
Iterácia 6 > Váhy: ['-0.35', '0.66', '0.37'] Bias: -0.181 Loss: 0.268
Iterácia 7 > Váhy: ['-0.39', '0.65', '0.35'] Bias: -0.243 Loss: 0.260
Iterácia 8 > Váhy: ['-0.43', '0.65', '0.34'] Bias: -0.300 Loss: 0.253
Iterácia 9 > Váhy: ['-0.47', '0.66', '0.34'] Bias: -0.351 Loss: 0.247

Upravíme kód aby nám vykreslil graf chybovej funkcie, pričom na osi X sú iterácie

Váhy: ['-0.47', '0.66', '0.34'] Bias: -0.351 Loss: 0.247

Výsledky pre 10 iterácií

Váhy: ['-0.47', '0.66', '0.34'] Bias: -0.351 Loss: 0.247

Výsledky pre 100 iterácií

Váhy: ['-1.79', '1.69', '1.37'] Bias: -1.744 Loss: 0.140

Výsledky pre 1000 iterácií

Váhy: ['-3.63', '4.15', '6.68'] Bias: -6.291 Loss: 0.029

Samozrejme pre trénovanie na troch prípadoch viac ako 10 iterácií nemá žiadny praktický zmysel

Pre zaujímavosť dosadíme váhy po 10-tich iteráciách do kódu perceptronu

Vážená suma, výstup: (-0.15299999999999997, 0) obr1-yorkšír
Vážená suma, výstup: (0.45100000000000007, 0) obr2-psík kríženec
Vážená suma, výstup: (0.8190000000000001, 1) obr3-mačka

Predpovede, či zviera na obrázku je, alebo nie je mačka sú tentoraz správne

Rekapitulácia doterajších dielov

Strojové učenie v Pythone 1 – prostredie Google Colab

Strojové učenie v Pythone 2 – knižnica Pandas na prácu s údajmi

ML v Pythone 3 – export a import údajov vo formáte CSV a Excel

Strojové učenie v Pythone 4 – práca s údajmi

ML v Pythone 5 – vizualizácia údajov pomocou grafov

ML v Pythone 6 – animované grafy a ich export ako video

ML v Pythone 7 – Využitie grafickej karty NVIDIA na výpočtovo náročné úlohy

ML v Pythone 8 – príklad rozpoznávanie obrazu

Zobrazit Galériu

Luboslav Lacko

Všetky autorove články
neuronova siet strojove ucenie machine learning perceptron neuron

Pridať komentár

Mohlo by vás zaujímať

Mohlo by vás zaujímať