A következő oldalon található két hasznos példaprogram is (az egyik q entitásokat szerializál xml fájllá, a másik reguláris kifejezéseket dolgoz fel):
itt
Monte Carlo módszer
Készítette: Györök Péter
A program a Monte Carlo módszer kiértékelését végzi és kiírja az eltérés
négyzetes közepét (RMS = root mean square).
Véletlen bolyongás (random walk): adott egy számegyenes, a pontjai 0-tól N-ig számozva.
N páros szám, tehát összesen páratlan számú pont van.
Az ágens egy belső pontból indul. Minden lépésben véletlenszerűen jobbra vagy balra
lép egyet, egyenlő valószínűséggel. Az a kérdés, hogy mennyi a valószínűsége annak,
hogy előbb jut el a jobb szélére (N. pont), mint a bal szélére (0. pont).
Valószínűségszámítási módszerekkel belátható, hogy ha K. pontból indulunk, akkor
ez a valószínűség K/N.
A Monte Carlo módszer a megerősítéses tanulás (RL = reinforcement learning) egyik
módszere. Az a lényege, hogy véletlen lefutásokat (epizódokat) generálunk, majd
azok alapján becsüljük meg a jutalom várható értékét. A véletlen bolyongásnál a
"jutalom" 0, ha a bal szélére érünk, és 1, ha a jobb szélére érünk, tehát épp a
kiszámítandó valószínűség lesz a várható érték. Mivel azonban tudjuk a pontos
megoldást, ezzel a mintafeladattal kiértékelhetjük a módszer hatékonyságát úgy,
hogy vesszük a becsült és a valós érték különbségét, majd valamilyen mértékszámot
adunk rá, jelen esetben ez az RMS.
/ Az alábbi sor beállítja a konzol méretét 10000*10000 karakterre. Célszerű kirakni,
/ mert az output függvények gyakran függnek tőle.
value "\\c 10000 10000";
/ Emlékezzünk rá, hogy a legtöbb q kifejezést célszerű jobbról balra olvasni.
/ A fő függvényünk. Egy paramétert vár, a mezők számát.
montecarlo:{[size]
/ Ennyi epizódot generálunk.
runs:100;
/ Ez a függvény legenerál egy epizódot a megadott kezdőponttal és mezőszámmal.
/ A függvény rekurzív. A visszaadott epizód első eleme a megadott kezdőpozíció,
/ ehhez konkatenáljuk a maradék epizódot.
/ Ezután egy elágazás jön:
/ Ha a 0 vagy a size-1 sorszámú pozícióban vagyunk, az végállapot, tehát
/ a maradék epizód üres lista.
/ Különben meghívjuk önmagunkat (.z.s) úgy, hogy a pozícióhoz hozzáadunk
/ véletlenszerűen 1-et vagy -1-et.
/ 1?2 generál 1 db véletlen egész számot a [0..2-1], azaz [0..1] intervallumban.
/ Ezt megszorozzuk 2-vel, majd kivonunk 1-et, így a kapott szám -1 vagy 1 lesz.
/ Mivel a ? operátor listát ad vissza, a "first" függvénnyel kivesszük az
/ "első" (egyetlen) elemét. Ezt hozzáadjuk a pozícióhoz, így kapjuk a rekurzív
/ hívás pozíció első paraméterét.
scen:{[pos;size]:pos,$[(pos=0)|(pos=size-1);();.z.s[pos+first (2*1?2)-1;size]]};
/ A # operátor megismétli a "scen" függvény nevét "runs" számszor.
/ A kapott listán végigfuttatunk (each) egy magasabb rendű függvényt.
/ Ez a függvény paraméterül kapja a méretet (mivel a lokális változót nem látja),
/ valamint a meghívandó függvényt, ami végig a "scen" lesz.
/ A függvényt a méret felével, valamint az egész mérettel hívja meg, vagyis
/ olyan epizódokat állít elő, amelyek a középső pontból indulnak.
/ Az eredmény listák listája, minden lista egy epizód. Ezeket ezután még kicsit
/ megtisztítjuk. A "distinct" kiszűri a duplikátumokat egy listából, de nem a
/ "nagy" listára, hanem az egyes "kis" listákra akarjuk használni, ezért megint
/ kell az "each".
scs:distinct each{y[x div 2;x]}[size]each(runs#scen);
/ Most kiszámítjuk a jutalmak futó összegét, vagyis hogy összesen hányszor
/ érintettük az egyes pontokat úgy, hogy utána a célba (jobb végpont) jutottunk.
/ Először konvertáljuk a listákat egy lambda függvénnyel:
/ a függvény készít egy X elemű nullákból álló listát (x#0, az x megint paraméter,
/ mert nem látszik a lokális size változó), majd ezt megindexeli a második
/ paraméterével, ami majd az érintett állapotok listája lesz. Az indexnek
/ értékül adja azt, hogy a lista utolsó eleme nem egyenlő-e nullával
/ (ekkor ugyanis a célállapotba jutottunk).
/ Így pl. 7 elemnél a 3 2 3 4 5 6 listából 0 0 1 1 1 1 1 lesz,
/ míg a 3 4 3 2 1 2 1 0 listából 0 0 0 0 0 0 0, mert ez nem a célállapotban
/ végződik.
/ A lambda függvényt végigfuttatjuk az előző sorban kapott "scs" listán,
/ majd előállítjuk az eredmény részösszegeit (sums).
/ Vagyis az i. elem megegyezik az i-1. elem és az scs[i] összegével.
Vs:sums{a:x#0;a[y]:`int$0<>last y;a}[size]each scs;
/ Ez a sor hasonló dolgot csinál, mint az előző, csak most mindenképpen belekerülnek
/ a listába az 1-esek, így a részösszegek az egyes pontok összes érintésének
/ számát adják.
Vt:sums{a:x#0;a[y]:1;a}[size]each scs;
/ A helyes eredmény: vesszük az első "size" darab természetes számot
/ (pl. 0 1 2 3 4 5 6) és elosztjuk size-1-gyel, így pontosan a kívánt eredményt
/ kapjuk. A bizonyítást ld. valószínűségszámítás órán.
Vj:(til size)%size-1;
/ Ez egy kis információs kiírás, amely tájékoztat az aktuális feladat
/ állapotszámáról. Az "1" itt a standard outputra írás.
1 "Monte Carlo for ",(string size)," states\n";
/ Végül kiírjuk a hibát. A becsült érték a fenti Vs és Vt vektorok hányadosa.
/ Az eredményből soronként kivonjuk a helyes eredményt (Vj), majd négyzetre
/ emeljük, összegezzük és négyzetgyököt vonunk belőle. Minden sorból tehát
/ egy szám keletkezik. Végül a sorokat megszámozzuk (,' azaz páronkénti
/ konkatenáció az 1-től "runs"-ig tartó listával).
/ Most a "show" függvénnyel írjuk ki, mert formázott output kell.
show (1+til runs),'{sqrt sum {x*x}y-x}[Vj]each(Vs%Vt)
};
/ Az alábbiak tesztesetek
montecarlo 7;
montecarlo 9;
montecarlo 11;