2. diel - Neurónové siete - Perceptron
V minulej lekcii, Neurónové siete - Úvod , sme si načrtli vstupné požiadavky a menné konvencie pre kurz Neurónovej siete - Krok za krokom.
V dnešnej lekcii kurzu Neurónovej siete – Krok za krokom si najskôr povieme o fungovaní neurónu a vzápätí sa pustíme do tréningu perceptrónu a písania algoritmov.
Neurón
Najprv si ukážeme zjednodušený pohľad na neurón z biologického hľadiska:
Dendrity sú vstupy neurónu, na ktoré sa napájajú okolité neuróny. Vstupy sú potom odovzdané do tela neurónu (nucleus), ktorý môže byť excitovaný (aktivovaný) alebo nie. Keď je neurón excitovaný, jeho výstup prechádza axónom do ostatných neurónov. To je obrovské zjednodušenie, ale na naše účely to postačí.
Ako môžeme niečo také modelovať pomocou umelého neurónu? Potrebujeme vytvoriť funkciu, ktorá prijíma vstupy od ostatných neurónov (dendrity), spočíta excitáciu (nucleus) a výsledok pošle ďalej (axón). Pre zjednodušenie sú vstupy vážené a sčítané, výsledok je potom odovzdaný aktivačnú funkciu a jej výsledkom je výstup neurónu. To môžeme vidieť na nasledujúcom obrázku.
(prah zatiaľ neberieme do úvahy)
Obrázok môžeme prepísať do matematického vzorca:
Než budeme pokračovať, zjednoťme si terminológiu. Vektory označím tučným textom (𝑥) a všetky jednorozmerné vektory budem považovať za riadkové vektory. Všetky vektorové násobenia budú bodové súčiny (ak nie je uvedené inak) - takže 𝑥𝑤 𝑇 je skalárny súčin a výsledok je skalár. Vektory budem indexovať od nuly (všimnite si, že obrázok používa číslo 1 ako prvý index).
Keďže je aktivačná funkcia väčšinou nastavená vopred, pri tréningu neurónu (alebo celej neurónovej siete), hľadáme váhy 𝑤, ktoré vedú k najlepšiemu riešeniu.
A to je všetko, čo zatiaľ potrebujeme vedieť. Teraz môžeme prejsť k prvému modelu – perceptrónu.
Perceptron
Perceptron je najjednoduchší model – používa funkciu
sign()
ako aktivačnú funkciu f
.
Funkciu sign()
definujeme nasledovne:
Vzorec definuje deliacu nadrovinu (tj nadrovinu, ktorá rozdeľuje priestor prvkov dát na dve polovice). Ak majú napríklad vstupné dáta dva rozmery, bude oddeľujúcou nadrovinou priamka, kde všetky dáta z prvej triedy budú nad priamkou, zatiaľ čo všetky dáta z druhej triedy skončia pod priamkou.
Ako už bolo naznačené v predchádzajúcom odseku, perceptrón môžeme použiť na klasifikačné úlohy, teda rozdelenie dát do dvoch tried. Je zaručené, že pre lineárne separovateľné dáta (dáta, ktoré je možné separovať) algoritmus učenia perceptrónu (pozri nižšie) vždy nájde nejakú nadrovinu deliacu obe triedy (viac v PDF od Shivaram Kalyanakrishnan).
Algoritmus učenia perceptrónu
Algoritmus učenia je veľmi jednoduchý. Váhy sú inicializované náhodne. Algoritmus hľadá inštanciu, ktorá je zle klasifikovaná. Ak je skutočná trieda inštancie kladná (a teda bola klasifikovaná ako negatívna), inštancia sa pridá k vektoru váhy. Ak je na druhej strane inštancia záporná (a bola klasifikovaná ako kladná), inštancia sa odpočíta od vektora váhy. Algoritmus končí, keď sú všetky inštancie správne klasifikované.
Pre klasifikáciu použijeme náhodné dáta generované sklearnou knižnicou:
# Načítanie knižníc import sklearn.datasets import sklearn.model_selection import sklearn.metrics import matplotlib.pyplot as plt import numpy as np # Definovanie funkcie sign def sign(x): return 0 if x < 0 else 1 # Generovanie dát data, classes = sklearn.datasets.make_blobs(n_samples=100, n_features=2, centers=2, random_state=42) # Vykreslenie dát plt.scatter(data[:,0], data[:,1], c=classes) plt.show()Toto sú údaje, ktoré sa snažíme klasifikovať. Poďme si teraz napísať algoritmus učenia perceptrónu:
# Inicializácia váh weights = np.random.RandomState(42).uniform(-2, 2, 2) # Opakovanie až do konvergencie weights_changed = True while weights_changed: weights_changed = False # pre každý výskyt v dátach for instance, target in zip(data, classes): # predpoveď výstupu perceptrónu prediction = sign(instance @ weights) if prediction == target: # správna klasifikácia continue elif target == 1: # pozitívna klasifikácia ako negatívna - pridanie inštancie k váham weights = weights + instance elif target == 0: # negatívna klasifikácia ako pozitívna - odčítanie inštancie od váh weights = weights - instance weights_changed = True
Ako som povedal, perceptrón definuje deliacu nadrovinu. Nadrovina je definovaná váhami perceptrónu a pretože sme v 2D, oddeľujúcou nadrovinou je priamka. Možno si pamätáte vzorec priamky v normálnom tvare 𝛼𝑥+𝛽𝑦+𝛾=0. V našom prípade je 𝑤 normála priamky a teda 𝛼=𝑤 0, 𝛽=𝑤 1 a 𝛾=0 (normála je kolmá na priamku). Poďme teraz nájdenú deliacu nadrovinu vykresliť:
# Výpočet sklonu priamky slope = - weights[0] / weights[1] # Vykreslenie dát plt.scatter(data[:,0], data[:,1], c=classes) # Vykreslenie oddeľovacej priamky plt.plot( [data.min(axis=0)[0], data.max(0)[0]], [slope * data.min(axis=0)[0], slope * data.max(axis=0)[0]], c='r') plt.show()Ako môžeme vidieť, priamka je medzi týmito dvoma triedami, ale je o niečo bližšie k žltej triede. Pravdepodobne by sme chceli mať hranicu presne medzi triedami. Pretože sú ale všetky body už klasifikované správne, algoritmus nemôže priamku nijako upraviť. To je všeobecne nevýhoda perceptrónu. Nájde deliacu nadrovinu, ale nemusí to byť tá najlepšia. Skúsme zmeniť dáta a znova perceptrón potrénovať:
# GENEROVANIE DÁT data, classes = sklearn.datasets.make_blobs(n_samples=100, n_features=2, centers=2, random_state=48) # Tréning perceptrónu # Inicializácia váh weights = np.random.RandomState(42).uniform(-2, 2, 2) # Opakovanie až do konvergencie weights_changed = True while weights_changed: weights_changed = False # pre každú inštanciu v dátach for instance, target in zip(data, classes): # predikcia výstupu perceptrónu prediction = sign(instance @ weights) if prediction == target: # správne zaradenie continue elif target == 1: # pozitívne klasifikované ako negatívne - pridanie inštancie k váham weights = weights + instance elif target == 0: # negatívne klasifikované ako pozitívne - odčítanie inštancie od váh weights = weights - instance weights_changed = True # VYKRESLENIE # Vypočítanie sklonu priamky slope = - weights[0] / weights[1] # Vykreslenie dát plt.scatter(data[:,0], data[:,1], c=classes) # Vykreslenie oddeľovacej priamky plt.plot( [data.min(axis=0)[0], data.max(0)[0]], [slope * data.min(axis=0)[0], slope * data.max(axis=0)[0]], c='r') plt.show()Ako môžeme vidieť, všetky body sú správne klasifikované, ale deliaca nadrovina je presne vedľa žltých bodov. To rozhodne nie je priamka, ktorú sme chceli získať! Algoritmus sa môžeme pokúsiť spustiť viackrát s rôznou inicializáciou váh a vybrať najlepší výsledok, ktorý môžeme získať. Môžeme tiež iterovať dáta nie sekvenčne, ale náhodne. Ďalšie prístupy (ktoré nevyžadujú zapojenie programátora) preberiem neskôr.
Zjednodušenie pravidla aktualizácie
Pravidlo aktualizácie môžeme trochu zjednodušiť pomocou vzorca 𝑤 = 𝑤 + (target - prediction) ∗ 𝑥. Keď je predpoveď správna, rozdiel target - prediction je 0 a nevykonáva sa žiadna aktualizácia. Keď je 𝑡𝑎𝑟𝑔𝑒𝑡 = 0 a 𝑝𝑟𝑒𝑑𝑖𝑐𝑡𝑖𝑜𝑛=1 (negatívna inštancia je klasifikovaná kladne), inštancia sa odpočíta. V prípade 𝑡𝑎𝑟𝑔𝑒𝑡 = 1 a 𝑝𝑟𝑒𝑑𝑖𝑐𝑡𝑖𝑜𝑛 = 0 (pozitívna inštancia klasifikovaná ako negatívna), inštancia sa prište k váham. Môžeme tak celú aktualizáciu prepísať na jeden riadok.
Nakoniec sa ešte musíme uistiť, že sledujeme zmeny váh w. Nové váhy môžeme porovnať s váhami z predchádzajúcej iterácie a pokiaľ neboli vykonané žiadne zmeny, môže algoritmus ukončiť:
# GENEROVANIE DÁT data, classes = sklearn.datasets.make_blobs(n_samples=100, n_features=2, centers=2, random_state=42) # Tréning perceptrónu # Inicializácia váh weights = np.random.RandomState(42).uniform(-2, 2, 2) # Opakovanie až do konvergencie old_weights = None while (weights != old_weights).any(): old_weights = weights # pre každú inštanciu v dátach for instance, target in zip(data, classes): # predikcia výstupu perceptrónu prediction = sign(instance @ weights) # aktualizácia váh weights = weights + (target - prediction) * instance # VYKRESLENIE slope = - weights[0] / weights[1] plt.scatter(data[:,0], data[:,1], c=classes) plt.plot( [data.min(axis=0)[0], data.max(0)[0]], [slope * data.min(axis=0)[0], slope * data.max(axis=0)[0]], c='r') plt.show()Základy máme úspešne za sebou. Nabudúce pôjdeme viac do detailu 🙂
V budúcej lekcii, Neurónové siete - Krokovanie, bias a viac dimenzií , si ukážeme krokovanie, bias a prípad s 10-timi dimenziami.