A q programozási nyelv

Példaprogramok

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;