Disclaimer: Welkom bij onze TechBlog!
Dit is een artikel in ons TechBlog. Ons Techblog bevat artikelen, geschreven door onze developers, over dingen die ze zijn tegengekomen tijdens hun werkzaamheden. Deze artikelen gaan (meestal) over een technisch onderwerp, en zijn met name bedoeld om te vermaken en (soms) te informeren. Claude mocht meespelen, maar wij hielden de VAR in eigen hand.

vrijdag 1 mei
Auteur: Mark Coenradie
Afbeeldingen en design: Jeroen Mimpen

Van keepershandschoenen tot commandoregel

Hoe ik met AI uitrekende waar Manchester United eindigt

Geen voetbalfan? Geen probleem

Ik beloof je: dit artikel gaat maar voor de helft over voetbal. Het grootste deel gaat over een wiskundig probleem dat verrassend lastig op te lossen is, en over hoe ik AI wilde inzetten om dat voor me uit te rekenen. Dus ook als je niet weet of er lucht of zand in een bal zit: bear with me. Het wordt interessant.

De spanning van het seizoenseinde

We schrijven 21 april en er zijn nog vijf speelrondes (plus wat losse wedstrijden) te gaan in de Premier League. Manchester United zit middenin een strijd om een Champions League-ticket voor volgend seizoen. Dat klinkt misschien overzichtelijk: je kijkt naar de stand, telt de punten, klaar. Maar dat is het niet. Er zijn nog meerdere teams die aanspraak maken op een plaats bij de eerste vijf teams, en er zijn nog veel wedstrijden tussen concurrenten voor die plaatsen. Een paar bijzondere resultaten kunnen de hele rangschikking door elkaar gooien.

Illustratie van voetbalresultaten die de stand op zijn kop zetten

Bijzondere resultaten kunnen de hele rangschikking door elkaar gooien.

Ik wil weten: wat is de hoogst mogelijke eindpositie voor United, en wat is de laagste? En het belangrijkste, hoeveel punten hebben we nog nodig om zeker bij de eerste vijf te eindigen? Met vijf rondes te gaan en zoveel variabelen is dat met pen en papier simpelweg niet te doen. Tijd voor een script.

PosTeamPtnGWGVDVDTDS
1Arsenal703321576326+37
2Manchester City673220576529+36
3Manchester United5833167105845+13
4Aston Villa583317974741+6
5Liverpool5533161075443+11
6Brighton50341310114839+9
7Chelsea4834131295345+8
8Brentford4833131194844+4
9Bournemouth48331171550500
10Everton4733131284039+1
11Sunderland46331211103640-4
12Fulham4533131464346-3
13Crystal Palace43321111103536-1
14Newcastle4233121564649-3
15Leeds United3933912124249-7
16Nottingham Forest363391593645-9
17West Ham333381694057-17
18Tottenham3133716104253-11
19Burnley203342183467-33
20Wolverhampton173332282461-37

Uitleg

De basis: bij winst krijgt de winnaar 3 punten, bij gelijkspel krijgen beide teams 1 punt. Als je op punten gelijk eindigt, bepaalt het doelsaldo (gescoorde doelpunten min doelpunten tegen) wie er hoger eindigt.

#TeamPuntenDoelsaldo
1.A*11+4
2.B8+3
3.C7-1
4.D3-6
Resterend programma:
  • A-D
  • B-C
  • B-D
  • A-C

Alle teams spelen nog twee wedstrijden, de vraag ligt voor: weten we zeker dat team A in de top-2 eindigt? Een simpele vraag doordat de situatie overzichtelijk is.

We kunnen aannemen dat team A beide wedstrijden verliest, we willen immers de slechtst mogelijke situatie bestuderen. Met 3-0, dat tikt lekker aan voor het doelsaldo. B en C kunnen A nog voorbij door twee keer te winnen (twee keer 3 punten), maar kunnen niet allebéí twee keer winnen omdat ze nog tegen elkaar spelen. Vul bij die wedstrijd een gelijkspel in (allebei 1 punt) en dan wordt dit de eindstand:

#TeamPuntenDoelsaldo
1.B12+4
2.C11+0
3.A*11-2
4.D6-6
Uitslagen:
  • A-D: 0-3
  • B-C: 0-0
  • B-D: 1-0
  • A-C: 0-3

Oftewel: nee, het is niet zeker dat team A in de top-2 eindigt, ze hebben minimaal nog één punt nodig om zeker te zijn.

Illustratie: snap er de ballen van

Komt dit je bekend voor?

Het is niet de eerste keer dat ik een script schrijf voor een dergelijk probleem. Vorig jaar schreef ik al een script om uit te rekenen wat theoretisch gezien je laagste positie in de eindstand wordt. Op een paar nuances na was dit precies hetzelfde probleem. Ik zette toen de huidige stand in een CSV-bestand, voerde alle resterende wedstrijden in, en liet het script als eerste United alle wedstrijden met 9-0 verliezen om vervolgens met wat slimme stappen de slechtst mogelijke uitslag van de andere wedstrijden te berekenen. Het bouwen was leerzaam, ik kon zien dat het werkte, maar het was ook bewerkelijk: elke speelronde moest ik de data met de hand bijwerken. Het was ook weer niet zóveel werk dat ik dat wilde automatiseren... dat zou ik een andere keer wel doen, als ik er tijd voor had.

We zijn inmiddels een jaartje verder en nu stond ik opnieuw voor de keuze: ga ik de data met de hand invoeren, of bouw ik een koppeling om die van een website als Fotmob uit te lezen? Dan hoef ik niets handmatig in te voeren en is de tool elke week meteen bruikbaar. Dit leek me het perfecte moment om Claude erbij te pakken; ook meteen een goede test om te zien of ik als programmeur echt overbodig aan het worden ben.

Illustratie: is Mark als programmeur overbodig aan het worden?

Claude als pair programmer

Ik vroeg Claude from scratch te beginnen en legde het probleem uit. Een groot voordeel aan modellen als Claude Sonnet en Opus, of vergelijkbare modellen van concurrenten, is dat ze behalve "programmeerkennis" ook kennis hebben over de echte wereld. Zo hoefde ik dus niet het concept "competitie" of "ranglijst" uit te leggen en kon ik volstaan met opzoeken wat de regels zijn bij een gelijke stand. Die zijn per competitie verschillend en ik verwachtte dat dit misschien mis zou gaan, dus ik dacht: ik help een handje.

Claude pakte de klus grotendeels uitstekend op. Het automatisch ophalen en parsen van de data was zo geregeld en vereiste ook geen enkele aansturing. Binnen no-time had ik werkende code die de actuele Premier League-stand en alle resterende wedstrijden ophaalde, zonder dat ik ook maar één CSV hoefde aan te raken. Ik kreeg verschillende andere competities cadeau in een mooi keuzemenuutje; haast te mooi om waar te zijn.

Illustratie: Claude's oplossing lijkt te mooi om waar te zijn

Bij dit soort taken maakt AI je leven echt een stuk simpeler. Boilerplate-code, data ophalen, structuren opzetten: Claude deed het zonder morren. Ik kon me richten op het grotere plaatje.

Waar het misging

Toen kwam het rekenwerk zelf... en daar ging het grondig fout. Claude genereerde code die (dacht ik) alle scenario's doorliep en berekende wat de hoogste en laagste eindpositie voor elk team kon zijn. We hoefden nog maar twee punten te halen uit de komende vijf wedstrijden! Hoewel onwaarschijnlijk - op het eerste gezicht zouden we nog een punt of 6 à 7 nodig hebben - wist ik wel dat veel van onze concurrenten nog tegen elkaar moesten spelen, en misschien pakte dat wel precies gunstig voor ons uit. Ik had de code niet echt goed bekeken, maar even vluchtig zag het er wel prima uit. Ik meldde het resultaat in de groepschat en ging met een heerlijk gevoel slapen.

Illustratie: Mark gaat met een heerlijk gevoel slapen na de eerste uitkomst

's Avonds werd er een aantal inhaalwedstrijden gespeeld en de volgende ochtend liet ik het tooltje opnieuw draaien. Het zat me toch niet helemaal lekker. Ik vroeg Claude de data om te zetten naar het CSV-formaat uit m'n oude tooltje - die had ik tot dat moment voor Claude verborgen gehouden - en vergeleek de uitkomsten. We konden nog als dertiende eindigen! Er klopte kort gezegd helemaal geen snars van.

Screenshot van de ranglijststand de volgende ochtend die niet klopt

Na wat graven ontdekte ik twee fundamentele fouten. Ten eerste ging de nieuwe tool ervan uit dat wedstrijden alleen konden eindigen in 1-0, 0-0 of 0-1. Dat is uiteraard onzin: een team kan ook met 4-0 winnen, wat voor de puntentelling weliswaar hetzelfde is, maar voor doelsaldo enorm scheelt. Ten tweede, en dit was de grotere fout: het algoritme keek voor elke wedstrijd wat op dát moment de slechtste uitkomst voor United zou zijn zonder de lange termijn mee te nemen. Als voorbeeld (pak de stand uit het begin van dit artikel er nog even bij) zou het Aston Villa nu van Liverpool laten winnen, zodat Aston Villa nu voorbijgaat aan Manchester United in de stand; het negeert dat Liverpool daardoor minder "kans" heeft om ook boven ons te eindigen. Die onderlinge afhankelijkheid is precies wat dit probleem zo complex maakt. En Claude had het volledig genegeerd.

Weet je nog hoe B en C gelijk moesten spelen in hun eerste wedstrijd om zo “samen te spannen” tegen team A? Het algoritme dat Claude had geschreven, maakt een heel andere (greedy) keuze voor die wedstrijd. Door team B te laten winnen van team C, gaat het voorbij aan team A in de ranglijst. Dus: het past die uitslag toe en kijkt niet naar de lange termijn, waarin het beter is om de twee concurrerende teams allebei een puntje “verder” te helpen.

#TeamPuntenDoelsaldo
1.B14+5
2.A*11+2
3.C10-1
4.D6-6
Uitslagen:
  • A-D: 0-1
  • B-C: 1-0
  • B-D: 1-0
  • A-C: 0-1

De conclusie van Claude zou hier zijn: we weten zeker dat team A in de top-2 eindigt.

Illustratie: minder fijn wakker worden na het ontdekken van de fout

De les: AI als assistent, niet als architect

Wat ik hieruit meeneem: AI is een uitstekende assistent voor het technische gedeelte: data ophalen, code structureren, integraties bouwen. Maar de domeinlogica, de inhoudelijke redenering over wat het programma moet doen, moet je zelf bewaken. Ik had gevraagd om "de beste en slechtst mogelijke uitkomst", maar in plaats daarvan koos Claude voor de makkelijke weg: een greedy algoritme, waarvan je op de universiteit in het eerste blok leert dat het nooit een optimale oplossing garandeert.

Illustratie: Claude negeert de instructies en kiest de makkelijke weg

Soms negeert de LLM je instructies, of kiest-ie voor de makkelijke weg.

Ik wist ongeveer wat het goede antwoord moest zijn, omdat ik een referentie had in mijn oude tool. Zonder die vergelijking had ik de fouten misschien nooit opgemerkt en was ik met verkeerde cijfers geëindigd. Claude weet veel, maar begreep niet de implicaties van mijn vraag. Dat betekent dat je dus zelf de algoritmes en de uitkomsten moet bewaken. Al dan niet met behulp van AI, die je dan ook weer controleert natuurlijk.

De waarde van overhead

Als dit een project voor een klant was geweest, had ik het sowieso anders aangepakt. Ervan uitgaande dat de klant akkoord is, had ik voor het grootste deel nog steeds AI kunnen gebruiken. Maar de tool had ook een interface gekregen, en op z'n minst een aantal unittests voor interessante gevallen. Over een paar jaar werk ik hier misschien niet meer en mijn toekomstige collega (noem je dat nog steeds een collega in dat geval?) heeft dan een ijkpunt als die een keer wat moet aanpassen.

Uiterlijk op dat moment was ik erachter gekomen dat de uitkomsten helemaal niet correct waren en de logica kant noch wal raakte. Waarschijnlijker had ik ook een elegant en efficiënt algoritme willen afleveren en had ik Claude vanaf het begin al veel uitgebreidere instructies gegeven.

Illustratie bij de woordgrap 'balgoritme' (algoritme met een bal)

Je dacht toch niet dat je een artikel zou krijgen zonder een slechte woordgrap?

Wat zegt de tool uiteindelijk?

Ik heb Claude mijn oude tool laten zien en, met wat extra aanwijzingen, in een aantal stappen de logica laten overnemen. De tool geeft nu een helder beeld: wiskundig gezien heeft United nog zes punten nodig om de Champions League zeker te stellen. Praktisch gezien is het bijna zeker dat dat gaat lukken: wij hebben niet een heel moeilijk programma meer en de concurrentie moet bijna bewust samenspannen om ons nog van die plek af te houden.

Ik kan het biertje op een plein in de Spaanse zon al bijna ruiken. Vanaf september eindelijk weer genieten van Champions League-voetbal, terwijl de tool keurig heeft uitgerekend dat het geen verrassing was; alleen maar wiskunde.

Illustratie van een biertje op een terras in de Spaanse zon