NOVINKA - Online rekvalifikační kurz Python programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 2 - Neuronové sítě - Perceptron

V minulé lekci, Neuronové sítě - Úvod, jsme si nastínili vstupní požadavky a jmenné konvence pro kurz neuronové sítě.

V této lekci tutoriálu Neuronové sítě - Pokročilé si nejprve povíme o fungování neuronu. Poté se pustíme do tréninku perceptronu a psaní algoritmů.

Neuron

Nejprve si ukážeme zjednodušený pohled na neuron z biologického hlediska:

Neuron - Neuronové sítě - Pokročilé

Dendrity jsou vstupy neuronu, na které se napojují okolní neurony. Vstupy jsou pak předány do těla neuronu (nucleus), který může být excitován (aktivován) nebo ne. Když je neuron excitován, jeho výstup prochází axonem do ostatních neuronů. To je sice obrovské zjednodušení, ale pro naše účely to postačí.

Jak můžeme něco takového modelovat pomocí umělého neuronu? Potřebujeme k tomu vytvořit funkci, která přijímá vstupy od ostatních neuronů (dendrity), spočítá excitaci (nucleus) a výsledek pošle dál (axon). Pro zjednodušení jsou vstupy váženy a sečteny, výsledek je pak předán aktivační funkci a jejím výsledkem je výstup neuronu. To můžeme vidět na následujícím obrázku:

Umělý neuron - Neuronové sítě - Pokročilé

(práh zatím nebereme v úvahu)

Obrázek můžeme přepsat do matematického vzorce:

vzorec4 - Neuronové sítě - Pokročilé

Než budeme pokračovat, sjednoťme si terminologii. Vektory označíme tučným textem (𝑥) a všechny jednorozměrné vektory budeme považovat za řádkové vektory. Všechna vektorová násobení budou bodové součiny (pokud není uvedeno jinak) - takže 𝑥𝑤𝑇 je skalární součin a výsledek je skalár. Vektory budeme indexovat od nuly (všimněme si, že obrázek používá číslo 1 jako první index).

Jelikož je aktivační funkce většinou nastavena předem, při tréninku neuronu (nebo celé neuronové sítě), hledáme váhy 𝑤, které vedou k nejlepšímu řešení.

A to je vše, co zatím potřebujeme vědět. Nyní můžeme přejít k prvnímu modelu - perceptronu.

Perceptron

Perceptron je nejjednodušší model – používá funkci sign() jako aktivační funkci f.

Funkci sign() definujeme následovně:

vzorec5 - Neuronové sítě - Pokročilé

Vzorec definuje dělící nadrovinu (tj. nadrovinu, která rozděluje prostor prvků dat na dvě poloviny). Pokud mají například vstupní data dva rozměry, bude oddělující nadrovinou přímka, kde všechna data z první třídy budou nad přímkou, zatímco všechna data z druhé třídy skončí pod přímkou.

Jak již bylo naznačeno v předchozím odstavci, perceptron můžeme použít pro klasifikační úlohy, tedy k rozdělení dat do dvou tříd. Je zaručeno, že pro lineárně separovatelná data (data, která lze separovat) algoritmus učení perceptronu (viz níže) vždy najde nějakou nadrovinu dělící obě třídy (více v PDF od Shivaram Kalyanakrishnan).

Algoritmus učení perceptronu

Algoritmus učení je velmi jednoduchý. Váhy jsou inicializovány náhodně. Algoritmus hledá instanci, která je špatně klasifikována. Pokud je skutečná třída instance kladná (a tedy byla klasifikována jako negativní), instance se přidá k vektoru váhy. Pokud je na druhou stranu instance záporná (a byla klasifikována jako kladná), instance se od vektoru váhy odečte. Algoritmus končí, když jsou všechny instance správně klasifikovány.

Pro klasifikaci použijeme náhodná data generovaná sklearn knihovnou:

# Načtení knihoven
import sklearn.datasets
import sklearn.model_selection
import sklearn.metrics
import matplotlib.pyplot as plt
import numpy as np

# Definování funkce sign
def sign(x):
    return 0 if x < 0 else 1

# Generování dat
data, classes = sklearn.datasets.make_blobs(n_samples=100, n_features=2, centers=2, random_state=42)

# Vykreslení dat
plt.scatter(data[:,0], data[:,1], c=classes)
plt.show()

Výsledný graf potom vypadá následovně:

graf1 - Neuronové sítě - Pokročilé

Toto jsou data, která se snažíme klasifikovat. Pojďme si nyní napsat algoritmus učení perceptronu:

# Inicializace vah
weights = np.random.RandomState(42).uniform(-2, 2, 2)

# Opakování až do konvergence
weights_changed = True
while weights_changed:
    weights_changed = False

    # pro každý výskyt v datech
    for instance, target in zip(data, classes):
    # předpověď výstupu perceptronu
        prediction = sign(instance @ weights)
        if prediction == target:
            # správná klasifikace
            continue
        elif target == 1:
            # pozitivní klasifikace jako negativní - přidání instance k vahám
            weights = weights + instance
        elif target == 0:
            # negativní klasifikace jako pozitivní - odečtení instance od vah
            weights = weights - instance
        weights_changed = True

Jak jsme si již řekli, perceptron definuje dělící nadrovinu. Nadrovina je definována vahami perceptronu, a protože jsme ve 2D, oddělující nadrovinou je přímka. Vzorec přímky v normálním tvaru je 𝛼𝑥+𝛽𝑦+𝛾=0. V našem případě je 𝑤 normála přímky, a tedy 𝛼=𝑤0, 𝛽=𝑤1 a 𝛾=0 (normála je kolmá na přímku). Pojďme teď nalezenou dělící nadrovinu vykreslit:

# Výpočet sklonu přímky
slope = - weights[0] / weights[1]

# Vykreslení dat
plt.scatter(data[:,0], data[:,1], c=classes)

# Vykreslení oddělovací přímky
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()

Výsledný graf je:

graf2 - Neuronové sítě - Pokročilé

Jak můžeme vidět, přímka je mezi těmito dvěma třídami, nicméně o něco blíže ke žluté třídě. Pravděpodobně bychom chtěli mít hranici přesně mezi třídami. Protože jsou už ale všechny body klasifikovány správně, algoritmus nemůže přímku nijak upravit. To je obecně nevýhoda perceptronu. Najde sice dělící nadrovinu, ale nemusí to být právě ta nejlepší. Zkusme tedy změnit data a znovu perceptron potrénovat:

# GENEROVÁNÍ DAT
data, classes = sklearn.datasets.make_blobs(n_samples=100, n_features=2, centers=2, random_state=48)

# TRÉNINK PERCEPTRONU
# Inicializace vah
weights = np.random.RandomState(42).uniform(-2, 2, 2)

# Opakování až do konvergence
weights_changed = True
while weights_changed:
    weights_changed = False

    # pro každou instanci v datech
    for instance, target in zip(data, classes):
        # predikce výstupu perceptronu
        prediction = sign(instance @ weights)
        if prediction == target:
            # správné zařazení
            continue
        elif target == 1:
            # pozitivní klasifikováno jako negativní - přidání instance k vahám
            weights = weights + instance
        elif target == 0:
            # negativní klasifikováno jako pozitivní - odečtení instance od vah
            weights = weights - instance
        weights_changed = True

# VYKRESLENÍ
# Vypočítání sklonu přímky
slope = - weights[0] / weights[1]

# Vykreslení dat
plt.scatter(data[:,0], data[:,1], c=classes)

# Vykreslení oddělovací přímky
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()

Výsledný graf vypadá nyní takto:

graf3 - Neuronové sítě - Pokročilé

Jak můžeme vidět, všechny body jsou správně klasifikovány, nicméně dělící nadrovina je přesně vedle žlutých bodů. To rozhodně není přímka, kterou jsme chtěli získat! Algoritmus se můžeme pokusit spustit vícekrát s různou inicializací vah a vybrat nejlepší výsledek, který můžeme získat. Můžeme také iterovat data nikoliv sekvenčně, ale náhodně. Další přístupy (které nevyžadují zapojení programátora) probereme později.

Zjednodušení pravidla aktualizace

Pravidlo aktualizace můžeme trochu zjednodušit pomocí vzorce 𝑤 = 𝑤 + (target - prediction) ∗ 𝑥. Když je předpověď správná, rozdíl target - prediction je 0, a neprovádí se žádná aktualizace. Když je 𝑡𝑎𝑟𝑔𝑒𝑡 = 0 a 𝑝𝑟𝑒𝑑𝑖𝑐𝑡𝑖𝑜𝑛=1 (negativní instance je klasifikována kladně), instance se odečte. V případě 𝑡𝑎𝑟𝑔𝑒𝑡 = 1 a 𝑝𝑟𝑒𝑑𝑖𝑐𝑡𝑖𝑜𝑛 = 0 (pozitivní instance klasifikována jako negativní), přičte se instance k vahám. Můžeme tak celou aktualizaci přepsat na jeden řádek.

Nakonec se ještě musíme ujistit, že sledujeme změny vah w. Nové váhy můžeme porovnat s váhami z předchozí iterace. Pokud nebyly provedeny žádné změny, může se algoritmus ukončit:

# GENEROVÁNÍ DAT
data, classes = sklearn.datasets.make_blobs(n_samples=100, n_features=2, centers=2, random_state=42)

# TRÉNINK PERCEPTRONU
# Inicializace vah
weights = np.random.RandomState(42).uniform(-2, 2, 2)

# Opakování až do konvergence
old_weights = None
while (weights != old_weights).any():
    old_weights = weights

    # pro každou instanci v datech
    for instance, target in zip(data, classes):
        # predikce výstupu perceptronu
        prediction = sign(instance @ weights)
        # aktualizace vah
        weights = weights + (target - prediction) * instance

# VYKRESLENÍ
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()

Takto pak vypadá řešení v grafu:

graf4 - Neuronové sítě - Pokročilé

Základy máme úspěšně za sebou. Příště půjdeme více do detailu 🙂

V příští lekci, Neuronové sítě - Krokování, bias a více dimenzí, si ukážeme krokování, bias a případ s 10-ti dimenzemi.


 

Předchozí článek
Neuronové sítě - Úvod
Všechny články v sekci
Neuronové sítě - Pokročilé
Přeskočit článek
(nedoporučujeme)
Neuronové sítě - Krokování, bias a více dimenzí
Článek pro vás napsal Patrik Valkovič
Avatar
Uživatelské hodnocení:
13 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity