Het doel van deze tutorial is niet om je een expert te maken in het bouwen van aanbevelingssystemen. Het idee is juist om je op weg te helpen met een overzicht van de soorten aanbevelingssystemen die er zijn en hoe je er zelf eentje kunt bouwen.
In deze tutorial leer je hoe je een basismodel bouwt van eenvoudige en contentbased-aanbevelingssystemen. Hoewel deze modellen qua complexiteit, kwaliteit of nauwkeurigheid niet in de buurt komen van de industrienorm, helpen ze je wel om te starten met het bouwen van complexere modellen die nog betere resultaten opleveren.
Aanbevelingssystemen behoren tot de populairste toepassingen van data science. Ze worden gebruikt om de "rating" of "voorkeur" te voorspellen die een gebruiker aan een item zou geven. Bijna elk groot techbedrijf past ze op de een of andere manier toe. Amazon gebruikt ze om producten aan te bevelen, YouTube om te bepalen welke video automatisch als volgende wordt afgespeeld, en Facebook om pagina's en mensen aan te raden om te volgen.
Meer nog: voor bedrijven als Netflix, Amazon Prime, Hulu en Hotstar draait het businessmodel en het succes om de kracht van hun aanbevelingen. Netflix loofde in 2009 zelfs een miljoen dollar uit aan iedereen die het systeem met 10% kon verbeteren.
Er zijn ook populaire aanbevelingssystemen voor domeinen als restaurants, films en online daten. Daarnaast zijn aanbevelers ontwikkeld voor het verkennen van onderzoeksartikelen en experts, samenwerkingen en financiële diensten. YouTube gebruikt aanbevelingen op grote schaal om video's voor te stellen op basis van je geschiedenis. Kijk je bijvoorbeeld veel educatieve video's, dan stelt het platform je dat type content voor.
Maar wat zijn die aanbevelingssystemen precies?
Grofweg kun je aanbevelingssystemen indelen in 3 typen:
Eenvoudige aanbevelers: bieden algemene aanbevelingen aan elke gebruiker, gebaseerd op filmpopulariteit en/of genre. Het basisidee is dat populairdere en geprezen films met hogere waarschijnlijkheid in de smaak vallen bij het gemiddelde publiek. Een voorbeeld is de IMDB Top 250.
Contentbased-aanbevelers: raden vergelijkbare items aan op basis van een specifiek item. Dit systeem gebruikt metadata van items, zoals genre, regisseur, beschrijving, acteurs, enz. bij films, om aanbevelingen te doen. Het idee is dat als iemand een bepaald item leuk vindt, hij/zij ook iets zal waarderen dat erop lijkt. Om dat te kunnen aanraden, gebruikt het de metadata van de items die de gebruiker eerder leuk vond. Een goed voorbeeld is YouTube, dat op basis van je geschiedenis nieuwe video's voorstelt die je mogelijk wilt bekijken.
Collaborative-filtering-engines: deze systemen worden veel gebruikt en proberen te voorspellen welke rating of voorkeur een gebruiker aan een item zou geven op basis van eerdere ratings en voorkeuren van andere gebruikers. Collaborative filtering heeft geen itemmetadata nodig, in tegenstelling tot contentbased systemen.
Eenvoudige aanbevelers
Zoals hierboven beschreven, zijn eenvoudige aanbevelers basale systemen die topitems aanraden op basis van een bepaalde metriek of score. In deze sectie bouw je een vereenvoudigde kloon van IMDB Top 250 Movies met metadata verzameld van IMDB.
De stappen zijn als volgt:
Bepaal de metriek of score om films op te beoordelen.
Bereken de score voor elke film.
Sorteer de films op basis van de score en geef de toppers weer.
Over de dataset
De datasetbestanden bevatten metadata voor alle 45.000 films in de Full MovieLens Dataset. De dataset bestaat uit films uitgebracht op of vóór juli 2017. Ze bevat features als cast, crew, plotkeywords, budget, omzet, posters, releasedata, talen, productiebedrijven, landen, TMDB-stemaantallen en gemiddelde stemmen.
Deze features kunnen mogelijk worden gebruikt om je machinelearningmodellen te trainen voor content- en collaborative filtering.
Deze dataset bestaat uit de volgende bestanden:
movies_metadata.csv: Bevat informatie over ~45.000 films uit de Full MovieLens-dataset. Features omvatten posters, backdrops, budget, genre, omzet, releasedata, talen, productielanden en -bedrijven.
keywords.csv: Bevat de plotkeywords voor onze MovieLens-films. Beschikbaar als een ge-stringifieerde JSON.
credits.csv: Bestaat uit cast- en crewinformatie voor alle films. Beschikbaar als een ge-stringifieerde JSON.
links.csv: Bevat de TMDB- en IMDB-ID's van alle films uit de Full MovieLens-dataset.
links_small.csv: Bevat de TMDB- en IMDB-ID's van een kleine subset van 9.000 films uit de volledige dataset.
ratings_small.csv: De subset met 100.000 ratings van 700 gebruikers op 9.000 films.
De Full MovieLens Dataset omvat 26 miljoen ratings en 750.000 tag-toepassingen, van 270.000 gebruikers op alle 45.000 films in deze dataset. Je kunt deze vinden op de officiële GroupLens-website.
Let op: De subset die in deze tutorial wordt gebruikt, kun je hier downloaden.
Om je dataset te laden, gebruik je de pandas DataFrame-bibliotheek. De pandas-bibliotheek wordt vooral gebruikt voor datamanipulatie en -analyse. Ze stelt je data voor in een rij-kolomstructuur. De Pandas-bibliotheek is gebouwd op de NumPy-array voor de implementatie van pandas-dataobjecten. pandas biedt kant-en-klare datastructuren en bewerkingen voor het manipuleren van numerieke tabellen, tijdreeksen, afbeeldingen en NLP-datasets. Kortom, pandas is nuttig voor datasets die zich eenvoudig in tabelvorm laten weergeven.
Voordat je een van de bovenstaande stappen uitvoert, laden we eerst je movies-metadata in een pandas DataFrame:
# Import Pandasimport pandas as pd
# Load Movies Metadata
metadata = pd.read_csv('movies_metadata.csv', low_memory=False)# Print the first three rows
metadata.head(3)Mogelijk gemaakt door
Een van de meest basale metrics is de ranking om te bepalen welke top 250 films er zijn op basis van hun ratings.
Maar een rating als metriek heeft een paar kanttekeningen:
Ten eerste houdt het geen rekening met de populariteit van een film. Een film met een rating van 9 van 10 stemmers wordt dus als 'beter' gezien dan een film met een rating van 8,9 van 10.000 stemmers.
Stel dat je Chinees wilt bestellen en je hebt twee opties: het ene restaurant heeft 5 sterren van slechts 5 mensen, het andere 4,5 sterren van 1000 mensen. Welke kies je? Waarschijnlijk de tweede, toch?
Natuurlijk kan het zijn dat het eerste restaurant pas net open is en dus minder stemmen heeft, terwijl het tweede al een jaar draait.
Gerelateerd hieraan zal deze metriek films met weinig stemmers en scheve en/of extreem hoge ratings bevoordelen. Naarmate het aantal stemmers toeneemt, stabiliseert de rating en benadert die een waarde die de kwaliteit beter weerspiegelt en de gebruiker een beter idee geeft welke film hij/zij moet kiezen. Bij extreem weinig stemmers is het lastig de kwaliteit te beoordelen en moet je mogelijk externe bronnen raadplegen.
Rekening houdend met deze tekortkomingen, moet je een gewogen rating bedenken die het gemiddelde en het aantal stemmen meeneemt. Zo krijgt een film met een 9 van 100.000 stemmers een (veel) hogere score dan een film met dezelfde rating maar slechts een paar honderd stemmers.
Omdat je een kloon van IMDB's Top 250 probeert te bouwen, gebruiken we hun gewogen ratingformule als metriek/score. Wiskundig weergegeven als volgt:
m is het minimum aantal stemmen om in de lijst te komen;
R is de gemiddelde rating van de film;
C is de gemiddelde rating over de hele dataset.
Je hebt al de waarden voor v (vote_count) en R (vote_average) voor elke film in de dataset. Ook C kun je direct uit deze data berekenen.
Een passende waarde voor m bepalen is een hyperparameter die je zelf kunt kiezen; er is geen 'juiste' waarde. Je kunt het zien als een negatieve voorfilter die simpelweg films verwijdert met minder stemmen dan een bepaalde drempel m. Hoe selectief je filter is, bepaal je zelf.
In deze tutorial gebruik je als afkapwaarde m het 90e percentiel. Met andere woorden: om in de lijst te komen moet een film meer stemmen hebben dan minstens 90% van de films. (Had je bijvoorbeeld het 75e percentiel gekozen, dan had je de top 25% qua aantal stemmen meegenomen. Hoe lager het percentiel, hoe meer films worden overwogen).
Als eerste stap berekenen we C, de gemiddelde rating over alle films met de pandas-functie .mean():
# Calculate mean of vote average column
C = metadata['vote_average'].mean()print(C)Mogelijk gemaakt door
Hieruit zie je dat de gemiddelde filmrating op IMDB rond de 5,6 op een schaal van 10 ligt.
Vervolgens berekenen we het aantal stemmen, m, dat een film in het 90e percentiel heeft. De pandas-bibliotheek maakt dit eenvoudig met de methode .quantile():
# Calculate the minimum number of votes required to be in the chart, m
m = metadata['vote_count'].quantile(0.90)print(m)Mogelijk gemaakt door
Nu je m hebt, kun je eenvoudig filteren op films met minimaal 160 stemmen:
Gebruik de methode .copy() om ervoor te zorgen dat de nieuwe q_movies DataFrame onafhankelijk is van je oorspronkelijke metadata-DataFrame. Met andere woorden: wijzigingen in q_movies beïnvloeden het origineel niet.
# Filter out all qualified movies into a new DataFrame
q_movies = metadata.copy().loc[metadata['vote_count']>= m]
q_movies.shape
Mogelijk gemaakt door
Hieruit blijkt dat ongeveer 10% van de films meer dan 160 stemmen heeft en in deze lijst komt.
De volgende, en belangrijkste, stap is het berekenen van de gewogen rating voor elke gekwalificeerde film. Hiervoor zul je:
Een functie weighted_rating() definiëren;
Aangezien je m en C al hebt berekend, geef je die door als argumenten;
Vervolgens selecteer je de kolommen vote_count (v) en vote_average (R) uit de q_movies-DataFrame;
Tot slot bereken je het gewogen gemiddelde en retourneer je het resultaat.
Je definieert een nieuwe feature score, waarvan je de waarde berekent door deze functie toe te passen op je DataFrame met gekwalificeerde films:
# Function that computes the weighted rating of each moviedefweighted_rating(x, m=m, C=C):
v = x['vote_count']
R = x['vote_average']# Calculation based on the IMDB formulareturn(v/(v+m)* R)+(m/(m+v)* C)Mogelijk gemaakt door
# Define a new feature 'score' and calculate its value with `weighted_rating()`
q_movies['score']= q_movies.apply(weighted_rating, axis=1)Mogelijk gemaakt door
Tot slot sorteren we de DataFrame aflopend op de kolom score en geven we de titel, het aantal stemmen, het gemiddelde en de gewogen rating (score) van de top 20 films weer.
#Sort movies based on score calculated above
q_movies = q_movies.sort_values('score', ascending=False)#Print the top 15 movies
q_movies[['title','vote_count','vote_average','score']].head(20)Mogelijk gemaakt door
title
vote_count
vote_average
score
314
The Shawshank Redemption
8358.0
8.5
8.445869
834
The Godfather
6024.0
8.5
8.425439
10309
Dilwale Dulhania Le Jayenge
661.0
9.1
8.421453
12481
The Dark Knight
12269.0
8.3
8.265477
2843
Fight Club
9678.0
8.3
8.256385
292
Pulp Fiction
8670.0
8.3
8.251406
522
Schindler's List
4436.0
8.3
8.206639
23673
Whiplash
4376.0
8.3
8.205404
5481
Spirited Away
3968.0
8.3
8.196055
2211
Life Is Beautiful
3643.0
8.3
8.187171
1178
The Godfather: Part II
3418.0
8.3
8.180076
1152
One Flew Over the Cuckoo's Nest
3001.0
8.3
8.164256
351
Forrest Gump
8147.0
8.2
8.150272
1154
The Empire Strikes Back
5998.0
8.2
8.132919
1176
Psycho
2405.0
8.3
8.132715
18465
The Intouchables
5410.0
8.2
8.125837
40251
Your Name.
1030.0
8.5
8.112532
289
Leon: The Professional
4293.0
8.2
8.107234
3030
The Green Mile
4166.0
8.2
8.104511
1170
GoodFellas
3211.0
8.2
8.077459
Zoals je ziet heeft de eenvoudige aanbeveler het prima gedaan!
De grafiek heeft veel overlap met de IMDB Top 250: je top twee films, "Shawshank Redemption" en "The Godfather", zijn dezelfde als bij IMDB en we weten allemaal dat dat geweldige films zijn. Eigenlijk verdienen alle top 20-films het om in die lijst te staan, toch?
Contentbased-aanbeveler
Aanbeveler op basis van plotbeschrijving
In deze sectie leer je een systeem bouwen dat films aanbeveelt die lijken op een bepaalde film. Hiervoor bereken je paargewijze cosine-similariteitsscores voor alle films op basis van hun plotbeschrijvingen en raad je films aan op basis van die drempel voor similariteitsscore.
De plotbeschrijving is beschikbaar als de feature overview in je metadata-dataset. Laten we de plots van een paar films bekijken:
#Print plot overviews of the first 5 movies.
metadata['overview'].head()Mogelijk gemaakt door
0 Led by Woody, Andy's toys live happily in his ...1 When siblings Judy and Peter discover an encha...2A family wedding reignites the ancient feud be...3 Cheated on, mistreated and stepped on, the wom...4 Just when George Banks has recovered from his ...Name: overview,dtype: object
Mogelijk gemaakt door
Het probleem is een NLP-vraagstuk. Je moet dus eerst features uit deze tekstdata extraheren voordat je similariteit/ongelijkheid tussen ze kunt berekenen. Simpel gezegd: je kunt de similariteit tussen twee overzichten niet in ruwe vorm berekenen. Je moet de woordvectoren van elk overzicht (of document) berekenen.
Zoals de naam zegt, zijn woordvectoren gevectoriseerde representaties van woorden in een document. De vectoren dragen semantiek met zich mee. Bijvoorbeeld: man & king zullen vectorrepresentaties dicht bij elkaar hebben, terwijl man & woman juist verder uit elkaar liggen.
Je berekent Term Frequency-Inverse Document Frequency (TF-IDF)-vectoren voor elk document. Dit levert een matrix op waarin elke kolom een woord in de overview-woordenschat vertegenwoordigt (alle woorden die in minstens één document voorkomen) en elke rij een film, zoals eerder.
In essentie is de TF-IDF-score de frequentie waarmee een woord in een document voorkomt, naar beneden gewogen door het aantal documenten waarin het voorkomt. Dit vermindert het belang van woorden die vaak in plotoverzichten voorkomen en dus minder betekenisvol zijn voor de uiteindelijke similariteitsscore.
Gelukkig biedt scikit-learn een ingebouwde TfIdfVectorizer-klasse die de TF-IDF-matrix in een paar regels produceert.
Importeer de Tfidf-module met scikit-learn;
Verwijder stopwoorden zoals 'the', 'an', etc., omdat ze geen nuttige informatie geven over het onderwerp;
Vervang not-a-number-waarden door een lege string;
Construeer vervolgens de TF-IDF-matrix op de data.
#Import TfIdfVectorizer from scikit-learnfrom sklearn.feature_extraction.text import TfidfVectorizer
#Define a TF-IDF Vectorizer Object. Remove all english stop words such as 'the', 'a'
tfidf = TfidfVectorizer(stop_words='english')#Replace NaN with an empty string
metadata['overview']= metadata['overview'].fillna('')#Construct the required TF-IDF matrix by fitting and transforming the data
tfidf_matrix = tfidf.fit_transform(metadata['overview'])#Output the shape of tfidf_matrix
tfidf_matrix.shape
Mogelijk gemaakt door
#Array mapping from feature integer indices to feature name.
tfidf.get_feature_names()[5000:5010]Mogelijk gemaakt door
['avails','avaks','avalanche','avalanches','avallone','avalon','avant','avanthika','avanti','avaracious']Mogelijk gemaakt door
Hieruit blijkt dat je dataset met 45.000 films 75.827 unieke woorden bevat.
Met deze matrix kun je nu een similariteitsscore berekenen. Er zijn verschillende metrics mogelijk, zoals manhattan, euclidisch, Pearson en cosine-similariteit. Er is geen eenduidig beste score. Verschillende metrics werken goed in verschillende scenario's; het is vaak goed om te experimenteren en de resultaten te bekijken.
Je gebruikt cosine similarity om een numerieke waarde te berekenen die de overeenkomst tussen twee films aangeeft. Je gebruikt cosine omdat die onafhankelijk is van de grootte en relatief eenvoudig en snel te berekenen is (zeker in combinatie met TF-IDF). Wiskundig gedefinieerd als volgt:
Omdat je de TF-IDF-vectorizer gebruikte, geeft het dotproduct tussen elk paar vectoren direct de cosine-similariteit. Daarom gebruik je sklearn's linear_kernel() in plaats van cosine_similarities(), omdat dat sneller is.
Dit levert een matrix van 45466x45466 op, wat betekent dat elk film-overview een cosine-similariteitsscore heeft met elk ander film-overview. Elke film is dus een 1x45466-kolomvector waarin elke kolom een similariteitsscore met een film is.
# Import linear_kernelfrom sklearn.metrics.pairwise import linear_kernel
# Compute the cosine similarity matrix
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)Mogelijk gemaakt door
Je gaat nu een functie definiëren die een filmtitel als invoer neemt en een lijst met de 10 meest vergelijkbare films geeft. Hiervoor heb je eerst een omgekeerde mapping nodig van filmtitels naar DataFrame-indexen. Met andere woorden: je hebt een mechanisme nodig om de index van een film in je metadata-DataFrame te vinden op basis van de titel.
#Construct a reverse map of indices and movie titles
indices = pd.Series(metadata.index, index=metadata['title']).drop_duplicates()Mogelijk gemaakt door
title
Toy Story 0
Jumanji 1
Grumpier Old Men 2
Waiting to Exhale 3
Father of the Bride Part II4
Heat 5
Sabrina 6
Tom and Huck 7
Sudden Death 8
GoldenEye 9dtype: int64
Mogelijk gemaakt door
Je kunt nu de aanbevelingsfunctie definiëren. De stappen zijn:
Haal de index op van de film met de gegeven titel.
Haal de lijst met cosine-similariteitsscores op voor die film met alle films. Zet dit om in een lijst van tuples waarin het eerste element de positie is en het tweede de similariteitsscore.
Sorteer de lijst op de similariteitsscores (het tweede element).
Neem de top 10 uit deze lijst. Negeer het eerste element omdat dit de film zelf is.
Geef de titels terug die horen bij de indices van de topelementen.
# Function that takes in movie title as input and outputs most similar moviesdefget_recommendations(title, cosine_sim=cosine_sim):# Get the index of the movie that matches the title
idx = indices[title]# Get the pairwsie similarity scores of all movies with that movie
sim_scores =list(enumerate(cosine_sim[idx]))# Sort the movies based on the similarity scores
sim_scores =sorted(sim_scores, key=lambda x: x[1], reverse=True)# Get the scores of the 10 most similar movies
sim_scores = sim_scores[1:11]# Get the movie indices
movie_indices =[i[0]for i in sim_scores]# Return the top 10 most similar moviesreturn metadata['title'].iloc[movie_indices]Mogelijk gemaakt door
12481 The Dark Knight
150 Batman Forever
1328 Batman Returns
15511Batman: Under the Red Hood
585 Batman
21194 Batman Unmasked: The Psychology of the Dark Kn...9230 Batman Beyond: Return of the Joker
18035Batman: Year One
19792Batman: The Dark Knight Returns, Part 13095Batman: Mask of the Phantasm
Name: title,dtype: object
Mogelijk gemaakt door
1178 The Godfather: Part II44030 The Godfather Trilogy:1972-19901914 The Godfather: Part III23126 Blood Ties
11297 Household Saints
34717 Start Liquidation
10821 Election
38030A Mother Should Be Loved
17729 Short Sharp Shock
26293 Beck 28- Familjen
Name: title,dtype: object
Mogelijk gemaakt door
Je ziet dat je systeem best aardige vergelijkbare films vindt op basis van de plotbeschrijving, maar de kwaliteit van de aanbevelingen is niet geweldig. "The Dark Knight Rises" levert alleen Batman-films op, terwijl het waarschijnlijker is dat mensen die die film leuk vonden ook andere films van Christopher Nolan waarderen. Dat kan je huidige systeem niet vangen.
Aanbeveler op basis van credits, genres en keywords
De kwaliteit van je aanbeveler wordt beter met rijkere metadata en fijnere details. Dat ga je hier doen. Je bouwt een aanbeveler op basis van de volgende metadata: de 3 belangrijkste acteurs, de regisseur, gerelateerde genres en de plotkeywords.
De keywords-, cast- en crewdata zitten niet in je huidige dataset, dus laad en merge die eerst in je hoofd-DataFrame metadata.
# Load keywords and credits
credits = pd.read_csv('credits.csv')
keywords = pd.read_csv('keywords.csv')# Remove rows with bad IDs.
metadata = metadata.drop([19730,29503,35587])# Convert IDs to int. Required for merging
keywords['id']= keywords['id'].astype('int')
credits['id']= credits['id'].astype('int')
metadata['id']= metadata['id'].astype('int')# Merge keywords and credits into your main metadata dataframe
metadata = metadata.merge(credits, on='id')
metadata = metadata.merge(keywords, on='id')Mogelijk gemaakt door
# Print the first two movies of your newly merged metadata
metadata.head(2)Mogelijk gemaakt door
Uit je nieuwe features cast, crew en keywords moet je de drie belangrijkste acteurs, de regisseur en de keywords bij de film extraheren.
Maar eerst: je data staat als "ge-stringifieerde" lijsten. Je moet die omzetten naar bruikbare objecten.
# Parse the stringified features into their corresponding python objectsfrom ast import literal_eval
features =['cast','crew','keywords','genres']for feature in features:
metadata[feature]= metadata[feature].apply(literal_eval)Mogelijk gemaakt door
Vervolgens schrijf je functies die helpen de benodigde informatie uit elke feature te halen.
Eerst importeer je NumPy om toegang te krijgen tot de constante NaN. Daarna schrijf je de functie get_director():
Haal de naam van de regisseur uit de crew-feature. Als er geen regisseur staat, retourneer NaN
defget_director(x):for i in x:if i['job']=='Director':return i['name']return np.nan
Mogelijk gemaakt door
Daarna schrijf je een functie die de top 3 elementen of anders de hele lijst retourneert. De lijst slaat op cast, keywords en genres.
defget_list(x):ifisinstance(x,list):
names =[i['name']for i in x]#Check if more than 3 elements exist. If yes, return only first three. If no, return entire list.iflen(names)>3:
names = names[:3]return names
#Return empty list in case of missing/malformed datareturn[]Mogelijk gemaakt door
# Define new director, cast, genres and keywords features that are in a suitable form.
metadata['director']= metadata['crew'].apply(get_director)
features =['cast','keywords','genres']for feature in features:
metadata[feature]= metadata[feature].apply(get_list)Mogelijk gemaakt door
# Print the new features of the first 3 films
metadata[['title','cast','director','keywords','genres']].head(3)Mogelijk gemaakt door
title
cast
director
keywords
genres
0
Toy Story
[Tom Hanks, Tim Allen, Don Rickles]
John Lasseter
[jealousy, toy, boy]
[Animation, Comedy, Family]
1
Jumanji
[Robin Williams, Jonathan Hyde, Kirsten Dunst]
Joe Johnston
[board game, disappearance, based on children'...
[Adventure, Fantasy, Family]
2
Grumpier Old Men
[Walter Matthau, Jack Lemmon, Ann-Margret]
Howard Deutch
[fishing, best friend, duringcreditsstinger]
[Romance, Comedy]
De volgende stap is om de namen en keywords naar kleine letters te brengen en alle spaties te verwijderen.
Spaties verwijderen is een belangrijke preprocessingstap. Zo telt de vectorizer de "Johnny" van "Johnny Depp" en "Johnny Galecki" niet als hetzelfde. Na deze stap worden de genoemde acteurs "johnnydepp" en "johnnygalecki" en dus verschillend voor je vectorizer.
Een ander voorbeeld waar het model dezelfde vector zou kunnen opleveren is "bread jam" en "traffic jam". Daarom is het beter eventuele spaties te strippen.
De onderstaande functie doet precies dat:
# Function to convert all strings to lower case and strip names of spacesdefclean_data(x):ifisinstance(x,list):return[str.lower(i.replace(" ",""))for i in x]else:#Check if director exists. If not, return empty stringifisinstance(x,str):returnstr.lower(x.replace(" ",""))else:return''Mogelijk gemaakt door
# Apply clean_data function to your features.
features =['cast','keywords','director','genres']for feature in features:
metadata[feature]= metadata[feature].apply(clean_data)Mogelijk gemaakt door
Je kunt nu je "metadata soup" maken: een string met alle metadata die je aan de vectorizer wilt voeren (acteurs, regisseur en keywords).
De functie create_soup voegt simpelweg alle vereiste kolommen samen met een spatie. Dit is de laatste preprocessingstap, en de output gaat je woordvectormodel in.
defcreate_soup(x):return' '.join(x['keywords'])+' '+' '.join(x['cast'])+' '+ x['director']+' '+' '.join(x['genres'])Mogelijk gemaakt door
# Create a new soup feature
metadata['soup']= metadata.apply(create_soup, axis=1)Mogelijk gemaakt door
De volgende stappen zijn hetzelfde als bij de aanbeveler op basis van plotbeschrijving. Een belangrijk verschil is dat je nu CountVectorizer() gebruikt in plaats van TF-IDF. Je wilt namelijk de aanwezigheid van een acteur/regisseur niet naar beneden wegen als diegene relatief veel films heeft gedaan. Dat is hier niet intuïtief.
Het grote verschil tussen CountVectorizer() en TF-IDF is de inverse documentfrequentie (IDF)-component, die wel in de laatste zit en niet in de eerste.
# Import CountVectorizer and create the count matrixfrom sklearn.feature_extraction.text import CountVectorizer
count = CountVectorizer(stop_words='english')
count_matrix = count.fit_transform(metadata['soup'])Mogelijk gemaakt door
Je ziet dat er 73.881 woorden in de metadata zitten die je hebt gevoed.
Vervolgens gebruik je cosine_similarity om de afstand tussen de embeddings te meten.
# Compute the Cosine Similarity matrix based on the count_matrixfrom sklearn.metrics.pairwise import cosine_similarity
cosine_sim2 = cosine_similarity(count_matrix, count_matrix)Mogelijk gemaakt door
# Reset index of your main DataFrame and construct reverse mapping as before
metadata = metadata.reset_index()
indices = pd.Series(metadata.index, index=metadata['title'])Mogelijk gemaakt door
Je kunt nu je functie get_recommendations() hergebruiken door de nieuwe matrix cosine_sim2 als tweede argument mee te geven.
12589 The Dark Knight
10210 Batman Begins
9311 Shiner
9874 Amongst Friends
7772 Mitchell
516 Romeo Is Bleeding
11463 The Prestige
24090 Quicksand
25038 Deadfall
41063 Sara
Name: title,dtype: object
Mogelijk gemaakt door
1934 The Godfather: Part III1199 The Godfather: Part II15609 The Rain People
18940 Last Exit
34488 Rege
35802 Manuscripts Don't Burn
35803 Manuscripts Don't Burn
8001 The Night of the Following Day
18261 The Son of No One
28683 In the Name of the Law
Name: title,dtype: object
Mogelijk gemaakt door
Mooi! Je ziet dat je aanbeveler meer informatie heeft weten te vangen dankzij rijkere metadata en betere aanbevelingen geeft. Er zijn uiteraard talloze manieren om hiermee te experimenteren en de aanbevelingen verder te verbeteren.
Enkele suggesties:
Voeg een populariteitsfilter toe: deze aanbeveler neemt de 30 meest vergelijkbare films, berekent de gewogen ratings (met de IMDB-formule), sorteert daarop en geeft de top 10 terug.
Andere crewleden: neem ook namen op van bijvoorbeeld scenarioschrijvers en producenten.
Meer gewicht voor de regisseur: noem de regisseur meerdere keren in de soup om de similariteitsscores van films met dezelfde regisseur te verhogen.
Collaborative filtering met Python
In deze tutorial heb je geleerd hoe je je eigen eenvoudige en contentbased film-aanbevelingssystemen bouwt. Er is ook nog een ander, zeer populair type: collaborative filtering.
Collaborative filters kun je verder indelen in twee typen:
User-based Filtering: deze systemen bevelen producten aan die vergelijkbare gebruikers leuk vonden. Stel dat Alice en Bob een vergelijkbare smaak in boeken hebben (ze vinden grotendeels dezelfde boeken goed of slecht). Er verschijnt een nieuw boek, Alice leest het en is enthousiast. Dan is de kans groot dat Bob het ook leuk vindt, dus raadt het systeem dit boek aan Bob aan.
Item-based Filtering: deze systemen lijken sterk op de content-aanbeveler die je bouwde. Ze identificeren vergelijkbare items op basis van hoe mensen ze eerder beoordeelden. Als Alice, Bob en Eve 5 sterren gaven aan The Lord of the Rings en The Hobbit, ziet het systeem die items als vergelijkbaar. Koopt iemand The Lord of the Rings, dan raadt het systeem ook The Hobbit aan.
Een voorbeeld van collaborative filtering op basis van een ratingsysteem:
Je gaat deze systemen niet bouwen in deze tutorial, maar je kent inmiddels de meeste ideeën die je nodig hebt. Een goed startpunt voor collaborative filtering is de MovieLens-dataset, die je hier vindt.
Conclusie
Gefeliciteerd met het afronden van deze tutorial!
Je hebt met succes onze tutorial doorlopen over aanbevelingssystemen in Python. Je leerde hoe je eenvoudige en contentbased-aanbevelers bouwt.
Een goede oefening is om collaborative filtering te implementeren in Python met de subset van de MovieLens-dataset die je hier gebruikte voor eenvoudige en contentbased-aanbevelers.
Duik in datawetenschap met Python en leer hoe je je data goed kunt analyseren en visualiseren. Je hoeft niet te kunnen programmeren of iets dergelijks.
Het doel van deze tutorial is niet om je een expert te maken in het bouwen van aanbevelingssystemen. Het idee is juist om je op weg te helpen met een overzicht van de soorten aanbevelingssystemen die er zijn en hoe je er zelf eentje kunt bouwen.
In deze tutorial leer je hoe je een basismodel bouwt van eenvoudige en contentbased-aanbevelingssystemen. Hoewel deze modellen qua complexiteit, kwaliteit of nauwkeurigheid niet in de buurt komen van de industrienorm, helpen ze je wel om te starten met het bouwen van complexere modellen die nog betere resultaten opleveren.
Aanbevelingssystemen behoren tot de populairste toepassingen van data science. Ze worden gebruikt om de "rating" of "voorkeur" te voorspellen die een gebruiker aan een item zou geven. Bijna elk groot techbedrijf past ze op de een of andere manier toe. Amazon gebruikt ze om producten aan te bevelen, YouTube om te bepalen welke video automatisch als volgende wordt afgespeeld, en Facebook om pagina's en mensen aan te raden om te volgen.
Meer nog: voor bedrijven als Netflix, Amazon Prime, Hulu en Hotstar draait het businessmodel en het succes om de kracht van hun aanbevelingen. Netflix loofde in 2009 zelfs een miljoen dollar uit aan iedereen die het systeem met 10% kon verbeteren.
Er zijn ook populaire aanbevelingssystemen voor domeinen als restaurants, films en online daten. Daarnaast zijn aanbevelers ontwikkeld voor het verkennen van onderzoeksartikelen en experts, samenwerkingen en financiële diensten. YouTube gebruikt aanbevelingen op grote schaal om video's voor te stellen op basis van je geschiedenis. Kijk je bijvoorbeeld veel educatieve video's, dan stelt het platform je dat type content voor.
Maar wat zijn die aanbevelingssystemen precies?
Grofweg kun je aanbevelingssystemen indelen in 3 typen:
Eenvoudige aanbevelers
Zoals hierboven beschreven, zijn eenvoudige aanbevelers basale systemen die topitems aanraden op basis van een bepaalde metriek of score. In deze sectie bouw je een vereenvoudigde kloon van IMDB Top 250 Movies met metadata verzameld van IMDB.
De stappen zijn als volgt:
Bepaal de metriek of score om films op te beoordelen.
Bereken de score voor elke film.
Sorteer de films op basis van de score en geef de toppers weer.
Over de dataset
De datasetbestanden bevatten metadata voor alle 45.000 films in de
Full MovieLens Dataset. De dataset bestaat uit films uitgebracht op of vóór juli 2017. Ze bevat features als cast, crew, plotkeywords, budget, omzet, posters, releasedata, talen, productiebedrijven, landen, TMDB-stemaantallen en gemiddelde stemmen.Deze features kunnen mogelijk worden gebruikt om je machinelearningmodellen te trainen voor content- en collaborative filtering.
Deze dataset bestaat uit de volgende bestanden:
De Full MovieLens Dataset omvat 26 miljoen ratings en 750.000 tag-toepassingen, van 270.000 gebruikers op alle 45.000 films in deze dataset. Je kunt deze vinden op de officiële GroupLens-website.
Let op: De subset die in deze tutorial wordt gebruikt, kun je hier downloaden.
Om je dataset te laden, gebruik je de
pandasDataFrame-bibliotheek. Depandas-bibliotheek wordt vooral gebruikt voor datamanipulatie en -analyse. Ze stelt je data voor in een rij-kolomstructuur. De Pandas-bibliotheek is gebouwd op deNumPy-array voor de implementatie van pandas-dataobjecten.pandasbiedt kant-en-klare datastructuren en bewerkingen voor het manipuleren van numerieke tabellen, tijdreeksen, afbeeldingen en NLP-datasets. Kortom,pandasis nuttig voor datasets die zich eenvoudig in tabelvorm laten weergeven.Voordat je een van de bovenstaande stappen uitvoert, laden we eerst je movies-metadata in een
pandasDataFrame:3 rijen × 24 kolommen
Een van de meest basale metrics is de ranking om te bepalen welke top 250 films er zijn op basis van hun ratings.
Maar een rating als metriek heeft een paar kanttekeningen:
Ten eerste houdt het geen rekening met de populariteit van een film. Een film met een rating van 9 van 10 stemmers wordt dus als 'beter' gezien dan een film met een rating van 8,9 van 10.000 stemmers.
Stel dat je Chinees wilt bestellen en je hebt twee opties: het ene restaurant heeft 5 sterren van slechts 5 mensen, het andere 4,5 sterren van 1000 mensen. Welke kies je? Waarschijnlijk de tweede, toch?
Natuurlijk kan het zijn dat het eerste restaurant pas net open is en dus minder stemmen heeft, terwijl het tweede al een jaar draait.
Rekening houdend met deze tekortkomingen, moet je een gewogen rating bedenken die het gemiddelde en het aantal stemmen meeneemt. Zo krijgt een film met een 9 van 100.000 stemmers een (veel) hogere score dan een film met dezelfde rating maar slechts een paar honderd stemmers.
Omdat je een kloon van IMDB's Top 250 probeert te bouwen, gebruiken we hun gewogen ratingformule als metriek/score. Wiskundig weergegeven als volgt:
\begin{equation} \text Weighted Rating (\bf WR) = \left({{\bf v} \over {\bf v} + {\bf m}} \cdot R\right) + \left({{\bf m} \over {\bf v} + {\bf m}} \cdot C\right) \end{equation}
In de bovenstaande vergelijking:
v is het aantal stemmen voor de film;
m is het minimum aantal stemmen om in de lijst te komen;
R is de gemiddelde rating van de film;
C is de gemiddelde rating over de hele dataset.
Je hebt al de waarden voor v
(vote_count)en R(vote_average)voor elke film in de dataset. Ook C kun je direct uit deze data berekenen.Een passende waarde voor
mbepalen is een hyperparameter die je zelf kunt kiezen; er is geen 'juiste' waarde. Je kunt het zien als een negatieve voorfilter die simpelweg films verwijdert met minder stemmen dan een bepaalde drempelm. Hoe selectief je filter is, bepaal je zelf.In deze tutorial gebruik je als afkapwaarde
mhet 90e percentiel. Met andere woorden: om in de lijst te komen moet een film meer stemmen hebben dan minstens 90% van de films. (Had je bijvoorbeeld het 75e percentiel gekozen, dan had je de top 25% qua aantal stemmen meegenomen. Hoe lager het percentiel, hoe meer films worden overwogen).Als eerste stap berekenen we C, de gemiddelde rating over alle films met de pandas-functie
.mean():Hieruit zie je dat de gemiddelde filmrating op IMDB rond de 5,6 op een schaal van 10 ligt.
Vervolgens berekenen we het aantal stemmen, m, dat een film in het 90e percentiel heeft. De
pandas-bibliotheek maakt dit eenvoudig met de methode.quantile():Nu je
mhebt, kun je eenvoudig filteren op films met minimaal 160 stemmen:Gebruik de methode
.copy()om ervoor te zorgen dat de nieuweq_moviesDataFrame onafhankelijk is van je oorspronkelijke metadata-DataFrame. Met andere woorden: wijzigingen in q_movies beïnvloeden het origineel niet.Hieruit blijkt dat ongeveer 10% van de films meer dan 160 stemmen heeft en in deze lijst komt.
De volgende, en belangrijkste, stap is het berekenen van de gewogen rating voor elke gekwalificeerde film. Hiervoor zul je:
weighted_rating()definiëren;menCal hebt berekend, geef je die door als argumenten;vote_count(v) envote_average(R) uit deq_movies-DataFrame;Je definieert een nieuwe feature
score, waarvan je de waarde berekent door deze functie toe te passen op je DataFrame met gekwalificeerde films:Tot slot sorteren we de DataFrame aflopend op de kolom
scoreen geven we de titel, het aantal stemmen, het gemiddelde en de gewogen rating (score) van de top 20 films weer.Zoals je ziet heeft de
eenvoudige aanbevelerhet prima gedaan!De grafiek heeft veel overlap met de IMDB Top 250: je top twee films, "Shawshank Redemption" en "The Godfather", zijn dezelfde als bij IMDB en we weten allemaal dat dat geweldige films zijn. Eigenlijk verdienen alle top 20-films het om in die lijst te staan, toch?
Contentbased-aanbeveler
Aanbeveler op basis van plotbeschrijving
In deze sectie leer je een systeem bouwen dat films aanbeveelt die lijken op een bepaalde film. Hiervoor bereken je paargewijze
cosine-similariteitsscores voor alle films op basis van hun plotbeschrijvingen en raad je films aan op basis van die drempel voor similariteitsscore.De plotbeschrijving is beschikbaar als de feature
overviewin jemetadata-dataset. Laten we de plots van een paar films bekijken:Het probleem is een NLP-vraagstuk. Je moet dus eerst features uit deze tekstdata extraheren voordat je similariteit/ongelijkheid tussen ze kunt berekenen. Simpel gezegd: je kunt de similariteit tussen twee overzichten niet in ruwe vorm berekenen. Je moet de woordvectoren van elk overzicht (of document) berekenen.
Zoals de naam zegt, zijn woordvectoren gevectoriseerde representaties van woorden in een document. De vectoren dragen semantiek met zich mee. Bijvoorbeeld: man & king zullen vectorrepresentaties dicht bij elkaar hebben, terwijl man & woman juist verder uit elkaar liggen.
Je berekent
Term Frequency-Inverse Document Frequency(TF-IDF)-vectoren voor elk document. Dit levert een matrix op waarin elke kolom een woord in de overview-woordenschat vertegenwoordigt (alle woorden die in minstens één document voorkomen) en elke rij een film, zoals eerder.In essentie is de TF-IDF-score de frequentie waarmee een woord in een document voorkomt, naar beneden gewogen door het aantal documenten waarin het voorkomt. Dit vermindert het belang van woorden die vaak in plotoverzichten voorkomen en dus minder betekenisvol zijn voor de uiteindelijke similariteitsscore.
Gelukkig biedt scikit-learn een ingebouwde
TfIdfVectorizer-klasse die de TF-IDF-matrix in een paar regels produceert.Hieruit blijkt dat je dataset met 45.000 films 75.827 unieke woorden bevat.
Met deze matrix kun je nu een similariteitsscore berekenen. Er zijn verschillende metrics mogelijk, zoals manhattan, euclidisch, Pearson en cosine-similariteit. Er is geen eenduidig beste score. Verschillende metrics werken goed in verschillende scenario's; het is vaak goed om te experimenteren en de resultaten te bekijken.
Je gebruikt
cosine similarityom een numerieke waarde te berekenen die de overeenkomst tussen twee films aangeeft. Je gebruikt cosine omdat die onafhankelijk is van de grootte en relatief eenvoudig en snel te berekenen is (zeker in combinatie met TF-IDF). Wiskundig gedefinieerd als volgt:Omdat je de TF-IDF-vectorizer gebruikte, geeft het dotproduct tussen elk paar vectoren direct de cosine-similariteit. Daarom gebruik je
sklearn's linear_kernel()in plaats vancosine_similarities(), omdat dat sneller is.Dit levert een matrix van 45466x45466 op, wat betekent dat elk film-
overvieween cosine-similariteitsscore heeft met elk ander film-overview. Elke film is dus een 1x45466-kolomvector waarin elke kolom een similariteitsscore met een film is.Je gaat nu een functie definiëren die een filmtitel als invoer neemt en een lijst met de 10 meest vergelijkbare films geeft. Hiervoor heb je eerst een omgekeerde mapping nodig van filmtitels naar DataFrame-indexen. Met andere woorden: je hebt een mechanisme nodig om de index van een film in je
metadata-DataFrame te vinden op basis van de titel.Je kunt nu de aanbevelingsfunctie definiëren. De stappen zijn:
Haal de index op van de film met de gegeven titel.
Haal de lijst met cosine-similariteitsscores op voor die film met alle films. Zet dit om in een lijst van tuples waarin het eerste element de positie is en het tweede de similariteitsscore.
Sorteer de lijst op de similariteitsscores (het tweede element).
Neem de top 10 uit deze lijst. Negeer het eerste element omdat dit de film zelf is.
Geef de titels terug die horen bij de indices van de topelementen.
Je ziet dat je systeem best aardige vergelijkbare films vindt op basis van de plotbeschrijving, maar de kwaliteit van de aanbevelingen is niet geweldig. "The Dark Knight Rises" levert alleen Batman-films op, terwijl het waarschijnlijker is dat mensen die die film leuk vonden ook andere films van Christopher Nolan waarderen. Dat kan je huidige systeem niet vangen.
Aanbeveler op basis van credits, genres en keywords
De kwaliteit van je aanbeveler wordt beter met rijkere metadata en fijnere details. Dat ga je hier doen. Je bouwt een aanbeveler op basis van de volgende metadata: de 3 belangrijkste acteurs, de regisseur, gerelateerde genres en de plotkeywords.
De keywords-, cast- en crewdata zitten niet in je huidige dataset, dus laad en merge die eerst in je hoofd-DataFrame
metadata.2 rijen × 27 kolommen
Uit je nieuwe features cast, crew en keywords moet je de drie belangrijkste acteurs, de regisseur en de keywords bij de film extraheren.
Maar eerst: je data staat als "ge-stringifieerde" lijsten. Je moet die omzetten naar bruikbare objecten.
Vervolgens schrijf je functies die helpen de benodigde informatie uit elke feature te halen.
Eerst importeer je NumPy om toegang te krijgen tot de constante
NaN. Daarna schrijf je de functieget_director():Haal de naam van de regisseur uit de crew-feature. Als er geen regisseur staat, retourneer
NaNDaarna schrijf je een functie die de top 3 elementen of anders de hele lijst retourneert. De lijst slaat op
cast,keywordsengenres.De volgende stap is om de namen en keywords naar kleine letters te brengen en alle spaties te verwijderen.
Spaties verwijderen is een belangrijke preprocessingstap. Zo telt de vectorizer de "Johnny" van "Johnny Depp" en "Johnny Galecki" niet als hetzelfde. Na deze stap worden de genoemde acteurs "johnnydepp" en "johnnygalecki" en dus verschillend voor je vectorizer.
Een ander voorbeeld waar het model dezelfde vector zou kunnen opleveren is "bread jam" en "traffic jam". Daarom is het beter eventuele spaties te strippen.
De onderstaande functie doet precies dat:
Je kunt nu je "metadata soup" maken: een string met alle metadata die je aan de vectorizer wilt voeren (acteurs, regisseur en keywords).
De functie
create_soupvoegt simpelweg alle vereiste kolommen samen met een spatie. Dit is de laatste preprocessingstap, en de output gaat je woordvectormodel in.De volgende stappen zijn hetzelfde als bij de
aanbeveler op basis van plotbeschrijving. Een belangrijk verschil is dat je nuCountVectorizer()gebruikt in plaats vanTF-IDF. Je wilt namelijk de aanwezigheid van een acteur/regisseur niet naar beneden wegen als diegene relatief veel films heeft gedaan. Dat is hier niet intuïtief.Het grote verschil tussen
CountVectorizer()enTF-IDFis de inverse documentfrequentie (IDF)-component, die wel in de laatste zit en niet in de eerste.Je ziet dat er 73.881 woorden in de metadata zitten die je hebt gevoed.
Vervolgens gebruik je
cosine_similarityom de afstand tussen de embeddings te meten.Je kunt nu je functie
get_recommendations()hergebruiken door de nieuwe matrixcosine_sim2als tweede argument mee te geven.Mooi! Je ziet dat je aanbeveler meer informatie heeft weten te vangen dankzij rijkere metadata en betere aanbevelingen geeft. Er zijn uiteraard talloze manieren om hiermee te experimenteren en de aanbevelingen verder te verbeteren.
Enkele suggesties:
Voeg een populariteitsfilter toe: deze aanbeveler neemt de 30 meest vergelijkbare films, berekent de gewogen ratings (met de IMDB-formule), sorteert daarop en geeft de top 10 terug.
Andere crewleden: neem ook namen op van bijvoorbeeld scenarioschrijvers en producenten.
Meer gewicht voor de regisseur: noem de regisseur meerdere keren in de soup om de similariteitsscores van films met dezelfde regisseur te verhogen.
Collaborative filtering met Python
In deze tutorial heb je geleerd hoe je je eigen eenvoudige en contentbased film-aanbevelingssystemen bouwt. Er is ook nog een ander, zeer populair type: collaborative filtering.
Collaborative filters kun je verder indelen in twee typen:
User-based Filtering: deze systemen bevelen producten aan die vergelijkbare gebruikers leuk vonden. Stel dat Alice en Bob een vergelijkbare smaak in boeken hebben (ze vinden grotendeels dezelfde boeken goed of slecht). Er verschijnt een nieuw boek, Alice leest het en is enthousiast. Dan is de kans groot dat Bob het ook leuk vindt, dus raadt het systeem dit boek aan Bob aan.Item-based Filtering: deze systemen lijken sterk op de content-aanbeveler die je bouwde. Ze identificeren vergelijkbare items op basis van hoe mensen ze eerder beoordeelden. Als Alice, Bob en Eve 5 sterren gaven aan The Lord of the Rings en The Hobbit, ziet het systeem die items als vergelijkbaar. Koopt iemand The Lord of the Rings, dan raadt het systeem ook The Hobbit aan.Een voorbeeld van collaborative filtering op basis van een ratingsysteem:
Je gaat deze systemen niet bouwen in deze tutorial, maar je kent inmiddels de meeste ideeën die je nodig hebt. Een goed startpunt voor collaborative filtering is de MovieLens-dataset, die je hier vindt.
Conclusie
Gefeliciteerd met het afronden van deze tutorial!
Je hebt met succes onze tutorial doorlopen over aanbevelingssystemen in Python. Je leerde hoe je eenvoudige en contentbased-aanbevelers bouwt.
Een goede oefening is om collaborative filtering te implementeren in Python met de subset van de MovieLens-dataset die je hier gebruikte voor eenvoudige en contentbased-aanbevelers.
Ben je net begonnen met Python en wil je meer leren? Volg dan DataCamps cursus Introduction to Data Science in Python.