Come creare un sistema di raccomandazione per film con Python

Mai sentito parlare di sistema di raccomandazione?

La “data science”, o scienza dei dati, è l’insieme di principi metodologici e tecniche volto ad interpretare ed estrarre conoscenza dai dati. Si tratta di uno dei termini figli del XXI secolo; cercando su Internet, le definizioni sembrano piuttosto generiche e confusionarie, e ancor di più il “come” arrivare a lavorare in questo settore. La figura del data scientist, o scienziato dei dati, è una professione in cui l’individuo può e deve vantare conoscenze in materia di gestione ed estrazione di dati da un database o da una fonte di dati non strutturati, per produrre visualizzazione di base, gestirne il contenuto e produrre nuovi dati. In altre parole? Il data scientist usa i dati in suo possesso come mezzo per estrarre nuove informazioni, come nel caso di Netflix: i dati prodotti dai suoi abbonati permettono a questa multinazionale di estrarre modelli di visione dei film per capire cosa è stimolante o meno per un utente e usano quegli stessi dati per creare suggerimenti per il singolo utente o per produrre delle nuove serie TV originali.

In questo breve, ma semplice, tutorial, andremo a vedere come creare tramite Python un sistema di raccomandazione per film, sfruttando i dati a nostro favore: è un piccolo passo per iniziare a muoversi in questo vastissimo settore e per avere un assaggio delle potenzialità. Cominciamo!

 

Repository

https://github.com/serenasensini/FZTH-Python-movieRS

 

Librerie Python
  • numpy
  • pandas
  • scikit-learn

 

STEP 1 — Studio del dataset

Il dataset è composto da quasi cinquemila record che descrivono film e contiene informazioni quali il regista che l’ha prodotto, i principali attori, la durata del film, i feedback ricevuti dai social, il paese di produzione, il budget, il punteggio sulla piattaforma, la lingua, le parole chiave che ne descrivono il contenuto, e via dicendo.

Nel nostro caso, andremo a progettare un cosiddetto “sistema di raccomandazione”, ovvero un sistema di filtraggio delle informazioni che cerca di prevedere la “valutazione” o “preferenza” che un utente darebbe a un dato articolo o prodotto.

Esistono diverse tipologie di sistemi di raccomandazione: quelli basati sul contenuto, quelli collaborativi, ibridi, e così via. In questo caso, andremo ad utilizzare un approccio basato sul contenuto, sfruttando alcuni degli attributi del dataset a nostra disposizione; quindi decidiamo di utilizzare come attributi principali regista, genere, attori e trama. Andremo a lavorare con questi campi per far sì che sia possibile valutare il grado di somiglianza tra un film e l’altro.

Tramite il pacchetto pandas, andiamo a caricare il dataset in un dataframe, ovvero una struttura dati simile a una tabella:

 

import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import CountVectorizer
pd.set_option('display.max_columns', 100)
df = pd.read_csv('movie_metadata.csv')
print(df.head())

 

STEP 2 — Preprocessamento dei dati

Il risultato che otteniamo non è del tutto comprensibile, né utilizzabile. Alcuni dei problemi sono i seguenti:

• Ci sono alcune righe che non hanno nomi di attori;

• I nomi e cognomi degli attori sono separati da spazi, il che non li rende semplici da gestire: Peter Jackson e Peter Berg hanno lo stesso nome, il che potrebbe far sì che il sistema li interpreti come simili, anche se è chiaro che non si tratta della stessa persona.

Il lavoro da fare non è poco… Procediamo per gradi. Cominciamo selezionando le colonne di nostro interesse, ovvero quelle elencate in precedenza; viene anche creata una colonna “actors” per unire i dati delle tre colonne “actor_1_name”, “actor_2_name” e “actor_3_name”.

 

df = df[['director_name', 'actor_1_name', 'actor_2_name', 'actor_3_name', 'plot_keywords', 'genres', 'movie_title']]
if not df['actor_1_name'].empty or not df['actor_2_name'].empty or not df['actor_3_name'].empty:
df['actors'] = df['actor_1_name'] + "," + df['actor_2_name'] + "," + df['actor_3_name']
df = df[['director_name', 'plot_keywords', 'genres', 'movie_title', 'actors']]
df.dropna()
print(df.head())

 

Il risultato dovrebbe essere questo:

 

 

A questo punto sarebbe utile andare a rimuovere tutte quelle righe che contengono valori nulli: in questo modo possiamo far sì che il grado di somiglianza tra i diversi film sia il più fedele possibile.

 

df.where((pd.notnull(df)), 'REMOVE')
df.replace(["NaN"], np.nan, inplace=True)
df = df.dropna()

 

A questo punto, è necessario normalizzare i dati. In che modo? Possiamo pensarla così: due attori/registi possono essere considerati simili se la stringa che li rappresenta è esattamente uguale. Riprendendo l’esempio precedente, se trovassimo due stringhe come “peterjackson” e “peterberg” potremmo dire con certezza che i due attori non sono uguali; andiamo quindi a trasformare queste stringhe in minuscolo, rimuovendo anche i segni di punteggiatura o di separazione come il carattere “|” utilizzato nella trama. Inoltre, andremo a rendere tutti i record minuscoli, così da evitare possibili problemi di somiglianza tra termini.

for index, row in df.iterrows():
# process actors names
app = row['actors'].lower().replace(' ', '')
app = app.replace(',', ' ')
row['actors'] = app

# process director_name
app = row['director_name'].lower().replace(' ', '')
row['director_name'] = app

# process genres
app = row['genres'].lower().replace('|', ' ')
row['genres'] = app

# process plot_keywords
app = row['plot_keywords'].lower().replace('|', ' ')
row['plot_keywords'] = app

 

Al termine del ciclo, andiamo a creare un’ultima colonna, chiamata “bag_of_words”: in questa colonna, andiamo ad aggiungere tutti i termini che descrivono il film, siano essi gli attori, il regista, il genere e le parole chiave. In questo modo, avremo un attributo di riferimento per misurare la somiglianza tra un film e l’altro.

Dopo questo lavoro, abbiamo ottenuto un dataset di 4827 voci; questo tipo di lavoro è frutto di molta esperienza; tuttavia, non è l’unica strategia attuabile, né è detto che sia la migliore. Infatti, ognuna delle precedenti fasi può essere utilizzata separatamente in altri casi d’uso, così come esistono altre tecniche per pulire i dati.

Andiamo infine a settare l’indice del dataframe: questo sarà il valore di riferimento che ci verrà restituito alla fine come raccomandazione:

 

df.set_index('movie_title', inplace = True)

 

STEP 3 — Creazione del modello

Per il calcolo della similarità tra due film, andremo ad utilizzare un approccio molto semplice, ovvero la funzione di coseno-similarità: si tratta di una misura della somiglianza tra due vettori diversi da zero di uno spazio interno del prodotto che misura il coseno dell’angolo tra di loro. Applicando la definizione di similarità, questa sarà di fatto uguale a 1 se i due vettori sono identici e sarà 0 se i due sono ortogonali. In altre parole, la somiglianza è un numero limitato tra 0 e 1 che ci dice quanto i due vettori sono simili. In altre parole, il nostro modello non può funzionare con le sole stringhe che abbiamo prodotto, ma è necessario vettorializzare quelle singole stringhe in numeri; a questo scopo, si utilizza la feature di scikit-learn CountVectorizer che converte una raccolta di documenti di testo in una matrice di frequenze di token; si è scelto in questo caso di utilizzare CountVectorizer anziché TfIdfVecorizer per un semplice motivo: si aveva bisogno di un semplice contatore di frequenza per ogni parola nella colonna “bag_of_words”.

 

count = CountVectorizer()
count_matrix = count.fit_transform(df['bag_of_words'])
indices = pd.Series(df.index)
indices[:5]
cosine_sim = cosine_similarity(count_matrix, count_matrix)
print(cosine_sim)

 

Dando una breve occhiata alla matrice prodotta, è possibile notare come tutti i numeri sulla diagonale siano pari a 1 perché, ovviamente, ogni film è identico a sé stesso. Anche la matrice è simmetrica perché la somiglianza tra A e B è la stessa della similarità tra B e A.

 

A questo punto, è possibile scrivere una funzione che prenda in input un titolo di un film e restituisca i primi 5 film simili. Dobbiamo quindi creare una serie di titoli di film usando degli indici numerici, per abbinare gli indici della matrice di similarità ai titoli di film attuali; infatti la funzione calcola i 5 numeri più alti all’interno della riga corrispondente al film inserito, ottenendo gli indici corrispondenti e abbinandoli ai titoli dei film della serie, per restituire l’elenco dei film consigliati. Per questa ragione, creiamo una funzione recommendations, che prende in input un titolo e la matrice precedentemente calcolata, e stampa il risultato:

 

def recommendations(title, cosine_sim = cosine_sim):

recommended_movies = []
idx = -1

for i in range(0, indices.size):
if indices[i] == title:
idx = i
break
score_series = pd.Series(cosine_sim[idx]).sort_values(ascending = False)
top_10_indexes = list(score_series.iloc[1:11].index)


for i in top_10_indexes:
recommended_movies.append(list(df.index)[i])

return recommended_movies

 

STEP 4 — Test

Possiamo finalmente testare il nostro programma: andiamo quindi ad inserire il titolo di un film e vediamo cosa ci propone il sistema; inserendo questo film:

 

recommendations('Batman v Superman: Dawn of Justice ')

 

Il risultato sarà:

 

 

Tutti film che in effetti hanno a che fare con Batman!

 

That’s all, folks!

 

Letture consigliate: Analisi del linguaggio con Python (disponibile su Amazon)

Condividi la tua opinione