Kategória: Neurális hálózatok

  • Bevezetés a neurális hálózatok világába 5. rész

    Bevezetés a neurális hálózatok világába 5. rész

    A korábbi részekben felépítettünk egy réteget, amely kiszámolja a neuronok nyers kimenetét – vagyis a súlyozott bemenetek és a bias összegét. Ez a kimenet azonban lineáris: ha a bemenetet kétszerezzük, a kimenet is kétszeresére nő. Egy ilyen hálózat csupán egyenes vonalakat, azaz lineáris összefüggéseket képes megtanulni.

    Csakhogy a világ nem lineáris. A képfelismerés, a beszédértés vagy a nyelvi feldolgozás mind olyan problémák, ahol bonyolult mintázatok jelennek meg. Ezeket egy tisztán lineáris modell sosem tudná megtanulni. Itt jönnek képbe az aktivációs függvények. Ezek azok a nemlineáris átalakítások, amelyek révén a neurális hálózat már nemcsak számol, hanem érzékelni és tanulni is képes mintázatokat.

    Mi az aktivációs függvény szerepe?

    Egy neuron kimenete aktiváció nélkül így néz ki:

    output = sum (inputs * weights) + bias

    Ha aktivációt is alkalmazunk:

    output = f(sum (inputs * weights) + bias)

    ahol f() az aktivációs függvény. Ez az apró, de kulcsfontosságú lépés adja meg a hálózat „intelligenciáját”.

    Leggyakrabban használt aktivációs függvények

    Step függvény

    A legelső és legegyszerűbb aktivációs függvény a Step. A működése pofonegyszerű: ha a neuron bemenete egy adott küszöb felett van, a kimenet 1, ha alatta, akkor 0.

    f(x) = \begin{cases} 1 & \text{ } x \geq 0 \\ 0 & \text{ } x < 0 \end{cases}

    Grafikusan ábrázolva:

    Ez a függvény jól szemlélteti, hogyan „kapcsol be” egy neuron, de tanításra nem alkalmas, mivel nem folytonos, és a gradiens-alapú tanulás (mint a backpropagation) itt nem működik. A Step függvény inkább tanítási célokra, demonstrációként hasznos – pont, ahogy mi is használtuk korábban.

    Sigmoid függvény

    A Sigmoid függvény már sokkal finomabb. Minden bemenetet 0 és 1 közé szorít, egy elegáns, S alakú görbe mentén:

    f(x) = \frac {1} {(1 + e^{-x})}

    Grafikusan ábrázolva:

    Ez azt jelenti, hogy a nagyon negatív értékek közel 0-t, a nagyon pozitívak közel 1-et adnak, a köztes tartományban pedig szépen fokozatosan nő az aktiváció. Emiatt jól használható ott, ahol valószínűséget akarunk becsülni – például bináris osztályozásban. Ugyanakkor a Sigmoid egyik gyenge pontja, hogy a szélső tartományokban a gradiens majdnem eltűnik, ezért a tanulás lelassulhat. Ezt hívjuk vanishing gradient problémának.

    Tanh függvény

    A tanh függvény, vagyis a hiperbolikus tangens, nagyon hasonlít a Sigmoidra, de a kimenetét -1 és 1 közé skálázza:

    f(x) = tanh(x)

    Grafikusan ábrázolva:

    Ez a kis különbség sokat számít, mert a kimenet így a nulla körül ingadozik, ami gyakran gyorsabb és stabilabb tanulást eredményez. Ugyanakkor a Tanh sem tökéletes – a szélső értékeknél ugyanúgy el tud tűnni a gradiens. Mégis, sok régi és kisebb hálózatban ma is népszerű, mert intuitívan jól viselkedik és gyakran ad jobb eredményt, mint a Sigmoid.

    ReLU (Rectified Linear Unit)

    A ReLU talán a legismertebb és leggyakrabban használt aktivációs függvény a modern neurális hálózatokban. A működése nagyon egyszerű:

    f(x) = max(0, x)

    Grafikusan ábrázolva:

    Ha a bemenet negatív, a kimenet 0. Ha pozitív, akkor az érték változatlanul továbbmegy. Ez a minimalizmus az ereje: gyors, egyszerű és segít elkerülni a Sigmoid-féle gradiensproblémákat. Ugyanakkor előfordulhat, hogy egy neuron negatív bemenetek miatt teljesen „meghal”, vagyis a továbbiakban sosem aktiválódik – ezt hívjuk dead neuron problémának. Mindezek ellenére a ReLU a legtöbb modern neurális hálózat alapértelmezett választása.

    Leaky ReLU

    A Leaky ReLU a ReLU továbbfejlesztett változata. A különbség csupán annyi, hogy a negatív tartományban sem nulláz le teljesen, hanem egy kis arányban tovább engedi az értéket:

    f(x) = \begin{cases} x & \text{ } x \geq 0 \\ 0.01*x & \text{ }x < 0 \end{cases}

    Grafikusan ábrázolva:

    Ez a kis „szivárgás” életben tartja a neuront, még akkor is, ha sok negatív bemenetet kap. Emiatt stabilabb és kiegyensúlyozottabb lehet a tanulás. Gyakran használják ReLU helyett, ha a hálózatban túl sok neuron válik inaktívvá.

    Összegzés

    Az aktivációs függvények adják a hálózat nemlineáris erejét. Nélkülük a modell csupán egy lineáris összefüggést tudna leírni – ami nagyjából egy ferde sík vagy egyenes lenne. Az aktivációk azonban lehetővé teszik, hogy a neurális hálózat bonyolult, nemlineáris döntési határokat tanuljon meg, és ezáltal valóban intelligens viselkedést mutasson.

    A következő részben megmutatjuk, hogyan valósíthatók meg ezek Pythonban és NumPy-val, és hogyan változtatják meg egy réteg működését a gyakorlatban.

  • Bevezetés a neurális hálózatok világába 4. rész

    Bevezetés a neurális hálózatok világába 4. rész

    Miért használjunk NumPy-t?

    Az előző részekben tiszta Python kóddal építettünk mesterséges neuront és egy egyszerű réteget. Láttuk, hogy a logika nem bonyolult: súlyozott összeadás, eltolás hozzáadása, majd esetleg aktivációs függvény alkalmazása.  
    De ahogy nő a hálózat mérete – több réteg, több száz vagy ezer neuron –, a tiszta Python megoldás:  

    • lassú lesz,  
    • áttekinthetetlenné válik,  
    • és rengeteg hibalehetőséget hordoz.  

    Ezért használjuk a NumPy könyvtárat, amely:  

    • nagyon gyors (C nyelven írták),  
    • megbízható (alaposan tesztelt),  
    • és egyszerűvé teszi a vektorokkal, mátrixokkal végzett műveleteket.  

    Vektorok, tömbök, mátrixok és tenzorok

    Mielőtt konkrét példákon keresztül megnéznénk a NumPy használatát, fontos tisztázni néhány fogalmat.

    Kezdjük a legegyszerűbb Python adattárolóval, a listával. A Python lista vesszővel elválasztott számokat tartalmaz szögletes zárójelek között. A korábbi részekben a tiszta Python megoldásainkban listákat használtunk az adatok tárolására.

    Példa egy listára:

    list = [1, 5, 6, 2]

    Listák listája:

    list_of_lists = [[1, 5, 6, 2],
    				         [3, 2, 1, 3]]

    Listák listáinak listája:

    list_of_lists_of_lists = [[[1, 5, 6, 2],
                               [3, 2, 1, 3]],
                              [[5, 2, 1, 2],
                               [6, 4, 8, 4]],
                              [[2, 8, 5, 3],
                               [1, 1, 9, 4]]]

    A fenti példák mindegyike nevezhető tömbnek is. Azonban nem minden lista lehet tömb.

    Például:

     [[1, 2, 3],
       [4, 5],
       [6, 7, 8, 9]]

    Ez a lista nem lehet tömb, mert nem „homológ”. Egy „listák listája” akkor homológ, ha minden sor pontosan ugyanannyi adatot tartalmaz és ez igaz valamennyi dimenzióra. A fenti példa nem homológ, mert az első lista 3 elemből, a második 2-ből, a harmadik pedig 4-ből.

    A mátrix definíciója egyszerű: ez egy kétdimenziós tömb. Vannak sorai és oszlopai. Tehát egy mátrix lehet egy tömb. Vajon minden tömb lehet mátrix? Nem. Egy tömb sokkal több lehet, mint sorok és oszlopok. Lehet 3, 5 vagy akár 20 dimenziós is.

    Végül mi az a tenzor? A tenzorok és a tömbök pontos definíciójáról oldalak százain keresztül vitáznak szakemberek. Ezt a vitát nagyrészt az okozza, hogy a résztvevők teljesen eltérő területek felől közelítik meg a témát. Ha a mélytanulás és a neurális hálózatok felől akarjuk megközelíteni a tenzor fogalmát, akkor talán a legpontosabb leírás: „A tenzorobjektum olyan objektum, amely tömbként ábrázolható.”

    Összefoglalva: A lineáris, vagy 1-dimenziós tömb a legegyszerűbb tömb, Pythonban ennek a lista felel meg. A tömbök többdimenziós adatokat is tartalmazhatnak, ennek legismertebb példája a mátrix, amely egy 2-dimenziós tömb.

    Még egy fogalmat fontos tisztázni, ez a vektor. Egyszerűen fogalmazva a matematikában használt vektor megegyezik a Python listával, vagy az 1-dimenziós tömbbel.

    Két fontos művelet: dot product és vektorösszeadás

    A dot product művelet elvégzésekor két vektort szorzunk össze. Ezt úgy tesszük, hogy sorra vesszük a vektorok elemeit és az azonos indexű elemeket összeszorozzuk, majd ezeket a szorzatokat összeadjuk. Matematikailag leírva ez így néz ki:

    \vec{a}\cdot\vec{b} = \sum_{i=1}^n a_ib_i = a_1\cdot b_1+a_2\cdot b_2+...+a_n\cdot b_n

    Fontos, hogy mindkét vektornak azonos méretűnek kell lennie. Ha Python kódban akarjuk ugyanezt leírni, akkor az így nézne ki:

    # Első vektor
    a = [1, 2, 3]
    
    # Második vektor
    b = [2, 3, 4]
    
    # Dot product kiszámítása
    dot_product = a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
    
    print(dot_product)
    
    >>> 20

    Látható, hogy ugyanazt a műveletet végeztük el, mint mikor egy neuron kimeneti értékét számoltuk ki, csak itt nem adtuk hozzá az eltolást. Mivel a Python nyelv alapesetben nem tartalmaz sem utasítást, sem funkciót a dot product kiszámolására, ezért használjuk a NumPy könyvtárat.

    Vektorok összeadásánál az egyes vektorok azonos indexű elemeit adjuk össze. Matematikailag leírva ez így néz ki:

    \vec{a}+\vec{b} = [a_1+b_1, a_2+b_2,...,a_n+b_n]

    Itt is fontos, hogy a vektoroknak azonos méretűnek kell lennie. Az eredmény egy ugyanilyen méretű vektor lesz. A NumPy ezt a műveletet is könnyen kezeli.

    NumPy használata

    Egy neuron

    Most kódoljuk le a korábban látott neuront NumPy használatával.

    import numpy as np
    
    # Bemenetek és súlyok
    inputs = np.array([0.5, 0.8, 0.3, 0.1])
    
    weights = np.array([0.2, 0.7, -0.5, 0.9])
    
    bias = 0.5
    
    # Neuron kimenete (dot product + bias)
    output = np.dot(inputs, weights) + bias
    
    print("Neuron kimenete:", output)

    Itt a np.dot(inputs, weights) kiszámolja a skaláris szorzatot, majd egyszerűen hozzáadjuk a bias-t.  

    Egy réteg

    Most építsünk egy teljes réteget 3 neuronból, amelynek mindegyik neuron 4 bemenetet kap.  

    import numpy as np
    
    # Példa bemenet (4 elem)
    inputs = np.array([1.0, 2.0, 3.0, 2.5])
    
    # 3 neuron súlyai (mátrix: 3 sor, 4 oszlop)
    weights = np.array([
                    [0.2, 0.8, -0.5, 1.0],       # Neuron 1
                    [0.5, -0.91, 0.26, -0.5],    # Neuron 2
                    [-0.26, -0.27, 0.17, 0.87]   # Neuron 3
    ])
    
    # Bias értékek (3 elem)
    biases = np.array([2.0, 3.0, 0.5])
    
    # Réteg kimenete (mátrix-szorzás + vektor összeadás)
    output = np.dot(weights, inputs) + biases
    
    print("Réteg kimenete:", output)
    
    >>> Réteg kimenete: [4.8   1.21  2.385]

    Itt a np.dot(weights, inputs) a mátrix (weights) és a vektor (inputs) szorzatát adja vissza, ami pont a neuronok súlyozott összege. Ehhez adjuk hozzá egyszerűen a bias vektort.  

    Következő rész

    A következő cikkben megismerkedünk az aktivációs függvényekkel, és megnézzük, hogyan adnak „nemlineáris erőt” a hálózatnak. Ezek nélkül a hálózatunk csak egyszerűen lineáris összefüggéseket tudna modellezni.  

  • Bevezetés a neurális hálózatok világába 3. rész

    Bevezetés a neurális hálózatok világába 3. rész

    Az előző részben láttuk, hogyan működik egyetlen mesterséges neuron. De egy neuron önmagában még nem túl sok dologra jó. Az igazi használhatósága akkor mutatkozik meg, amikor több neuront összekapcsolunk egy réteggé. Ebben a részben ezt fogjuk egy kicsit részletesebben megnézni.

    Mi az a réteg?

    Egyszerűen fogalmazva a réteg nem más, mint egy rakás neuron, melyek ugyanazokkal a bemenő adatokkal dolgoznak, azonban mindegyik neuron más-más súllyal és eltolással dolgozza fel ezeket az adatokat. Ezek az adatok származhatnak közvetlenül a bemenetről, vagy egy előző rétegtől. A különböző súlyoknak és eltolásoknak köszönhetően az egyes neuronok más-más mintát tudnak felismerni ugyanabban az adatban.

    Például ha egy képet elemzünk neurális hálózattal, akkor egyes neuronok a függőleges, mások a vizszintes, megint mások a ferde vonalakat ismerhetik fel. Ezek megfelelő kombinálásával lehetségessé válik bonyolultabb alakzatok felismerése. Így működik például a Facebook azon funkciója, ami arcokat ismer fel fényképeken.

    Nézzünk egy példát

    A szemléltetés érdekében építsünk fel egy egyszerű réteget, amelynek:

    • 4 bemenete van: x1, x2, x3, x4
    • 3 neuron tartalmaz

    Minden egyes neuron négy súlyt (minden bemenethez egyet-egyet) és egy eltolást használ, ezekből számolja ki a saját kimeneti értékét.

    z_j= w_{j1} \cdot x_1 + w_{j2} \cdot x_2 + w_{j3} \cdot x_3 + w_{j4} \cdot x_4 + b_j

    Ebben a képletben a j az egyes neuronokra vonatkozik (1, 2, 3). A számolások elvégzése után a réteg kimenete egy három elemű vektor lesz: [z1, z2, z3]. Ez lehet akár egy következő réteg bemenete, vagy egy végleges eredmény, amelyet már nem dolgozunk fel tovább.

    Python példa: egy réteg kimenetének számítása

    Nézzük meg, hogyan tudjuk Python nyelven leprogramozni a fenti példát.

    Fontos: ebben a példában nem használunk aktivációs függvényt, csak a „nyers” kimeneti adatokat számoljuk ki.

    # Egy réteg 3 neuronból, 4 bemenettel
    
    inputs = [1, 2, 3, 2.5]
    weights = [[0.2, 0.8, -0.5, 1.0],
               [0.5, -0.91, 0.26, -0.5],
               [-0.26, -0.27, 0.17, 0.87]]
    biases = [2, 3, 0.5]
    
    # A réteg kimenete
    layer_outputs = []
    
    # Minden egyes neuron kimenetének kiszámítása
    for neuron_weight, neuron_bias in zip(weights, biases):
        # Súlyozott összeg kiszámítása
        neuron_output = 0
        for n_input, weight in zip(inputs, neuron_weight):
            neuron_output += n_input * weight
        # Eltolás hozzáadása
        neuron_output += neuron_bias
        # A neuron kimenetének hozzáadása a réteg kimenetéhez
        layer_outputs.append(neuron_output)
    
    print("A réteg kimenete:",layer_outputs)
    
    >>>
    A réteg kimenete:[4.8, 1.21, 2.385]

    Miért jó ez?

    Egy több neuronból álló réteg képes egyszerre többféle mintát felismerni az adatokban. Ez az első lépés afelé, hogy mélyebb hálókat építsünk, ahol több réteget egymásra helyezve egyre összetettebb problémákat tudunk megoldani.

    Következő rész

    A következő cikkben megnézzük, hogy a tiszta Python megoldások helyett miért érdemes a NumPy könyvtárat használni. Egy réteget vagy akár egy teljes hálózatot sokkal gyorsabban és elegánsabban ki tudunk számolni vele, különösen akkor, ha a hálózat nagyobb és több rétegből áll.

  • Bevezetés a neurális hálózatok világába 2. rész

    Bevezetés a neurális hálózatok világába 2. rész

    Az előző részben megismerkedtünk a neurális hálózatok alapgondolatával, és láttuk, hogy a mesterséges neuron az agy idegsejtjeinek egyszerűsített másolata. Most nézzük meg kicsit részletesebben, hogyan működik egy biológiai neuron, és hogyan modellezzük ezt a számítógépben.

    Hogyan működik egy neuron?

    Agyi neuron

    Kerülve a tudományos alaposságot, az agyi neuron négy fő részből épül fel:

    • Dendritek: ezeken keresztül kapja a többi neurontól az információkat.
    • Sejtmag (Soma): ez dolgozza fel a dendritek által kapott jeleket.
    • Axon: a neuron ezen keresztül küldi (vagy nem küldi) tovább a feldolgozott jelet.
    • Axon végződések: az axon végének leágazásai, melyeken keresztül a többi neuron érzékeli a kimeneti jelet.

    Tehát a sejtmag a dendriteken keresztül veszi a többi neurontól érkező jeleket, azokat feldolgozza és ha a feldolgozás eredményeként előálló jel elér egy bizonyos szintet, akkor a neuron „tüzel”, azaz az axonon keresztül jelet küld a többi, vele kapcsolatban lévő neuron felé.

    Mesterséges neuron

    A mesterséges neuron ezt a működést próbálja utánozni matematikai úton. A legfontosabb részei:

    • Bemenetek: ezek szimulálják a dendriteket, ezeken keresztül kapja a neuron az adatokat.
    • Súlyok: minden bemenethez tartozik egy-egy súly, amely megmutatja, hogy az adott bemeneten érkező adat mekkora mértékben befolyásolja a kimeneti értéket.
    • Eltolás (bias): a bemeneti jelek súlyozott összegéhez hozzáadódik egy eltolás, amellyel szintén befolyásolható a kimeneti eredmény.
    • Aktivációs függvény: ez hozza meg a döntést, hogy a korábban összegzett adatok alapján milyen érték kerüljön a neuron kimenetére („tüzel” vagy nem).

    Matematikai formában

    Legyenek a bemenetek rendre x1, x2…xn, a hozzájuk tartozó súlyok w1, w2…wn, az eltolás pedig b. A neuron által elvégzett művelet a következő:

    z=w_1 \cdot x_1 + w_2 \cdot x_2 + \ldots + w_n \cdot x_n + b

    Az így kiszámolt z értéket kapja meg az aktivációs függvény. Jelen esetben vegyünk egy egyszerű step függvényt, ami megvizsgálja a beadott értéket és ha az nulla vagy annál nagyobb, akkor 1-et ad kimenetként, ha nullánál kisebb akkor pedig 0-t.

    y=\begin{cases} 1 & \text{ha } z \geq 0 \\ 0 & \text{ha } z < 0 \end{cases}

    Az aktivációs függvényeknek sok típusa van (például Sigmoid, ReLU, tanh), ezekről később egy külön részben fogunk beszélni.

    Python példa

    Nézzük meg, hogyan is programozzunk egy neuront Python nyelven:

    # Bemenetek és súlyok
    inputs = [0.5, 0.8]     # két bemenet
    weights = [0.4, 0.7]    # hozzájuk tartozó súlyok
    bias = -0.5             # eltolás
    
    # Bemeneti értékek összegzése
    sum = (inputs[0]*weights[0] + inputs[1]*weights[1] + bias) # 0.26
    
    # Kimenet számítása a step függvénnyel
    output = 1 if sum >= 0 else 0
    
    print("A neuron kimenete:", output) # 1

    Következő rész

    A következő részben több neuront is összekapcsolunk, és megnézzük, hogyan épül fel egy egyszerű réteg. Ezzel már közelebb kerülünk egy teljes neurális hálóhoz.

  • Bevezetés a neurális hálózatok világába 1. rész

    Bevezetés a neurális hálózatok világába 1. rész

    Az utóbbi években a mesterséges intelligencia szinte mindenhol ott van: felismeri az arcunkat a telefon kamerájában, lefordítja a szövegeket idegen nyelvekről, sőt, képes képeket rajzolni és történeteket írni. Ezeknek a látványos fejlesztéseknek az egyik motorja a neurális hálózat.

    De mi az a neurális hálózat valójában? És mi köze van az agyunkhoz?

    Egy kis analógia

    Képzeljünk el egy apró döntéshozót: egy „neuron” egyetlen bemenetet figyel (pl. hőmérséklet), és egy egyszerű szabály alapján eldönti, hogy „igen” vagy „nem” választ adjon. Ha sok ilyen apró döntéshozót összekapcsolunk, akkor képesek lesznek együtt bonyolultabb problémákat megoldani. Ez az alapgondolat áll a mesterséges neurális hálózatok mögött.

    Mi az a mesterséges neuron?

    A mesterséges neuron az emberi agy idegsejtjeinek (neuronjainak) leegyszerűsített matematikai modellje.

    A biológiai neuron bemeneteket kap más sejtektől, feldolgozza az információt, majd eldönti, továbbadja-e a jelet.

    Hasonlóképp, egy mesterséges neuron is:

    • több bemeneti értéket kap,  
    • ezeket megszorozza súlyokkal, amelyek meghatározzák, mennyire fontos az adott bemenet,  
    • hozzáad egy bias értéket, ami egyfajta „alapbeállítás” vagy eltolás,  
    • majd az eredményt egy aktivációs függvény segítségével átalakítja, és tovább küldi a következő neuronoknak.  

    Röviden: a súlyok mondják meg, hogy mit mennyire vegyen komolyan a neuron, a bias pedig segít finomhangolni a döntést.

    Miért fontosak a neurális hálózatok?

    • Képfelismerés: amikor a Facebook automatikusan megjelöli, ki van a képen.  
    • Fordítás: amikor a Google Translate egész mondatokat fordít le, nem csak szavakat.  
    • Beszélgető robotok (chatbotok): amikor egy weboldalon megjelenik egy virtuális ügyfélszolgálati asszisztens.

    Mindezt azért tudják, mert a neurális hálózatok rendkívül jók abban, hogy mintákat ismerjenek fel adatokban.

    Miről lesz szó a sorozatban?

    Ebben a cikksorozatban lépésről lépésre megpróbálom bemutatni:

    1. Mi az a mesterséges neuron, és hogyan írhatjuk le Pythonban.  
    2. Hogyan épül fel egy egyszerű hálózat több neuronból.  
    3. Mit jelent a veszteségfüggvény, és miért fontos.  
    4. Hogyan tanul egy hálózat? (backpropagation)  
    5. Hogyan használjuk őket valós problémákra.  

    A cikkekben törekszem a közérthető fogalmazásra, megpróbálom kerülni a nagyon száraz, technikai szöveget és a bonyolult matematikai formulákat. A működés mögötti matematikáról csak annyit fogok írni, amennyi a megértéshez feltétlenül szükséges.

    A sorozatban az alapoktól kezdve le fogok programozni egy egyszerű neurális hálózatot annak minden funkciójával. Erre a Python nyelvet fogom használni.

    Felmerülhet a kérdés, hogy mi értelme van nulláról leprogramozni egy neurális hálózatot, amikor használatra kész keretrendszerek vannak, ahol pár soros programmal bármilyen hálózat összeállítható? Nos, szerintem azért, mert izgalmas egy kicsit benézni a „kulisszák mögé” és megérteni, hogyan működnek a dolgok. És mert jó mulatság!

    Következő rész

    A következő cikkben közelebbről is megnézzük, hogyan működik pontosan egy mesterséges neuron, és elkészítjük az első Python-kódot, ami a gyakorlatban is bemutatja ezt.

hu_HU