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í:
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:
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:
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:
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é:
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:
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ů.