Pohľad do hĺbky neurónovej siete

alebo radšej niečo poriadne ako o všetkom niečo

Blog Admin

V dnešnom uponáhľanom svete a vo svete dostupnosti takmer ľubovoľných informácií vďaka internetu chceme vedieť čo najviac a narážame pritom na známy problém, či dilemu (v angl. trade off, alebo niečo za niečo), čo uprednostniť, či celkový prehľad alebo byť dobrý len v jednej oblasti. Svoje by o tom mohli hovoriť programátori, ktorí sa snažia mať aj prehľad, ale hlavne chcú byť experti v jednom, dvoch programovacích jazykoch. Isté je, že mať prehľad v programovaní nestačí, lebo nakoniec by ste mohli byť len teoretici, ktorí nevedia nejaký zložitejší problém a jeho riešenie naprogramovať. Dnes túto dilemu určite nemajú len programátori. Napríklad v každom národnom hospodárstve sa rieši trade off medzi nízkou infláciou a zároveň vysokým outputom, inak povedané vysokou mierou rastu reálneho HDP, nakoľko novodobá Philipsova krivka ukazuje, že jedno s druhým nejde dohromady. A takých príkladov by sme našli v našom živote strašne veľa.

V dnešnej dobe možno povedať, že prežívame už 4. revolúciu. Po agrárnej a priemyselnej revolúcii sme koncom minulého storočia zažili aj revolúciu informačných technológii. Dnes sme na prahu novej doby, kedy nastal obrovský boom v umelej inteligencii, machine learning, deep learning a neurónových sieťach. Mnohé teoretické backgroundy sú už známe z minulého storočia. Ale GPU (Graphics Processor Unit, niekoľko násobne rýchlejšie ako CPU), hlavne od NVIDIA, spolu s takými nástrojmi ako python, či tensorflow od Google nám dali mocný nástroj ako výpočty urýchliť a ako natrénovať v rozumnom čase aj obrovské modely určené napr. na detekciu objektov. Už dnes existuje veľa ľudí, ktorí zbystria sluch, keď započujú niečo o umelej inteligencii. A robia dobre. Mali by sme sa o to zaujímať, ak chceme, aby sa dĺžka nášho bytia predĺžila na 150 rokov a aby sa nám žilo lepšie, musíme z ekonomického pohľadu rapídne zvýšiť produktivitu práce. Ak chceme predĺžiť čas žitia, bude musieť pokročiť lekárska veda a my sa budeme musieť šetriť a delegovať určité činnosti na neživé objekty, ktoré menej alebo takmer vôbec nepodiehajú únave.

Treba povedať, že už dnes existuje dosť ľudí, ktorí chcú byť aktívni vo svete umelej inteligencie, neprijímať nové objavy len pasívne a ktorí chcú aj sami k rozvoju umelej inteligencie prispieť. Aj tu platí, že nikdy nie je neskoro, zvlášť nie teraz na prahu boomu. V umelej inteligencii medzi základné pojmy patrí neurónová sieť. Mnohí o neurónových sieťach rozprávajú, píšu ale mnohí z nich možno nevedia, ako to v hĺbke neurónovej siete funguje. Pošuškáva sa, že to je veľmi zložité a že to je samá matematika. Tento blogový príspevok sa snaží tento mýtus o zložitosti vyvrátiť a cieľom tohto príspevku je aj presvedčiť čitateľa, aby začal študovať túto podoblasť umelej inteligencie práve úplnym pochopením toho, čo sa deje v hĺbke neurónovej siete a aby uprednostnil kvalitu pred kvantitou, inak povedané, aby preferoval, že dobre niečomu porozumieť je lepšie ako vedieť zo všetkého niečo a dohromady nič.

Poďme teda konečne k veci. Dodržíme postup od jednoduchého k zložitému a mýtus o zložitosti sa pokúsime vyvrátiť na tej najjednoduchšej neurónovej sieti, ktorá sa nazýva aj perceptrón. Máme teda len 2 vrstvy a to input layer a output layer a aktivačnú funkciu signum. Schéma perceptrónu je znázornená na tomto obrázku:

perceptron

Možno už mnohí z vás vedia, že takáto jednoduchá neurónová sieť „nepokryje“ ani logickú operáciu XOR. XOR je negácia ekvivalencie a platí vtedy, ak práve jeden z dvoch výrokov je pravdivý a teda ten druhý nepravdivý. V slovenčine je to známe buď–alebo. Ako si toto predstaviť? Jednoducho! Zakreslime si body, [0,0], [1,1], [0,1] a [1,0] do 2D súradnicového systému v rovine. Prvým dvom bodom prislúcha pravdivostná hodnota 0 a druhým dvom prislúcha 1. Keďže v machine learning, deep learning a v neurónových sieťach často riešime kategorizačné úlohy, aj tu bude úloha separovať tieto zakreslené body do 2 kategórií tak, aby prvé dva patrili do kategórie 0 (lebo vtedy je XOR nepravdivé) a druhé dva do kategórie 1 (lebo vtedy je XOR pravdivé). Teraz si vyslovíme jednu definíciu. Množinu bodov v rovine, z ktorých každý patrí do jednej z dvoch kategórií, nazveme lineárne separovateľnou, ak vieme cez ne preložiť priamku tak, aby sa ňou oddelili body patriace do rôznych kategórií (viď obrázok nižšie).

linear separable set

Skúsme si prvé dva zakreslené body [0,0], [1,1] označiť modrou farbou na znak toho, že patria do kategórie 0 a body [0,1], [1,0] označíme červenou farbou na znak toho, že patria do kategórie 1. Keď to máme takto prehľadne zakreslené je nám jasné, že tieto body nie sú lineárne separovateľné. Nevieme priamkou oddeliť modré body od červených tak, aby na jednej strane priamky boli modré body a na opačnej červené.

xor

Tí, čo už čosi vedia z teórie neurónových sietí, by si v tejto situácii vedeli pomôcť. Určite by poradili, že tu už nevystačíme len s input a output layers. Áno, treba pridať ďalšiu vrstvu, ktorá už bude mať názov hidden layer, teda skrytá vrstva, lepšie povedané jedna zo skrytých vrstiev, nakoľko v deep learning, čo sú zložitejšie neurónové siete, ich používame 2 a viac. Ale vráťme sa k veci a pokúsme sa teda vysvetliť inú logickú operáciu a to AND alebo OR. V prípade AND máme opäť 4 body ako 4 možnosti platnosti, či neplatnosti výrokov: [0,0], [1,1], [0,1] a [1,0]. Ako vieme z elementárnej matematickej logiky konjunkcia, teda AND, dvoch výrokov je pravdivá, iba ak sú oba výroky pravdivé. Je to teda len v prípade [1,1] a tu patrí červená farba. Všetky ostatné body sú modré, lebo vtedy je AND nepravdivé. A takto zakreslené body v rovine sú už lineárne separovateľné. Už viete aj vy nájsť niekoľko takých priamok, ktoré oddelia modré body od červených.

and

A presne toto dokáže aj naša jednoduchá neurónová sieť. Hĺbavý človek ľahko zistí, že taká istá situácia je s OR. Aj tam vieme modré body (ten je iba jeden a to [0,0], lebo OR je nepravdivé ak sú oba výroky nepravdivé) oddeliť od zvyšných troch červených. Pre ďalší postup sme zvolili operáciu AND s predpokladom, že čitateľ bude vedieť modifikovať program v jazyku python nižšie, aby sa dopracoval k výsledkom pre OR, nakoľko je to úplne analogická úloha k AND. Nezastupiteľnú úlohu v objasnení toho, čo sa deje v hĺbke neurónovej siete má button Spustiť skript umiestnený dole pod kódom, lebo tam sú zobrazené všetky „črevá“ siete. Pod črevami siete rozumieme hlavne úpravu váh a konvergenciu k optimálnym váham. Určite sa oplatí venovať aspoň 30 minút úplnemu pochopeniu. Pri riešení/programovaní neurónovej siete musíme poznať základné pojmy ako tréning siete, spätná propagácia (backpropagation), propagácia vpred (forward propagation alebo feedforward) a aktivačná funkcia. Ak čitateľ pozná základný backpropagation algoritmus s optimalizačnou metódou najprudšieho alebo najväčšieho spádu, tak tu v tomto prípade nie je vôbec použitá, lebo na kategorizáciu do dvoch kategórií používame signum (pre záporné hodnota/kategória -1 a pre kladné karegória 1), ktorá má, ako je známe, nulovú deriváciu všade okrem bodu 0, kde nemá deriváciu. Metóda najväčšieho spádu používa gradient a teda deriváciu pri upravovaní váh pri spätnej propagácii. Tu, v tomto príklade, by sme si nepomohli s metódou najväčšieho spádu a používame špeciálny algoritmus, ktorý je nakoniec aj základom Block-Novikoffho tvrdenia o konečnosti tohto algoritmu, ktorý nakoniec nájde priamku (vo vyšších dimenziách nadrovinu), ktorá oddelí lineárne separovateľnú množinu bodov patriacich do dvoch kategórií od seba. A toto je už silné matematické tvrdenie, ktoré sme tiež chceli čitateľovi priblížiť a aj grafickým výstupom programu dole pod kódom po spustení skriptu ilustrovať. Keď to teda zhrnieme, išlo nám o to, aby čitateľ pochopil, že takto postavená neurónová sieť len s input a output vrstvou a aktivačnou funkciou signum hľadá vlastne priamku, resp. nadrovinu (a s matematickou istotou ju nájde), ktorá oddelí lineárne separovateľnú množinu bodov z dvoch kategórií na dve disjunktné oblasti, kde sú v každej z oblastí len body z tej istej kategórie. Pre náročnejších pridávame ešte aj dôkaz Block-Novikoffej vety a ďalšie podrobnosti k programu a algoritmu v angličtine. Treba tiež zdôrazniť, že takto jednoduchá sieť nájde s istotou jednu z priamok, ktorá dané tréningové (lineáne separovateľné) dáta lineárne separuje. Váhy sú v tejto neurónovej sieti optimalizované tak, aby inputy čo najlepšie „trafili“ na outputy (v algoritme zmenšujeme chybu vyjadrenú ako rozdiel zadaných outputov od vypočítaných outputov na základe doprednej propagácie a starých váh, ktoré následne zmeníme na nové váhy s nádejou, že chyba sa zmenší!!!). Táto neurónová sieť sa teda vôbec nestará o to, či nájde maximálnu hranicu medzi týmito lineárne separovateľnými tréningovými dátami/vzorkami. Hľadanie maximálnej hranice (znázornená dole na obrázku) medzi lineárne separovateľnými tréningovými dátami a dokonca aj lineárne neseparovateľnými dátami rieši známy SVM (Support Vector Machine) v slovenčine známy aj ako Metóda podporných vektorov.

support vector machine

Záver

Mnohí si možno po prečítaní tohto príspevku položia otázku: „Ale aj keď to nie je až také zložité, ako sa povráva, k čomu je toto všetko dobré?“. Tak pre takých je odpoveď jednoduchá. Ak máte emailovú adresu od vášho providera, tak ten sa pri prijímaní emailov možno práve riadi takouto kategorizáciou. Jedna kategória je email nie je spam a druhá je email je spam. Úlohou je nastaviť taký centrálny filter na všetky emaily, ktorý nepustí spamy do vašej schránky. A takýchto príkladov čiernobielej kategorizácie je v praxi strašne veľa. Určite by sa vám nechcelo triediť milióny emailov denne tak, aby ste vášmu klientovi nedoručili spam.

Pre zapamätanie

Perceptrón ako najjednoduchšia neurónová sieť dokáže vyriešiť také jednoduché kategorizačné úlohy, ako napr. oddeliť dobro od zla (samozrejme za predpokladu lineárnej separovateľnosti), inak povedané rieši len dichotómiu, teda, keď máme problém postavený na áno/nie (je email spam alebo nie). Keby sme mali prečítať rukou písané cifry od 0 po 9 napísané na obrázkoch, tak tu je už perceptrón krátky. Potrebovali by sme postaviť zložitejšiu neurónovú sieť, nakoľko tu sa jedná už o kategorizačnú úlohu s 10 kategóriami a nie len s dvomi.

Poznámky

Logické operácie AND, OR a IMPLIKÁCIA (neplatí len v prípade [1,0], teda, keď je predpoklad pravdivý a tvrdenie nepravdivé; v prípadoch [0,0], [0,1] a [1,1] platí) sú lineárne separovateľné. XOR a EKVIVALENCIA (platí, keď sú oba výroky pravdivé alebo oba nepravdivé; neplatí, ak nemajú výroky rovnakú pravdivostnú hodnotu, teda v prípadoch [0,1] a [1,0]) nie sú lineárne separovateľné.

Čítajte aj Operácia XOR a ekvivalencia, Ako si vytvoriť prvý model neurónovej siete

Zdrojový kód programu v jazyku python 3.x:

1
2
#!/opt/python/Python-3.6.5/python
# -*- coding: utf-8 -*-
import cgi
import cgitb
import sys
import os
cgitb.enable()

print ("Content-Type: text/html; Charset='UTF-8'")
print ("")#use this double quote print statement to add a blank line in the script

# we need the above commands for python cgi script, you can change it if you have different python version/location

import time
import numpy as np
from numpy import exp, array, random, dot
import matplotlib.pyplot as plt
import pylab
from pylab import figure, axes, title

# Create instance of FieldStorage 
data = cgi.FieldStorage() 

# Get data from fields
runnn = data.getvalue('runnn')
filename = data.getvalue('filename')

# modify these 2 lines if needed
mydir = '/var/www/masternn/masternn/collect_static/blog/test/'
myfile="/var/www/masternn/masternn/collect_static/blog/test/" + filename

now = time.time()
old = now - 7200

for f in os.listdir(mydir):
    path = os.path.join(mydir, f)
    if os.path.isfile(path):
        stat = os.stat(path)
        if stat.st_ctime < old:
            os.remove(path) 

sys.stderr = sys.stdout

class NeuralNetwork():
    def __init__(self):
        # Seed the random number generator, so it generates the same numbers
        # every time the program runs.
        #random.seed(1)

        # We model a single neuron, with 3 input connections and 1 output connection.
        # We assign random weights to a 3 x 1 matrix, with values in the range -1 to 1
        # and mean 0.
        self.synaptic_weights = 2 * random.random((3, 1)) - 1  

    # We train the neural network through a process of trial and error.
    def train(self, training_set_inputs, training_set_outputs, number_of_train_examples, number_of_training_iterations):
        self.t = 1        
        self.k = 1
        while self.t == 1 and self.k < number_of_training_iterations:
            # Pass the training set through our neural network (a single neuron).
            output = self.think(training_set_inputs)

            # Calculate the error (The difference between the desired output
            # and the predicted output).
            error = training_set_outputs - output
            self.t = 0
            for i in range(number_of_train_examples):
                z= i+1
                print ('<br>')
                print ('<b style="color: #d4566b;">Prva podmienka zotrvania v cykle je t = 1, aktualne t je: <font size="5" color="#3300cc">', self.t)
                print ('</font></b><br>')
                print ('<b style="color: #d4566b;">Druha podmienka zotrvania v cykle je pocet iteracnych krokov k, aktualny krok k je: <font size="5" color="#3300cc">', self.k)                
                print ('</font></b><br>') 
                print('Chyba pri vzorke: ' + str(z) + ' je ' + str(int(error[i][0])))
                print ('<br>')
                print('vaha 1 je ',self.synaptic_weights[0][0])
                print ('<br>')
                print('vaha 2 je ',self.synaptic_weights[1][0])
                print ('<br>')
                print('bias b je ',self.synaptic_weights[2][0])
                print ('<br>')
                if error[i][0] != 0:
                    self.t = 1
                    # Adjust the weights.
                    print ('uprava vah 1 - stare vahy: ', self.synaptic_weights[0][0])
                    print ('<br>')
                    self.synaptic_weights[0][0] = self.synaptic_weights[0][0] + training_set_outputs[i][0]*training_set_inputs[i][0]
                    print ('uprava vah 1 - nove vahy: ', self.synaptic_weights[0][0])
                    print ('<br>')
                    print ('uprava vah 2 - stare vahy: ', self.synaptic_weights[1][0])
                    print ('<br>')
                    self.synaptic_weights[1][0] = self.synaptic_weights[1][0] + training_set_outputs[i][0]*training_set_inputs[i][1]
                    print ('uprava vah 2 - nove vahy: ', self.synaptic_weights[1][0])
                    print ('<br>')
                    print ('uprava bias b ako vahy 3, stare vahy: ', self.synaptic_weights[2][0])
                    print ('<br>')
                    self.synaptic_weights[2][0] = self.synaptic_weights[2][0] + training_set_outputs[i][0]
                    print ('uprava bias b ako vahy 3 - nove vahy: ', self.synaptic_weights[2][0])
                    print ('<br>')

            self.k = self.k+1 
            #time.sleep(1)                       


    # The neural network thinks.
    def think(self, inputs):
        # Pass inputs through our neural network (our single neuron).
        return np.sign(dot(inputs, self.synaptic_weights))

if __name__ == "__main__" and runnn=="1":

    #Intialise a single neuron neural network.
    neural_network = NeuralNetwork()
    print ("Jednotlive medzivysledky sa mozu lisit, pretoze generujeme zakazdym ine vahy!")
    print ("<br>")
    print ("Nahodne zvolene vahy: ")
    print("<br>")
    print (neural_network.synaptic_weights)
    print("<br>")
    print("Vahy maju 3 suradnice, ktore nizsie oznacujeme ako vahy 1, 2 a 3, 3 je pre bias")
    print("<br>")    
    print ('<b>Upravujeme len vahy pri vzorkach, kde je nenulova chyba!')
    print("</b><br>")
    # The training set. We have 4 examples, each consisting of 3 input values
    # and 1 output value.
    training_set_inputs = array([[1, 0, 1], [0, 1, 1], [0, 0, 1], [1, 1, 1]])
    training_set_outputs = array([[-1, -1, -1, 1]]).T      

    number_of_train_examples = len(training_set_outputs)
    print ('<b style="color: #d4566b;"> Pocet vzoriek na trening je: <font size="5" color="#3300cc">', number_of_train_examples)
    print("</font></b><br>")
    number_of_training_iterations = 10000
    print ('<b style="color: #d4566b;"> Maximalny pocet krokov je nastaveny na: <font size="5" color="#3300cc">', number_of_training_iterations)
    print("</font></b><br>")
    # Train the neural network using a training set.
    neural_network.train(training_set_inputs, training_set_outputs, number_of_train_examples, number_of_training_iterations)
    print("<br>")
    print ("<b>Optimalne vahy po treningu: ")
    print("<br>")
    print (neural_network.synaptic_weights)
    print("</b><br>") 
    print("<br>") 
    # Test the neural network with a new situation.
    guess_point = [5, 4, 1]
    guess_point_1 = '[5, 4]'
    guess_point1 = [-3, 2, 1]
    guess_point1_1 = '[-3, 2]'
    print ('<b style="color: #d4566b;">Do ktorej z kategorii -1 a 1 patri bod' + guess_point_1 + '-> <font size="5" color="#3300cc">')
    print (int(neural_network.think(array(guess_point))[0]))
    print("<br>") 
    print ('<b style="color: #d4566b;">Do ktorej z kategorii -1 a 1 patri bod' + guess_point1_1 + '-> <font size="5" color="#3300cc">')
    print (int(neural_network.think(array(guess_point1))[0]))
    print("</font></b><br>")
    print("<br>") 
    print ('<b style="color: #d4566b;">celkovy pocet krokov je <font size="5" color="#3300cc">', neural_network.k-1)
    print("</font></b><br>")
    print ('<b style="color: #d4566b;">t je momentalne: <font size="5" color="#3300cc">', neural_network.t)    
    print("</font></b><br>")
    if neural_network.k >= number_of_training_iterations and neural_network.t == 1:
        print ('<b style="color: #d4566b;">Z cyklu v priebehu treningu sme vyskocili, pretoze sa dosiahol maximalny pocet krokov/iteracii, hoci su chyby stale nenulove! <font size="5" color="#3300cc">')    
        print("</font></b><br>")   
    if neural_network.k >= number_of_training_iterations and neural_network.t == 0:
        print ('<b style="color: #d4566b;">Z cyklu v priebehu treningu sme vyskocili, pretoze sa dosiahol maximalny pocet krokov/iteracii, ale zaroven su vsetky chyby nulove! <font size="5" color="#3300cc">')    
        print("</font></b><br>")
    if neural_network.k < number_of_training_iterations and neural_network.t == 0:
        print ('<b style="color: #d4566b;">Z cyklu v priebehu treningu sme vyskocili, pretoze t = 0 a vsetky chyby su nulove! <font size="5" color="#3300cc">')    
        print("</font></b><br>") 
    print("<br>")
    print ('<b style="color: #d4566b;">Deliaca priamka zobrazena na obrazku dole je dana rovnicou a*x + b*y + c = 0, kde a, b a c su optimalne vahy presne v poradi ako su zobrazene, cize a je prva vaha, b druha a c je bias.')    
    print("<br>")
    print ('Z vyssie uvedeneho je zrejme, ze ciste teoreticky moze program vyratat zakazdym ine vahy a teda tym sa urci aj ina priamka.')
    print("<br>")
    print ('Ale aj tato ina priamka bude oddelovat modre a cervene body a naviac bude zobrazovat zname a dane inputy co najlepsie (vo vseobecnosti nie vzdy presne) na zname a dane outputy.')          
    print("</b><br>")
    fig = plt.figure()
    ax = fig.add_subplot(111)
    A = 0, 1, 0, 1, 5, -3
    B = 0, 0, 1, 1, 4, 2
    plt.grid()
    plt.plot(0, 0, 'bo')    
    plt.plot(1, 0, 'bo')
    plt.plot(0, 1, 'bo')
    plt.plot(1, 1, 'ro')
    if int(neural_network.think(array(guess_point))[0]) == 1:
        plt.plot(5, 4, 'ro')
    else:
        plt.plot(5, 4, 'bo')    
    if int(neural_network.think(array(guess_point1))[0]) == 1:
        plt.plot(-3, 2, 'ro')
    else:
        plt.plot(-3, 2, 'bo')   
    title('AND', bbox={'facecolor': '0.8', 'pad': 5}) 
    for xy in zip(A, B):                                       
        ax.annotate('(%s, %s)' % xy, xy=xy, textcoords='data')  
    x = np.linspace(-6, 6, 100)
    ax.plot(x, (-neural_network.synaptic_weights[0][0]*x - neural_network.synaptic_weights[2][0])/neural_network.synaptic_weights[1][0], 'g', label='priamka separuje body' ) 
    plt.legend(loc='upper center')
    # change this line if needed
    pylab.savefig('/var/www/masternn/masternn/collect_static/blog/test/' + filename)     
else:
    print ("You have no access to results !!!")         

Autor príspevku: F. Gachulinec Dr., PhD.

Tágy:
umelá inteligencia xor lineárna separovateľnosť and or implikácia ekvivalencia Python neurónová sieť