unbornchikken's profilechikkenthingsPhotosBlogListsMore Tools Help

chikkenthings

unbornchikken voice

Occupation
Location

Custom HTML

Köszönjük a látogatást!
Please wait...
Sorry, the comment you entered is too long. Please shorten it.
You didn't enter anything. Please try again.
Sorry, we can't add your comment right now. Please try again later.
To add a comment, you need permission from your parent. Ask for permission
Your parent has turned off comments.
Sorry, we can't delete your comment right now. Please try again later.
You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
Complete the security check below to finish leaving your comment.
The characters you type in the security check must match the characters in the picture or audio.
Photo 1 of 2
July 03

Neural Network Express: Elmélet

Úgy nagy általánosságban a számítási hálózatokról már írtam régebben. Az NN Express projekt a speciális, un. feed forward multilayer neuronhálózatokról fog szólni. Tréning algoritmusnak felírunk egy egyszeri Back Propagation-t, ami segítségével kipróbáljuk, hogy mit tud a rendszerünk supervised osztályozási és unsupervised time series feladatokkal kezdeni. Majd vesszük a legegyszerűbben implementálható sztochasztikus tréninget, vagyis a Simulated Annealing-et, és ennek egy módosított változatának segítségével megtanítjuk a rendszerünket, mondjuk, squash-ozni (reinforcement learning).

Bocs, hogy félig latin kifejezésekkel tarkítva fogalmazok, de ezeknek a fogalmaknak vagy nincs magyar megfelelőjük, vagy igen idiótán hangzanak magyarul.

Menjünk végig akkor a latinon!

Feed Forward Multilayer NN topológia

Azért nevezik ezt a játékost feed forward-nak, mert tényleg előre etet. Bevezetjük a rendszer elején az input adatokat, a számítási folyamat végén pedig a seggén kilöki az eredményt, és a részeredmények nincsenek visszavezetve a hálózatba (ennek a visszavezetésnek például a memória kialakításánál van szerepe, ahogy régebben írtam). Így néz ki a dolog:

Az input és az output layer a fix, az inputon vezetjük be az adatokat, végigszámolunk, majd az outputról leolvassuk az eredményt. Az IO közé aztán tetszőlegesen lehet tenni rejtett rétegeket, és minden rejtett réteg kapcsolódik a két szomszédos réteghez. Ezek a kapcsolatok súlyozva vannak, az ábrázolásuk egyszerű weight-matrix. Nem muszáj az összes elképzelhető kapcsolatot ábrázolni, bizonyos feladatoknál a hidden-hidden és a hidden-output kapcsolatok meglétét is külön szabályozzák (nullable-weight-matrix + topology training). A rejtett rétegek bevezetésére azért van szükség, hogy a hálózat képes legyen nemlineárisan szeparálható vagy hipersík mentén szeparálható adatok megkülönböztetésére. A legtöbb feladathoz egy rejtett réteg elég, csak olyan problémáknál van szükség 2 (extrém esetekben több) rejtett réteg bevetésére, amikor a feldolgozást nem a nyers adatokan akarjuk elvégezni, hanem szükség van előfeldolgozásra, amit szintén a hálózattal kívánunk elvégeztetni (pl: arcfelismerés).

Activation neuron

A neuronszimuláció ez a játékos. Ha elképzelitek a fennti topológiát működni, akkor egy-egy (nem input) neuron a következőképpen fog kinézni:

Az X(n) a felül kapcsolt neuronok kimentének értékei, a W(n) az adott kapcsolat súlya, az f az a függvény, amit az adott neuron reprezentál, az Y pedig a neuron által számított kimeneti érték (ami a következő réteg X(n) értékeit fogja adni). Tehát egy neuron nem más, mint egy f(sum(X(n)*W(n)))=Y függvény, aminek viselkedését a W(1..n) vezérlőértékek szabályozzák. Az, hogy pontosan milyen függvényt használunk, a tréning algoritmustól és a hálózat felhasználásától is függ. Ha gradient descent számításon alapuló tréninget használunk, akkor a függvénynek differenciálhatónak kell lennie, ha valami sztochasztikus tréninget alkalmazunk (pl. S. Annealing, GA), akkor a lineáris függvény a legjobb választás. A függvény kimenete unipoláris esetben 0..1, bipoláris esetben –1..1. Szintén feladata és tréning módszere válogatja, hogy milyen polaritásút alkalmazunk. Ha a függvény kimenete 0..1 illetve –1..1, akkor ebből egyenesen következik, hogy az X(1..n) bemenet is ebbe a tartományba esik. A W értékeket –1..1 közé szokás venni. A negatív súlyú szinapszisok lesznek a kioltó kapcsolatok, a pozitívak pedig az aktiváló szinapszisok. Nézzünk 2 pédát!

Bipolar Sigmoid:

Gradient Descent tréninggel (pl. BP) operáló hálózatoknál alkalmazzuk, mivel differenciálható. Bipoláris kimenete miatt remekül alkalmazható például függvényérték közelítéses feladatokhoz. Így néz ki:

sigmoid_bipolar

Látható, hogy ahol nem –1 és 1 a kimenet, nagyon szépen fel lehet rajzolni a meredekség egyenesét (differenciál). Képlete a következő:

(2.0 / (1.0 + Math.Exp(-Alpha * value))) - 1.0

Minek a deriváltja:

Alpha * (1.0 - value * value) / 2.0

Az Alpha érték fogja szabályozni annak a tartománynak a méretét, ahol a függvény –1 .. 1 közé eső számokat fog eredményül adni – tulajdonképpen ezért nincs szükség –1..1-től különböző weight tartományokra, mert azzal pontosan ugyan ezt a részt szabályoznánk.

Neuronként való alkalmazása a következő:

N darab input jön, mindegyikhez tartozik egy-egy weight. Ha a megfelelő input-weight párokat összeszorozzuk, megkapjuk a súlyozott input értékeket. Ezeket összeadjuk, így megkapjuk a sigmoid függvény bemenő értékét, abból kiszámoljuk az outputot, és ready, ez az érték lesz a következő réteg bemeneti értéke, illetve az output rétegen a kimenet.

Unipolar Linear:

Pontosan ugyan úgy kell számolni a bemeneteket mint az előbb, amikből aztán a következő képlet számolja a kimeneti értéket:

double result = (value * Alpha) + 0.5;
if (result < 0.0) return 0.0; else if (result > 1.0) return 1.0;
return result;

Alphaval állítunk a meredekségen, majd feltoljuk az értéket féllel, és vágunk (a bipolar ugyan ez, csak ott nem toljuk fel, és –1 .. 1 közé kell vágni). A gradient descent alapú tréning módszerek ezzel a függvénnyel nem nagyon tudnak mit kezdeni, viszont a kimenet linearitása azt fogja eredményezni, hogy egységnyi weight változtatás a függvény bármely nem teljesen aktív vagy deaktív tartományában egységnyi kimenőérték változást fog előidézni, így remekül tud működni globális keresésen alapuló, vagyis sztochasztikus tréning algoritmusok esetén.

Supervised Learning + Back Propagation

Van 40 000 darab adat, amit egy statisztikával foglalkozó cég összeszedett. Mit tudom én, arról szól, hogy milyen időjárás, páratartalom, légnyomás adatok mellett az adott napon mennyi halálos baleset történt az országban. Az adatok úgy néznek ki, hogy egy sorban ott vannak az időjárási adatok, mellette pedig egy szám, hogy mennyi baleset volt aznap. Ezeket a számokat első lépésként 0..1 tartományba súlyozzuk. Mivel egyik számadat sincs közvetlen összefüggésben a másikkal, ezért egymástól független lesz a súlyozás. Ez úgy történik, hogy meghatározunk egy maximumot és egy minimumot az adott értékre, kivonjuk az értékből a minimumot, és osztunk a maximum – minimum értékkel, így kapunk egy 0..1 közé eső valós számot. Tehát ez lesz a tréning adat-transzformációs lépése, vagyis amikor a tréning adathalmaz egyes elemeihez hozzárendelünk egy valós számokból álló sztringet, amit már be tudunk vezetni a neuronhálózatba.

Egy tréning iteráció (szaknyelven epoch) a következőképpen zajlik:

nyers adatok –> adattranszformáció –> input valós sztring bevezetése –> output kiszámolása –> számolt output valós sztring és az elvárt output sztringből számolt hibafaktor alapján futtatott tréning algoritmus, ami a weight értékeket állogatja –> GOTO 10, amíg a hiba egy előre megadott érték alá nem esik

Ezt a folyamatot nevezik az okosok supervised learning-nek.

Batch

Még mielőtt elővennénk a tréning algoritmust, fontos beszélni arról, hogy hogyan érdemes a rendelkezésre álló 40 000 adatból az adott epoch input + output adatait kiválasztani? Ezt a folyamatot az okosok batch trainignek nevezik. Arról van szó, hogy egy körben valamilyen algoritmus alapján kiválasztanak n darab adatot, azzal lefuttatják az n darab epoch-t, kiszámolják az adott adag átlag négyzetes hibáját (Mean Square Error), és ez lesz az aktuális hibaérték.

A legpuritánabb megoldás a Monte Carlo algoritmus. Teljesen véletlenül kiválasztunk n darabot a 40 000-ből, és ezen n darab adatot véletlenszerű sorrendben etetjük a hálózattal, és ez lesz egy batch csoport. Ez tud működni, azonban a 40 000 adat közt lehetnek olyan összefüggések, amiket ha fel tudnánk használni a tréningezés során az adatok csoportosítására, sokkal gyorsabb tréning lehet az eredmény. Például az esős napokat együtt vennénk, majd a száraz napokat, majd a magas légnyomásúakat, majd az alacsonyakat, akkor előfordulhat, hogy sokkal hamarabb érnénk el az áhított alacsony hibafaktort (Supervised Learning Feature Selection @ Wikipedia – itt erről a sokkal nagyobb okosság). Azonban ezeket a csoportokat meghatározni egy külön művészet lenne, így sokkal jobban járunk, ha valami globális optimalizációt használunk az összefüggő csoportok automatikus megkeresésére. 2 bevált módszert lehet itt alkalmazni: az egyik a részecske raj optimalizáció a másik meg a genetikus algoritmus. Ezek nagyon szépen meg fogják határozni a tréning futása során az összefüggő csoportokat, sokkal jobban, mint ahogy arra egy ember valaha is képes lenne.

Gradient Descent

Még mindig nem tréningezünk, matekozunk. A neuronhálózat aktuális hibáját kiszámítva (elvárt output – aktuális output) megkapjuk a cost függvényt, minek értékének minimalizálása tulajdonképpen a tréning célja. Egy differenciálható függvény minimumát a leggyorsabban a gradient descent számítással lehet meghatározni. Igen egyszerű, megnézem, hogy merre lejt, és lépek arra egy kicsit, majd GOTO 10. A számítás első paramétere a step size, vagyis az az értéksúly, amennyit egy lépés alatt elmozdulhatok. A második paraméter pedig az un. momentum, vagyis az a faktor, ami kijelöli, hogy egy adott lépés értékébe milyen súllyal számít bele az előző lépésem. Ennek alkalmazásával egy sokkal folyamatosabb lépéssorozat lesz az eredmény.

Azonban ez a számítás neuronhálózatoknál még édeskevés, hiszen ott több függvény együttműködésének eredménye a cost, ráadásul ezeknek a függvényeknek a működése szintén több súly értékének összességéből adódik, így ki kellett találni az egészhez a következő mókát:

Back Propagation

Az ötlet az, hogy neuronhálózatoknál output neuronokra meg tudjuk mondani, hogy ott pontosan mennyi a hiba. Ezt a hibát aztán visszavezetve és súlyozva el tudjuk juttatni ahhoz a neuronhoz és olyan mértékben, ahogy azt ő az adott iterációban okozta. Ha megvan a hibaérték az adott neuronnál, és tudjuk, hogy ezt a hibát a súlyozott input okozta a neuron (differenciálható) függvénye mellet, így már meg tudjuk állapítani a lokális cost gradiens teret, ami mentén tudjuk léptetni a neuronhoz tartozó súlyokat. Nem olyan bonyolult, mint ahogy hangzik: itt van erről egy szép rajzos összefoglaló. Itt a step size-t úgy nevezik, hogy learning rate, a momentumot pedig momentumnak érdekes módon. Ezek értékeinek beállítása tulajdonképpen a BP algoritmus rákfenéje, hiszen rengeteg időt el lehet azzal pöcsölni, hogy megtaláljuk az egy adott tréninget a leginkább előrevivő learning rate + momentum + activation function alpha mesterhármast. Léteznek különböző módszerek arra, hogy ezeket az értékeket az adott hiba (MSE) függvényében hangolják, de szvsz nem érdemes ezzel szórakozni, mikor vannak már a BP-nél jóval fejlettebb algoritmusok is, miknek alkalmazásával sokkal gyorsabb és pontosabb eredményt kapunk, mint a BP konstans bűvölésekkel valaha is (majd az NN Expresshez is írunk fel egy-két ilyet, de kezdetnek a BP is bőven megteszi).

Más, frissebb, lágyabb, jobb tréning algoritmusok

A BP óta azért történtek előrelépések. Az egyik ilyen, hogy a Conjugate Gradient számítást alkalmazzuk léptetésnél, illetve ennek újabb változatát, a Scaled Conjugate Gradient számítást, ahol a lépésközt is maga az algoritmus állítja be magának, így jóval gyorsabb és pontosabb tréning lesz az eredmény. De van ezeknél is még gyorsabb és pontosabb, az un. Sensitivity-Based Linear Learning Method, szóval lehet válogatni. Az igazság viszont az, hogy ezek a számítások bár gyorsak, egy bizonyos hibaértéknek nem tudnak alámenni az alkalmazott matematikai számítások természete miatt. Ezért olyan esetben, ahol a tréningre van idő, és a rendszer minél nagyobb pontossága a végcél, sokkal értelmesebb megoldás egy globális optimalizációt választani a weight értékek meghatározására számolgatás helyett. Ezek a globális optimalizációs eljárások valamivel (aka sokkal) lassabbak, ellenben jóval pontosabb működésű hálózatot adnak eredményül. Ilyen algorimusok a GA és a Simulated Annealing is. Globális optimalizációt kell felhasználnunk olyan tréning esetben is, ahol nem tudjuk eldöntetni, hogy egy működési hiba pontosan melyik outputon pontosan melyik időpontban keletkezett. Ez viszont már a következő témakör ügye, ami a:

Reinforcement Learning + Adaptive Simulated Annealing

Neuronhálózatok esetén egy kicsit mást jelent az RL mint az öreg Markov döntikéjénél alkalmazva. Itt a státusz és az akciótér egy folyamatos, interpolálódó értéktartomány, és a neuronhálózat feladata megteremteni azt a transzformációs függvényt, ami a státusz értékeket az akció értékekre képezi le. Magyarul ez annyit tesz, hogy a “szenzorok” nyers input értékét vezetjük be, és az “output” pedig azon vezérlőjeleket képezik, amin keresztül a rendszer hat a környezetére. Például a squash esetében bevezetjük az ellenfél, a labda, és a játszó RL agent koordinátáit, és két output értéket vezetünk ki, az egyik az X tengelyen való elmozdulást, a másik pedig az Y tengelyen való elmozdulást fogja jelölni az adott időpillanatban. Tehát az állapotteret itt a három koordináta értéke, az akcióteret pedig az elmozdulási parancsok értékei jelenti. Az NN-t így egy olyan függvénnyé akarjuk tréningezni, ami ezen állopattérből azokat az akcióértékeket adja meg, amik az adott esetben a labda pályán tartását és az ellenfél kiejtését fogja eredményezni a lehető legjobb eséllyel. Belegondolva ez pontosan ugyan az, mint a Markov Decision Process, csak itt folyamatos, interpolálódó értékek szereplenek a játékban.

Az RL tréning un. unsupervised módú. Soha nem tudjuk megmondani, hogy egy hiba honnan és mikor jött, így nem is tudjuk hova a hibát visszavezetni. Ebből következik, hogy itt a GD számításokat el is lehet felejteni. Továbbá az RL egy időben változó, dinamikus folyamat, ahol a tréning célja egy későbbi jutalom elérése, így egyszerre több inputra adott output számítás eredményét kell kiértékelni, vagyis még csak adott időpillanatokra bontva sem rendelkezünk pontos hibainformációkkal. Tehát itt a tréning úgy zajlik, hogy működik az NN, megy a squash, és ha valami jó történik, eltalálja az agent a labdát, kiesik az ellenfél, stb, akkor azért megjutalmazzuk a hálót, ha pedig valami rossz, pl elmegy az agent mellett a labda, akkor megbüntetjük.

Ez a jutalom illetve büntetés egy szám, elért eredményektől illetve vétett hibák minőségétől változó mértékű valós szám, általában 0..1 közt. Ez a számot felhasználva alakítjuk aztán a tréning algorimusunk segítségével az agent-et vezérlő neuronhálózat weight értékeit, így zajlik a tanítás. Több algoritmus létezik, a legtöbb valami globális optimizációra épül. Ilyen például a részecske raj optimalizáció, illetve a:

Simulated Annealing

Itt a wiki: SA @ Wikipedia. Úgy működik az algo NN esetében, hogy az SA célja megkeresni azokat a weight értékeket, ahol a legkisebb a cost minimuma. Az algot a hőmérséklet paraméter szabályozza, ez nem más, mint a cost értéke. A hőmérséklet függvényében meghatározok egy a jelenlegi weight értéktartománnyal szomszédos értéktartományt, megnézem, hogy az azzal kiszámolt hiba kisebb-e mint az előző, ha kisebb, és ha teljesül a hőmérséklettől függő esélyellenőrzés, akkor ezt veszem alapul a továbbiakaban. Ahogy megy előre az algoritmus, egyre csökken a hőmérséklet (vagyis a hiba), a lépésközök egyre kisebbek lesznek, és a weight disztribúció egyre inkább egy jó megoldás körül fog kikristályosodni. A szomszédos értéktartomány kiválasztása is a hőmérséklettől függ, fogom a jelenlegieket, és adok rá egy random zajt az aktuális hőmérséklet függvényében. Ebből következik, hogy az NN-re alkalmazott Simulated Annealing igazából Quantum Annealing, csak ezen a néven terjed el a dolog eredetileg.

Hogyan lesz ebből dinamikus Reinforcement Learning? Van az SA-nak egy variánsa, az Adaptive SA, ami arról szól, hogy a hőmérsékletet, és a szomszédos weight disztribúció kiválasztásának módját egy független körülmény által befolyásoljuk. Az RL esetén folyamatosan jönnek a reward/punishment visszajelzések, ez alapján fel tudjuk írni a hőmérséklet időbeni változásának dinamikáját, így képessé tudjuk tenni az egyszeri ASA-t RL NN tréning feladatok megvalósításához is. Na, ezzel sincs tele a Net, jórészt csak fizetős doksik vannak ez ügyben, de annyira azért nem összetett dolog megcsinálni, én QSA-hoz írtam fel ilyet, az meg alkalmazás szempontjából ugyan az, mint az SA. Megoldjuk. Fasza lesz.

READY.

Durván ennyi az elmélet, ami ehhez tartozik. Legközelebb már kódolni fogunk nagyon keményen, tulajdonképpen a Neural Network Express projekt egy amolyan lightweight NeoComp Framework lesz, a kód jelentős részét onnan fogom kopipésztelni. Ha elér bétába a NeoComp, és lesz free verzió is, akkor le fogjuk cserélni az NN motort. De addig is jó lesz a projekt arra, hogy bemutassam, hogyan lehet C#-ban ezt a sok mindent megvalósítani a lehető legkisebb energiabefektetés felhasználásával.

July 01

7 szegmenses kijelző, 16-os számrendszer


Éppen indulunk barátnőm szülinapját innepűni, így van ideje a gépemnek egy kicsit számolgatni. A feladat továbbra is a 7 szegmenses kijelző, azzal a *kis* különbséggel, hogy ezúttal az A-B-C-D-E-F számok is ábrázolva vannak, így a bonyolultság hatványozottan nőtt. Ez már tényleg nagyon rázós számítás, 700 mega RAM-ot eszik a processz, 150 darab 25 elemű populációt futtatok párhuzamosan. Meglátjuk, hogy mi lesz? Jelenleg 5 perc futás után 20 hibánál tart a legjobb áramkör, ami a lehetséges 7*16*2, vagyis 224-ből nem is olyan rossz ennyi idő alatt! \o/ Majd beszámolok!

June 28

F# off. Mi legyen a következő téma?


Ígértem ugye F#-ot. Erről leteszek. Az első fellángolás után arra jutottam, hogy:

  • Az F# kód átláthatósága valahol a béka segge környékén van, de lehet, hogy az alatt valamivel.
  • Egy normálisan implementált C# kódhoz képest a sebessége minimum 5x lassabb.
  • Bár van .NET interop, szabvány .NET felületeket és osztályokat implementálni vele egy agyrém.
  • Bár az aszinkron F# csak néhány plusz krix-krax, de ebből következik, hogy teljességgel áttekinthetetlen.

Tehát, ha gyors és újrafelhasználható számítási algoritmusokkal operáló libet írok F#-ban C# helyett, az egyenlő a szakmai öngyilkossággal. Játék az egész, de maguk a fejlesztők sem vitatják, hogy az. Ergo: ne pazaroljuk ezzel egymás drága idejét, ha nem muszáj!

Akkor miről olvasnátok szívesen? Mindenképp valami .NET 4-es dologgal kéne foglalkozni, hiszen mindjárt itt az új frémvörk. A WPF és Sync kilőve, Janka Jancsi területe, engem amúgy sem izgat annyira. Az “adatbázis-buzizás” (EF v2, ADO.NET DS v2, RIA) már egy érdekesebb téma, de ezekkel a jelenlegi készenléti stádiumban nem lehet érdemben foglalkozni, a Beta2-ig elő sem veszem őket (idő hiányában). Mindenképpen valami gyakorlati dolgot akarok bemutatni, annak semmi értelme sincs, hogy kezdőknek szóló tutorialokkal lökjem tele a blogot, roskadozik a Net az ilyen írásoktól, plusz minden technológia honlapjáról letölthetők a white paperek.

Két dolog jutott az eszembe:

  • WF4
  • C# 4.0 + PFX + Contract, teljes projekt: Neural Network Express.

A WF4-ből az új element rendszer az érdekes, különösképpen a saját aktivitások szerkesztése és saját designerek gyártása. Ezzel hamarosan elkezdek komolyabban foglalkozni, de ha ez az érdekesebb téma, akkor a hamarosan helyett azonnal lesz, és írok róla pár postot, ahogy haladok.

A második pedig egy teljesen egyszerű Feed Forward Back Propagation neuronhálózat lib projekt lenne (mint az előző postban a kutyafuttatási kísérletben), ahol a nulláról C# 4.0-ben, PFX + Contract felhasználásával összeütünk egy ilyen cuccot itt a blogon, aminek az eredménye egy saját kis NN osztálykönyvtár, amit aztán odahaza mindenki arra használ fel, amire csak akar (lovi, tipp-mix, tőzsde :D).

Ez utóbbiról van 2-3 cikk a CodeProjecten:

http://www.google.co.uk/search?hl=en&q=neural+network+codeproject&meta=

de iszonyú lámer mind (már bocs, de tényleg), plusz valamikor a .NET 2.0-s időkben készültek, ennél mi jobbat fogunk gyártani, amit aztán odahaza az elvetemültebbek kiegészíthetnek a BP-nél fejlettebb algoritmusokkal is.

Szóval, mi legyen?

June 23

Nyerjünk pénzt! (neuronhálózatokkal)

Találtam egy érdekes doksit a Neten:

Expert Prediction, Symbolic Learning, and Neural Networks: An Experiment on Greyhound Racing

Egy ‘93-as kísérletről van benne szó. Azt csinálták az okosok, hogy kinéztek maguknak egy kutyafuttató versenyt, és megszerezték a kutyák paramétereit leíró táblázatokat:

Felfogadtak 3 szakértőt, implementáltak egy ID3 nevezetű decision tree algoritmust:

plusz bedobtak e 4 versenyző mellé egy egyszeri (‘93-ban übernek számító) 25 rejtett neuronos Back Propagation neuronhálózatot.

A verseny abból állt, hogy a 3 szakértő, az ID3 és a kutyák adataiból és az előző versenyeredmények alapján feltréningezett neuronhálózat megtippelte a következő 100 futam eredményét, majd mind az 5 jósolmányt megtették a következő futamokon, és az nyert, aki a végén minél nagyobb pluszban jött ki.

Az eredmény magáért beszél:

Technique Correct Incorrect Did not bet Payoffs ($)
Expert 1 19 81 0 -71.40
Expert 2 17 83 0 -61.20
Expert 3 18 82 0 -70.20
ID3 34 50 26 69.20
Backprop. 20 80 0 124.80

El lehet képzelni, hogy a mai számítási kapacitás és az újabb algoritmusok felhasználásával a szájbőr-spekulánsok mi pénzeket tesznek zsebre!

Így legalább tuti megtérül a NeoComp fejlesztésének az ára … :D

June 13

F# vs C# compo hamarosan!


Éppen egy 2-opt implementáción dolgozom, ami bármilyen IList<T>-t képes 2-opt optimalizálni (2-opt @ Wiki + Java Applet DEMO). Ezt elég nehéz megoldani funkcionálisan, de sikerült kitalálnom egy fasza és arcpirítóan gyors algoritmust, ami ráadásul parallelizálható. Éppen az F# verzión dolgozom. Elég nehezen haladok, mert ezek az online referenciák igen gagyik, van 2 PDF-em, de azt meg nem tudom kinyomtatni, mert a Jó Isten tintája nem lenne hozzá elég. De kész lesz! Megírom C#-ban is pontosan ugyan ezt, és akkor lehet összehasonlítgatni, Relflectorozgatni, benchmarkozni meg örvendezni, szóval figylejétek a blogot, hamarosan jön a cucc!

June 10

Dynamic Interface implementation for any data structure using C# 4.0 dynamic features and CodeDom


Introduction

First of all, this is my first blog post in English, so please forgive me for my funny English which is rather Hungrish. I’ve been reading tons of documentations in English but I ain’t got enoght opportunity to speak in it.

Without any unnecessary tale about C# dynamic or CodeDom, let me intruducing the target code:

public interface IPerson
{
    string Name { get; set; }

    string Address { get; set; }

    int Age { get; set; }

    bool IsMale { get; set; }
}
class Program
{
    static void Main(string[] args)
    {
        // XElement: Properties in sub elements.
        var xPerson1 = XElement.Parse(
          @"<Person>
                <Name>Gabor Mezo</Name>
                <Address>4024 Debrecen, Blabla</Address>
                <Age>30</Age>
                <IsMale>true</IsMale>
            </Person>");

        // XElement: Properties in element attributes.
        var xPerson2 = XElement.Parse(
          @"<Person Name='Jakab Gipsz' Address='5555 Kukutyim, Blabla' Age='11' IsMale='true'/>");

        // Simple string, object dictionary.
        var dPerson3 = new Dictionary<string, object>
        {
            { "Name", "Gizi Kiss" },
            { "Address", "4024 Debrecen, Blabla" },
            { "Age", 54 },
            { "IsMale", false },
        };

        // Untyped data table w/ one row.
        var rPerson4InTable = new DataTable();
        rPerson4InTable.Columns.Add("Name");
        rPerson4InTable.Columns.Add("Address");
        rPerson4InTable.Columns.Add("Age");
        rPerson4InTable.Columns.Add("IsMale");
        
        var rPerson4 = rPerson4InTable.NewRow();
        rPerson4["Name"] = "John Woo";
        rPerson4["Address"] = "Zimbabwe";
        rPerson4["Age"] = 56;
        rPerson4["IsMale"] = false;

        // Implement IPerson from any data structure on the fly,
        // using C# 4.0 dynamic features and CodeDom for proxy generation.
        IPerson person1 = xPerson1.Implement<IPerson>();
        IPerson person2 = xPerson2.Implement<IPerson>();
        IPerson person3 = dPerson3.Implement<IPerson>();
        IPerson person4 = rPerson4.Implement<IPerson>();

        // And everything is working through the strongly typed interface
        // without any further tic-tac-toe.
        
        Console.WriteLine(person1.Name);
        Console.WriteLine(person2.Age);

        for (int i = 1; i < 6; i++)
        {
            person2.Age = i * 10;
            Console.WriteLine(person2.Age);
        }

        Console.WriteLine(person3.IsMale + " " + person3.Age++);
        Console.WriteLine(person3.Age);

        Console.WriteLine(person4.Address + ", " + person4.Age--);
        Console.WriteLine(person4.Age);

        // C# 4.0 dynamic is cool!

        Console.ReadKey();
    }
}

How did i achieved this?

1 - Dynamic Proxy Class

We need a proxy class which implements the desired interface through C# 4.0 dynamic feature. For IPerson it looks like this:

internal sealed class PersonProxy : IPerson
{
    internal PersonProxy(dynamic obj)
    {
        Contract.Requires(obj != null);
        this.obj = obj;
    }

    dynamic obj;
    
    public string Name
    {
        get
        {
            return obj.Name;
        }
        set
        {
            obj.Name = value;
        }
    }

    public string Address
    {
        get
        {
            return obj.Address;
        }
        set
        {
            obj.Address = value;
        }
    }

    public int Age
    {
        get
        {
            return obj.Age;
        }
        set
        {
            obj.Age = value;
        }
    }

    public bool IsMale
    {
        get
        {
            return obj.IsMale;
        }
        set
        {
            obj.IsMale = value;
        }
    }
}

With this proxy any object can act as strongly typed IPerson. Ok, but if I have many interfaces, proxy implementation can be a lot of pain. So let’s use CodeDom and C# compiler for this task!

/// <summary>
/// Dynamic Interface proxy class generator.
/// </summary>
internal sealed class DynamicProxyGenerator
{
    const string Prefix = "__DYNAMIC_PROXY_";

    // Cache of generated proxy class types.
    static Dictionary<Type, Type> cache = new Dictionary<Type, Type>();
    
    /// <summary>
    /// The constructor.
    /// </summary>
    /// <param name="interfaceType">Interface type for proxy.</param>
    internal DynamicProxyGenerator(Type interfaceType)
    {
        Contract.Requires(interfaceType != null);
        Contract.Requires(interfaceType.IsInterface);
        Contract.Requires(!interfaceType.IsGenericTypeDefinition);
        
        InterfaceType = interfaceType;
    }

    internal Type InterfaceType { get; private set; }

    private string ProxyClassName
    {
        get { return Prefix + InterfaceType.Name; }
    }

    /// <summary>
    /// Generates the proxy class type.
    /// </summary>
    /// <returns>Proxy class type.</returns>
    internal Type Generate()
    {
        // Generate with cache support.
        Type result;
        lock (cache)
        {
            if (!cache.TryGetValue(InterfaceType, out result))
            {
                result = DoGenerate();
                cache.Add(InterfaceType, result);
            }
        }
        return result;
    }

    private Type DoGenerate()
    {
        // Creating compile unit, namespace and class.
        var unit = new CodeCompileUnit();
        CodeNamespace codeNamespace = new CodeNamespace(InterfaceType.Namespace);
        codeNamespace.Imports.Add(new CodeNamespaceImport("System"));
        var proxyClass = new CodeTypeDeclaration(ProxyClassName);
        proxyClass.IsClass = true;
        proxyClass.TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed;
        codeNamespace.Types.Add(proxyClass);
        unit.Namespaces.Add(codeNamespace);

        // Base is the interface type.
        proxyClass.BaseTypes.Add(new CodeTypeReference(InterfaceType));

        // Proxy class body generation: 
        AddDynamicField(proxyClass);
        AddConstructor(proxyClass);
        AddProperties(proxyClass);
        AddMethods(proxyClass);
        AddEvents(proxyClass);

        // Compile in-memory assembly with C# 4.0 compiler.
        CodeDomProvider provider = CodeDomProvider.CreateProvider("csharp", 
new Dictionary<string, string> { { "CompilerVersion", "v4.0" } }); CompilerParameters options = new CompilerParameters(); options.CompilerOptions = "/target:library /optimize"; options.GenerateExecutable = false; options.GenerateInMemory = true; options.IncludeDebugInformation = false; // Add assembly references. AddRefs(options.ReferencedAssemblies); // Do compile. var results = provider.CompileAssemblyFromDom(options, unit); if (results.Errors.HasErrors) { // If error throw ex with compiler errors in ex.Data. var ex = new InvalidOperationException(
"Cannot create proxy. See provided data 'compilerErrors' for details."); ex.Data["compilerErrors"] = results.Errors; throw ex; } // Compiled assembly has the generated proxy. Done. return results.CompiledAssembly.GetType(InterfaceType.Namespace + '.' + ProxyClassName); } private void AddRefs(StringCollection assemblies) { // Add all useful references. foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { if (asm.GlobalAssemblyCache) { // GAC. assemblies.Add(asm.ManifestModule.FullyQualifiedName); } else if (File.Exists(asm.Location)) { // Non in-memory assemblies. assemblies.Add(asm.Location); } } } private void AddEvents(CodeTypeDeclaration proxyClass) { // Add event wrappring code:
// CodeDom doesn't supports event handlers add/remove syntax. // So add a code snippet for this task. foreach (var ei in InterfaceType.GetEvents()) { var sb = new StringBuilder(); sb.Append("public event "); AppenEventHadlerType(ei.EventHandlerType, sb); sb.Append(' '); sb.Append(ei.Name); sb.Append(" { add { this.obj."); sb.Append(ei.Name); sb.Append(" += value; } remove { this.obj."); sb.Append(ei.Name); sb.Append(" -= value; } }"); proxyClass.Members.Add(new CodeSnippetTypeMember(sb.ToString())); } } private void AppenEventHadlerType(Type type, StringBuilder sb) { // Append C# format of generic type: if (type.IsGenericType) { string name = type.FullName; int pos = name.IndexOf('`'); // ! name = name.Substring(0, pos); sb.Append(name); sb.Append('<'); bool first = true; foreach (var ga in type.GetGenericArguments()) { if (first) { first = false; } else { sb.Append(", "); } AppenEventHadlerType(ga, sb); } sb.Append('>'); } else { sb.Append(type.FullName); } } private void AddMethods(CodeTypeDeclaration proxyClass) { // Add method wrapping code: foreach (var mi in InterfaceType.GetMethods() .Where(mi =>
(mi.Attributes & MethodAttributes.SpecialName)
!= MethodAttributes.SpecialName)) { CodeMemberMethod method = new CodeMemberMethod(); method.Attributes = MemberAttributes.Public | MemberAttributes.Final; method.Name = mi.Name; method.ReturnType = new CodeTypeReference(mi.ReturnType); foreach (var par in mi.GetParameters()) { method.Parameters.Add(
new CodeParameterDeclarationExpression(
par.ParameterType, par.Name)); } var objRef = new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), "obj"); var invoke = new CodeMethodInvokeExpression(objRef, mi.Name, mi.GetParameters()
.Select(p =>
new CodeArgumentReferenceExpression(p.Name))
.ToArray()); if (mi.ReturnType == typeof(void)) { method.Statements.Add(invoke); } else { method.Statements.Add(
new CodeMethodReturnStatement(invoke)); } proxyClass.Members.Add(method); } } private void AddProperties(CodeTypeDeclaration proxyClass) { // Add property wrapping code: foreach (var pi in InterfaceType.GetProperties()) { CodeMemberProperty property = new CodeMemberProperty(); property.Attributes = MemberAttributes.Public | MemberAttributes.Final; property.HasGet = pi.CanRead; property.HasSet = pi.CanWrite; property.Type = new CodeTypeReference(pi.PropertyType); property.Name = pi.Name; var idxPars = pi.GetIndexParameters(); if (idxPars.Length == 0) // not indexer { if (property.HasGet) { property.GetStatements.Add(new CodeMethodReturnStatement( new CodePropertyReferenceExpression( new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), "obj"), pi.Name))); } if (property.HasSet) { property.SetStatements.Add(new CodeAssignStatement( new CodePropertyReferenceExpression( new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), "obj"), pi.Name), new CodeArgumentReferenceExpression("value"))); } } else // indexer { property.Name = pi.Name; foreach (var par in idxPars) { property.Parameters.Add(
new CodeParameterDeclarationExpression(
par.ParameterType, par.Name)); } if (property.HasGet) { property.GetStatements.Add(new CodeMethodReturnStatement( new CodeIndexerExpression( new CodePropertyReferenceExpression( new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), "obj"), pi.Name), idxPars.Select(p =>
new CodeArgumentReferenceExpression(p.Name))
.ToArray()))); } if (property.HasSet) { property.SetStatements.Add(new CodeAssignStatement( new CodeIndexerExpression( new CodePropertyReferenceExpression( new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), "obj"), pi.Name), idxPars.Select(p =>
new CodeArgumentReferenceExpression(p.Name))
.ToArray()), new CodeArgumentReferenceExpression("value"))); } } proxyClass.Members.Add(property); } } private void AddDynamicField(CodeTypeDeclaration proxyClass) { // Create the 'dynamic obj' field. CodeMemberField dynamicField = new CodeMemberField(); dynamicField.Attributes = MemberAttributes.Private; dynamicField.Name = "obj"; dynamicField.Type = new CodeTypeReference("dynamic"); proxyClass.Members.Add(dynamicField); } private void AddConstructor(CodeTypeDeclaration proxyClass) { // Declare the constructor CodeConstructor constructor = new CodeConstructor(); constructor.Attributes = MemberAttributes.Public | MemberAttributes.Final; // Add parameters. constructor.Parameters.Add(new CodeParameterDeclarationExpression("dynamic", "obj")); // Add field initialization logic CodeFieldReferenceExpression objReference = new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), "obj"); constructor.Statements.Add(new CodeAssignStatement(objReference, new CodeArgumentReferenceExpression("obj"))); proxyClass.Members.Add(constructor); } }

Yay! This class generates the same PersonProxy code above, and returns the type for the proxy class, e.g.:

Type personProxyClassType = new DynamicProxyGenerator(typeof(IPerson)).Generate();

Ok, but what about custom data types?

2 – XElement, IDictionary<string, object> and DataRow as dynamic object

In .NET 4.0 we can specialize how an object dispatched dinamically through System.Dynamic.DynamicObject class. Simply declare a class which inherits from DynamicObject and everything is working like a charm.

To support dynamic data access we have to override TryGetMember and TrySetMember methods. Unfortunately those methods ain’t got any information about requested member type, only name:

public override bool TryGetMember(GetMemberBinder binder, out object result)

public override bool TrySetMember(SetMemberBinder binder, object value)

And binders only have one useful property whitch is the member name. But we need member type, because we have to convert member values, so we need a base type for this task:

/// <summary>
/// DynamicObject with ProperyInfo support on get and set member operation.
/// </summary>
public abstract class DynamicObjectWithPropertyInfos : DynamicObject
{
    // Cache.
    static Dictionary<Type, Dictionary<string, PropertyInfo>> cache = 
        new Dictionary<Type, Dictionary<string, PropertyInfo>>();
    
    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="targetType">Target type.</param>
    protected DynamicObjectWithPropertyInfos(Type targetType)
    {
        Contract.Requires(targetType != null);

        this.TargetType = targetType;

        Analyze();
    }

    protected Type TargetType { get; private set; }

    private void Analyze()
    {
        // Store prop. infos.
        Dictionary<string, PropertyInfo> cacheEntry;
        lock (cache)
        {
            if (!cache.TryGetValue(TargetType, out cacheEntry))
            {
                cache.Add(TargetType, TargetType.GetProperties()
                     .ToDictionary(pi => pi.Name));
            }
        }
    }

    protected PropertyInfo GetPropertyInfo(string memberName)
    {
        // Get property info for member from cache.
        Dictionary<string, PropertyInfo> cacheEntry;
        lock (cache)
        {
            if (cache.TryGetValue(TargetType, out cacheEntry))
            {
                PropertyInfo pi;
                if (cacheEntry.TryGetValue(memberName, out pi)) return pi;
            }
        }
        return null;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        // Call new method with prop. info parameter first:
        var pi = GetPropertyInfo(binder.Name);
        if (pi != null)
        {
            bool ok = TryGetMember(binder.Name, pi, out result);
            if (ok) return true;
        }
        return base.TryGetMember(binder, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        // Call new method with prop. info parameter first:
        var pi = GetPropertyInfo(binder.Name);
        if (pi != null)
        {
            bool ok = TrySetMember(binder.Name, pi, value);
            if (ok) return true;
        }
        return base.TrySetMember(binder, value);
    }

    #region New Methods

    protected virtual bool TrySetMember(string name,
        PropertyInfo propertyInfo,
        object value)
    {
        return false;
    }

    protected virtual bool TryGetMember(string name,
        PropertyInfo propertyInfo,
        out object result)
    {
        result = null;
        return false;
    }

    #endregion
}

Ok. The specialized versions:

XElement

internal sealed class DynamicXElement : DynamicObjectWithPropertyInfos
{
    internal DynamicXElement(XElement element, Type interfaceType)
        : base(interfaceType)
    {
        Contract.Requires(element != null);

        this.element = element;
    }
    
    XElement element;

    protected override bool TryGetMember(string name, 
PropertyInfo propertyInfo,
out object result) { var me = element.Element(name); if (me != null) { result = Convert.ChangeType(
me.Value,
propertyInfo.PropertyType,
CultureInfo.InvariantCulture); return true; } var ma = element.Attribute(name); if (ma != null) { result = Convert.ChangeType(
ma.Value,
propertyInfo.PropertyType,
CultureInfo.InvariantCulture); return true; } result = null; return false; } protected override bool TrySetMember(string name,
PropertyInfo propertyInfo,
object value) { var me = element.Element(name); if (me != null) { me.Value = value != null ? Convert.ToString(value, CultureInfo.InvariantCulture) : null; return true; } var ma = element.Attribute(name); if (ma != null) { ma.Value = value != null ? Convert.ToString(value, CultureInfo.InvariantCulture) : null; return true; } return false; } public override string ToString() { return element.ToString(); } }

IDictionary<string, object>

internal sealed class DynamicDictionary : DynamicObjectWithPropertyInfos
{
    internal DynamicDictionary(IDictionary<string, object> dict, Type interfaceType)
        : base(interfaceType)
    {
        Contract.Requires(dict != null);

        this.dict = dict;
    }

    IDictionary<string, object> dict;

    protected override bool TryGetMember(string name, 
PropertyInfo propertyInfo,
out object result) { object v; if (dict.TryGetValue(name, out v)) { result = v; return true; } result = null; return false; } protected override bool TrySetMember(string name,
PropertyInfo propertyInfo,
object value) { dict[name] = value; return true; } }

DataRow

internal sealed class DynamicDataRow : DynamicObjectWithPropertyInfos
{
    internal DynamicDataRow(DataRow row, Type interfaceType)
        : base(interfaceType)
    {
        Contract.Requires(row != null);

        this.row = row;
    }

    DataRow row;

    protected override bool TryGetMember(string name, 
PropertyInfo propertyInfo,
out object result) { result = row[name]; result = Convert.ChangeType(
result,
propertyInfo.PropertyType,
CultureInfo.InvariantCulture); return true; } protected override bool TrySetMember(string name,
PropertyInfo propertyInfo,
object value) { row[name] = value; return true; } }

Yo! Almost ready.

3 - Implement<T> extension methods

public static class DynamicInterfaceExtensions
{
    public static T Implement<T>(this object obj)
    {
        Contract.Requires(typeof(T) != null);
        Contract.Requires(typeof(T).IsInterface);
        Contract.Requires(!typeof(T).IsGenericTypeDefinition);
        Contract.Requires(obj != null);

        if (obj is T) return (T)obj;

        var generator = new DynamicProxyGenerator(typeof(T));
        var proxyType = generator.Generate();
        var proxy = Activator.CreateInstance(proxyType, (dynamic)obj);
        return (T)proxy;
    }

    public static T Implement<T>(this XElement element)
    {
        Contract.Requires(typeof(T) != null);
        Contract.Requires(typeof(T).IsInterface);
        Contract.Requires(!typeof(T).IsGenericTypeDefinition);
        Contract.Requires(element != null);

        var generator = new DynamicProxyGenerator(typeof(T));
        var proxyType = generator.Generate();
        var proxy = Activator.CreateInstance(proxyType, new DynamicXElement(element, typeof(T)));
        return (T)proxy;
    }

    public static T Implement<T>(this IDictionary<string, object> dictionary)
    {
        Contract.Requires(typeof(T) != null);
        Contract.Requires(typeof(T).IsInterface);
        Contract.Requires(!typeof(T).IsGenericTypeDefinition);
        Contract.Requires(dictionary != null);

        var generator = new DynamicProxyGenerator(typeof(T));
        var proxyType = generator.Generate();
        var proxy = Activator.CreateInstance(proxyType, new DynamicDictionary(dictionary, typeof(T)));
        return (T)proxy;
    }

    public static T Implement<T>(this DataRow row)
    {
        Contract.Requires(typeof(T) != null);
        Contract.Requires(typeof(T).IsInterface);
        Contract.Requires(!typeof(T).IsGenericTypeDefinition);
        Contract.Requires(row != null);

        var generator = new DynamicProxyGenerator(typeof(T));
        var proxyType = generator.Generate();
        var proxy = Activator.CreateInstance(proxyType, new DynamicDataRow(row, typeof(T)));
        return (T)proxy;
    }
}

That’s all folks!

Maybe this code will be useful for mocking or WPF data binding scenarios.

VS 2010 Beta 1 solution available here.

Enjoy.

May 30

.NET 4 – Contracts

Nem célom bemutatni, hogy működik, hogyan kell használni, hiszen a dokumentációban minden benne van. Viszont azt a kérdést, hogy: “Má’ meg’ mi ez, minek még egy macerás dolgot megtanulni, mire fogom én ezt az izét egyáltalán felhasználni, kell-e nekem ez bármire?” mindenképpen szeretném egy kicsit körbe járni, hiszen nekem sem volt teljesen világos, amíg igazán bele nem ástam magam a dolog rejtelmeibe.

Az írás a legújabb, .NET 4 / VS 2010-es változatról fog szólni. Igaz, hogy a .NET 4.0 része lett a Contracts (System.Diagnostics.Contracts), azonban a szükséges eszközök még nincsenek benne a VS bétában, így le kell tölteni a cuccot: innen. Valószínűleg hiányos még a béta, hiszen a Contract.Requires<T>(…) definíció hiányzik. Én ezt úgy oldottam meg, hogy tettem egy TODO kommentet azon Requires hivatkozások elé, ahol ilyet kell majd alkalmazni később. A működésre ez nincs hatással, max. runtime módban nem argumentum kivételeket hanem contract kivételeket fog dobni. (Ha ez az utóbbi pár mondat nem volt érthető, akkor javaslom, hogy olvassátok el a linkelt dokumentációt, ugyanis anélkül az egész bejegyzés sem lesz érthető.)

WTF is Contracts?

Ebben az egész szerződéses dologban a világon semmi újdonság nincs. Furcsán hangzik, de ha közületek bárki írt már olyan kódot, amit újrafelhasználhatóvá akart tenni önmaga vagy mások számára, akkor bizony egy rakás olyan kódot írt, ami különböző programmodulok együttműködésének szabályait rögzítette, és ez nem más mint a Contract. Továbbá mindenki írt már olyan internal segédosztályt, amit a projekt különböző részein újra felhasznált. Ezek - internal osztályok révén - egy magasabb bizalmi szinten kerültek implementálásra, így az argumentum és a környezeti állapot definícióit készpénznek vették működésük során. De hamar felejt az ember, a doksi sincs mindig kéznél, így ezeket a az állapotokat Debug.Assert() állításokba zártuk, így biztosak lehettünk benne, hogy ha később nem teljesítjük az internal objektum felhasználásának feltételeit, akkor a debugger erről egy nagy csúnya ablakban tájékoztat minket. Vázolom mindkét esetet:

Csinálunk egy osztályt, ami egy bizonyos felületben rögzített objektum működtetését végzi, felhasználva azt. A felületet természetesen mi írjuk meg, és az osztályunkat felhasználó más fejlesztőknek az a dolga, hogy ezt implementálják, így biztosítva, hogy a kódunkkal együtt tudjon működni az övé. Legyen ez a felület:

public interface IFoo
{
    double State { get; }

    int GetSomeInterestingValue();
}

Piszkos egyszerű a példa kedvéért. Az ilyen IFoo implementumok képesek megmondani a státuszukat, ami egy valós szám, és képesek adni egy érdekes egész számot, mondjuk, ezt úgy számolják ki hosszan, ezért ez egy metódus. Mi tervezzük a rendszert, így a felület által meghatározott objektum számunkra szükséges állapotait valamilyen módon elő kell írnunk. Szabjuk ki például, hogy a státusz nem lehet negatív, és a 0 pedig nem lehet érdekes szám. A mi rendszerünket használó fejlesztőknek ezt valahogy jeleznünk kell, de arról is gondoskodnunk kell, hogy a hibás számok ne tudjanak a mi rendszerünkbe beszivárogni nem várt működést eredményezve, így ezek megjelenése esetén azonnal kivételt kell dobnunk, hiszen ennek (eddig) ez (volt) az egyetlen épkézláb módja.

Tehát a mi rendszerünkben közvetlenül nem használhatjuk a felületet, kell írnunk egy proxy-t, ami biztosítja, hogy azon keresztül hülyeség ne jöjjön be hozzánk:

internal sealed class FooProxy : IFoo
{
    internal FooProxy(IFoo foo)
    {
        Debug.Assert(foo != null);
        this.foo = foo;
    }

    IFoo foo;

    public double State
    {
        get
        {
            double state = foo.State;
            if (state < 0.0) 
                throw new InvalidOperationException(foo.GetType().ToString() + 
                    ".State value is lower than 0.");
            return state;
        }
    }

    public int GetSomeInterestingValue()
    {
        int iv = foo.GetSomeInterestingValue();
        if (iv == 0)
            throw new InvalidOperationException(foo.GetType().ToString() + 
                ": 0 is not interesting.");
        return iv;
    }
}

Majd ennek segítségével használjuk az IFoo objektumokat, így idiótabiztos lesz a rendszerünk. Tulajdonképpen egy szerződést, vagyis egy Contract-et rögzítünk e kis kódrészlet által. De itt már magunkkal szemben is rögzítünk egy szerződés kitételt, nevezetesen, hogy a proxy-nak nem adhatunk át null objektumot. Ez egy internal osztály, csak mi használjuk, debugoljuk, így teljesen felesleges lassítás lenne, ha argumentumellenőrzést végeznénk a konstruktorban, bőven elég ide a debug módú állítás, ami csak akkor fordul, ha Debug módban fordítjuk a projektet.

Még mindig nem csináltunk mást, mint azt a felületet rajzolgattuk, amire épül az osztályunk. Legyen ez egy ilyen:

public abstract class FooDumper
{
    protected FooDumper(IFoo foo)
    {
        if (foo == null) throw new ArgumentNullException("foo");
        safeFoo = new FooProxy(Foo = foo);
    }

    FooProxy safeFoo;

    public IFoo Foo { get; private set; }

    public override string ToString()
    {
        return Foo.ToString() +
            " State is " +
            safeFoo.State +
            " and " +
            safeFoo.GetSomeInterestingValue() +
            " is interesting.";
    }

    public void Dump()
    {
        using (var w = DoGetWriter())
        {
            w.WriteLine(ToString());
        }
    }

    private TextWriter DoGetWriter()
    {
        var w = GetWriter();
        if (w == null)
            throw new InvalidOperationException(GetType() +
                ".GetWriter() has returned null.");
        return w;
    }

    protected abstract TextWriter GetWriter();
}
public sealed class FooConsoleDumper : FooDumper
{
    public FooConsoleDumper(IFoo foo)
        : base(foo)
    {
    }

    protected override TextWriter GetWriter()
    {
        return Console.Out;
    }
}

Ez egy absztrakt (példa megvalósítás mellékelve), ami kiírja egy IFoo adatait egy olyan TextWriter-re, amit majd a megvalósítása fog meghatározni (tudom, konstruktorban át lehet adni egy writert, akkor nem kell absztrakt, de ez csak egy illusztráció). Absztrakt, örökléssel újrahasznosítható kódról van szó, így szintén rögzíteni kell szerződésekben az idiótabiztosságot. Előszőr is, null-lal nem inicializálható az objektum, ezt a konstruktorban kikötjük, Arg.Ex., ha nem teljesül. Másodszor, az objektum egy tetszőleges IFoo-n működik, így be kell csomagolni a védő proxy-ba használat előtt, hogy a mi rendszerünkre ne legyen hatással a hibás implementáció (magyarul a hiba detektálása pillanatában kivételt szórunk). Viszont van itt nekünk egy absztrakt metódusunk, amit bárki megvalósíthat, és bármilyen TextWriter objektumot dobhat a mi rendszerünk felé. Viszont a mi rendszerünk csak úgy tud működni, ha ez nem null, így ezt a szerződés kitételt szintén egy ellenőrző-kivételgeneráló kóddal rögzítjük. Ez felesleges ellenőrzésnek tűnhet, azonban a mi cuccunkat használó emberkéknek nagyon nem lesz mindegy, ha azt olvassák a kivétel szövegében, hogy: “NullReferenceException in más elcseszett kódjában valahol, baszod!”, vagy azt, hogy ne küldjél már null-t a GetWriter-ren keresztül, légyszíves. Ez csak egy kis nonszensz példa, de ezek a nem ellenőrzött lukak a kódban, ahol a mi hanyagságunk következtében beszivároghatnak rossz adatok, olyan hibákhoz is vezethetnek, amik teszem azt, több milliós veszteséget okoznak a megrendelőnknek.

Ergo eddig is írtunk szerződéseket a kódunkba, azonban:

  • Az összes szerződés runtime. Ezek fordításkor nem játszanak szerepet, addig a dokumentáción kívül semmi nem figyelmeztet a létükre, amíg el nem követi valaki a hibákat.
  • A szerződések bele vannak égetve a kódba, azokat onnan ember ki nem veszi. Például csinálunk egy osztálykönytárt, ami egy előzőleg általunk készített osztálykönyvtáron alapul, de publikusan elfedi azt. Így a mag publikus metódusain lévő argumentumellenőrző kód teljesen feleslegesen fog lefutni, ha egyszer ezt az új réteggel elfedjük, így lassabb működés lesz az eredmény annál, amilyen lehetne.

Ennek gatyába rázására találták ki az okosok a Contracts nevű új .NET szolgáltatást. Nézzük!

Contracts

Definiáljuk az előbbi felületet, azonban ezúttal saját ellenőrző proxy helyett az új lehetőségekkel:

[ContractClass(typeof(IFooContract))]
public interface IFoo
{
    double State { get; }

    int GetSomeInterestingValue();
}

[ContractClassFor(typeof(IFoo))]
sealed class IFooContract : IFoo
{
    double IFoo.State
    {
        get
        {
            Contract.Ensures(Contract.Result<double>() >= 0);
            return 0;
        }
    }

    int IFoo.GetSomeInterestingValue()
    {
        Contract.Ensures(Contract.Result<int>() != 0);
        return 0;
    }
}

Ez pontosan ugyan az a szerződés, mint az első példában a proxy-ban, azonban itt az egész ellenőrzési procedúrát az új rendszerre bízzuk, mi csak definiáljuk a szükséges szerződéseket a felülethez. Erre szolgál a két attribútum. A szerződést megvalósító osztály csak egy dummy, nem kell semmi értelmes működést implementálni, csak a Contract definíciókra van szükség. Én megadtam, hogy a State visszatérési értéke >= 0, a GetSomeInterestingValue() pedig 0-val nem térhet vissza.

Innentől kezdve ha bárki az én kódomban definiált felületet megvalósítja, és a kódja ezen rögzített szabályoknak nem tesz eleget, akkor statikus módban ordenáré nagy Warning-eket, runtime módban pedig Debug Assertion Failed üzeneteket vagy kivételeket fog kapni a bámuló mafla pofájába. Az a szép az egészben, hogy ezt lehet konfigurálni. Fordíthatunk olyan debug assembly-t, ami Assertion Faileket szór, de fordíthatunk olyan Debug assembly-t, ami kivételeket szór, de fordíthatunk olyan Release assembly-t, ami csak a publikus metódusok előtt ülő argumentumellenőrzéseket (Contract.Requieres) végzi runtime módban, így szabadítva meg minket az állandó argumentumellenőrzés-kivétel szórás kód írásától. A legszebb viszont az, hogy a szerződéseket külön assembly-be is fordíthatjuk (ilyenkor az eredeti kódból a fordító minden ellenőrzést kigyomlál), így lehetővé tehetjük, hogy a kódunkat felhasználó fejlesztő maga döntse el, hogy milyen módú és szintű ellenőrzést kíván használni a mi kódunkban is – mindezt anélkül, hogy nekünk ehhez bármi spéci dogot kellett volna tennünk! Döbbenetesen hatékony rendszer! (Pontos részletek a dokumentációban.)

Nézzük tovább a példát:

[ContractClass(typeof(FooDumperContract))]
public abstract class FooDumper
{
    protected FooDumper(IFoo foo)
    {
        Contract.Requires(foo != null);
        this.foo = foo;
    }

    IFoo foo;

    public IFoo Foo
    {
        get { return foo; }
    }

    public override string ToString()
    {
        return Foo.ToString() +
            " State is " +
            Foo.State +
            " and " +
            Foo.GetSomeInterestingValue() +
            " is interesting.";
    }

    public void Dump()
    {
        using (var w = GetWriter())
        {
            w.WriteLine(ToString());
        }
    }

    protected abstract TextWriter GetWriter();

    [ContractInvariantMethod]
    protected void ObjectInvariant()
    {
        Contract.Invariant(foo != null);
    }
}

[ContractClassFor(typeof(FooDumper))]
abstract class FooDumperContract : FooDumper
{
    protected FooDumperContract() : base(null) { }
    
    protected override TextWriter GetWriter()
    {
        Contract.Ensures(Contract.Result<TextWriter>() != null);
        return null;
    }
}

Pontosan ugyan azzal a módszerrel szerződök absztrakt metódusra, mint ahogy a felületnél tettem, megmondom, hogy ez nem adhat vissza null-t. A konstruktorban az argumentum ellenőrzét is átírtam szerződésre, így konfigurálható lesz a Project - Code Contracts fülön, hogy mi legyen az argumentum ellenőrzés módja (Debug Assert, Exception, Static), nem égetem be a fix ellenőrzök-kivételt dobok módszert. Továbbá az osztályom helyes működéséhez szükség van arra, hogy a Foo tulajdonság soha se legyen null, ezért beírom egy Object Invariant szerződésbe, hogy a kiolvasott mező soha nem lehet null, ott van az ilyen dolgok helye. Mi értelme van ennek, ha egyszer a kontruktorban már biztosítottam, hogy nem jöhet be null? Hát a saját ökörségem kivédésére! Ugyanis a Foo egy privát írható tulajdonság. Jöhetek egy nap halál másnapos fejjel, és írhatok ebbe az osztályba valahol olyan kódot, ami miatt a Foo egyszer csak null lesz. De mivel az elején szerződésben kikötöttem (magamnak és a kollégáknak, akik a kódomban esetleg turkálhatnak), hogy ez nem lehet null, ezért kapok a kócos arcomba egy marha nagy Warning-et már fordításkor, így látni fogom, hogy mekkora marhaságot írtam, és inkább vissza fekszem aludni még egy keveset.

Összefoglalásul tehát: a Contracts rendszerrel kötött szerződések ellenőrzése nem csak runtime, hanem fordítási idejű is lehet. Külön lehet szabályozni a publikus metódusokon végzett argumentmellenőrzések módját. A szerződéseinből a rendszer képes automatukusan dokumentációt generálni. Ráadásul az egész móka kilóra kevesebb kód, mintha nem használnánk az új rendszert, hanem a régi módszerekkel dolgoznánk (álá első példa)!!! Nagyon-nagyon-nagyon fasza!

Még azt hozzá lehet tenni, hogy a doksiban látszik, hogy szinte minden feltételre lehet szerződéseket kötni, amik csak előfordulhatnak egy rendszer állapotában, szóval nagyon rugalmas dologról van szó. A teljes .NET 4 osztálykönyvtár Code Contract alapú szerződéseket tartalmaz, így például, ha írunk egy saját kódot, ami IList-re működik, akkor egész addig kapjuk a Warning-eket, amíg nem teljesítjük az összes ezzel kapcsolatos feltételt a kódunkban (például biztosan nem hivatkozunk rossz indexre, ilyesmi). Nagyon állat az egész, öt raklap hibát meg lehet spórolni vele már fordítási időben is.

Sajnos a VS 2010 Beta 1 még nem az igazi, a figyelmeztetések gyakran hibásan jelennek meg. Ha rákattintunk egyikre-másikra, akkor a kódnavigáció helyett csak egy hibaüzi ablak fogad, és hasonló betegségek vannak, de pontosan erre van a béta teszt és a Forum feedback. Részemről lelkesen küldözgetem a bugreportokat, annyira várom már, hogy egy valóban használható, stabil rendszer szülessen ebből a végén. A PEX támogatást még nem teszteltem, mert 2 napja jelent meg VS 2010-s verzió, nem volt időm kipróbálni.

Néhány tipp a végére:

  • Generikus osztályok és felületek szerződéseit: így kell írni.
  • A VS 2010-ben van pár nagyon hasznos Code Snippet, érdemes azokat használni (cr, ce, cim, cintf, …).
  • Igyatok egy sört az MS DevLabs-es srácok egészségére minden kedden pontban éjfélkor Redmond felé fordulva, talán akkor lesz egy jobb Beta 1-es verzió hamarosan. :)
May 28

NeoComp Framework

Tisztelettel jelentem, hogy sikerült elérnem azt a 3 mérföldkövet, amit egy éve kitűztem magamnak:

1. mérföldkő: 7 Segment Decoder – Genetic Algorithm

Ez egy igen bonyolult áramkör. Úgy működik, hogy a 4 bináris bemeneten bevezetjük a számokat 0–tól 9–ig (0,0,0,0; 0,0,0,1; 0,0,1,0; 0,0,1,1 .. 1,0,0,1) és a kimeneten lévő 7 bit fogja vezérelni a számkijelző 7 darab LED-jét, ami megjeleníti a kívánt számokat 0-9-ig. Így: kép.

Aki tanult az iskolában logikai áramkör tervezést, az biztos tudja, hogyan kell egy ilyet megtervezni. Itt található erről egy összefoglaló: 7 segment decoder.

Tömören: vesszük az egyes kimeneti biteket, és mindegyeikhez felírjuk az igazságtáblát. Ezt beírjuk 1-1 Karnaugh táblába, onnan csoportonként kiolvassuk a mintermeket. A kiolvasott mintermek AND műveletek, köztük pedig OR művelet van. Ezekből NOT, AND és OR kapukkal fel lehet rajzolni a megfelelő áramkört. Ha az adott technológia által meghatározott kapukészlet nem teszi lehetővé az AND és OR kapuk használatát, akkor logikai azonosságok segítségével felbontjuk a műveleteket alapműveletekre (NOR vagy NAND) és ezekből írjuk fel az adott LED-et vezérlő áramkör részt. Ezzel elbohóckodunk még hatszor, majd összeillesztjük az áramkör egyes részeit, és meg is vagyunk. Órákig el lehet ezzel pöcsörészni, és az áramkör szerkezete attól függ, hogy mennyire hatékonyan voltunk képesek kiolvasni az egyszerűsítéseket a táblázatból.

Így kapunk a végén egy ilyet: 7 segment decoder circuit.

Azonban ez az áramkör még mindig nem optimális. Létezhetnek olyan egyszerűsítési lehetőségek, amiket az egyes LED-eket vezérlő áramkörök bizonyos részeinek összevonásával érhetnénk el. Na, itt kezdődik a magasiskola, ehhez más nem lesz elég az öreg Karnaugh módszere, gyakorlatilag a fél matematikát be kell ahhoz vetni, hogy minél kevesebb kapuból és csatlakozásból álló (aka jóval olcsóbb és gyorsabb) áramkör legyen az eredmény.

Ha az áramkör tervezésére GA-t használnánk, akkor az algoritmus már alapból olyan áramköröket fog létrehozni, ahol az egyes LED-ek vezérlése közös logikai hálózaton keresztül működik, így kialakulhatnak azok az egyszerűsítések, amik alapján jóval hatékonyabb logikai hálózat jöhet létre mint az ember által tervezett. Ezt mindenki tudja, azonban:

Google: seven segment circuit genetic

Semmi nem van, még csak ötvened ilyen bonyolult áramkörhöz sem. Csak a NASA és az MS Research oldalán találtam elméleti fejtegetéseket arról, hogy hogyan lehetne megcsinálni azt, hogy nagyon bonyolult számítási hálózatokat fejlesszünk GA segítségével. Nekem viszont ez gyakorlatban is sikerült! A számítás másfél óráig tartott, az eredmény egy 14 logikai kapuból álló áramkör lett (minden létező kapu felhasználható volt a tesztben). Ezt össze lehet vetni az előbb linkelt áramkörrel, ahol 56 kapu kell a működéshez, ha jól számolom. Kipróbáltam Espresso ellen is, 8 input XOR, NAND logika. Espresso: 28 NAND2, GA (2 perc futás): 21 NAND2.

Az egészhez abszolút nem kellett erőmű, sima 2 magos P4-en futott a teszt, és közben filmet is néztem, hogy ne unatkozzam. Ha ezt a rendszert egy igazi bivaly gépen futtatnám, akkor sokkal összetettebb áramkörök tervezésére is képes lenne a dolog, nulla költségen (nem kell spec. szoftver, 8 szaki és 5 matematikus, csak az igazságtáblát kell megadni az egészhez, oszt’ elmenni sörözni amíg kiszámolja). Itt a VSDL processz teljesen kimarad, a GA egyből a logikai szintézist végzi, maximálisan optimizálva az eredményt.

Ez most már nagyon OK. Jelenleg azon gondolkodom, hogyan lehetne GA-val (vagy más globális optimizációval) az áramkör layoutot is megtervezni, ugyanis a hatékony szilikonra ültetés kiagyalása is egy külön művészet. Ehhez is készül a rendszerem (Self Organizing Layout System – by chikk, kemény lesz).

2. mérföldkő: Neuronhálózat tervezés – Genetic Algorithm

Vannak olyan neuronhálózattal kapcsolatos feladatok, amikor egyszerűen a szabvány multilayer hálózat nem tud működni. Egy feladat:

0.0 0.0 0.0 = 0.0 0.0
0.0 0.0 1.0 = 0.0 1.0
0.0 1.0 0.0 = 0.0 1.0
0.0 1.0 1.0 = 1.0 0.0
1.0 0.0 0.0 = 0.0 1.0
1.0 0.0 1.0 = 1.0 0.0
1.0 1.0 0.0 = 1.0 0.0
1.0 1.0 1.0 = 1.0 1.0

Ez nem más mint egy Full Adder igazságtábla, vagyis azt szeretnénk, hogy a neuronhálózat egy logikai hálózatként működjön. Ebben az eseten az az érdekes, hogy nincs folytonosság az egyes I/O tartomány elemek közt, vagyis nincs olyan interpolálódó függvény, ami a fenti esetet leírná. Ennek a példának van egy minimuma (MSE: 0.1675), ahonnan a Jó Isten algoritmusa sem lenne képes előrébb vinni a megoldást multilayer neuronhálózat esetén (ki lehet próbálni). Ez a feladat tehát egy nagyon speciális classification probléma, ilyet nem csak logikai műveletekkel, hanem általános példával is fel lehet írni, és ilyen esetben egyszerűen a feladat szabvány neuronhálózattal nem megoldható. Arra van szükség, hogy magát a hálózat szerkezetet alakítsuk, így nem csak a transzfer folyamat, hanem a szinapszisok által létesített útvonalak határozzák meg az osztályozás működését a hálózatban. Ennek egyik módja a GA. Nekem sikerült elérni, hogy a fenti feladatot 0 hibával megoldó neuronhálózat 1 percen belül létrejöjjön, és ehhez mindössze 5 neuronra és 16 kapcsolatra van szükség. Hasonló problémákhoz alkalmazzák az okosok az Ant Colony optimizációt, próbáltam én is, de a GA megoldásom ezerszer gyorsabb, és sokkal kisebb hálózatot eredményez.

Az egész mókát pontosan ugyan az az algoritmus hajtja mint a logikai hálózattervezést. Ezt úgy sikerült megoldanom, hogy mind a GA mind pedig a számítási hálózat szimulációs rendszer teljesen generikus, néhány felületet kell implementálni, néhány generikus absztrakt osztályt kell aktualizálni, és gyakorlatilag bármilyen megoldáshoz felhasználható e két ismertetett problématípuson kívül is.

3. mérföldkő: Gradient Descent sebességének és hatékonyságának túlszárnyalása: Quantum Stabilizer Algorithm (ez lett aztán a neve)

Jelenleg a leggyorsabb feed forward multilayer neuronhálózat tréning módszer a Gradient Descent számításon alapszik. Ilyen például a Back Propagation, a Conjugate Gradient Method és a Scaled Conjugate Gradient. E két utóbbi egy újabb fejlesztés, valamivel gyorsabbak mint a BP. Ezek mind úgy működnek, hogy a hiba függvény terében lépésenként egyre közelebb jutnak a legközelebbi lokális minimumhoz. Lokális optimizációk, a hiba vissza van vezetve az egyes együtthatókhoz, ahol csak számolgatni kell, és az egész működik. Előnyük, hogy jóval gyorsabbak tudnak lenni mint a globális optimizációk (GA, *.Annealing, …), hátrányuk, hogy sokkal hajlamosabbak beleragadni egy lokális minimumba, ahonnan aztán se előre se hátra, a tréning folyamat befagy. Ezért általában úgy használják ezeket, hogy egy globális optimizáció cost kiértékelése előtt lefuttatnak az adott változaton n lépést egy nagyon gyors lokális optimizációból, így a két módszer előnyeit lehet ötvözni.

Az én célom az volt, hogy találjak egy olyan globális optimizációt, ami képes ugyan arra a sebességre mint a lokális változatok, így egy kalap alatt el tudom érni a jóval bonyolultabb, ötvözött tréning módszerek előnyeit. Ezt sikerült, a megoldásom neve: Quantum Stabilizer Algorithm (nem keverendő össze a Quantum Annealing algoritmussal). Erről annyit mondok, hogy 10 neuronos iksznégyzet problémánál pontosan ugyan annyi idő alatt van a QSA MSE 0.001-en mint a BP MSE 0.02-n (SL tartomány: –5, –4, –3, –2, –1, 0, 1, 2, 3, 4, 5), tehát több mint egy nagyságrenddel gyorsabb a módszerem a BP-hez képest. Globális mivoltából adódóan sokkal kevésbé hajlamos beragadni az algoritmus egy lokális minimumba, XOR problémára tesztelve ez kevesebb mint a futások 2%-a (ellenben a BP ~20%-ával).

Az egészben az a vicc, hogy ha valaki megnézné a QSA forráskódját (93 sor), akkor a pofámba röhögne, annyira egyszerű a dolog. :)

NeoComp Framework

A.K.A.: Ami ezt az egészet hajtja. 4 nagy egység lesz a v1-ben: Networks, Optimization (globális – GA, QSA, Ant Colony, Simulated/Quantum Annealing, Particle Swarm; lokális – BP, CGM, SCG, n-opt), SOLS (ez az önszervező layout rendszer, WPF-hez is lehet majd használni, részletek később), Applications (előre kidolgozott alkalmazási minták az első három kombinációira). v2-től tervezem az aszinkron hálózatok, WCF fölött kihelyezett alhálózatok és a spiking neurális hálózatok támogatását. Terveim szerint a v2 már mindent fog tudni, ami itt van (jó könyv, tudom ajánlani, bár egy kicsikét túlhaladott, lévén 96-os), kiegészítve a saját algoritmusaimmal persze.

Mire lesz ez jó?

Nem csak bitkergetésre, hanem a leghétköznapibb alkalmazásokban is fel lehet ezeket a dolgokat használni. Van a MS Research oldalán egy projekt:

Infer.NET

Ez egy Bayesian logikára épülő döntési hálózatokat szimuláló rendszer. Ebből például Bayesian Point Machine-eket lehet építeni, amit aztán fel lehet használni classification problémákra. Magyarul: osztályozni dolgokat on-the-fly.

Aztán meg mire jó ez?

Ha letöltitek a demot, akkor lesz benne egy csomó teszt projekt (érdemes kipróbálni, nagyon állat). Az egyik ilyen az Image Classifier Demo, ami egy WPF-es alkalmazás. Nagyon érdekes a dolog. Arról szól, hogy le van dobálva huszonix darab kép. Van felül 2 terület, ahová ezeket a képeket fel lehet dobni. Az piros terület azt jelenti, hogy az a kép szerinted rossz, a zöld terület meg azt, hogy szerinted az a kép jó: kép.

Ahogy dobálod a képeket, a program alul megpróbálja az ízlésed szerint elválogatni a lent maradó képeket, így segítve a további osztályozási munkát. Amilyen egyszerű, olyan nagyszerű az egész, én fél órát képes voltam eljátszadozni vele mikor először letöltöttem. Ezt a módszert aztán bármilyen adatmenedzser alkalmazáshoz fel lehet használni, nem csak ilyen képrendezgetési feladatra. Bármit lehet osztályozni anélkül, hogy külön algoritmusokat kellene gyártani ehhez, hiszen a rendszer önmagát optimalizálja használat közben.

Viszont ez az Infer.NET dolog nem ma fog kikerülni a köztudatba. Egyrészt MS Research licence, másrészt meg a döntési hálózatot nem szimulálja, hanem fordítja (CodeDom), így az egész Full Trust. Plusz a Bayesian logika felhasználása nagyon korlátozott, messze nincs akkora rugalmasság mögötte mint egy mezei neuronhálózat esetében. Ismerni kell az öreg Bayes agymenését ahhoz, hogy ebben a rendszerben bármi működőt tudjon az ember alkotni, az a nagy büdös helyzet.

Én ezt a programot átírtam úgy, hogy NeoComp NN+QSA, és pontosan ugyan így működik az egész, a rendezés szemre ugyan olyan hatékony, sebességre is ugyan az, kivéve, hogy NN modellnél nem kell fordítani sem, így megy XBAP-pal is. Silverlight 3.0 verziót is tervezek a jövőben, jó lesz demózni a rendszert.

Tehát a NeoComp FW használhatósága a WPF és a Silverlight korában – szerintem – nem csak a bitkeregetésben fog kimerülni, így hamarosan jönni fog az első alpha verzió. Sajnos nem sok időm van erre mostanában, de az első alpha-ig csak a Code Contract és az adat-transzformációs réteg megrajzolása van a TODO listámon.

Továbbá úgy döntöttem, hogy csinálok egy WPF-es demo-t (Graph# alapokon), ami arról fog szólni, hogy a GA tervezi a logikai és/vagy a neuronhálózatot, és el fogom küldeni a NASA és az MS Research ezzel foglalkozó kutatóinak, mert a projekt ezen része már nem halandó embereknek szól, így nem sok értelme van ezt populárisan promozgatni. Arra meg se időm se pénzem, hogy ebből még ebben az évmilliomodban Magyarországon éles áramkörtervező rendszert tudjak létrehozni.

May 18

VS 2010 Beta 1 – Megérkezett!!!

Ebben a pillanatban tették fel az MSDN-re! Erre kíváncsi leszek.

Be fogok számolni még az éjjel a tapasztalatokról!

Itt: http://devportal.hu/forums/p/2706/9359.aspx megy a diskurzus az új VS-ről.

May 13

F#? Valaki?

Hájj! Érdekel valakit az F#? Ma éjjel ráértem, és megtanultam a nyelvet, fog kelleni a mest. int. fejlesztésekhez, mert rengeteg dolgot ezerszer egyszerűbben és gyorsabban meg lehet oldani vele mint C#-ban, ugyan olyan végrehajtási sebesség mellett (a közhiedelmekkel ellentétben ez nem script mint a Python, hanem teljesen statikus, fordított nyelv). Nagyon tetszik, számítási algoritmusokat én innentől kezdve nem leszek hajlandó másban implementálni, annyira egyszerű és áttekinthető az egész, főleg a variant típusozás tetszik! Egy bináris fa definíció három sor … he?? Király! Ha mást is érdekel, akkor jelezzétek, írok róla némi előemésztett információt és egy - két bemutató példát.

Pl.:

let rec pairs = function
  | h1::(h2::_ as t) -> (h1, h2) :: pairs t
  | _ –> []

Mit csinál ez?

Először is, F#-ul a :: operátor listaelem-hivatkozást jelent, a [] meg egy üres lista. Ebből következik, hogy a függvény List<T>-re fog vonatkozni. Pattern matching lamdát jelöl a function kulcsszó, mögötte jönnek a minták a vonallal felsorolva egymás alatt. Az első minta azt jelenti, hogy ha a bemenet egy olyan lista, ami illeszkedik egy olyan mintára, aminek van első eleme, ezt az elemet nevezzük el h1-nek. Továbbá a maradék is illeszkedjen egy olyan listára, aminek van első eleme, ezt nevezzük el h2-nek. A maradék listát pedig nevezzük el t-nek. Ha ez a minta OK, akkor pározzuk össze egy tuple típusba az első két elemet, ezt tegyük bele egy lista elejére, amit úgy képzünk, hogy a maradékra (ami t) hívjuk meg a pairs függvényt (rec – vagyis recursive kulcsszó). Ha a minta nem illeszkedik az előzőre, akkor térjünk vissza üres listával (_ = default mintaillesztés).

Magyarul párokba szervezi egy lista elemeit ez a függvény. Ez csodaszép F#-ban, gyakorlatilag az egész tökéletesen következik a mintailleszkedési szabályokból, és ennél ezerszerte bonyolultabb dolgokat is fel lehet írni ilyen hihetetlenül áttekinthető és tömör kódban. C#-ban ez így nézne ki:

public struct DoubleTuple<T>
{
  public T Value1 { get; internal set; }

  public T Value2 { get; internal set; };
}


public static List<DoubleTuple<T>> Pairs<T>(this IList<T> list)
{
  var result = new List<DoubleTuple<T>>(list.Count / 2);
  for (int i=0; i < list.Count; i+=2)
  {
    var v1 = list[i];
    var v2 = list[i + 1];
    result.Add(new DoubleTuple<T> { Value1 = v1, Value2 = v2 });
  }
  return result;
}

Ez alapján már el lehet képzelni, hogy miben rejlik az F# ereje számítási algoritmusok fejlesztésénél.

Vagy egy type inference példa:

F#:

#light

open System

let rec fold_range f x0 x1 a =
    if x0 = x1 then a
    else f x0 (fold_range f (x0 + 1) x1 a)
    
let sum_range x0 xl = fold_range ( + ) x0 xl 0

let product_range x0 xl = fold_range ( * ) x0 xl 1

Console.WriteLine(sum_range 1 100)
Console.WriteLine(product_range 1 10)
let k = Console.ReadKey(true)

C#:

class Program
{
    static int FoldRange(Func<int, int, int> f, int x0, int x1, int a)
    {
        if (x0 == x1)
        {
            return a;
        }
        else
        {
            return f(x0, FoldRange(f, x0 + 1, x1, a));
        }
    }

    static int SumRange(int min, int max)
    {
        return FoldRange((x0, x1) => x0 + x1, min, max, 0);
    }

    static int ProductRange(int min, int max)
    {
        return FoldRange((x0, x1) => x0 * x1, min, max, 1);
    }
    
    static void Main(string[] args)
    {
        Console.WriteLine(SumRange(1, 100));
        Console.WriteLine(ProductRange(1, 10));
        Console.ReadKey();
    }
}

Látható, hogy az F#-nál már a deklarációknál is van ilyen. Fasza. Persze funkcionális nyelveknél alapból rekurzívan kell gondolkodni, arra van az egész kitalálva, azért ez a neve.

Itt a specifikáció: F# Spec. Két-három óra alatt ki lehet belőle bogarászni a lényeget egy kockásfüzetbe, és ready. \o/