Szerző: MlatilikZsolt

  • 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.  

  • Gondolatok a „vibe-coding”-ról

    Gondolatok a „vibe-coding”-ról

    Az elmúlt időszak leginkább felkapott kifejezése a „vibe-coding”. Azt ígéri a felhasználóknak, hogy már nem is kell tudni programozni, mert elég az AI ügynöknek természetes nyelven, mintegy vele „beszélgetve” elmondani, hogy mit is szeretnénk, az ügynök pedig legyártja a kész, működőképes programot. Rövid Google keresés alapján százával találunk sikertörténeteket, amelyek arról szólnak, hogy emberek programozási tudás nélkül, pár óra alatt működőképes programot készítettek valamelyik kódoló ügynökkel és tarolnak a piacon. Az évek, vagy évtizedek óta a piacon dolgozó fejlesztők ugyanakkor gyanakodva figyelik ezt az új trendet, sokan a megélhetésüket féltik a kódoló ügynökök elterjedésétől.
    Én mindig úgy gondoltam, hogy egy dologról akkor érdemes véleményt mondani, ha megismerjük azt, még ha nem is megyünk bele a legapróbb részletekbe. Nem árt, ha az ember tudja, hogy miről beszél.

    Az első találkozás

    Jó néhány éve használtam már fejlesztésre a Visual Studio programot, a kódok tárolására pedig sokakhoz hasonlóan a GitHub platformot használtam. Amikor 2021-ben a GitHub elindította a Copilot szolgáltatását, úgy hirdették, mint egy programozó társat, aki megérti az általam megírt kódot és segít azt jobbá tenni. Valamikor 2022-ben döntöttem úgy, hogy adok neki egy lehetőséget és kipróbálom. Bár a 10 dolláros havidíj nem volt eget rengető összeg, de az igazi lökést a 30 napos ingyenes próbaidőszak adta meg. Úgy gondoltam, itt tényleg nincs mit veszíteni.
    Nos, ez a 30 nap elég gyorsan elszaladt és be kell vallanom, hogy a Copilot teljesen lenyűgözött. Először csak arra használtam, hogy mások által írt, általam nem teljesen átlátható kódok működését magyaráztattam el. Utána előszedtem olyan régi programjaimat, amikkel elakadtam, vagy amik rejtélyes hibákat produkáltak. És láss csodát, a kódok elemzése után olyan javaslatokat és új szempontokat tudott adni (konkrét kódrészletekkel), amikkel tovább tudtam lépni. De a legnagyobb „durranás” számomra az volt, amikor megtapasztaltam, hogy milyen jól át tudja venni az unalmas, vagy nem igazán szeretett feladatokat. Komplett teszteket ír annyi idő alatt, amennyi alatt én még azt sem igazán tudom átgondolni, hogy milyen eseteket is kéne tesztelni. Ráadásul olyan esetekre is ír tesztet, amikre én nem is gondoltam volna. Pár perc alatt legenerálja egy projekt gerincét, nekem már csak a „húst” kell rápakolni a gerincre. Ráadásul az automatikus kódkiegészítés is a kezem alá dolgozik. Csak elkezdek gépelni és adja a komplett javaslatokat a befejezésre. Ha tetszik, egy gombnyomás és már be is illeszti. Ha nem tetszik, csak gépelek tovább és kis idő múlva már egy módosított javaslatot kapok.
    Az egy hónapos használat alatt valahogy úgy éreztem, hogy újra élvezem a programozást. Mintha két segítőt kaptam volna magam mellé. Egy gépelő „rabszolgát”, aki megírja helyettem a hosszú, unalmas és örökké ismétlődő részeket és egy mentort, aki mindig tovább tud lendíteni, amikor elakadok valamiben. A próbaidőszak végén úgy döntöttem, hogy nem mondom le az előfizetést, mert ennyit nekem megér. Természetesen nem volt hibátlan a működése, többször is javasolt olyan kódot, ami magában nem működött, mert hiányzott például egy segédfüggvény, amit „elfelejtett” megírni. De 1-2 finomítás után mindig lett valamilyen eredmény.
    Természetesen a Copilot ezen verziója még nem volt a mai értelemben vett kódoló ügynök. Csak javaslatokat adott, de automatikusan nem végzett semmilyen módosítást a kódokon. Nem is hagytam volna, mert szeretem érteni azt, amit végül beépítek a programomba.

    Az ügynök színre lép

    A Copilot ügynök módja valamikor 2025 elején jelent meg. Ekkor jelent meg a Claude Code is, de voltak már a piacon más szereplők, például a Cursor, vagy a Replit. Az ígéret az volt, hogy ezek az ügynökök már teljesen önállóan, csupán a szöveges prompt alapján képesek komplett programot elkészíteni, melynek során nem csak megírják a kódot, hanem le is futtatják, a hibákat kijavítják és az egész folyamatot addig ismétlik, míg a végén egy hibátlanul működő programot kapunk. Elég szkeptikus voltam a dologgal kapcsolatban, mert bár egy ideje akkor már foglalkoztam az AI egyes területeivel, tanulmányoztam a nagy nyelvi modellek működését, de valahogy nem hittem az egészben. Persze a videó megosztók is gyorsan tele lettek olyan anyagokkal, amik azt bizonygatták, hogy ez a dolog működik, de bennem mindig maradt egy kis hiányérzet. Ugyanis minden ilyen videóban csak viszonylag egyszerű, 1-2 funkciót tartalmazó, amolyan hobbi programok elkészítését mutatták be. De mi a helyzet egy kicsit komolyabb alkalmazással? Ennek a bemutatására valamiért senki nem akart vállalkozni.
    Ahogy telt az idő, folyamatosan fejlesztették a Copilot ügynök módját. Folyton jöttek a hírek, hogy milyen új funkciókat kapott, milyen nyelvi modellek érhetők el stb. Bennem pedig egyre erősebben motoszkált az, hogy ki kéne próbálni. Úgy gondoltam, mostanra már biztosan kinőtte a gyermekbetegségeit, javították a kezdeti hibákat. Augusztus közepén aztán vettem egy nagy levegőt és belevágtam.

    A teszt

    Az elképzelésem az volt, hogy egy nem túl bonyolult fitness alkalmazás elkészítésével teszem próbára a Copilot Agent módját. Mivel itt többféle modell közül is lehet választani, én a Claude Sonnet 4 mellett döntöttem, mert a vélemények szerint ez az egyik legjobb modell a kódoláshoz.
    Azt már tudtam, hogy az ügynökök mögött különféle nagy nyelvi modellek dolgoznak, amik igénylik a minél részletesebb kontextust ahhoz, hogy pontos választ tudjanak adni. Ezért egy termékkövetelmény-dokumentummal (PRD) kezdtem. Ennek összeállításához már szintén igénybe vettem a Claude-ot. Egy 5-6 mondatból álló promptban leírtam az elképzelést és generáltattam vele egy PRD-t. 1-2 perc alatt megvolt az eredmény, amin azért még kellett finomítani és pontosítani. 3-4 iteráció után végül összeállt egy olyan anyag, amivel már el lehetett kezdeni a tesztelést. A dokumentum alapján a tervezett alkalmazás az alábbi funkciókra képes:

    • felhasználók kezelése (ki/bejelentkezés, saját adatok kezelése)
    • fizikai adatok (testsúly, has, comb, mellkas stb. méretek) rögzítése naponta
    • admin felhasználó által előre rögzített és a felhasználó által rögzített saját tornagyakorlatok kezelése
    • edzések összeállítása a gyakorlatokból, edzés adatok rögzítése (időtartam, elhasznált kalória stb.)
    • első körben egy Blazor webes frontend készüljön, de később legyen lehetőség mobil alkalmazás kezelésére is
    • az előző pont miatt API felület kialakítása JWT authentikációval
    • PostgreSQL adatbázis
    • .NET Core környezet, Entity Framework az adatbázis kezeléséhez

    Készítettem egy új mappát a projektnek, elindítottam a VS Code szerkesztőt, a PRD-t becsatoltam a Copilotnak és kértem, hogy ez alapján készítse el az alkalmazást. Aztán hátra dőltem és vártam. Némi gondolkodás után beindult a gépezet. A chatablakban folyamatosan látszott, ahogy újabb és újabb fájlok születnek, ezek funkció szerint külön projektekbe szerveződnek. Az adatmodellek és az API elkészülése után a Copilot megállt és kérte, hogy adjam meg az adatbázis szerver hozzáférési adatait. Mikor ezek megadtam, tovább folytatódott a munka. Láttam, ahogy a Copilot elkészíti az adatbázist, lefordítja és futtatja az elkészült programot, felismeri és kijavítja a felmerülő hibákat. Ha a futtató környezet szempontjából kritikusabb részhez ért (például fájlrendszer módosítás), akkor megállt és engedélyt kért a tovább lépéshez. Végül 15-20 perc alatt elkészült az API felület működő adatbázissal és adatmodellekkel. Eddig le a kalappal!
    Ekkor feltette a kérdést: „Folytassam a webes frontend felület elkészítésével?” Még szép, azért vagyunk itt!
    Újabb 5-10 perc alatt elkészült a Blazor frontend. Az látszott, hogy a projekt lefordítható és futtatható, de vajon működik is? A Copilot jelezte, hogy elkészült, próbáljam ki a programot. És itt kezdődött egy 3 órás szenvedés.
    Mivel az volt a célom, hogy leteszteljem az ügynök önálló működését, ezért úgy döntöttem, hogy nem fogok belejavítani a kódba, csak a prompton keresztül kommunikálom a tapasztalt problémákat.
    Szóval a program elindult és a böngészőben megjelent a felület. Eléggé minimál dizájn, de nem is ez volt a fókuszban, hanem a működés. Elindítok egy regisztrációt, megadom az adatokat, de az elküldés után nem történik semmi változás. Az adatbázist ellenőrizve nem jött létre új felhasználó. Leírom a problémát az ügynöknek, gondolkodik, majd közli, hogy megtalálta és kijavította a hibát. Újabb próba, ugyanaz az eredmény. Probléma leírása, gondolkodás, javítás. Újabb próba, nem működik. Mivel egy kicsit gyanús volt a dolog, megnéztem a backend API konzolján az üzeneteket. Az látszott, hogy elmegy egy kérés az API felé, de rögtön vissza is dobja 404-es hibával. Szóval sikerült olyan frontend kódot legyártani, ami az általa gyártott API egy nem létező végpontját próbálja hívni! Kicsit értetlenkedve megírom a problémát a Copilot-nak. Gondolkodás, majd jóváhagyólag nyugtázza, hogy tényleg igazam van. Javítja a hibát, most már tudok regisztrálni. A felület szerint be vagyok jelentkezve, bár a felhasználói nevem nem jelenik meg a menüben. OK, ezen most lépjünk túl. Megnyitom a testadatok lapját és megpróbálok adatot rögzíteni. Nem sikerül, mert az elküldés után csak „pörög” a betöltést jelző ikon, de nem történik semmi. Újraindítom az alkalmazást és az előzőekből tanulva már figyelem a konzol ablakok üzeneteit. Ezek alapján az előző bejelentkezésből származó token még megvan, így beléptet az alkalmazás. Újabb próba egy adat rögzítésére, elküldés után a konzolon 401-es hibaüzenet érkezik (Unauthorized). Érdekes. Probléma leírása a Copliot-nak, gondolkodás, ötletek feldobása, javítás, újra próbálás. Nem működik. Végül 3 óra küzdelem, 15-20 javítás után feladtam, mert belefáradtam.

    De miért nem működik?

    Nem hagyott nyugodni a dolog, ezért 3 nappal később újra elővettem a programot azzal a céllal, hogy kiderítem, miért nem működik. A backend viszonylag egyértelmű, szokásos .NET Core web API. Ami kicsit furcsa volt, hogy a felhasználók kezelésére nem a Microsoft Identity Framework-öt használta, de egyébként minden rendeben lévőnek tűnt. A frontend komponensek szintén elég egyértelműek, HTML elemek némi C# kóddal fűszerezve. Ami feltűnt, hogy szinte az összes komponens be van ágyazva egy AuthComponent nevű komponensbe. Ennek annyi a feladata, hogy ellenőrizze a felhasználó bejelentkezett állapotát és ennek megfelelően vagy az adott komponenst, vagy a bejelentkezési felületet jelenítse meg. Ehhez egy AuthService nevű komponenst használ, ami a böngésző helyi tárolójából megpróbálja kiolvasni a JWT tokent és ez alapján beállítani a státuszt, illetve kiolvasni a felhasználói nevet a tokenből. Erre ráadásul egy külön TokenService komponenst használ.
    Próbából elindítottam az alkalmazást és aktívvá tettem a frontend projekt konzolját. Megdöbbenve látom, hogy a felület megjelenítéséig legalább 10 alkalommal hívódik meg az AuthService azon metódusa, ami a felhasználó tokenjének ellenőrzését végzi. OK, erre majd alaposan rá kell nézni. Bejelentkezek a felületen, de a felhasználói név továbbra sem jelenik meg a menüben és adatot sem tudok rögzíteni, mert folyton 401-es hibát küld az API vissza. Kijelentkezni sem tudok. Hirtelen ötlet alapján elindítom a Postman-t és megpróbálom onnan elérni az API-t. A bejelentkezés működik és a visszakapott token alapján tudok adatot is küldeni az adatbázisba, mert innen nem kapok 401-es hibát. Érdekes.
    A frontend kód alapos átnézésekor bizony elkerekedett a szemem. Az AuthComponent nevű komponensnek az lett volna a dolga, hogy az AuthService használatával ellenőrizze a felhasználó bejelentkezett állapotát és ennek megfelelően jelenítse meg a beágyazott komponenseket. Viszont a beágyazott komponensekbe külön is be volt injektálva az AuthService és mindegyik külön is ellenőrizte a bejelentkezett állapotot. Miért? Ráadásul, mivel ezek a komponensek induláskor egyszerre próbálták ellenőrizni a felhasználó állapotát, ezért az AuthService mindenféle trükkös lock megoldásokkal próbálta megakadályozni, hogy a komponensek egymással versenyezve állítgassák az állapotot. Pedig sokkal egyszerűbb lett volna, ha az AuthComponent egy paraméteren keresztül tette volna elérhetővé a felhasználó állapotát. Némi munkával „kigyomláltam” ezeket az anomáliákat, egyszerűsítettem pár helyen a kódot és újra elindítottam az alkalmazást. Örömmel láttam, hogy az AuthService már csak egyszer hívódik meg. Viszont az API továbbra is 401-es hibával küldi vissza a kapcsolódási próbálkozásokat.
    Itt bizony eltelt vagy 2 óra, mire eredményre jutottam. Beleástam magam a JWT tokenek működésébe, hogyan kezeli a .NET Core a tokeneket, milyen adatot célszerű beletenni a tokenbe. Ezeket ki is próbáltam, eredmény nélkül. A végén már egyesével átmásoltam a Postman kérésekből a HTTP fejléceket a frontend kódba, de ez sem vezetett eredményre. Már éppen feladni készültem a dolgot, amikor az API projekt kódjában megakadt valamin a szemem. A CORS policy beállítások gyanúsak voltak, mert mintha nem szerepelt volna ott a frontend projekt futtatásakor használt URL. Leellenőriztem és tényleg! A frontend olyan URL-ről akart kapcsolódni, ami nem volt felvéve a CORS beállítások közé. Mikor ezt kijavítottam, elkezdett működni a dolog. Hogy ez a Copilot-nak hogyan nem tűnt fel, azt nem értem.
    Már csak az maradt megoldatlan, hogy miért nem működik a kijelentkezés. Annyit sikerült kiderítenem, hogy a gombra kattintáskor nem hívja meg a NavMenu komponensben az eseménykezelőt. Amíg ennek az okát kerestem, azt vettem észre, hogy van néhány sor kód, amit a komponensen belül jobb lenne áthelyezni abba az eseménykezelőbe, ami a komponens inicializálása után fut le. Miután ezt megcsináltam, azt vettem észre, hogy ezek a kódok sem futnak le. Tehát a komponens inicializálása elakad valahol. Újabb nyomozás és dokumentáció olvasás után kiderült, hogy a komponens RenderMode paramétere nem volt jól beállítva. Miután ezt javítottam, hirtelen minden a helyére került. A felhasználó neve megjelent a menüben és a kijelentkezés is működött.
    Ezután még találtam pár érdekes hibát. Például nem tudtam saját tornagyakorlatot rögzíteni az adatbázisban. Mint kiderült, a frontend felől küldött adat formátuma nem egyezett a backendben használt adatmodellel. Vagy például bizonyos frontend felületek csak félig készültek el. De ezekkel már nem foglalkoztam, mert úgy éreztem, hogy a kísérletből ennyi éppen elég volt.

    Végkövetkeztetés

    A kódoló ügynökök elég jó munkát tudnak végezni, ha egyszerű, jól körülírható feladatokról van szó. De attól még nagyon messze vannak, hogy akár csak egy közepesen bonyolult programot önállóan összerakjanak. A feladatok megoldásához fontos a részletes, jól definiált kontextus. A több részből álló, bonyolultabb programoknál viszont érdemes a feladatot kisebb egységekre bontani, azokat egyesével, több iterációban elkészíttetni, majd az így elkészült modulokat a kontextushoz hozzáadva továbblépni a következő részre.
    Fontosnak tartom azt is, hogy a folyamatba mindig be kell építeni az emberi ellenőrzést. Ugyanis az ügynökök mögött működő nagy nyelvi modelleket nyilvánosan elérhető kódbázisokon tanították, ezek viszont sok esetben tartalmaznak nem optimalizált, tesztelésre szánt, vagy biztonsági rést tartalmazó kódokat. Így az ügynökök által generált kód is nagy eséllyel fog biztonsági hibát vagy nem optimális kódot tartalmazni, ami egy éles felhasználásra kiadott programnál további problémák forrása lehet.
    Szóval véleményem szerint a kódoló ügynökök egy ideig még nem fogják elvenni a fejlesztők munkáját, azonban alaposan átalakítják azt. A junior fejlesztőknek nehezebb lesz belépni a szakmába, mert az általuk eddig végzett egyszerűbb, jellemzően jobban automatizálható kódolási feladatokat át tudják venni az ügynökök. A tapasztaltabb fejlesztők pedig többet fognak foglalkozni az ügynökök által generált kódok ellenőrzésével és javításával.

  • 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