Bygg en rekommendationstjänst
Traditionellt har vi förlitat oss på omdömen: har en film fått ett bra betyg, eller ett dåligt? Det rör sig alltså om ett medelbetyg baserat på hur andra som har sett en film har röstat. Skulle vi ha en skala på 5 stjärnor, med en röst per stjärna, skulle det alltså ge oss ett medelvärde på 3:
x = antal röster per stjärna, X = totalt antal röster
(1*x + 2*x + 3*x + 4*x + 5*x) / X
2009 infördes Gilla-knappen vilket istället gjorde det möjligt att algoritmisera allt innehåll. Det som drev engagemang och intresse kunde lyftas fram och beroende på hur du själv interagerat kunde man nu också ta hänsyn till relevans, snarare än att bara ha en sekventiell feed. Har du gillat en bild på en katt kryper det numera in lite fler kattbilder i din feed, har du streamat en film kommer du få tips på vad du kan se härnäst och har du lagt något i varukorgen kommer du säkert också att se sektionen "Andra som köpte denna vara köpte även..." nära intill Betalknappen.
Å ena sidan, när det gäller exempelvis politiska frågor har detta lett till positiva feedback-loopar och polarisering i världen. Istället för att ge oss en nyanserad bild och olika perspektiv, presenteras vi övervägande av argumenten för det ena eller det andra. Med positiv feedback-loop menar jag då inte "positiv feedback", utan en ekokammare; en signal som rekursivt förstärks likt rundgång i ett högtalarsystem. Att "leva i en bubbla" vore ett annat sett att se på det.
Å andra sida, föreställ dig att du ska köpa tandkräm och att det finns 500 olika sorter att välja bland. Ett fritt val snarare än ett begränsat urval kan spontant verka bättre, såsom att exempelvis bara ha två sorter att välja mellan, eller hur? Men hur mycket tid är du egentligen villig att lägga på att välja tandkräm? 1 minut? 10 minuter? 1h? För det andra: vad tror du sannolikheten är att du väljer rätt sort? Kanske inte riktigt 1 på 500... men inte så stor. Att ha en oändlig meny av val kräver komplexa, kognitiva, överslagsberäkningar av oss vi inte alltid tar oss tid till. Tvärtom, är det kanske något vi förväntar oss återförsäljarens hjälp med?
I detta inlägg ska vi med hjälp av machine learning bygga en rekommendationsmotor. I mitt projekt har jag valt att matcha kandidater med företag och uppdrag, vilket var något jag kunde relatera till. Jag kommer kort förklara några grundläggande begrepp samt ge ifrån mig kodbasen (React).
Det här mönstret är en Graf. I detta sammanhang finns det i huvudsak två olika typer utav grafer: enkelriktad (directed) och dubbelriktad (undirected). Instagram vore ett exempel på en enkelriktad graf, där jag kan följa dig utan att du behöver följa mig tillbaka. Facebook använder istället en dubbelriktad graf, där vi blir "Vänner" som är en ömsesidig bindning. Varje nod/prick i grafen kallas för en Vertex och varje streck/bindning emellan dem kallas för en Edge. På många sätt kan vid likna detta med vår hjärna!
Vi kommer dela upp vår applikation i tre lager: Input layer, Hidden layer och Output layer. Som namnet indikerar kommer vi i det första lagret att samla in data, både om våra kandidater samt vår användares sökfråga. I andra lagret (hidden) kommer vi att skapa en graf (likt bilden ovan) samt transversera genom denna, vilket motsvarar själva sökningen. Algoritmen vi kommer använda oss av kallas för en Breadth-First Search algoritm (BFS) och används för bland annat karttjänster, streamingtjänster och sökmotorer. Detta är kärnan i hela projektet. Jag kommer även skicka med några bonusfunktioner samt en Depth-First Search algoritm (DFS), även om vi i just detta exempel inte har någon användning av dem. Output-lagret kommer kort och gott att presentera resultatet.
Kandidaterna kan komma från vilken eller vilka källor som helst, men jag hämtar in dem från en JSON-fil som finns inkluderad i projektet. Detta för att undvika överflödig kod som inte hör till sak; det är en Array av Objekt och ska formateras enligt följande (förutsatt att du vill köra min kod utan att behöva göra några modifikationer):
dataset.json
[
{
"id": string,
"createdDate": number,
"city": {
"label": string,
"value": string
},
"company": {
"label": string,
"value": string
},
"position": {
"label": string,
"value": string
}
},
...
]
När vi läst in våra kandidater kommer vi iterera igenom dem och skilja ut alla unika företag, orter och roller som finns bland kandidaterna, samt lagra detta i vårt globala state (har lagt med en hook för detta som bygger på React context API). Återigen för att hålla ned på orelaterad kod har jag inte stylat i någon större utsträckning, utan använt ett bibliotek för att få lite färg och form på formuläret, som består av följande fält:
Välj roller som du söker (Flerval är möjligt. Anges i fallande prioriteringsordning. Ska du rekrytera en projektledare kan det exempelvis vara bra att även ta med projektadministratörer i sökningen.)
Välj om du vill rekrytera åt ett specifikt företag eller i en särskild stad
Om företag, bocka i om du vill inkludera internkandidater
Välj vilket företag alt. stad (beroende på vad du valde i fält 2)
Det finns viss villkorsstyrning för fälten. När de obligatoriska fälten är ifyllda går det att klicka på sök-knappen, vilket tar oss vidare till det dolda lagret. Här är koden uppdelad i två separata hooks, en för att skapa grafen och en för att genomföra sökningen. Vi dirigeras automatiskt vidare till presentationslagret (output layer) när sökningen är klar, där vi nu kan se de bästa förslagen baserat på relevans (rekommendationer).
Bonus
När det som i detta fall gäller just kandidatdata blir detta snabbt utdaterat. För att ta hänsyn till datakvaliteten har jag även lagt till en graderings-hook, som gör en bedömning av profilstyrkan. Graderingen skiljer sig också ifrån ett omdöme, då en rating eller "favoritmarkering" av en kandidat skulle kunna ses som en subjektiv preferens. Istället tittar vi på hur länge sedan profilen registrerades eller uppdaterades, om den är komplett ifylld samt om värdena är standardiserade eller avvikande/udda. Detta tillägg går lätt att ta bort eller skriva om ifall det inte är aktuellt för just ditt use case. Resultatet av detta ses i listningen, som en färgskala bakom resultatet som indikerar kvaliteten (har också en procentuell bredd från vänster till höger). Jag lade även till en svartstreckad linje som visar vilka resultat som matchar sämre än medel. Detta skulle kunna sofistikeras ytterligare, genom att exempelvis ta hänsyn till antal bindningar (noder ifrån).
Till projektet: Rekommendationer på GitHub