Leren programmeren (duel)leren (deel 2)

Leren programmeren (duel)leren (deel 2)

Mensen neigen nog vaak het idee te hebben dat de generatie die na hun komt, minder is dan hun eigen generatie. Misschien is dat toevallig voor jouw generatie wel waar, maar over het algemeen wordt de mensheid gemiddeld met de generaties, slimmer, sterker, sneller en ouder. Tenminste dat lijkt toch redelijk evident als je naar bijvoorbeeld wereldrecords kijkt, of naar het feit dat ik voor dit blog een computer zelf heb laten leren om boter-kaas-en-eieren te spelen, dat was een paar generaties geleden toch ondenkbaar. Dat de volgende generatie beter presteert dan de vorige is ook meteen hoe ik dat heb gedaan. In deel 2 van leren duelleren ga ik het hebben over een genetisch algoritme. De nieuwe bots kun je vinden op de volgende link: http://undercover.horse/tictactwo/

Maar eerst even een stukje administratie. Uit de reacties op mijn vorige blog begreep ik dat velen van jullie dachten dat Marlies deel 2 zou schrijven. Zoals jullie nu merken is dat niet het geval. Het oorspronkelijke plan was dat ik in dit deel iets meer uitleg zou geven over mijn algoritme, Marlies over haar algoritme, en dat ik dat dan aan elkaar zou knippen en plakken met hier en daar een stukje begeleidende tekst. De planning heeft alleen wat last gehad van mooi weer, vakantie en sociale verplichtingen, die niet in alle gevallen verplicht waren. Hierdoor is Marlies’ algoritme achterin de queue gekomen (dat is een briljant woordgrapje, maar je zult hem pas na Marlies’ blog snappen, maar geloof me, briljant). Dat betekent dat als je hier was voor het stukje van Marlies, dat ik je helaas danig (kijk ook een briljant woordgrapje, maar hier is dan weer geen voorkennis voor nodig) teleur moet stellen. Marlies schrijft deel 3, en dan zullen we dus de algoritmes op elkaar los laten, maar vandaag dus eerst deel 2: Survival of the fittest: kunstmatige selectie en intelligentie.

Charles Darwin, bij sommige misschien wel bekend, onderscheidt in zijn boek ‘on the Origin of Species’ (1859) twee soorten selectie die de evolutie aandrijven. Natuurlijke selectie, zoals het in de natuur gaat, en kunstmatige selectie zoals de mens dat doet bij bijvoorbeeld dure hondenrassen. Beide staven ze echter op dezelfde genetische principes. De ‘beste stukjes’ blijven over, en de ‘slechtste stukjes’ genetisch materiaal sterft langzaam af. Wat iets een goed stukje maakt is zeker in de biologie nogal een rekbaar begrip, maar heel kort door de bocht is dat hoe het werkt.

Dat idee wordt in de kunstmatige intelligentie geïmplementeerd middels een genetisch algoritme. Een genetisch algoritme werkt volgens volgend principe:
– Je begint met een zwik ‘bots’ met verschillende eigenschappen
– Je laat de bots los in de wereld waarin ze moeten overleven
– Je geeft de bots een score (de zogenaamde fitness score) aan de hand van hoe goed ze het doen
– Je laat de bots met een lage fitness score uitsterven, en die met een hoge fitness score laat je zichzelf voorplanten
– Bij het voortplanten laat je de bots muteren
– Zodra de huidige generatie voldoet aan je eisen stop je met het proces en de bots die je dan overhebt hebben de ‘juiste’ waardes.
Dat principe werkt niet helemaal geweldig met mijn boter-kaas-en-eieren spelletje, maar omdat volgens mij een genetisch algoritme een stuk makkelijk uit te leggen is dan bijvoorbeeld een neuraal netwerk, heb ik wat dichterlijke vrijheid toegepast en ga ik jullie laten zien hoe dat werkt.

Eerst kies ik een zwik aan mogelijke spel verlopen. Een mogelijk spelverloop is bijvoorbeeld [4, 8, 0, 6, 7, 2, 1, 3, 5]. In dit spelverloop kiest speler 1 als eerst vakje 4, vervolgens kiest speler 2 vakje 8, dan speler 1 vakje 0, etc. De volgende afbeelding laat zien welk vakje welke is:

Boter-kaas-en-eieren experts zullen direct gezien hebben dat nadat speler 1 vakje 1 heeft gekozen hij 3 op een rij heeft (1,4,7) en dat de opties die daarna komen dus helemaal niet meer kunnen. Maar onze kunstmatige intelligentie is geen boter-kaas-en-eieren expert, sterker nog, hij kent de spelregels niet, zover hij ‘weet’ is dit een geldig spelverloop. De manier waarop ik de begin set maak is al volgt: Ik bereken alle mogelijkheden waarin de getallen 0 tot en met 8 in een volgorde kunnen staan (dat zijn er 362880) en dan geef ik elke combinatie een kans van 30 procent om in de begin set te komen. Vervolgens kopieer ik die set zodat ik 2 sets van ongeveer 100.000 spelverlopen heb. Eentje voor wanneer de AI begint, en eentje voor wanneer de tegenstander begint. Dit alles gebeurt voordat er een zet is gedaan, dus mogelijk dat je computer even bezig is als je een geneticAI speler kiest.

Vervolgens kijk ik naar het huidige spelverloop. Maak ik een verzameling van alle spelverlopen die voldoen aan het huidige spelverloop, en kies een willekeurige bot uit. Ik neem de volgende zet uit dat spelverloop en kies die. Als voorbeeld stel we zitten in het begin van het spel en de tegenstander heeft vakje 2 gekozen. Dan kijk ik in mijn verzameling met spelverlopen waar de tegenstander begint en daar zie ik 3 spelverlopen die beginnen met een 2:
[2, 8, 0, 6, 7, 4, 1, 3, 5] en [2, 4, 8, 0, 6, 7, 1, 3, 5] en [2, 8, 6, 0, 7, 4, 1, 3, 5]
Daar kies ik er willekeurig eentje van, bijvoorbeeld de eerste, de zet die ik dan ga zetten is de 8. De tegenstander is dan weer en kiest de 6. Ik ga opnieuw kijken welke spelverlopen ik nog heb. Het spel verloop is nu [2, 8, 6] waar ik net spelverloop 1 had gekozen kan dit nu niet meer, alleen spelverloop 3 is nu nog geldig, dus kies ik die, en zet ik de 0.

Dat proces herhaal ik tot dat het spelletje me vertelt dat het spel voorbij is. Zodra het spel afgelopen is, geef ik het huidige spelverloop een fitness waarde. 1 als ik heb gewonnen, 0 als ik gelijk heb gespeeld, en -1 als ik heb verloren.

Zo speel ik een aantal spelletjes, tot dat ik er een X-aantal heb gespeeld. Nu is het tijd voor een nieuwe generatie. Omdat er ik een heleboel spelverlopen nog niet heb gespeeld en die dus nog geen fitness score hebben laat ik die ongemoeid. Ik kijk alleen maar naar de spelverlopen met een negatieve fitness score, en ik wissel de laatste zet die ik heb gemaakt om met een willekeurige andere zet. Op basis van deze mutaties heb ik een nieuwe generatie. Dit blijf ik herhalen tot er geen spelverlopen meer zijn met een negatieve fitness waarde.

Iedereen die al naar de nieuwe bots heeft gekeken zal zijn opgevallen dat er 2 versies zijn. GeneticAI1 en geneticAI2, het verschil zit hem in hoe de mutatie werkt. In versie 1 wordt de zet verwisseld met een willekeurig andere zet. In versie 2 wordt er een lijst bijgehouden van spelverlopen met een positieve fitness score. Bij de mutatie wordt het spelverloop dat gemuteerd wordt vergeleken met een spelverloop met een positieve fitness code en wordt er alleen verwisseld met een zet die niet in het spelverloop met de positieve fitness score zit. Voorbeeldje? Voorbeeldje: Spelverloop waarbij de AI begint dat gemuteerd moet worden is: [4, 8, 0, 5, 6, 2, 1, 3, 7], spelverloop met een positieve fitness score: [4, 8, 0, 5, 2, 6, 1, 7, 3]. Het spelverloop heeft verloren na zet 6, zet 6 moet dus verwisseld worden met een andere zet. In versie 1 had dat elke zet kunnen zijn, en had je dus bijvoorbeeld spelverloop [6, 8, 0, 5, 4, 2, 1, 3, 7] kunnen krijgen. In versie 2 mag dat niet omdat de 4 in het winnende spelverloop op dezelfde plek zit, er mag daar alleen met de 2, 7 of de 3 gewisseld worden.

Nu hoor ik sommige van jullie denken: “Ik vraag me af hoe dat er in code uit ziet”, sommige andere hoor ik denken: “Dit is me al vaag en ingewikkeld genoeg, ik hoop niet dat hij ook nog code laat zien”, weer sommige andere: “Hmm ik heb honger, ik vraag me af of er nog iets lekkers in de kast ligt” en één iemand: ”Jemig wat een verhaal, moet ik ook zo’n enorm verhaal aan elkaar ratelen?“. Voor groep 4 heb ik een geruststellende mededeling: Nee Marlies, natuurlijk niet. Aan groep 3 zou ik adviseren gewoon even in de kast te kijken, het geheel is al complex genoeg voor op een volle maag, op een lege maag zou ik het iedereen afraden. Voor groep 2, geen zorgen als ik de code ga bespreken zitten we hier morgen nog. Voor groep 1 heb ik dan nog wel een klein beetje goed nieuws, je kunt de code zelf bekijken op respectievelijk http://undercover.horse/tictactwo/geneticAI.js en http://undercover.horse/tictactwo/geneticAI2.js en in versie zit nog wat extra commentaar zodat het hopelijk redelijk te volgen is. Ook zie je dan nog dat er een truc in zit voor als een spelverloop niet bestaat. Mocht je nou desondanks nog vragen, suggesties of opmerkingen er over hebben, mail me gerust.

Leuk zo’n kunstmatige intelligentie, maar werkt het überhaupt wel, en wat heb je er eigenlijk aan? Goede vragen, al zeg ik het en stel ik ze zelf. Nou heb ik het algoritme tijdens het schrijven van dit blog laten spelen tegen voorkeur2 en de voortgang een beetje bijgehouden, en dat ziet er als volgt uit:

In het begin werden de meeste spelletjes verloren. Tegenover 50 verloren spelletjes stond 1 gewonnen spel en 10 gelijkspellen. Dat was te verwachten, het is een lerend algoritme, en leren kost tijd. Dat zie je ook mooi terug in de verdere verloop van de spellen, vooral in het aantal gelijkspellen. Voor wie het zich niet meer uit het vorige blog kan herinneren, voorkeurAI2 gebruikt altijd dezelfde volgorde van zetten, maar als hij een kans ziet om te winnen of een verlies te voorkomen doet hij dat. Het is voor het algoritme dus makkelijker om gelijk te spelen, en daar wordt hij ook niet voor bestraft, immers alleen spelverlopen met een negatieve fitness score worden gemuteerd. Maar het algoritme gaat ook steeds meer winnen, eerst was het tegenover 50 verloren spellen 1, op het laatst is het tegenover 7 verloren spellen 1. En uiteindelijk zal het algoritme vaker winnen dan verliezen

Het algoritme werkt dus, het leert, maar wat heeft dat voor zin? Zoals ik in het vorige blog al zei, voor dit specifieke voorbeeld niet zo veel. Het MiniMax-algoritme is snel genoeg om alle mogelijke zetten te voorzien en geeft dus altijd de beste zet terug. Toch wint het nooit van voorkeurAI2 omdat het ervan uitgaat dat voorkeurAI2 de best mogelijk zet gaat doen, terwijl deze dat niet altijd doet. Echter verliest MiniMax nooit, wat een duidelijk voordeel heeft. Maar ondanks dat het in dit voorbeeld niet heel zinvol is kan je er wel iets leuks aan zien. Ik heb het algoritme niks uitgelegd van de spelregels. Het enige wat het van tevoren ‘weet’ is dat je alleen maar een leeg vakje mag kiezen. Vervolgens vertel ik het zodra hij verloren, gewonnen of gelijk gespeeld heeft, maar meer niet. Maar het MiniMax-algoritme moest weten wat de waarde van een zet was, en dus moest weten wanneer er gewonnen zou worden, kent hij impliciet de spelregels. Ook voorkeurAI2 weet dat hij drie op een rij moet krijgen en zijn tegenstander niet. Je kunt een genetisch algoritme dan ook uitstekend gebruiken als je zelf het antwoord niet, of niet precies weet.

Dat is de grote kracht van kunstmatige intelligentie, het kan dingen leren die je zelf niet weet. Hoewel we nog niet bang hoeven te zijn voor filmscenario’s als Skynet uit Terminator of WarGames (Shall we play a game?), omdat kunstmatige intelligenties heel erg specifiek één ding leren, worden ze wel heel goed in dat ene specifieke ding. Er is daarom niemand meer die kan winnen van de beste kunstmatige intelligenties met schaken, jeopardy en sinds kort ook GO. Maar al die rekenkracht kan je natuurlijk ook voor praktischere dingen inzetten. Zo gaat er een bekende anekdote dat de Amerikaanse supermarktketen Target alle verkopen van klanten bij hield en een algoritme uit al die data  vast liet stellen of de klant zwanger was of niet, en ze op basis daarvan kortingsbonnen stuurde voor baby artikelen. Op het laatst was het algoritme zo goed dat het in sommige gevallen eerder wist dat een vrouw zwanger was dan de vrouw zelf. Helaas blijkt deze anekdote niet waar, het algoritme is opgesteld door een statisticus en was niet zelflerend, en er gaan nog wel geruchten dat vaders er door Target coupons achter kwamen dat hun dochter zwanger was, maar in hoeverre dat waar is, is ook nog maar de vraag. Maar dit is wel iets dat prima met een KI-algoritme gedaan had kunnen worden.

Je zult steeds meer te maken krijgen met kunstmatige intelligenties, zelfs als je het zelf niet door hebt. Bijvoorbeeld de video’s die YouTube je aanraadt om te kijken, daarvan weten de mensen bij YouTube zelf niet op basis waarop dat gebeurt, dat zit allemaal verwerkt in een kunstmatig intelligent algoritme. Dat klinkt heel spannend, maar ik hoop dat ik met dit blog heb laten zien, dat het allemaal geen magie is. Tenminste volgens mij is het geen magie, ik weet natuurlijk niet wat mijn tegenstander in de strijd gaat gooien. Marlies?

Leren programmeren (duel)leren (deel 1)

Leren programmeren (duel)leren (deel 1)

Nu ik inmiddels bezig ben met mijn 3de blog, is het misschien wel handig om uit te leggen wat het overkoepelende idee achter de blogs is. Trouwe lezers zullen inmiddels de gelijkenissen in de titel zijn opgevallen. Wees gerust, ik heb niet de illusie dat ik met een paar blogs van mijn lezers software developers kan maken. De titel is dan ook niet leren programmeren. Wat ik wel probeer, waarschijnlijk met afwisselend succes, is het idee van programmeren wat minder mysterieus te maken. In deel 2 van deze blog hoop ik daar nog een stapje bovenop te doen, en uit te leggen hoe kunstmatige intelligentie werkt. In een poging om dat geen saaie lap tekst te maken heb ik de hulp ingeroepen van één van mijn collega’s, Marlies. We gaan allebei een ‘kunstmatige intelligentie’ schrijven en die nemen het dan tegen elkaar op in een potje boter-kaas-en-eieren.

Voordat het echter zover is eerst deel 1: boter-kaas-en-eieren.
Voordat de bots voor deel 2 geschreven kunnen worden, is er het spel boter-kaas-en-eieren nodig waarmee de bots kunnen communiceren. Dat spel kun je hier spelen: tictactoe en als je wilt kan je de code voor het spelletje hier vinden: tictactoe.zip. Ik denk dat iedereen de regels van het spel wel kent, maar voor de vorm, en omdat het vrij simpel is, leg ik het nog een keer uit. Er zijn 2 spelers, elke speler mag om de beurt een vakje kiezen, dat is voor de rest van het spel zijn vakje. Wie het eerst 3 vakjes op een rij heeft (horizontaal, verticaal of diagonaal) wint. Zijn alle vakjes bezet en heeft geen van beide 3 op een rij, dan is het gelijkspel.

Op dit moment zijn er 6 bots die je kan kiezen: Human, RandomAI, VoorkeurAI, VoorkeurAI2, VoorkeurAI3 en MinmaxAI. Als je human kiest dan moet je zelf een vakje kiezen als je aan de beurt bent, als je een bot kies doet die een zet. Voordat ik omschrijf wat de bots doen en hoe ‘goed’ ze zijn is het het leukst als je zelf het spelletje speelt tegen de verschillende bots en kijkt of je de tactiek die ze gebruiken kan ontdekken, en welke vervolgens het succesvolst is. Ik wacht wel even.




Klaar? Ok, mijn verwachting is dat je van de RandomAI sowieso hebt kunnen winnen maar niet van MinmaxAI. Ik zal kort toelichten hoe de verschillende bots werken.

RandomAI
RandomAI is de simpelste van de bots, de bot krijgt het huidige speelbord binnen, kijkt welke vlakjes beschikbaar zijn en kiest er daarvan willekeurig eentje. Tegen de random bot is het makkelijk om expres te verliezen of te winnen, maar verdacht lastig om gelijk te spelen, terwijl als je een beetje goed bent in het spelletje dat tegen een menselijke tegenstander eigenlijk altijd de uitslag is.

VoorkeurAI
VoorkeurAI is niet veel complexer dan RandomAI, deze bot heeft een vaste voorkeur voor welke vakjes hij wil hebben. Hij gaat deze voorkeur af en zodra er eentje beschikbaar is, claimt hij die.
Zijn voorkeur is als volgt:

Voor een simpele tactiek is dit helemaal nog niet zo slecht en je zal zien als je RandomAI tegen VoorkeurAI laat spelen dat VoorkeurAI meestal wint. Zelf zul je, als je dit weet echter makkelijk kunnen winnen, je kunt immers altijd de onderste rij (in de afbeelding dus 3,5,8) kiezen en winnen.

VoorkeurAI2
VoorkeurAI2 is al iets beter dan de andere 2 bots, hij heeft namelijk een win/verlies algoritme, als hij het spelletje kan winnen door een vlakje te kiezen, kiest hij deze. Zo niet, dan kijkt hij of de tegenstander kan winnen door een vlakje te kiezen, en zo ja dan kiest hij deze. Zo niet, dan valt hij terug op de tactiek van zijn kleine broertje en kiest hij de eerste lege op basis van dezelfde voorkeursvolgorde. Deze wint alles van VoorkeurAI en doet het een stuk beter tegen RandomAI. Je zult zien dat je ook van deze bot nog wel kunt winnen, maar je moet wel van te voren een goede tactiek bedenken!

VoorkeurAI3
VoorkeurAI3 is een beetje een instinker, als je er tegen gespeeld hebt, zal je waarschijnlijk het gevoel hebben dat hij iets beter is dan 2, ook al weet je niet precies waarom. In werkelijkheid is deze bot hetzelfde als VoorkeurAI2, met als enige verandering dat met een trucje hij er iets langer over doet om een oplossing uit de voorkeursvolgorde te halen.
Hier ga ik straks op verder, maar eerst laat ik Marlies de werking van de volgende bot uitleggen, die heeft zij namelijk gemaakt. Deze bot is de reden waarom we eigenlijk voor dit spelletje geen kunstmatige intelligentie nodig hebben. Er is namelijk een beter manier: Het minmax-algoritme.

MinmaxAI
De MinmaxAI bot gebruikt het minmax-algortime. Om dit algoritme te kunnen gebruiken, is er een aantal eigenschappen die een spelletje moet hebben. De eerste is dat jij wilt winnen, maar waarom speel je anders ook een spelletje, en dat je tegenstander ook wilt winnen, maar waarom speelt hij anders ook een spelletje, en dat de winst van de één betekent dat de ander verliest. Daarnaast is het nodig om ieder mogelijk vervolg van het spelletje te kunnen bepalen (dat is bij dit spel vrij makkelijk omdat het er niet veel zijn, alleen de eerste zet kost even wat tijd om uit te rekenen), en is het nodig om een waarde te kunnen hangen aan het einde van het spelletje.

Op het moment dat hij een zet moet doen, bedenkt het minmax-algoritme voor iedere mogelijke zet wat hiervan het gevolg is voor het vervolg van het spelletje. Op basis van dit vervolg, heeft een zet een bepaalde waarde. Zo heeft winst een waarde van +10, verlies een waarde van -10, en gelijkspel een waarde van 0. Daar waar jij probeert om deze waarde zo hoog mogelijk te maken, of dus te maximaliseren, probeert jouw tegenstander juist het omgekeerde te doen; oftewel te minimaliseren.

Op deze manier kan je ook voor een zet die er niet voor zorgt dat het spelletje is afgelopen een waarde bepalen. Als het spelletje nog niet is afgelopen, kan je namelijk beredeneren wat de tegenstander zal gaan doen, namelijk proberen om op een uitkomst met een zo laag mogelijke waarde uit te komen. Als hij echter het spelletje ook niet afmaakt, is het weer jouw beurt, en probeer jij juist weer op een zo hoog mogelijke waarde uit te komen. Als jij dan het spelletje niet afmaakt … enfin, je begrijpt het idee.

Echter missen we nu nog een essentieel onderdeel. Op dit moment heeft het winnen in de volgende zet dezelfde waarde als het winnen in een zet over vier beurten, namelijk +10. Daarnaast is de volgende beurt verliezen evenveel, of eigenlijk even weinig, waard als verliezen over vier beurten, namelijk -10. Tijd voor een voorbeeldje, als jij al twee vakjes op een rij hebt, is het niet zo handig om niet het derde vakje erbij te kiezen. Dat derde vakje kiezen, moet dus eigenlijk een hogere waarde hebben dan niet dat derde vakje kiezen. Ander voorbeeldje, als de tegenstander vakjes op een rij heeft, is het niet zo handig als je niet zelf het derde vakje kiest, anders wint hij namelijk zodra hij aan zet is. Oftewel, wel dat derde vakje kiezen moet een hogere, of minder lage, waarde hebben dan niet dat derde vakje kiezen.

Om hier rekening mee te houden, wordt de waarde van de uitkomst niet alleen bepaald door de uitkomst zelf, maar ook door het aantal beurten dat het nog kost om tot deze uitkomst te komen. Winnen in de volgende beurt krijgt dus een waarde van +10, terwijl winnen over vier beurten de waarde van +10 – 4 krijgt. Het omgekeerde is het geval voor verliezen. De volgende beurt verliezen is -10 punten, en pas over vier beurten verliezen heeft een waarde van -10 + 4. Hierdoor zal het algoritme winnen wanneer dat kan, en zorgen dat het niet de volgende beurt verliest.

Dankje Marlies voor deze heldere uitleg.
Deze tactiek maakt het minmax-algoritme onverslaanbaar. Immers hij berekent alle mogelijkheden, gaat uit van het scenario dat zijn tegenstander net zo goed is als hij en kiest dan de beste zet. Hoewel we hier een algoritme hebben dat altijd de beste zet doet, zal er niet zo snel iemand zijn die het kwalificeert als een intelligent algoritme. Maar als het maken van de slimste beslissing geen intelligentie is, wat dan wel?

Groot voordeel, daarover is geen algemene consensus, dus je mag zelf kiezen wat je intelligent noemt en als iemand het daar niet mee eens is, komt dat omdat hij/zij zelf niet intelligent genoeg is. Ik hoop dat je tegen voorkeurAI2 en 3 hebt gespeeld, en mijn voorspelling dat je het gevoel had dat 3 slimmer aanvoelde dan 2 uit is gekomen. Het illustreert namelijk iets mafs. Hoewel het niet zo is, lijkt de ene bot slimmer dan de andere. Dat kan door twee dingen komen:

1. De naam: Omdat voorkeurAI2 duidelijk slimmer is dan 1, zal 3 nog wel weer beter zijn. Dit is een hele menselijke vergissing waar veel reclamemakers graag gebruik van maken. Dit is ook de reden waarom we over een paar jaar scheermesjes hebben met acht mesjes, met namen als elemental fusion power daimond stealh razor deluxe hybrid. Want stom genoeg werkt het.

2. Het algoritme doet er langer over om een ‘moeilijke’ beslissing te nemen. Dit is iets wat je ziet gebeuren en je maakt een aanname waarom dat zo is: Het algoritme moet langer nadenken/rekenen over de keuze. Dat is echter niet zo, wat er in werkelijkheid gebeurt is dat het algoritme een willekeurig getal neemt tussen de 0 en 1, en als dat groter is dan 0,05 slaat hij een cyclus over. Het spelletje werkt met ongeveer 60 cycli per minuut, en dus duurt het even voordat hij daadwerkelijk zijn zet kiest.

Door ‘na te denken’ over de zet lijkt het algoritme menselijker, en wordt daarom sneller gezien als intelligent. In 1950 bedacht briljant wiskundige Alan Turing de Turing test. De Turing test werkt grofweg als volgt: Twee mensen voegen je toe op WhatsApp, geen van beide wil (video)bellen en je mag met beide 10 minuten appen. Na die 10 minuten moet je zeggen welk van deze 2 mensen ook echt een mens is en welke stiekem een computer. Als maar genoeg mensen het verschil niet kunnen onderscheiden tussen de mens en de computer dan is de computer intelligent.

Dat blijkt nog helemaal niet zo makkelijk, want als je iemand vraagt wat 23598 keer 123987 is en hij/zij komt direct met het goede antwoord, dan gaat al snel het vermoeden dat hij te goed kan rekenen en dus een computer is. Dus worden de bots trucjes aangeleerd, ze doen langer over rekensommen en maken bewust fouten. Maar dan ben je er nog lang niet, we zijn als mensen misschien niet zo goed in hoofdrekenen, het voeren van een conversatie kunnen we over het algemeen wel een stuk beter dan een computer. Er zijn veel projecten die deze uitdaging aangaan een voorbeeld is www.cleverbot.com. Chatten met cleverbot voelt als een discussie met mijn bijna 3-jarige nichtje, die als je vraagt of ze 2 handen heeft, heel stellig zegt: “Nee 3!”, terwijl ze evenveel vingers ophoudt als ze zondag jaren oud wordt. Nou klinkt dat niet zo bijzonder, maar dat betekent dat, volgens Turings redenatie, cleverbot de intelligentie heeft van een bijna 3-jarig meisje.

De Turing test wordt door veel mensen nog steeds gezien als de graadmeter voor intelligentie, maar hoewel we dat nog niet gehaald hebben vinden veel mensen het het doorstaan van de Turing niet genoeg om iets intelligent te noemen. Die vinden dat een intelligentie zou moeten kunnen functioneren in de echte wereld.
Nou wil ik niet voor Marlies praten, maar dat gaat mij wat te ver voor een blog. Wat wij gaan doen is een kleine doch zeer belangrijke subset van intelligentie, leren. We gaan allebei een algoritme schrijven dat na het spelen van potjes steeds beter wordt. Idealiter begint de bot zo goed als RandomAI en eindigt hij zo goed als MinmaxAI. Ik weet niet hoe het met jou zit Marlies, maar ik vrees dat ik hem niet zo goed ga krijgen als MinmaxAI, aan de andere kant verwacht ik wel dat hij kan winnen van mijn nichtje, ook al is ze inmiddels 3 als het algoritme af is. Kan het algoritme daarom beter leren dan mijn nichtje? Of beter nog, is het intelligent? Dat mag je zelf beslissen. Ik ben allang blij als het niet genadeloos verslagen wordt door dat van Marlies.

Wil je zelf ook een bot maken? In de zip-file staat een dummyAI, die doet nu nog helemaal niets, maar als je dummyAI.js aanpast kan je hem wel alles laten doen wat je wilt! Dus mocht je na het vorige blog de smaak van het programmeren te pakken hebben, maak van het duel een 3-strijd, laat je met de andere bots als voorbeeld inspireren en maak je eigen bot. Dan kan je in het volgend blog kijken hoe die zich verhoudt tegen die van ons! Mocht je een bot maken, zou ik het superleuk vinden als je je oplossing mailt. Het meest leer je immers van elkaar.

Rest me nog de competitie heel veel succes te wensen. Marlies, je hebt tot het volgende blog, succes, je tijd gaat nú in!

leren programmeren proberen

leren programmeren proberen

In mijn vorige blog heb ik proberen uit te leggen dat programmeren eigenlijk niet zo moeilijk is, zolang je maar alle mogelijke gebeurtenissen voorziet en daar rekening mee houdt. Ik ben er vervolgens op gewezen dat als je geen idee hebt hoe je moet programmeren, dat je kan anticiperen wat je wilt, maar daar schiet je dan weinig mee op. Dit is natuurlijk een goed punt, en ik ga in dit blog dan ook op verzoek jullie leren programmeren. Het belangrijkste aan programmeren is het proberen, dus wil je het echt leren, doe dan gezellig mee.

Vandaag gaan we programmeren in JavaScript, waarom JavaScript hoor ik sceptici vragen. Omdat je alles om te programmeren in JavaScript al op je computer hebt staan, dus eigenlijk om het voor de lezer makkelijk te houden. We gaan vandaag een spelletje programmeren (in een poging de stof niet te droog te houden), de keus voor het spelletje is gevallen op Space Invaders, maar omdat ik geen DCMA-takedowns wil afroepen noemen we het: Orcado Invaders. Je kunt het eindresultaat alvast hier spelen: OrcadoInvaders Als je achter een computer zit kun je de pijltjes en het toetsenbord gebruiken, op je mobiel zou tikken op je scherm moeten werken. Het is overigens op je mobiel een stuk lastiger, dus ik zou aanraden achter een computer te gaan zitten, hetgeen voor het hele mee doen met het programmeren idee ook een heel stuk gaat helpen.

Voor iedereen die nu denkt: “Is het niet veel te moeilijk om uit het niets een spelletje als Orcado Invaders te maken?”. Ja, dat is het helaas inderdaad, maar niet getreurd ik heb al een ander leuk spelletje op het oog, namelijk “Raad het getal!”. Dat klinkt alleen een heel stuk minder stoer, dus ik ben begonnen met een onrealistische teaser, dat heb ik geleerd van de filmindustrie, werkt altijd. Maar om het goed te maken, gaan we een kunstmatige intelligentie maken die zelf het getal gaat raden, dat maakt toch wel weer wat goed toch?
Ok, genoeg gewauwel als inleiding, tijd om aan de slag te gaan.

Voor deze intro zijn de volgende vaardigheden als voorkennis benodigd:
– Het kunnen downloaden van een zip bestand
– Het kunnen uitpakken van een zip bestand
– Knippen
– Plakken
– Opslaan
– Verversen van een pagina
– Doorzettingsvermogen

Dat viel mee toch? Mocht je nog aan het twijfelen zijn of je wel genoeg doorzettingsvermogen hebt, als je nu nog leest zit je wat mij betreft in de veilige zone.

Om te beginnen heb ik een zip-bestandje gemaakt dat je hier kunt downloaden: starter pack Download dit zip bestand en pak het ergens uit. Ga naar de locatie waar je het uitgepakt hebt, hier vind je twee bestandjes “de_pagina.html” en “de_code.js”.

Als je dubbelklikt op de_pagina.html opent je internetbrowser, op de pagina staat vervolgens uitgelegd hoe je in de console komt. Volg die instructie en type in de console het volgende in: hoiConsole(); Druk op enter en de console zegt hoi terug, dat ziet er als het goed is ongeveer zo uit:

Gelukt? Mooi! Gaan we nu kijken wat ik voor bergen aan code als voorwerk heb moeten schrijven om dit te laten werken. Ga terug naar de map met bestandjes klik met je rechtermuisknop op de_code.js en kies: “Openen met” en kies uit de lijst Notepad (of een andere teksteditor) (eventueel moet je ergens op klikken om meer programma’s te krijgen als Notepad er niet direct tussen staat, maar daar kom je vast wel uit!)

Nu zie je de code die ervoor zorgt dat de console je terug groet. Dat stelt nog niet veel voor, ik zal uitleggen wat het doet.
function (dit geeft aan dat we een functie gaan maken, een functie is dat je kunt aanroepen en dat dan vervolgens een stuk code uitvoert)
hoiConsole (dit is de naam van de functie, die gebruik je om de functie aan te roepen)
() (tussen deze twee haakjes staan eventuele argumenten, kom ik straks op terug
{ (geeft aan dat hier de code van de functie begint)
return (code om aan te geven dat er iets terug moet worden gegeven, dit wordt terug gegeven aan de geen die de functie aanroept, in dit voorbeeld ben jij dat in de console)
"Hoi" (alles tussen aanhalingstekens is geen code maar tekst, omdat deze tekst achter return staat wordt hij terug gegeven)
; (geeft aan dat we het einde van een regel gehaald hebben, elke regel code wordt met een puntkomma afgesloten)
} (geeft aan dat hier de code van de functie stopt)

Dat is heel veel uitleg voor een heel klein stukje code, maar hopelijk allemaal nog niet heel ingewikkeld. Wat we nu gaan doen is samen de functie uitbreiden. Dat gaan we doen met een argument, ja ik kom er maar snel op terug voor dat je ze al weer vergeten bent. Argumenten kan je gebruiken om een functie iets mee te geven wat hij nodig heeft voor zijn code. We gaan aan de functie meegeven, wat hij terug moet geven, om dat te doen zet tussen de haakjes een x, en vervang de tekst “hoi” ook met een x:

function hoiConsole(x){
return x;
}

De x staat nu niet tussen aanhalingstekens, en is nu geen tekst meer maar code, hij zal nu de x teruggeven die hij in zijn aanroep mee heeft gekregen. Kom, we gaan het testen! Sla het tekstbestand op, en ververs de internetpagina (bijvoorbeeld met F5, of als het niet wil lukken sluit hem en open hem nog een keer). We moeten de functie nu iets anders aanroepen, de functie verwacht namelijk nu een argument. Probeer eens: hoiConsole(“Hoi”) dat geeft als het goed is dit:

Zo lijken we niet veel opgeschoten, dat konden we net ook al, maar we kunnen hem nu alles laten zeggen, kijk maar:

Ok slechte grapjes achterwege gelaten, we kunnen hem nu meegeven hoe we heten en dan groet hij ons persoonlijk terug:

Stel dat we een teruggroet robot aan het bouwen zijn, dan hoeft hij voor het teruggroeten natuurlijk alleen de naam te weten, hoi is altijd hetzelfde. Wat we dus gaan doen, we gaan hoi terug in de functie zetten, en dan plakken we de naam erachteraan:
function hoiConsole(x){
return “Hoi ”+x;
}

(Let op de spatie achter hoi, anders staat er straks HoiDaan) kijken wat hij doet:

Dat was nog niet zo lastig toch? Technisch gezien kan je nu al programmeren. Echter begint het nu pas leuk te worden, dus we doen nog een paar extra dingetjes, en dan kan je ook echt iets bouwen wat iets ‘doet’.
We kunnen ook meerdere argumenten meegeven aan een functie. Laten we een nieuwe functie maken, de oude mag je laten staan en daaronder mag je het volgende toevoegen:

function hoger(x,y){
if(x > y){
return "ja dat is hoger";
}else{
return "nee dat is lager"
}
}

We gaan eerst kijken wat dat betekent:
function hoger(x,y){ (dit ken je al, dit is een functie met de naam hoger en de argumenten x en y)
if(x > y){ (dit is een zogenaamd if-statement, als wat er tussen de haakjes staat waar is voert hij de code uit, anders niet)
return "ja dat is hoger"; (dit ken je al, geef tekst terug)
}else{ (het else-statement gebruik je indien wat tussen de haakjes van het vorige if-statement niet waar is, dan deze code uit te voeren, je ziet een else-statement dus nooit zonder if, andersom kan wel)
return "nee dat is lager"; (dit ken je al, geef tekst terug)
} (dit ken je al, einde van een blok code)
} (dit ken je al, einde van een blok code)
Als we nu de functie hoger(5,3) aanroepen, dan gaat het if-statement kijken of 5 groter is dan 3 (> is het groter dan teken) en zo ja geeft hij terug “ja dat is hoger”, zo niet, dan “nee dat is lager”. Zoals altijd gaan we dit proberen, dat is het idee van het geheel ?

En gelukkig, het klopt.
Mocht het nou niet werken, en je krijgt rode blokken met tekst zoals dit:

Controleer dan of je
– het bestandje de_code.js hebt opgeslagen
– de pagina hebt ververst
– nergens een typefout hebt gemaakt (programmeertalen zijn heel streng op je spelling)
Voor de zekerheid testen we ook de andere opties:

Trouwe lezers hadden het natuurlijk al geanticipeerd, als iets niet hoger is, hoeft het niet per se lager te zijn. We kunnen dat natuurlijk oplossen door de tekst te veranderen naar “nee het is niet hoger”, maar mooier is een extra check om te kijken of het niet toevallig gelijk is:

function hoger(x,y){
if(x > y){
return "ja dat is hoger";
}else{
if(x == y){
return "nee dat is gelijk"
}else{
return "nee dat is lager"
}
}
}

Wat je ook mag schrijven als:

function hoger(x,y){
if(x > y){
return "ja dat is hoger";
}else if(x == y){
return "nee dat is gelijk"
}else{
return "nee dat is lager"
}
}

Dat scheelt weer een paar haakjes ? Nu geeft de functie wel de juiste melding (vergeet het niet te proberen). Let op het dubbele = teken, we kijken of ze het zelfde zijn, als we een enkel = teken dan krijgt x de waarde y.
En we gaan al weer verder, in mijn vorige blog had ik het over delen, nu gaan we eerst vermenigvuldigen. Dat is in JavaScript vrij triviaal, je kan bijvoorbeeld zeggen 5*3, en dan krijg je terug 15, kijk maar:

Maar vandaag is het enige rekenteken dat we mogen gebruiken de plus. Gelukkig kan je vermenigvuldigen makkelijk omschrijven naar optellen, immers 5*3 is vijf keer een drie dus hetzelfde als: 3+3+3+3+3. Als we in JavaScript iets een aantal keer moeten doen, hebben we daar een aantal opties voor; we gaan de for-loop gebruiken. De for-loop werkt als volgt:

for(var i = 1; i<=x;i++){
//doe iets
}

Wat vertaalt naar:
for (geeft aan dat we een for-loop beginnen)
( (deze ken je al, begin van de argumenten)
var i = 1 (var staat voor een variable, de variable slaat voor ons een waarde op, de naam van deze variable is i, en de waarde is 1)
i<=x (we gaan door de code uitvoeren zolang de variable i kleiner of gelijk is aan de variable x (let op als je een argument meegeeft aan een functie is dat ook een variable))
i++ (Na elke keer dat je de code hebt uit gevoerd verhoog variable i met 1, dit kan je ook opschrijven als i = i +1 of i+=1)
) (deze ken je al, einde van de argumenten)
{ (deze ken je al, begin van de uit te voeren code)
//doe iets (de code die uitgevoegd gaat worden)
} (deze ken je al, begin van de uit te voeren code)

De for-loop heeft 3 argumenten, met het eerste kan je een variable aanmaken, met het tweede geef je een voorwaarde om het te doen (eigenlijk een soort if-statement) en het derde argument wordt na elke loop uitgevoerd. De blokken worden in een for-loop gescheiden door een puntkomma.

Ok, probeer zelf nu eens om de functie te schrijven. Ja je kan gewoon naar beneden scrollen, maar het hele idee van dit blog is dat je het zelf gaat proberen. Geef niet direct op, maar blijf ook niet te lang bezig, ook als je code niet werkt, als je er zelf over hebt nagedacht dan ben je al een heel stuk verder dan als je alleen doorleest. Bedenk wat voor stappen de code moet doen en schrijf dat stap voor stap op. Mocht je er niet uitkomen, lees eerst de hints!

Hint1:
Je hebt een extra variable nodig.

Hint2:
Vul in op de puntjes:
function keer(x,y){
var uitkomst = 0;
for(var i = 1; i <= ...; i++){
...........
}
return ...
}

lees verder

Leren programmeren anticiperen

Leren programmeren anticiperen

Vaak als ik tegen mensen zeg dat ik programmeer voor mijn werk, krijg ik direct de vraag: “Is dat niet heel moeilijk?”. Volledig politiek verantwoord zeg ik dan dat het wel meevalt en dat zij misschien niet kunnen programmeren, maar dat ik dan weer helemaal geen verstand heb van hun werk. Dat is niet helemaal waar, ik heb meestal best wel veel verstand van wat voor werk ze dan ook mogen doen, maar je wilt niet te arrogant overkomen, dus je vlakt het allemaal wat af. Maar zonder gekheid, qua moeilijkheidsgraad is programmeren goed te vergelijken met schilderen, als je het plafond van de sixtijnse kapel moet schilderen, ja dat is niet makkelijk. Als het echter één groot blauw vlak moet worden, dat is prima te doen. Nu kan het tweede technisch gezien misschien wel geen schilderen, maar verven zijn, of sauzen, maar van schilderen heb ik geen verstand.

Wat programmeren vaak lastig maakt, is dat je programma met alle mogelijke scenario’s om moet kunnen gaan. Hoe meer verschillende mogelijkheden er zijn, hoe moeilijker het is om te maken. De truc is dan ook om zoveel mogelijk te anticiperen, wat kan er gebeuren en wat moet het programma dan doen? Ik zal dit proberen toe te lichten met een voorbeeld. Ik ga proberen het makkelijk te houden, zodat ook iedereen zonder programmeer kennis het kunnen volgen, maar mocht je meer uitleg willen mag je me altijd mailen.

Stel, je wilt de volgende wiskundige stelling bewijzen: y*x = y / (1/x) (Wat zoveel betekend als: getal A keer getal B is het zelfde als getal A gedeeld door 1 gedeeld door getal B, Bijvoorbeeld: 2*4 = 8 = 2/¼ (mocht je de wiskunde niet volgen, verlies niet de moed, het is voor de rest van het voorbeeld niet perse nodig). Mensen die goed zijn in wiskunde zullen direct zien dat die stelling klopt. Mensen die heel goed zijn in wiskunde zien dat die stelling klopt, behalve als x gelijk is aan nul, want je kunt immers niet delen door nul.

Dat is dan ook iets waar je in je programma rekening mee moet houden, wat als X nul is, of wat als het null is? Nu moet ik natuurlijk eerst uitleggen wat het verschil tussen nul en null is. Misschien is het toch allemaal niet zo makkelijk als ik dacht, maar we geven niet op! Voor wiskundige is 0 hetzelfde als niets, voor een computer is een 0 een getal en null het ontbreken van data. Ik zal een voorbeeld geven, neem de volgende tabel met vollédig verzonnen en willekeurige data:

medewerkerblogs
Jurrie1
Michael2
Sjaak2
Peter1
Patrick7
Kees13
Bert0
Manfred0
Katinka0
Wouter V1
Bonneke0
Bart0
Daan0
Marlies1
Liesbeth0
Wouter B0

Als je aan de computer vraagt hoeveel blogs heeft Daan geplaatst, dan is het antwoord 0. Als je vraagt hoeveel blogs heeft Aaron geplaatst dan is het antwoord null. Hij heeft namelijk de naam Aaron niet in de tabel staan, heeft dus niks om terug te geven en geeft dan het antwoord null, hoe de programmeertaal daarmee omgaat wisselt dan vervolgens ook weer.

Terug naar het voorbeeld, we hebben de volgende som: Y / (1 / X ). Voor het voorbeeld nemen we voor Y = 2, en voor X = 4 of 0 of null, en bekijken wat voor resultaat dat geeft. Voor wie wel kan programmeren, ik ga het in 3 talen doen: SQL (in een Oracle database), javascript (in de Chrome console) en haskell (via tryhaskell.org), probeer de uitkomsten eens te voorspellen. Voor als je niet kunt programmeren negeer de code waarmee het resultaat wordt berekend, het gaat nu even alleen om het resultaat.

Eerst in SQL:

Zoals verwacht geeft de eerste berekening, 2 gedeeld door een kwart, het antwoord 8, normale situaties zijn gelukkig vaak makkelijk te voorspellen. Het tweede scenario, 2/ (1/0) geeft een foutmelding: “de deler is gelijk aan 0”. Ook dat was te verwachten, immer kan je volgens de wiskunde niet delen door 0. De laatste optie, 2/(1/null) geeft geen foutmelding, maar null (in sql wordt dit weergegeven door een leeg waarde. Dat betekend dus dat als je een berekening hebt met een deling, dat je altijd moet bedenken: wat moet mijn programma doen als de deler 0 is, en wat als de deler null is? Want een foutmelding of niks, is vaak niet wat je de gebruiker wilt laten zien.

Laten we eens kijken hoe hetzelfde werkt in Javascript:

De eerste waarde is weer 8, zoals ik al zei zijn normale situaties gelukkig vaak makkelijk te voorspellen. Bij de twee volgende scenario’s krijgen we alleen andere resultaten, het antwoord is niet een foutmelding of een lege waarde, maar het getal 0.

Dan Haskell:

Bij Haskell zijn de eerste 2 het zelfde als bij JavaScript, de laatste is misschien een beetje oneerlijk, Haskell is een computertaal die eigenlijk geen null waarde kent, dat heeft te maken met het feit dat het een functionele programmeertaal is en geen variabele kent, maar dat leg ik misschien een andere keer nog wel uit. Om het na te bootsen haal ik hier de 3de waarde uit een lijst met 2 waardes, waarop ik de foutmelding “index is too large” terug krijg, als ik hetzelfde grapje in JavaScript uit haal krijg ik geen foutmelding maar “undefined” terug, SQL geeft ook geen foutmelding, maar helemaal niks (ook geen null dus). Bij Haskell hoef je er dus geen rekening mee houden dat er door null gedeeld kan worden, het kent namelijk geen null. Wel moet je bedenken wat je doet als iemand het toch probeert.

Het was een heel verhaal, maar uit dit voorbeeld blijkt dus dat je bij een deling altijd moet oppassen voor nul en null waardes. Want het is iets wat eigenlijk niet zou mogen gebeuren, en kan leiden tot onverwachte resultaten. Hetzelfde geldt ook voor het gebruik van de modulo functie (mocht je die niet kennen verwijs ik je naar wikipedia, anders wordt dit stuk veel te lang) en de wortel van een getal, 0 geeft foutmeldingen of onverwachte antwoorden.

Nou vooruit we maken het nog íets ingewikkelder. In Javascript en Haskell is 2/(1/0) = 0. Maar als we dat weten, wat is dan 1/0? Beide talen geven gelukkig weer hetzelfde antwoord:

Om je uit te kunnen leggen waarom dat ‘klopt’ moet ik je het idee van het drijvendekommagetal gaan uitleggen, en hoewel dat een topscrabblewoord is, lijkt het me voor wat ik probeer uit te leggen niet echt zinvol, dus neem van mij aan, dat ‘klopt’. Het verklaart overigens wel mooi waarom we het resultaat 2/(1/0) = 0 krijgen, Wat je eigenlijk doet is dus 2/Infinity en dat is dan weer 0, tenminste zolang je dat niet met een wiskundige overlegt kom je daar wel mee weg. Wat ook nog wel leuk is, in Haskell kan je ook delen met de div functie, dat geeft het volgende resultaat:

Daar moet je dan wel weer rekening mee houden. Kort samengevat, programmeren is niet moeilijk zolang je alle mogelijke scenario’s en gebruikersinvoer weet te voorspellen. Gelukkig zijn er testers, die proberen alle scenario’s die zij kunnen bedenken, en testen die uit. Dat vangt vaak het grootste gedeelte wel af.

Gelukkig heeft dit verhaal ook een gouden randje. De volgende keer dat je een programma hebt dat vastloopt of een verkeerd resultaat geeft, realiseer je dan, dat jij iets hebt gedaan dat zo origineel en vindingrijk was, dat de programmeur het van tevoren niet kon bedenken! Dus hoe vaker je computer vastloopt hoe fantasievoller/vindingrijker/inventiever jij bent!

Nou vooruit, om het nog nét iets ingewikkelder te maken. Met de round functie kun je getallen afronden. Dus round(1,2) = 1 en round(1,9) = 2. Probeer eens te voorspellen wat gebeurt er als we 1/0 afronden in Javascript en in Haskell.
Klik hier voor het antwoord

Ik had afgesproken dat ik het simpel zou houden geloof ik, ik denk dat ik het dan hierbij maar laat, want ik voorzie dat dit allemaal toch nog wel wat vragen op gaat leveren.

O rest me nog 1 dingetje:
update orcado_blogs
 set    blogs = 1
 where  medewerker = 'Daan';
 commit;

We moeten onze fictieve data wel integer houden.

Hier komt de sidebar

Volg ons op

© Orcado B.V. | 1999 - 2017