Corso
Lo scopo di questo tutorial non è renderti un esperto nella creazione di modelli di sistemi di raccomandazione. Piuttosto, l'obiettivo è farti iniziare dandoti una panoramica dei tipi di sistemi di raccomandazione esistenti e di come puoi costruirne uno da solo
In questo tutorial imparerai a costruire un modello di base di semplici sistemi di raccomandazione e content-based. Anche se questi modelli saranno lontani dagli standard industriali in termini di complessità, qualità o accuratezza, ti aiuteranno a muovere i primi passi verso modelli più complessi che producano risultati ancora migliori.
I sistemi di raccomandazione sono tra le applicazioni più popolari della data science oggi. Vengono usati per prevedere il "voto" o la "preferenza" che un utente darebbe a un elemento. Quasi tutte le grandi aziende tech li hanno applicati in qualche forma. Amazon li usa per suggerire prodotti ai clienti, YouTube per decidere quale video riprodurre in autoplay, e Facebook per raccomandare pagine da mettere Mi piace e persone da seguire.
Inoltre, per alcune aziende come Netflix, Amazon Prime, Hulu e Hotstar, il modello di business e il suo successo ruotano attorno all'efficacia delle loro raccomandazioni. Netflix ha persino offerto un milione di dollari nel 2009 a chiunque riuscisse a migliorare il suo sistema del 10%.
Esistono anche sistemi di raccomandazione popolari per settori come ristoranti, film e appuntamenti online. Sono stati sviluppati sistemi di raccomandazione anche per esplorare articoli di ricerca e per trovare esperti, collaboratori e servizi finanziari. YouTube usa il sistema di raccomandazione su larga scala per suggerirti video basati sulla tua cronologia. Per esempio, se guardi molti video educativi, ti suggerirà quel tipo di contenuti.
Ma cosa sono questi sistemi di raccomandazione?
In generale, i sistemi di raccomandazione si possono classificare in 3 tipi:
- Semplici raccomandatori: offrono raccomandazioni generalizzate a ogni utente, basate sulla popolarità e/o sul genere dei film. L'idea di base è che i film più popolari e acclamati dalla critica avranno una probabilità più alta di piacere al pubblico medio. Un esempio potrebbe essere l'IMDB Top 250.
- Raccomandatori content-based: suggeriscono elementi simili partendo da un particolare elemento. Questo sistema usa i metadati degli elementi, come genere, regista, descrizione, attori, ecc. per i film, per fare queste raccomandazioni. L'idea generale dietro questi sistemi è che se a una persona piace un certo elemento, piacerà anche un elemento simile. E per consigliarlo, utilizzerà i metadati degli elementi passati dell'utente. Un buon esempio è YouTube: in base alla tua cronologia, ti suggerisce nuovi video che potresti guardare.
- Motori di collaborative filtering: questi sistemi sono molto usati e cercano di prevedere il voto o la preferenza che un utente darebbe a un elemento sulla base dei voti e delle preferenze passate di altri utenti. I filtri collaborativi non richiedono metadati degli elementi, a differenza dei sistemi content-based.
Raccomandatori semplici
Come descritto nella sezione precedente, i raccomandatori semplici sono sistemi di base che raccomandano i migliori elementi in base a una certa metrica o punteggio. In questa sezione costruirai un clone semplificato dell'IMDB Top 250 Movies usando metadati raccolti da IMDB.
I passaggi coinvolti sono i seguenti:
-
Decidere la metrica o il punteggio con cui valutare i film.
-
Calcolare il punteggio per ogni film.
-
Ordinare i film in base al punteggio e restituire i risultati migliori.
Informazioni sul dataset
I file del dataset contengono i metadati di tutti i 45.000 film elencati nel Full MovieLens Dataset. Il dataset comprende film pubblicati entro luglio 2017. Questo dataset cattura punti di caratteristiche come cast, troupe, parole chiave della trama, budget, incassi, poster, date di uscita, lingue, case di produzione, paesi, conteggi di voti TMDB e voti medi.
Questi punti di caratteristiche potrebbero essere usati per addestrare i tuoi modelli di machine learning per il filtering content-based e collaborativo.
Questo dataset è composto dai seguenti file:
- movies_metadata.csv: questo file contiene informazioni su ~45.000 film presenti nel Full MovieLens dataset. Le caratteristiche includono poster, backdrop, budget, genere, incassi, date di uscita, lingue, paesi e case di produzione.
- keywords.csv: contiene le parole chiave della trama per i film del MovieLens. Disponibile come oggetto JSON convertito in stringa.
- credits.csv: consiste nelle informazioni su cast e troupe per tutti i film. Disponibile come oggetto JSON convertito in stringa.
- links.csv: questo file contiene gli ID TMDB e IMDB di tutti i film presenti nel Full MovieLens dataset.
- links_small.csv: contiene gli ID TMDB e IMDB di un sottoinsieme ridotto di 9.000 film del dataset completo.
- ratings_small.csv: il sottoinsieme di 100.000 valutazioni da 700 utenti su 9.000 film.
Il Full MovieLens Dataset comprende 26 milioni di valutazioni e 750.000 applicazioni di tag, da 270.000 utenti su tutti i 45.000 film in questo dataset. È accessibile dal sito ufficiale di GroupLens.
Nota: il sottoinsieme usato nel tutorial di oggi può essere scaricato da qui.
Per caricare il tuo dataset userai la libreria DataFrame pandas. La libreria pandas è usata principalmente per la manipolazione e l'analisi dei dati. Rappresenta i dati in formato riga-colonna. La libreria Pandas si appoggia agli array NumPy per l'implementazione dei suoi oggetti dati. pandas offre strutture dati pronte all'uso e operazioni per manipolare tabelle numeriche, serie temporali, immagini e dataset di elaborazione del linguaggio naturale. In sostanza, pandas è utile per quei dataset che possono essere facilmente rappresentati in modo tabellare.
Prima di eseguire uno qualsiasi dei passaggi sopra, carichiamo i metadati dei film in un DataFrame pandas:
# Import Pandas
import pandas as pd
# Load Movies Metadata
metadata = pd.read_csv('movies_metadata.csv', low_memory=False)
# Print the first three rows
metadata.head(3)
| adult | belongs_to_collection | budget | genres | homepage | id | imdb_id | original_language | original_title | overview | ... | release_date | revenue | runtime | spoken_languages | status | tagline | title | video | vote_average | vote_count | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | False | {'id': 10194, 'name': 'Toy Story Collection', ... | 30000000 | [{'id': 16, 'name': 'Animation'}, {'id': 35, '... | http://toystory.disney.com/toy-story | 862 | tt0114709 | en | Toy Story | Led by Woody, Andy's toys live happily in his ... | ... | 1995-10-30 | 373554033.0 | 81.0 | [{'iso_639_1': 'en', 'name': 'English'}] | Released | NaN | Toy Story | False | 7.7 | 5415.0 |
| 1 | False | NaN | 65000000 | [{'id': 12, 'name': 'Adventure'}, {'id': 14, '... | NaN | 8844 | tt0113497 | en | Jumanji | When siblings Judy and Peter discover an encha... | ... | 1995-12-15 | 262797249.0 | 104.0 | [{'iso_639_1': 'en', 'name': 'English'}, {'iso... | Released | Roll the dice and unleash the excitement! | Jumanji | False | 6.9 | 2413.0 |
| 2 | False | {'id': 119050, 'name': 'Grumpy Old Men Collect... | 0 | [{'id': 10749, 'name': 'Romance'}, {'id': 35, ... | NaN | 15602 | tt0113228 | en | Grumpier Old Men | A family wedding reignites the ancient feud be... | ... | 1995-12-22 | 0.0 | 101.0 | [{'iso_639_1': 'en', 'name': 'English'}] | Released | Still Yelling. Still Fighting. Still Ready for... | Grumpier Old Men | False | 6.5 | 92.0 |
3 righe × 24 colonne
Una delle metriche più basilari che puoi immaginare è la classifica per decidere quali sono i primi 250 film in base ai rispettivi voti.
Tuttavia, usare il voto come metrica ha alcune insidie:
-
Per cominciare, non tiene conto della popolarità di un film. Pertanto, un film con voto 9 da 10 votanti verrebbe considerato "migliore" di un film con voto 8,9 da 10.000 votanti.
Per esempio, immagina di voler ordinare cibo cinese: hai un paio di opzioni, un ristorante ha 5 stelle ma solo 5 recensioni, mentre l'altro ha 4,5 stelle con 1000 recensioni. Quale preferiresti? Il secondo, giusto?
Ovviamente, potrebbe esserci l'eccezione che il primo ristorante abbia aperto solo pochi giorni fa; quindi, meno persone hanno votato, mentre al contrario il secondo è operativo da un anno.
- In modo analogo, questa metrica tenderà anche a favorire film con un numero minore di votanti con voti distorti e/o estremamente alti. All'aumentare del numero di votanti, il voto di un film si regolarizza e si avvicina a un valore più rappresentativo della qualità del film, dando all'utente un'idea molto migliore su quale film scegliere. Mentre è difficile discernere la qualità di un film con pochissimi voti, potresti dover considerare fonti esterne per trarre conclusioni.
Tenendo conto di queste carenze, devi ideare un voto pesato che tenga in considerazione il voto medio e il numero di voti accumulati. Un sistema del genere assicurerà che un film con voto 9 da 100.000 votanti ottenga un punteggio (ben) più alto di un film con lo stesso voto ma poche centinaia di votanti.
Dato che stai cercando di costruire un clone della Top 250 di IMDB, usiamo la sua formula di voto pesato come metrica/punteggio. Matematicamente è rappresentata come segue:
\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}
Nell'equazione sopra,
-
v è il numero di voti per il film;
-
m è il numero minimo di voti richiesti per essere in classifica;
-
R è il voto medio del film;
-
C è il voto medio su tutto il report.
Hai già i valori di v (vote_count) e R (vote_average) per ogni film nel dataset. È anche possibile calcolare direttamente C da questi dati.
Determinare un valore appropriato per m è un iperparametro che puoi scegliere di conseguenza, dato che non esiste un valore "giusto" per m. Puoi considerarlo come un filtro negativo preliminare che rimuoverà semplicemente i film con un numero di voti inferiore a una certa soglia m. La selettività del filtro dipende da te.
In questo tutorial userai come cutoff m il 90° percentile. In altre parole, per essere inserito in classifica un film deve avere più voti di almeno il 90% dei film in elenco. (D'altra parte, se avessi scelto il 75° percentile, avresti considerato il 25% dei film con il maggior numero di voti. Al diminuire del percentile, aumenta il numero di film considerati).
Come primo passo, calcoliamo il valore di C, il voto medio su tutti i film, usando la funzione .mean() di pandas:
# Calculate mean of vote average column
C = metadata['vote_average'].mean()
print(C)
5.618207215133889
Dal risultato sopra puoi osservare che il voto medio di un film su IMDB è intorno a 5,6 su una scala da 10.
Successivamente, calcoliamo il numero di voti, m, ricevuti da un film al 90° percentile. La libreria pandas rende questo compito estremamente semplice tramite il metodo .quantile():
# Calculate the minimum number of votes required to be in the chart, m
m = metadata['vote_count'].quantile(0.90)
print(m)
160.0
Ora che hai m puoi semplicemente usare una condizione maggiore o uguale per filtrare i film con conteggio voti maggiore o uguale a 160:
Puoi usare il metodo .copy() per assicurarti che il nuovo DataFrame q_movies creato sia indipendente dal DataFrame dei metadati originale. In altre parole, qualsiasi modifica fatta a q_movies non influenzerà il DataFrame metadata originale.
# Filter out all qualified movies into a new DataFrame
q_movies = metadata.copy().loc[metadata['vote_count'] >= m]
q_movies.shape
(4555, 24)
metadata.shape
(45466, 24)
Dal risultato sopra è chiaro che circa il 10% dei film ha un conteggio voti superiore a 160 e si qualifica per essere in questa lista.
Il passaggio successivo, e più importante, è calcolare il voto pesato per ogni film qualificato. Per farlo, dovrai:
- Definire una funzione,
weighted_rating(); - Poiché hai già calcolato
meC, li passerai semplicemente come argomenti alla funzione; - Quindi selezionerai le colonne
vote_count(v) evote_average(R) dal DataFrameq_movies; - Infine, calcolerai la media pesata e restituirai il risultato.
Definirai una nuova feature score, di cui calcolerai il valore applicando questa funzione al DataFrame dei film qualificati:
# Function that computes the weighted rating of each movie
def weighted_rating(x, m=m, C=C):
v = x['vote_count']
R = x['vote_average']
# Calculation based on the IMDB formula
return (v/(v+m) * R) + (m/(m+v) * C)
# Define a new feature 'score' and calculate its value with `weighted_rating()`
q_movies['score'] = q_movies.apply(weighted_rating, axis=1)
Infine, ordiniamo il DataFrame in ordine decrescente in base alla colonna score e stampiamo titolo, conteggio voti, voto medio e voto pesato (score) dei primi 20 film.
#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)
| 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 |
Come vedi dall'output, il raccomandatore semplice ha fatto un ottimo lavoro!
La classifica ha molti film in comune con la Top 250 di IMDB: per esempio, i tuoi primi due film, "Shawshank Redemption" e "The Godfather", sono gli stessi di IMDB e sappiamo tutti che sono davvero straordinari; in effetti, tutti i primi 20 meritano di essere in quella lista, non è così?
Raccomandatore content-based
Raccomandatore basato sulla descrizione della trama
In questa sezione del tutorial imparerai a costruire un sistema che raccomanda film simili a un particolare film. Per farlo, calcolerai i punteggi di similarità cosine a coppie per tutti i film basandoti sulle descrizioni della trama e raccomanderai film in base a una soglia di quel punteggio di similarità.
La descrizione della trama è disponibile come feature overview nel dataset metadata. Diamo un'occhiata alle trame di alcuni film:
#Print plot overviews of the first 5 movies.
metadata['overview'].head()
0 Led by Woody, Andy's toys live happily in his ...
1 When siblings Judy and Peter discover an encha...
2 A 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
Il problema in questione è un problema di elaborazione del linguaggio naturale. Serve quindi estrarre qualche tipo di feature dai testi sopra prima di poter calcolare la similarità e/o dissimilarità tra di essi. In parole semplici, non è possibile calcolare la similarità tra due overview nella loro forma grezza. Per farlo, devi calcolare i vettori delle parole di ogni overview o documento, come lo chiameremo da ora in avanti.
Come suggerisce il nome, i word vector sono rappresentazioni vettoriali delle parole in un documento. I vettori portano con sé un significato semantico. Per esempio, man & king avranno rappresentazioni vettoriali vicine tra loro, mentre man & woman avranno rappresentazioni lontane.
Calcolerai i vettori Term Frequency-Inverse Document Frequency (TF-IDF) per ogni documento. Questo ti darà una matrice in cui ogni colonna rappresenta una parola nel vocabolario delle overview (tutte le parole che compaiono in almeno un documento) e ogni colonna rappresenta un film, come prima.
Nella sua essenza, il punteggio TF-IDF è la frequenza di una parola in un documento, ridotta in base al numero di documenti in cui essa compare. Questo serve per ridurre l'importanza di parole che compaiono frequentemente nelle trame e, quindi, la loro rilevanza nel calcolo del punteggio finale di similarità.
Fortunatamente, scikit-learn fornisce una classe TfIdfVectorizer integrata che produce la matrice TF-IDF in poche righe.
- Importa il modulo Tfidf tramite scikit-learn;
- Rimuovi le stop word come 'the', 'an', ecc., poiché non forniscono informazioni utili sull'argomento;
- Sostituisci i valori not-a-number con una stringa vuota;
- Infine, costruisci la matrice TF-IDF sui dati.
#Import TfIdfVectorizer from scikit-learn
from 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
(45466, 75827)
#Array mapping from feature integer indices to feature name.
tfidf.get_feature_names()[5000:5010]
['avails',
'avaks',
'avalanche',
'avalanches',
'avallone',
'avalon',
'avant',
'avanthika',
'avanti',
'avaracious']
Dall'output sopra, osservi che nel tuo dataset di 45.000 film ci sono 75.827 vocaboli o parole differenti.
Con questa matrice in mano, puoi ora calcolare un punteggio di similarità. Esistono diverse metriche di similarità che puoi usare, come manhattan, euclidea, Pearson e coseno. Anche qui, non esiste una risposta giusta su quale sia la migliore. Metriche diverse funzionano bene in scenari diversi; spesso è una buona idea sperimentare e osservare i risultati.
Userai la cosine similarity per calcolare una quantità numerica che indica la somiglianza tra due film. Usi il coseno perché è indipendente dalla magnitudine ed è relativamente semplice e veloce da calcolare (soprattutto insieme ai punteggi TF-IDF, come spiegato dopo). Matematicamente, è definita come segue:

Dato che hai usato il vettorizzatore TF-IDF, calcolare il prodotto scalare tra ogni vettore ti darà direttamente il punteggio di similarità coseno. Pertanto userai linear_kernel() di sklearn invece di cosine_similarities() poiché è più veloce.
Questo restituirebbe una matrice di dimensione 45466x45466, il che significa il punteggio di similarità coseno tra l'overview di ogni film e quella di ogni altro film. Quindi ogni film sarà un vettore colonna 1x45466 in cui ogni colonna sarà un punteggio di similarità con ciascun film.
# Import linear_kernel
from sklearn.metrics.pairwise import linear_kernel
# Compute the cosine similarity matrix
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
cosine_sim.shape
(45466, 45466)
cosine_sim[1]
array([0.01504121, 1. , 0.04681953, ..., 0. , 0.02198641,
0.00929411])
Definirai una funzione che prende in input il titolo di un film e produce un elenco dei 10 film più simili. Per prima cosa, per farlo, ti serve una mappatura inversa tra titoli dei film e indici del DataFrame. In altre parole, ti serve un meccanismo per identificare l'indice di un film nel DataFrame metadata, dato il suo titolo.
#Construct a reverse map of indices and movie titles
indices = pd.Series(metadata.index, index=metadata['title']).drop_duplicates()
indices[:10]
title
Toy Story 0
Jumanji 1
Grumpier Old Men 2
Waiting to Exhale 3
Father of the Bride Part II 4
Heat 5
Sabrina 6
Tom and Huck 7
Sudden Death 8
GoldenEye 9
dtype: int64
Ora sei in buone condizioni per definire la tua funzione di raccomandazione. Seguirai i seguenti passaggi:
-
Ottenere l'indice del film dato il suo titolo.
-
Ottenere l'elenco dei punteggi di similarità coseno per quel film con tutti i film. Convertirlo in una lista di tuple dove il primo elemento è la posizione e il secondo è il punteggio di similarità.
-
Ordinare la lista di tuple in base ai punteggi di similarità, cioè al secondo elemento.
-
Prendere i primi 10 elementi di questa lista. Ignorare il primo elemento poiché si riferisce a sé (il film più simile a un film è il film stesso).
-
Restituire i titoli corrispondenti agli indici dei primi elementi.
# Function that takes in movie title as input and outputs most similar movies
def get_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 movies
return metadata['title'].iloc[movie_indices]
get_recommendations('The Dark Knight Rises')
12481 The Dark Knight
150 Batman Forever
1328 Batman Returns
15511 Batman: Under the Red Hood
585 Batman
21194 Batman Unmasked: The Psychology of the Dark Kn...
9230 Batman Beyond: Return of the Joker
18035 Batman: Year One
19792 Batman: The Dark Knight Returns, Part 1
3095 Batman: Mask of the Phantasm
Name: title, dtype: object
get_recommendations('The Godfather')
1178 The Godfather: Part II
44030 The Godfather Trilogy: 1972-1990
1914 The Godfather: Part III
23126 Blood Ties
11297 Household Saints
34717 Start Liquidation
10821 Election
38030 A Mother Should Be Loved
17729 Short Sharp Shock
26293 Beck 28 - Familjen
Name: title, dtype: object
Vedi che, sebbene il sistema abbia fatto un lavoro discreto nel trovare film con trame simili, la qualità delle raccomandazioni non è eccezionale. "The Dark Knight Rises" restituisce tutti film di Batman, mentre è più probabile che a chi è piaciuto quel film piacciano anche altri film di Christopher Nolan. Questo è qualcosa che il sistema attuale non riesce a catturare.
Raccomandatore basato su cast, generi e parole chiave
La qualità del tuo raccomandatore aumenterebbe usando metadati migliori e catturando più dettagli. È proprio quello che farai in questa sezione. Costruirai un sistema di raccomandazione basato sui seguenti metadati: i 3 attori principali, il regista, i generi correlati e le parole chiave della trama del film.
Le parole chiave, il cast e i dati della troupe non sono disponibili nel dataset attuale, quindi il primo passo sarà caricarli e fonderli nel tuo DataFrame principale 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')
# Print the first two movies of your newly merged metadata
metadata.head(2)
| adult | belongs_to_collection | budget | genres | homepage | id | imdb_id | original_language | original_title | overview | ... | spoken_languages | status | tagline | title | video | vote_average | vote_count | cast | crew | keywords | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | False | {'id': 10194, 'name': 'Toy Story Collection', ... | 30000000 | [{'id': 16, 'name': 'Animation'}, {'id': 35, '... | http://toystory.disney.com/toy-story | 862 | tt0114709 | en | Toy Story | Led by Woody, Andy's toys live happily in his ... | ... | [{'iso_639_1': 'en', 'name': 'English'}] | Released | NaN | Toy Story | False | 7.7 | 5415.0 | [{'cast_id': 14, 'character': 'Woody (voice)',... | [{'credit_id': '52fe4284c3a36847f8024f49', 'de... | [{'id': 931, 'name': 'jealousy'}, {'id': 4290,... |
| 1 | False | NaN | 65000000 | [{'id': 12, 'name': 'Adventure'}, {'id': 14, '... | NaN | 8844 | tt0113497 | en | Jumanji | When siblings Judy and Peter discover an encha... | ... | [{'iso_639_1': 'en', 'name': 'English'}, {'iso... | Released | Roll the dice and unleash the excitement! | Jumanji | False | 6.9 | 2413.0 | [{'cast_id': 1, 'character': 'Alan Parrish', '... | [{'credit_id': '52fe44bfc3a36847f80a7cd1', 'de... | [{'id': 10090, 'name': 'board game'}, {'id': 1... |
2 righe × 27 colonne
Dalle nuove feature, cast, troupe e parole chiave, devi estrarre i tre attori più importanti, il regista e le parole chiave associate a quel film.
Ma prima di tutto, i tuoi dati sono presenti sotto forma di liste "convertite in stringa". Devi convertirle in una forma per te utilizzabile.
# Parse the stringified features into their corresponding python objects
from ast import literal_eval
features = ['cast', 'crew', 'keywords', 'genres']
for feature in features:
metadata[feature] = metadata[feature].apply(literal_eval)
Successivamente, scriverai funzioni che ti aiuteranno a estrarre le informazioni richieste da ciascuna feature.
Per prima cosa, importerai il pacchetto NumPy per accedere alla costante NaN. Poi potrai usarla per scrivere la funzione get_director():
# Import Numpy
import numpy as np
Ottieni il nome del regista dalla feature crew. Se il regista non è elencato, restituisci NaN
def get_director(x):
for i in x:
if i['job'] == 'Director':
return i['name']
return np.nan
Ora scriverai una funzione che restituirà i primi 3 elementi o l'intera lista, a seconda di quale sia più appropriato. Qui la lista si riferisce a cast, keywords e genres.
def get_list(x):
if isinstance(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.
if len(names) > 3:
names = names[:3]
return names
#Return empty list in case of missing/malformed data
return []
# 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)
# Print the new features of the first 3 films
metadata[['title', 'cast', 'director', 'keywords', 'genres']].head(3)
| 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] |
Il passo successivo sarà convertire in minuscolo i nomi e le istanze delle parole chiave e rimuovere tutti gli spazi tra di essi.
Rimuovere gli spazi tra le parole è un importante passo di preprocessing. Si fa per evitare che il vettorizzatore conti il Johnny di "Johnny Depp" e "Johnny Galecki" come lo stesso. Dopo questo passaggio, gli attori sopra saranno rappresentati come "johnnydepp" e "johnnygalecki" e saranno distinti per il tuo vettorizzatore.
Un altro buon esempio in cui il modello potrebbe produrre la stessa rappresentazione vettoriale è "bread jam" e "traffic jam". Quindi è meglio eliminare qualsiasi spazio presente.
La funzione qui sotto farà esattamente questo per te:
# Function to convert all strings to lower case and strip names of spaces
def clean_data(x):
if isinstance(x, list):
return [str.lower(i.replace(" ", "")) for i in x]
else:
#Check if director exists. If not, return empty string
if isinstance(x, str):
return str.lower(x.replace(" ", ""))
else:
return ''
# Apply clean_data function to your features.
features = ['cast', 'keywords', 'director', 'genres']
for feature in features:
metadata[feature] = metadata[feature].apply(clean_data)
Ora sei in grado di creare la tua "metadata soup", una stringa che contiene tutti i metadati che vuoi passare al vettorizzatore (ovvero attori, regista e parole chiave).
La funzione create_soup unirà semplicemente tutte le colonne richieste separate da uno spazio. Questo è l'ultimo passo di preprocessing e l'output di questa funzione sarà fornito al modello di word vector.
def create_soup(x):
return ' '.join(x['keywords']) + ' ' + ' '.join(x['cast']) + ' ' + x['director'] + ' ' + ' '.join(x['genres'])
# Create a new soup feature
metadata['soup'] = metadata.apply(create_soup, axis=1)
metadata[['soup']].head(2)
| soup | |
|---|---|
| 0 | jealousy toy boy tomhanks timallen donrickles ... |
| 1 | boardgame disappearance basedonchildren'sbook ... |
I prossimi passaggi sono gli stessi di quanto fatto con il tuo raccomandatore basato sulla descrizione della trama. Una differenza chiave è che userai CountVectorizer() invece di TF-IDF. Questo perché non vuoi ridurre il peso della presenza di un attore/regista se ha recitato o diretto in relativamente più film. In questo contesto non ha molto senso intuitivo soppesarli al ribasso.
La differenza principale tra CountVectorizer() e TF-IDF è la componente inverse document frequency (IDF), presente nella seconda e non nella prima.
# Import CountVectorizer and create the count matrix
from sklearn.feature_extraction.text import CountVectorizer
count = CountVectorizer(stop_words='english')
count_matrix = count.fit_transform(metadata['soup'])
count_matrix.shape
(46628, 73881)
Dall'output sopra, puoi vedere che ci sono 73.881 vocaboli nei metadati che gli hai fornito.
Successivamente userai la cosine_similarity per misurare la distanza tra gli embedding.
# Compute the Cosine Similarity matrix based on the count_matrix
from sklearn.metrics.pairwise import cosine_similarity
cosine_sim2 = cosine_similarity(count_matrix, count_matrix)
# Reset index of your main DataFrame and construct reverse mapping as before
metadata = metadata.reset_index()
indices = pd.Series(metadata.index, index=metadata['title'])
Ora puoi riutilizzare la funzione get_recommendations() passando la nuova matrice cosine_sim2 come secondo argomento.
get_recommendations('The Dark Knight Rises', cosine_sim2)
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
get_recommendations('The Godfather', cosine_sim2)
1934 The Godfather: Part III
1199 The Godfather: Part II
15609 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
Ottimo! Vedi che il tuo raccomandatore ha catturato più informazioni grazie a metadati più ricchi e ha fornito raccomandazioni migliori. Ci sono, ovviamente, numerosi modi per sperimentare con questo sistema e migliorare le raccomandazioni.
Alcuni suggerimenti:
-
Introdurre un filtro di popolarità: questo raccomandatore prenderebbe i 30 film più simili, calcolerebbe i voti pesati (usando la formula IMDB vista sopra), ordinerebbe i film in base a questo voto e restituirebbe i 10 migliori.
-
Altri membri della troupe: si potrebbero includere anche i nomi di altri membri della troupe, come sceneggiatori e produttori.
-
Aumentare il peso del regista: per dare più peso al regista, lo si potrebbe menzionare più volte nella soup per aumentare i punteggi di similarità dei film con lo stesso regista.
Collaborative filtering con Python
In questo tutorial hai imparato a costruire i tuoi semplici sistemi di raccomandazione e quelli content-based per i film. Esiste anche un altro tipo estremamente popolare di raccomandatore noto come filtro collaborativo.
I filtri collaborativi possono essere ulteriormente classificati in due tipi:
User-based Filtering: questi sistemi raccomandano prodotti a un utente che sono piaciuti a utenti simili. Per esempio, supponiamo che Alice e Bob abbiano gusti simili in fatto di libri (cioè in gran parte apprezzano e non apprezzano gli stessi libri). Ora, supponiamo che sia uscito un nuovo libro e Alice l'abbia letto e adorato. È quindi molto probabile che piaccia anche a Bob e, di conseguenza, il sistema raccomandi quel libro a Bob.
Item-based Filtering: questi sistemi sono molto simili al motore di raccomandazione basato sui contenuti che hai costruito. Identificano elementi simili in base a come le persone li hanno valutati in passato. Per esempio, se Alice, Bob ed Eve hanno dato 5 stelle a The Lord of the Rings e a The Hobbit, il sistema identifica gli elementi come simili. Pertanto, se qualcuno compra The Lord of the Rings, il sistema raccomanda anche The Hobbit.
Un esempio di collaborative filtering basato su un sistema di valutazione:

Non costruirai questi sistemi in questo tutorial, ma conosci già la maggior parte delle idee necessarie per farlo. Un buon punto di partenza con i filtri collaborativi è esaminare il dataset MovieLens, che puoi trovare qui.
Conclusione
Congratulazioni per aver terminato questo tutorial!
Hai seguito con successo il nostro tutorial che ti ha insegnato tutto sui sistemi di raccomandazione in Python. Hai imparato a costruire raccomandatori semplici e content-based.
Un buon esercizio per voi sarebbe implementare il collaborative filtering in Python usando il sottoinsieme del dataset MovieLens che hai usato per costruire i raccomandatori semplici e content-based.
Se stai iniziando con Python e vuoi saperne di più, segui il corso di DataCamp Introduction to Data Science in Python.

