NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!
NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.

Lekce 3 - Neuronové sítě - Krokování, bias a více dimenzí

V minulé lekci, Neuronové sítě - Perceptron, jsme zmínili Perceptron a ukázali si některé druhy algoritmů vhodných pro generování a třídění dat.

V této lekci tutoriálu Neuronové sítě - Pokročilé si ukážeme krokování algoritmu, povíme si něco o biasu a lekci zakončíme případem s 10-ti dimenzemi.

Krokování algoritmu

Nyní, když máme algoritmus, můžeme se na něj podívat blíže. Každou aktualizaci si také vykreslíme. Nejprve si však vytvoříme datovou sadu. Aby byl příklad jasnější, použijeme nižší počet datových bodů:

data, classes = sklearn.datasets.make_blobs(
    n_samples=10,
    n_features=2,
    centers=[[-1,0.4],[1,0.6]],
    cluster_std=0.8,
    random_state=82
)
plt.scatter(data[:,0], data[:,1], c=classes)
plt.show()

Zde je graf řešení:

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

Teď si vykreslíme všechny změny. Na níže uvedených grafech je červená přímka oddělující nadrovinou a černá přímka, její normála. Špatně klasifikovaná instance (u které aktualizujeme váhy) je označena zeleně. Zelená přímka představuje instanční vektor, tj. vektor, který chceme přičíst (nebo odečíst) od vah. Konečně, světle červené a modré přímky jsou novou dělící nadrovinou (po aktualizaci) a odpovídající normála:

# TRÉNINK PERCEPTRONU
# Inicializace vah
weights = np.random.RandomState(42).uniform(size=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)
        # aktualizace vah
        weights_new = weights + (target - prediction) * instance

        # vykreslení změny
        if (weights != weights_new).any():
            slope = - weights[0] / weights[1]  # vypočítání sklonu
            plt.figure(figsize=(8, 5))
            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')  # vykreslení dělící nardoviny
            plt.plot([0, weights[0]],[0, weights[1]], c='black')  # vykreslení normály
            plt.scatter(data[:,0], data[:,1], c=classes)  # vykreslení dat
            plt.scatter(instance[0], instance[1], c='g')  # vykreslení chybně klasifikované instance
            plt.plot([0, instance[0]], [0, instance[1]], c='g', alpha=0.4)  # vykreslení instančního vektoru
            plt.plot(
                [weights[0], weights[0] + (target - prediction) * instance[0]],
                [weights[1], weights[1] + (target - prediction) * instance[1]],
                c='g', alpha=0.4)  # vykreslení instančního vektoru z normály
            plt.plot(
                [0, weights[0] + (target - prediction) * instance[0]],
                [0, weights[1] + (target - prediction) * instance[1]],
                c='c', alpha=0.4)  # vykreslení nové normály
            slope_new = - weights_new[0] / weights_new[1]  # výpočet sklonu nové přímky
            plt.plot(
                [data.min(axis=0)[0], data.max(0)[0]],
                [slope_new * data.min(axis=0)[0], slope_new * data.max(axis=0)[0]],
                c='r', alpha=0.2)  # vykreslení dělící nadroviny
            plt.axis('equal')
            plt.ylim(-2,4)
            plt.show()

        weights_changed = weights_changed or (weights != weights_new).any()
        weights = weights_new

# VYKRESLENÍ KONEČNÝCH VAH
slope = - weights[0] / weights[1]
plt.figure(figsize=(8, 5))
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()

Podoba výsledku kódu v grafu:

Jak vidíme, po každé aktualizaci se normála posune blíže (pozitivní data) nebo dále (negativní data) od nesprávně klasifikované instance. To je hlavní myšlenka algoritmu učení perceptronu.

Bias

Zatím jsme nepoužili bias. V rámci obecné rovnice přímky ?? + ?? + ? = 0 jsme nastavili ? na nulu. Z tohoto důvodu prochází přímka vždy souřadnicí [0, 0]. Ale co když máme data, která nelze takto oddělit? Rádi bychom zavedli posun ? (anglicky bias) a umožnili tím algoritmu posunout dělící nadrovinu z nulové souřadnice. Nejprve si tedy vygenerujeme novou datovou sadu:

data, classes = sklearn.datasets.make_blobs(
    n_features=2,
    centers=2,
    random_state=44
)
plt.scatter(data[:,0], data[:,1], c=classes)
plt.show()

V grafu vypadá takto:

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

Tuto datovou sadu nelze oddělit pomocí našeho perceptronového algoritmu, protože ji nemůže oddělit žádná přímka procházející [0, 0]. Pro bias proto přidáme term (parametr) ? do lineární kombinace vstupů: ?=?(???+?)

Bias se učí stejným způsobem jako váhy. Když algoritmus nesprávně klasifikuje pozitivní instanci, zvýší bias o 1. Když je chybně klasifikována negativní instance, sníží jej o 1:

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

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

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

# 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] - bias / weights[1], slope * data.max(axis=0)[0] - bias / weights[1]],
    c='r')
plt.show()

Výsledná přímka v grafu:

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

Jak vidíme, přímka se posunula dolů a neprochází už přes [0, 0]. Někdy je zbytečné řešit bias samostatně. Můžeme ho zahrnout do vah a doplnit datové body jedničkami. Je to stejné, jako bychom přesunuli data do nové dimenze. Ukažme si to na jednorozměrných datech. Máme pozitivní data {1, 2, 3} a negativní data {4, 5, 6}. Rádi bychom je oddělili nadrovinou (to je v tomto případě bodem), která prochází 0. To není možné, protože oddělující nadrovinou může být pouze bod 0:

data = np.array([[1],[2],[3],[4],[5],[6]])
classes = np.array([0,0,0,1,1,1])
plt.scatter(data, np.zeros((6,)), c=classes)
plt.scatter(0,0,c='r')
plt.show()

Situace v grafu:

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

Pokud bychom však přesunuli data do nové dimenze (ty by bylo v tomto případě 2D), oddělující nadrovinou už je přímka. Data jsou pak lineárně separovaná:

data = np.array([[1],[2],[3],[4],[5],[6]])
classes = np.array([0,0,0,1,1,1])
plt.scatter(data, np.ones((6,)), c=classes)
plt.scatter(0,0,c='r')
plt.plot([0, 6],[0, 1.75], c='r')
plt.show()

Z grafu řešení je to zřejmé:

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

S biasem se můžeme vypořádat stejným způsobem. Přidáme novou dimenzi s jedničkami a v algoritmu perceptronu použijeme tři váhy:

# GENEROVÁNÍ DAT
data, classes = sklearn.datasets.make_blobs(
    n_features=2,
    centers=2,
    random_state=44
)
data = np.hstack([data, np.ones((data.shape[0],1))])

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

# 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]
bias = weights[2] / 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] - bias, slope * data.max(axis=0)[0] - bias],
    c='r')
plt.show()

Výsledek vypadá takto:

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

Vícedimenzionální případ

Zatím jsme si ukázali pouze dvourozměrné případy. Není nic, co by bránilo použití více dimenzionálních dat, kromě toho, že je nebudeme moci vykreslit. Zkusme například 10 dimenzí, ale bez vykreslování, protože vizualizaci 10D dat by nebylo snadné provést:

# GENEROVÁNÍ DAT
data, classes = sklearn.datasets.make_blobs(
    n_samples=1000,
    n_features=10,
    centers=2,
    random_state=42
)
data = np.hstack([data, np.ones((data.shape[0],1))])

train_data, test_data, train_classes, test_classes = sklearn.model_selection.train_test_split(data, classes, test_size=0.15)

# TRÉNINK PERCEPTRONU
weights = np.random.RandomState(42).uniform(-2, 2, 11)
old_weights = None
while (weights != old_weights).any():
    old_weights = weights
    for instance, target in zip(train_data, train_classes):
        prediction = sign(instance @ weights)
        weights = weights + (target - prediction) * instance

# TEST
predictions = [sign(instance @ weights) for instance in test_data]
print(f'Přesnost: {sklearn.metrics.accuracy_score(test_classes, predictions)}')

Výsledek řešení:

Výstup
Přesnost: 1.0

Jak vidíme, algoritmus dosáhl 100% přesnosti. Správně oddělil data, protože jsou lineárně separovaná.

V příští lekci, Neuronové sítě - Normalizace, se zaměříme na normalizaci neboli standardizaci datových bodů.


 

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