Category: Uncategorized


Mivel mindig kitalálok valami forradalmi baromságot így hát miért ne elven… LINQ használata Neurális hálózatokban alaposan leegyszerűsíti a hálózat kezelését minden tekintetben persze ez mind tényleges programozás eredménye szóval felhasználás csak ÉSSZEL hiba van benne nem is egy de lusta vagyok én ezt javítani, majd ha élesben programozom meglesz a javított verzió.

Minden neuronnak van egy DoWork metódusa ami semmi mást nem csinál mintsem kiszámolja az adott neuron kimenetét szépen ahogy az a nagy könyvben meg vagyon írva. Indexelési hiba van benne házi feladat megtalálni marha egyszerű amúgy…

private void DoWork()
{
    var getInputs = select * from (ISynapseLayer)GetSynapseLayer[this.Layer-1] where (ISynapse)Connection.Target.Equals(this);
    foreach(Isynapse _synapse in getInputs)
    {
        this._output += _synapse.Weight * _synapse.Value;
    }
    this._output.Activate;
    var getOutputs = select * from (ISynapseLayer)GetSynapseLayer[this.Layer+1] where (ISynapse)Connection.Source.Equals(this);
    for(int i = 0; i < (ISynapse)getOutputs.Count; i++)
    {
        getOutputs.Value = this._output;
    }
}

public void Propagate()
{
    foreach(ILayer _layer in Network)
    {
        foreach(INeuron _neuron in _layer)
        {
            _neuron.DoWork();
        }
    }
}

private void BackPropagate(double delta)
{
    for(int i = this.Network.GetNumberOfNeuralLayers() – 1; i > 0; i–)
    {
        for(int j = 0; j < this.Network.NeuralLayers[i].GetNumberOfNeurons(); j++)
        {
            var Synapse = select * from (ISynapseLayer)GetSanypaseLayer[this.Layer-1] where (ISynapse)Connection.Target.Equals(this.Network.NeuralLayers[this.Network.GetNumberOfNeuralLayers() – 1].Neurons[GetNumberOfNeurons() – 1];
            INeuron SourceNeuron = select * from (INeuralLayer)GetNeuralLayer[this.Layer-1] where (INeuron)Neuron.OutputSynapses.Foreach(delegate(){Synapse.Target.Eqals(this.Network.NeuralLayers[i].Neurons[j]);});
            SourceNeuron.Delta += Synapse.Weight * delta;
        }
    }
}

private void PropagateSetWeights()
{
    for(int i = 0; i < this.Network.GetNumberOfNeuralLayers(); i++)
    {
        for(int j = 0; j < this.Network.NeuralLayers[i].GetNumberOfNeurons(); j++)
        {
            var Inputs = select * from (ISynapseLayer)GetSynapseLayer[this.Network.NeuralLayers[i]-1] where (ISynapse)Connection.Target.Equals(this.Network.NeuralLayers[i].Neurons[j]);
            for(int k = 0; k < Inputs.Count; k++)
            {
                Inputs[k].Weight += this.Network.NeuralLayers[i].Neurons[j].Etha * this.Network.NeuralLayers[i].Neurons[j].Delta * this.Network.NeuralLayers[i].Neurons[j] * Inputs[k].Value;
                Inputs[k].Weight = Inputs[k].Weight.ActivateDerivation;
            }
        }
    }
}
public double TrainNetwork(double treshold, int max_iteration)
{
    double _error = Double.Max_Double;
    int _iteration = 0;
   
    do
    {
        Propagate();
        for(int i = 0; i < (IVector)Network.GetOutputVector.Count; i++)
        {
            _error += Math.Pow(this.RealOutput[i] – Network.OutputVector[i], 2.0);
        }
        BackPropagate(_error);
        PropagateSetWeights();
        ++_iteration;
    }while(_error <= treshold || _iteration < max_iteration);
}

A többi elég magától értetődő kódocska tele sok sok hibával hála a jó istennek legalább jó munkát nem mutatok a külvilágnak így mire kijavítás megtörténne mindenki feladja de az elv látható ami alapján el lehet indulni. Aránylag gyors is de lehet rajta optimalizálni túl sok függvényhívás például le lehet cserélni delegáltakra és nincs függvényhívás…

Lássunk egy másik megközelítést is. Az lőzőekben tárgyalt algoritmusok nem túl hatákonyak mival vagy csak egy csúcsot vagy csak egy irányított utat figyeltek. Nekünk olyan kéne ami extra ciklus ezzel együtt lassítás nélkül hajlandó lenne figyelni minden lehetséges utat. Na erre adnak lehetőséget a keresőfával kereső rendszerek. A tárgyalásuk ugyanazon az elven történik mint eddig:
  1. Adatbázis: Az állapottér gráf adott most is mint minden eddigi esetben. No de, hogy fogunk ebben nyilvántartani minden lehetséges utat? Hát kicsit másként tároljuk a dolgot. Továbbra is csomópontok lesznek az adatbázisban egy hajszálnyi módosítással de a struktúrája az adatbázisnak egy csöppet más. Ketté fogjuk bontani az adatbázist de lássuk csak, hogy pontosín miért. Az adatbázisban a gráf már bejárt részét feszítő fát fogjuk eltárolni amit igen szellemesen keresőfának szokás nevezni. Ezt a keresőfát fogjuk nyilvántartani a csomópontokban de a csomópontokban most is variálunk egy kicsit.
    1. Az a állapot amelyet az adott csúcs reprezentál.
    2. Egy mutatót amely megmutatja, hogy az aktuális csúcs mely csúcsnak a leszármazottja.
    3. Azt az operátort amellyel előállt az aktuális állapot.
    4. Egy Státusz nevű jelzőt ami meg fogja osztani az adatbázisunkat. Pontosan két részre fogja osztani az adatbázist:
      • Nyílt: Ez azt jelenti, hogy az adott csúcs ugyan előállt már a keresés során valamilyen operátor alkalmazásának hatásaként, de arra még nem alkalmaztunk egy darab operátort sem. Ez azt jelenti, hogy annak még nem állítottuk elő egyik leszármazottját sem (ha van neki).
      • Zárt: Totál ellentettje az előzőnek, neki már előállítottuk az összes leszármazottját.
  2. Műveletek: A műveletek itt is két csoportba lesznek sorolva mint a backtrack algoritmusoknál. Bezony ennek van köze ahhoz, hogy az adatbázis megoszlik két csoportra.
    1. Operátorokból származtatott műveletek.
    2. Technikai műveletek: Nos szép és jó, hogy vannak nyílt csomópontok és zárt csomópontok de mire lesz ez nekünk jó? A dolog egyszerű de még nem lövöm le a poént. Azt máris ki lehet találni, hogy milyen technikai műveletünk lesz. A nyílt csomópontok azok amelyeknek még nem állítottuk elő egyik leszármazottját sem. Ez a keresőfában a levélelemeket jelenti. Ezesetben kizárásos alapon a zárt csomópontok a nem levél elemeket jelölik a keresőfánkban. Akkor ahhoz, hogy építhessük a keresőfát vagyis az adatbázist nyilván a csúcsoknak elő kell állítani a leszármazottjait. Ezt a folyamatot amikor egy nyílt csúcsnak előállítjuk az összes leszármazottját kiterjesztésnak nevezzük. Ez lenne a technikai műveletünk és nagyon hasznos és egyszerű dologról van szó.
        1. Procedure kiterjeszt(<A, kezdo, c, O>, Csúcs, Nyíltak, Zártak)
        2.     FOR EACH o IN O LOOP
        3.         IF o.alkalmazási_előfeltétel(Csúcs->Állapot) THEN
        4.             állapot <- o.alkalmaz(Csúcs->Állapot)
        5.             ny <- SELECT * FROM Nyíltak WHERE Állapot = állapot
        6.             z <- SELECT * FROM Zártak WHERE Állapot = állapot
        7.             IF ny IS NULL AND z IS NULL THEN
        8.                 új_csomópont->Állapot <- állapot
        9.                 új_csomópont->Szülő <- Csúcs
        10.                 új_csomópont->Operátor <- o
        11.                 új_csomópont->Státusz <- Nyílt
        12.                 Nyíltak.Add(új_csomópont)
        13.             ENDIF
        14.         ENDIF
        15.     END LOOP
        16.     Csúcs->Státusz <- Zárt
        17.     Zártak.Add(Csúcs)
        18.     Nyíltak.Remove(Csúcs)
        19. END
  3. Vezérlő: A vezérlő feladata ezúttal is az, hogy ellenőrizze előállt-e a célfeltételnek megfelelő állapot amely esetben megvan egy megoldás, valamint egy nyílt csomópont kiválasztása amelyet majd kiterjesztünk. Valamint itt bejön egy nagyon jó tulajdonsága ezen keresőknek. Ha elfogytak a nyílt csomópontok – ami azt jelenti, hogy bejártuk az egész gráfot ahová vezetett irányított út a startcsúcsból – de nem volt egy darab terminális csúcs sem, akkor nem létezik megoldása a problémának az adott reprezentációban. Bizony az algoritmus fölismeri, hogy van-e megoldás avagy sem. Ha van megoldás akkor véges sok lépés után talál egyet, de ha nincs akkor ezt felismeri azon jelenség bekövetkezésekor, hogy elfogynak a nyílt csomópontok. Az azonban, hogy milyen gráfban talál rá egy megoldásra nagyban függ az alkalmazott keresési stratégiától azaz attól, hogy mi alapján választja ki a kiterjesztendő csúcsot.

No és akkor lássunk egy általános sémát a keresőfával kereső algoritmusra is ha már korábban is adtam. A dolog ismételten rendkívül egyszerű mert a séma ugyanaz maradt mint eddig volt.

  1. Procedure Keresőfával_Kereső(<A, kezdo, c, O>)
  2.     Terminálisok <- Inicializál(c)
  3.     Csomópont->Állapot <- kezdő
  4.     Csomópont->Szülő <- NULL
  5.     Csomópont->Operátor <- NULL
  6.     Csomópont->Státusz <- Nyílt
  7.     Nyíltak.Add(Csomópont)
  8.     Zártak <- NULL
  9.     WHILE(TRUE) DO
  10.         IF Nyíltak IS NULL THEN
  11.             STOP
  12.         ENDIF
  13.         Csomópont <- SELECT * FROM Nyíltak
  14.         IF Csomópont IS IN Terminálisok THEN
  15.             STOP
  16.         ENDIF
  17.         Kiterjeszt(<A, kezdo, c, O>, Csomópont, Nyíltak, Zártak)
  18.     END DO
  19.     IF Nyíltak IS NOT NULL THEN
  20.         Kiír(Csomópont)
  21.     ELSE
  22.         Kiír("Nincs megoldás")
  23.     ENDIF
  24. END

Ennyi lenne a keresőfával megoldást keresők általános sémája. A következőkben tárgyalt algoritmusok mivel mindegyike ugyanerre a sémára épül, csak azt fogom elmondani ami másként van és kihagyom a szokásos Adatbázis/Műveletek/Vezérlő tárgyalását mert az mindegyiknél ugyanaz lesz.

Van egy olyan spéci algoritmus-család amelyben minden algoritmust le lehet bontani három nagyon jól elkülöníthatő részre:
    • Adatbázis: Az adatbázis tárolja az állapottér-gráfnak a keresés során már előállított részét. Ezt áldalában csomópontokkal tartja nyilván de, hogy mit tárol egyy adott csomópont azt az adott algoritmus dönti el.
    • Műveletek: Ez trükkös mert két részből tevődik össze.
      • Operátorokból származtatott műveletek: Ez ismét visszaköszön mert mik is ezek?Hát az operátorok amiket bevezettünk a gráfban is. Ugye nekünk csak a gráf egyetlen csúcsa adott a startcsúcs, és a kereséssel építjük fel az adott operátorok segítségével. Miért is működik ez? Egy operátort egy állapotra alkalmazva egy másik állapotot kapunk, ami a gráfban úgy jelenik meg, hogy az adott állapotot reprezentálú csúcsból elindul egy nyilacska, valahol megáll; majd a nyíl végén megjelenik egy másik állapotot reprezentáló csúcs. Így szépen épül a fa, nade mikor érünk el egy terminális csúcsot?!
      • Technikai műveletek: Olykor-olykor nem elég nekünk, hogy van 288 alkalmazási előfeltétel és adott 324 kényszerfeltétel mert a keresés piszkosul lassú szépen lassan komótosan építgeti a gráfot de mindíg belefut egy körbe vagy zsákutcába aztán ott behal. A technikai műveleteket pont arra találták ki valamikor a ’60-as ’70-es években, hogy ne dögöljön már ki a keresés hanem akkor csináljon valamit és menjen tovább. Ilyen lehetne például a kezd előrről a keresést igaz nem sok értelme lenne.
    • Vezérlő: A vezérlő az a része az algoritmusnak ami figyeli, hogy az adatbázisban megjelent-e már a célfeltételeknek eleget tévő állapotok valamelyikét reprezentáló csúcsok valamelyike. Ha igen akkor leállítja a keresést mert lett egy megoldásunk, ha pedig nem akkor eldönti, hogy mely produkciós szabály legyen alkalmazva a gráf mely részére. Apropó produkciós szabály semmi extra dologra nem kell itt gondolni, a produkciós szabályok halmazát az összes művelet alkotja.

Azokat az algoritmusokat amelyek teljesítik ezt keresőrendszereknek nevezzük. A kereső rendszereket számos módon lehet osztályozgatni mint például:

  1. A keresés iránya szerint:
    • Előrehaladó: Ez azt jelenti, hogy a startcsúcsból indulva keres az algoritmus egészen addig amíg nem talál egy terminális csúcsot, vagy esetleg rájön, hogy nincs megoldás.
    • Visszafelé haladó: Ez nagyon szellemesen pont azt jelenti, hogy kap egy terminális csúcsot, és abból próbálja meg majd rekonstruálni azt az irányított utat amelyen a startcsúcsból eljuthatuunk a kapott terminális csúcsig. Igen erre nagyon jól tippeltetetk elég körülményes implementálni és amennyire én udom elég kevés helyen akalmazzák, nem úgy mint a harmadikat amin meglepődtem.
    • Kétirányú kereső: Ez elindul a startcsúcsból és egy terminális csúcsból is aztán majd találkozik egyszer valamikor.
  2. Módosíthatóság szerint:
    • Nem módosítható kereső: Akkor beszélünk nem módosítható keresőről, amikor egy operátor hatását nem tudjuk visszavonni. Vagyis egy csúcsból választunk egy élt és továbbmegyünk az élen a következő csúcshoz, de gőzünk nincs utánna arról, hogy honnan is kerültünk ide.
    • Módosítható kereső: Nagyon szellemesen pont az előző ellentettje ha lehet nem fejteném ki.
  3. Speciális ismeret áll-e a kereső rendelkezésére a keresés során: Itt nem árt először tisztázni, hogy mit is értünk speciális ismeret alatt. Nos az egész ott kezdődik, hogy a Vezérlő is lebontható két részre aszerint, hogy milyen vezérlési stratégiákat alkalmaz. Így megkülönböztetünk elsődleges vezérlési stratégiákat, amelyek lényegében adottak az állapottér-reprezentációból (minden perátor cakk-um-pakk); illetve a másik csoport a másodlagos vezérlési stratágiákat tartalmazza. Na most mik is tartoznak ide amiket nem írtunk bele az állapottér-reprezentációba? Nem-nem bizony, hogy nem felejtettük el! Ide olyan spéci ismeretek tartoznak amik problémaspecifikusak, azaz csak az adott problémára vonatkozva egyszerűsíthetik a keresést.
    • Szisztematikus: Ezt nem igazán szeretném ragozni szisztematikusan mindenki tud keresni, csak nem biztos, hogy talál. Hacsak nem tud valami spéci módszert amivel garantáltan találni fog megoldást. Igen igazad van bizony, hogy létezik ilyen módszer nem is egy!
    • Heurisztikus: Mi is az a heurisztika? Ha heurisztika, az egy olyan tipikusan másodlagos vezérlési stratégia amelyet pont azért építünk be, hogy:
      • Jobb "minőségű" megoldást kapjunk.
      • Gyorsabban találjunk megoldást

DE van a heurisztikának egy jó nagy hátránya is. Nem, hogy azt nem garantálja amit elvárnánk tőle, de még azt sem garantálja, hogy egyáltalán megoldást találunk. Ráadásul még bonyolítja az algoritmusunkat is (növeli a futiási idejét). Hát ez van ezt kell szeretni.

Aztán mint gondolni való volt, akárcsak minden algoritmust, úgy a keresőrendszereket is lehet osztályozni az alábbi szempontok alapján:

  1. Teljesség: Bármilyen esetben amikor egy adott problmának létezik megoldása, akkor az algoritmus azt meg is fogja találni.
  2. Optimalitás: Jobban szorítja az algoritmust általában ez az ún. Bottleneck, ugyanis itt nem elég az, hogy talál egy megoldást de ha egy adott problémának több megoldása is létezik akkor ő a legjobbat találja meg. Hogy miként értelmezett a legjobb azt, most hanyagoljuk, maradjunk annyiban, hogy a legrövidebb irányított út lesz a legjobb. De vannak más szempontok is amire nem szeretnék kitérni mert nem írtam bele az állapottér reprezentációba.
  3. Időigény: Nagyon szellemes értelmezés következik, az az idő ami alatt az algoritmus vagy sikeresen vagy sikerertelenül de befejezi a keresést. Magyarul tökmindegy, hogy talál e megoldást avagy sem csak áljon le!
  4. Tárigény: Ismét szellemes értelmezés, az a lényege, hogy a keresés során maximum mennyi memóriát zabbantott föl.

És ennyi lenne általánosan a keresőrendszerekről. A továbbiakban minden spéci kereőrendszer ezt a sémát fogja felvenni. Ja igen elfelejtettem megmutatni, hogy miként is fest egy ilyen algoritmus:

    1.  Procedure kereso(<A, kezdo, c, O>, …)
    2.     Műveletek <- Inicializál(O)
    3.     Terminálisok <- Inicializál(c)
    4.     VanMegoldás <- False
    5.     aktuális <- kezdo
    6.     WHILE(IGAZ) LOOP
    7.         o <- SELECT * FROM Műveletek WHERE alkalmazási_előfeltétel(aktuális)
    8.         aktuális <- o.alkalmaz(aktuális)
    9.         IF aktuális IS IN Terminálisok THEN 
    10.             VanMegoldás <- True
    11.             STOP
    12.         ENDIF
    13.     END LOOP
    14.     IF VanMegoldás IS True THEN
    15.         KiírMegoldás(aktuális)
    16.     ENDIF
    17. END

Ez lenne a lehető legegyszerűbb általános megoldáskereső rendszer formája.Amennyire lehet ragaszkodni fogok később is ehhez a formához főleg azért mert itt nincs lehetőségem logikai formulákat bevésni, ahhoz meg türelmem nincs, hogy mindet megrajzoljam és beillesztgessem a megfelelő helyre. Amint látható ez SQL sintaxist tartalmaz amit kiemeltem kékkel és szerintem ettől jobban magáért kitevő szintaxissal még nem találkoztam. Szinte ordít, hogy mit csinál.