Deel 1: Inleiding
Dit is de eerste post van een serie van twee over het gebruik van BigQuery om (een onderdeel van) een aanbevelingssysteem te bouwen. Dit eerste deel is vooral conceptueel, terwijl ik in het tweede deel in een feitelijke code duik en uitleg hoe je in de praktijk een dagelijkse pijplijn voor trainingen opzet.
Stel, je hebt een website waarop mensen hun zelfgemaakte digitale kunstwerken kunnen laten zien (elk uiteraard gekoppeld aan een NFT) en andere mensen kunnen deze beoordelen met bijvoorbeeld 1 (probeer nog eens…) tot 5 (doe dit nog eens!!).
Je wilt aanbevelingen tonen voor kunstwerken die een gebruiker leuk zou kunnen vinden, gebaseerd op zijn “smaak”. Je kunt “soortgelijke” kunstwerken laten zien, maar het is niet zo eenvoudig om kenmerken uit deze kunstwerken te halen om je op te baseren. Je laat mensen hun kunst taggen en beschrijven; de meesten van hen zijn echter te lui om dit goed te doen, áls ze het al doen.
Dus wat men noemt “op inhoud gebaseerde gelijkenissen” is niet gemakkelijk in dit geval.
Je streamt echter wel alle gebruikersbeoordelingen naar een tabel – een zogenaamde rating matrix (vaak ook een user-item matrix genoemd) – in je database. Deze tabel heeft (tenminste) drie kolommen: de user id, de item id en de rating.
Meestal is het ook nuttig een tijdstempel op te slaan, bijvoorbeeld om je in staat te stellen de gegevens op te splitsen in training, validatie en test sets.
Collaborative filtering
Met dit soort gegevens kun je op andere manieren aanbevelingen genereren. Stel dat je wilt weten of je gebruiker 1 item A moet aanbevelen. Als andere gebruikers die “vergelijkbaar zijn met” gebruiker 1 (in de zin dat ze andere items vergelijkbaar hebben beoordeeld), item A hoog waarderen, zou gebruiker 1 het ook leuk kunnen vinden.
Of, als items die “vergelijkbaar zijn met” item A (in de zin dat ze vergelijkbaar zijn beoordeeld door andere gebruikers) een hoge waardering hebben gekregen van gebruiker 1, zou gebruiker 1 item A ook leuk kunnen vinden. Nadat je de vorige twee zinnen een paar keer hebt gelezen, sla je ze op in je brein onder de noemer “collaborative filtering”.
Collaborative filtering is een algemene term voor veel verschillende doch verwante algoritmen, en wordt eigenlijk vaak gecombineerd met andere technieken in wat hybride recommenders worden genoemd.
Het is zowel kunst als een wetenschap om uit te zoeken welke aanpak het beste in jouw ‘use case’ past. Daar komt nog bij dat de technologie voortdurend evolueert. Een van de uitdagingen is dat je meestal eindigt met wat een zeer karige rating matrix wordt genoemd: je hebt misschien duizenden items en miljoenen gebruikers, maar elke gebruiker zal slechts een handvol items expliciet hebben beoordeeld, als ze al bereid zijn om ook maar iets te beoordelen. Eén van de redenen voor de steeds verdergaande verfijning van state of the art benaderingen is het vinden van nieuwe manieren om deze schaarste te bestrijden.
In deze posts zal ik me concentreren op het gebruik van expliciete feedback, gebaseerd op expliciete gebruikersbeoordelingen zoals hierboven beschreven. Maar het is goed om in gedachten te houden dat je je model ook kunt baseren op wat impliciete feedback wordt genoemd. In deze benadering vertrouw je niet op expliciete ratings, maar probeer je andere soorten gebruikersgedrag te interpreteren, zoals klikken op items, afspeeltijd van nummers, tijd doorgebracht op een pagina of shares op sociale media als een niveau van interesse dat een gebruiker in een item zou kunnen hebben.
Een dergelijk model is uiteraard ingewikkelder te bouwen en subtieler te interpreteren, maar het biedt ook een manier om het schaarsteprobleem gedeeltelijk te ondervangen. Daartoe kan het zelfs worden gecombineerd met expliciete feedback modellering.
Veel collaborative filtering benaderingen zijn gebaseerd op een techniek die matrix factorisatie wordt genoemd. Er zijn weer verschillende manieren waarop dit kan worden bereikt, maar in essentie is het doel van matrix factorisatie de rating matrix te schrijven als een product van twee “kleinere” en meer dichte matrices (die respectievelijk gebruikers en items vertegenwoordigen) met behulp van iets dat een latent representation wordt genoemd. Gebruikers en items worden beide voorgesteld als hoger dimensionale vectoren in dezelfde “latent space”. Elk component in deze latent space wordt een latente factor genoemd en vertegenwoordigt conceptueel een aspect van de interesses van een gebruiker of het overeenkomstige itemkenmerk.
Bijvoorbeeld, als je een aanbeveler voor films aan het bouwen bent, zou één van de latente factoren kunnen aangeven hoeveel een gebruiker van komedie houdt, en op dezelfde manier, hoeveel komedie een film bevat. Als deze factor hoog is bij zowel gebruiker 1 als film A, zal het de waarschijnlijkheid verhogen dat dit een goede aanbeveling is. In werkelijkheid zullen de latente factoren subtieler zijn dan dat, en worden ze tijdens de training door het model aangeleerd.
BigQuery ML
Oké, je vraagt je vast af, nu we allemaal collaborative filtering experts zijn en ik een rating matrix gebaseerd op expliciete feedback klaar heb liggen, hoe je dit eigenlijk in de praktijk toepast?
Eén manier is om je eigen model te bouwen met behulp van je favoriete Python framework, maar ook al zijn er een heleboel implementaties beschikbaar, zelfs als er eentje perfect aan je behoeften voldoet, dan nóg moet je een preprocessing pipeline opzetten, infrastructuur voor training, modelvalidatie en een manier om aanbevelingen uit het model te halen (zelfs als dat via een batch proces is).
In werkelijkheid is het nog ingewikkelder dan dat.
Waarschijnlijk weet je van tevoren niet welke aanpak het beste werkt voor jouw toepassing, dus zal je voor elk model minstens een paar hyperparameters moeten uitproberen om te zien welke versie van welk model het beste werkt.
Ik wed dat jij ziet waar dit heen gaat… En natuurlijk heb je zo vroeg in het spel nog niet eens een solide grip op de return on investment die deze vernuftige aanbeveler daadwerkelijk zal opleveren.
Al met al, zelfs als je uiteindelijk investeert in een meer betrokken machine learning infrastructuur, is het goed om te beginnen met een basislijn die relatief snel op te zetten is en waarmee je gemakkelijk kunt spelen met een aantal hyperparameters. Het belangrijke daarvan is het aantal latente factoren.
BigQuery is een uiterst schaalbare dienst voor gestructureerde gegevensopslag en een SQL-engine die vaak wordt gebruikt als de basis van een data warehouse en als algemene “speeltuin” voor data analisten. Sinds kort biedt het ook meer mogelijkheden voor machine learning, waardoor je ML-modellen kunt trainen met vaak maar een paar regels SQL.
Eén van de mogelijkheden hiervan is een matrix factorization model. Het maakt gebruik van een veelgebruikte en beproefde variant genaamd Alternating Least Squares (ALS) voor expliciete feedback of Weighted Alternating Least Squares (WALS) voor impliciete feedback. Met andere woorden, het is heel goed mogelijk dat dit vrij snel op te zetten basismodel het model is dat je uiteindelijk in productie gebruikt.
Maar niet zo snel…
Ik zal de SQL en BigQuery introductie overslaan die je op veel andere plaatsen kunt vinden (zoals hier, hier en hier). Ik wil mij graag richten op een ander, aanvankelijk wat omslachtig, aspect van de workflow. Weet je nog dat ik zei dat BigQuery ML het trainen en draaien van ML-modellen tot een koud kunstje maakt? Nou… wat blijkt: matrix factorization vereist wel een extra stap.
Als je begint met BigQuery, zullen de meeste mensen gebruik maken van het on-demand prijsschema, waarbij je gewoon betaalt voor het aantal bytes dat per (ongecachede) query wordt gelezen. Dit maakt de instapdrempel erg laag, omdat je snel kunt beginnen met het verkennen van je gegevens zonder je zorgen te maken over de kosten, zolang de grootte van de betrokken gegevens binnen bepaalde grenzen blijft – wat “lage kosten” en “bepaalde grenzen” betekenen zal afhangen van je situatie.
Voor sommige soorten werk is dit echter geen eerlijke weergave van de hoeveelheid benodigde rekenkracht. Matrix factorization met ALS is zo’n geval, omdat er veel iteraties over de gegevens nodig zijn om goede modelprestaties te bereiken. Daarom wordt in dit geval een ander prijsschema gebruikt – in plaats van te betalen voor de hoeveelheid gelezen gegevens, betaal je voor de hoeveelheid benodigde verwerkingskracht. Daartoe moet je eerst het aantal verwerkingseenheden, slots genaamd, reserveren dat je nodig hebt (of bereid bent te betalen) om de gegevens te verwerken.
Nu zul je wel tegen je scherm schreeuwen “Alex, hou op met dat algemene geklets, en laat mij nu eens wat code zien!”. In het volgende deel zal ik Workflows gebruiken om een kleine pijplijn te automatiseren die de BigQuery slots reserveert die we nodig hebben, hoe je een matrix factorization model traint, en zeer belangrijk, hoe je de slotreservering verwijdert als we klaar zijn. Hopelijk lees je dan weer mee!