Progetto di Data Science per la predizione delle valutazioni dei film di Rotten Tomatoes Secondo Approccio

'Data Science project for predicting Rotten Tomatoes film ratings - Second Approach')

Questo progetto di data science è stato utilizzato come compito da svolgere nel processo di selezione presso Meta (Facebook). In questo compito da svolgere a casa, scopriremo come Rotten Tomatoes sta classificando i film come ‘Rotten’, ‘Fresh’ o ‘Certified Fresh’.

Link a questo progetto di data science: https://platform.stratascratch.com/data-projects/rotten-tomatoes-movies-rating-prediction

Per fare ciò, svilupperemo due approcci diversi.

Durante la nostra esplorazione, discuteremo la pre-elaborazione dei dati, vari classificatori e possibili miglioramenti per aumentare le prestazioni dei nostri modelli.

Alla fine di questo post, avrai acquisito una comprensione di come l’apprendimento automatico può essere impiegato per predire il successo dei film e di come questa conoscenza possa essere applicata nell’industria dell’intrattenimento.

Ma prima di approfondire, scopriamo i dati su cui lavoreremo.

Secondo Approccio: Predire lo Stato del Film in Base al Sentimento della Recensione

Nel secondo approccio, abbiamo intenzione di predire il successo del film valutando il sentimento delle sue recensioni. Applicheremo specificamente l’analisi del sentimento per valutare il sentimento complessivo della recensione e classificare il film come ‘Fresh’ o ‘Rotten’ in base a questo sentimento.

Tuttavia, prima di iniziare l’analisi del sentimento, dobbiamo prima preparare il nostro dataset. A differenza della strategia precedente, questa implica il trattamento di dati testuali (recensioni) anziché variabili numeriche e categoriche. Per questa sfida, continueremo ad utilizzare il modello Random Forest. Diamo un’occhiata più da vicino ai nostri dati prima di procedere.

Prima di tutto, leggiamo i dati.

Ecco il codice.

df_critics = pd.read_csv('rotten_tomatoes_critic_reviews_50k.csv')
df_critics.head()

Ecco l’output.

Perfetto, iniziamo con la Pre-elaborazione dei Dati.

Pre-elaborazione dei Dati

In questo dataset, non abbiamo i nomi dei film e i relativi stati. Per questo dataset, abbiamo le variabili review_content e review_type.

Ecco perché uniremo questo dataset con quello precedente, su rotten_tomatoes_link, e selezioneremo le caratteristiche necessarie con l’indicizzazione tra parentesi quadre come segue.

Ecco il codice:

df_merged = df_critics.merge(df_movie, how='inner', on=['rotten_tomatoes_link'])
df_merged = df_merged[['rotten_tomatoes_link', 'movie_title', 'review_content', 'review_type', 'tomatometer_status']]
df_merged.head()

Ecco l’output.

In questo approccio, useremo solo la colonna review_content come caratteristica di input e review_type come etichetta di verità fondamentale.

Per assicurare che i dati siano utilizzabili, dobbiamo filtrare eventuali valori mancanti nella colonna review_content poiché le recensioni vuote non possono essere utilizzate nell’analisi del sentimento.

df_merged = df_merged.dropna(subset=['review_content'])

Dopo aver filtrato i valori mancanti, visualizzeremo la distribuzione di review_type per avere una migliore comprensione della distribuzione dei dati.

# Plot distribution of the review
ax = df_merged.review_type.value_counts().plot(kind='bar', figsize=(12,9))
ax.bar_label(ax.containers[0])

Questa visualizzazione ci aiuterà a determinare se ci sono squilibri di classe nei dati e ci guiderà nella selezione di una metrica di valutazione appropriata per il nostro modello.

Ecco l’intero codice:

df_merged = df_merged.dropna(subset=['review_content'])
# Plot distribution of the review
ax = df_merged.review_type.value_counts().plot(kind='bar', figsize=(12,9))
ax.bar_label(ax.containers[0])

Ecco l’output.

Sembra che abbiamo un problema di sbilanciamento tra le nostre caratteristiche.

Inoltre, abbiamo troppi punti dati, il che potrebbe diminuire la nostra velocità.

Quindi, prenderemo prima 5000 voci dal dataset originale.

df_sub = df_merged[0:5000]

Poi faremo l’encoding ordinale.

review_type = pd.DataFrame(df_sub.review_type.replace(['Rotten','Fresh'],[0,1]))

Infine, creeremo un dataframe che contiene le etichette codificate con il contenuto delle recensioni utilizzando il metodo concat() in Python e visualizzeremo le prime 5 righe utilizzando il metodo head().

df_feature_critics = pd.concat([df_sub[['review_content']]
                        ,review_type], axis=1).dropna()
df_feature_critics.head()

Ecco tutto il codice.

# Prendi solo 5000 voci dal dataset originale
df_sub = df_merged[0:5000]

# Codifica l'etichetta
review_type = pd.DataFrame(df_sub.review_type.replace(['Rotten','Fresh'],[0,1]))

# Costruisci il DataFrame finale
df_feature_critics = pd.concat([df_sub[['review_content']]
                        ,review_type], axis=1).dropna()
df_feature_critics.head()

Ecco l’output.

Ottimo, ora come ultimo passaggio per questa sezione, dividiamo il nostro dataset in trainset e test set.

X_train, X_test, y_train, y_test = train_test_split( df_feature_critics['review_content'], df_feature_critics['review_type'], test_size=0.2, random_state=42)

Default Random Forest

Per utilizzare le recensioni di testo nel nostro DataFrame per i metodi di apprendimento automatico, dobbiamo trasformarle in un formato che possa essere elaborato. Nell’Elaborazione del Linguaggio Naturale, questo è noto come tokenizzazione, dove traduciamo il testo o le parole in vettori n-dimensionali e quindi utilizziamo queste rappresentazioni vettoriali come dati di addestramento per il nostro algoritmo di apprendimento automatico.

Per fare ciò, useremo la classe CountVectorizer di scikit-learn per trasformare le recensioni di testo in una matrice di conteggio dei token. Iniziamo creando un dizionario di termini unici dal testo di input.

Ad esempio, basandoci sulle due recensioni “Questo film è buono” e “Il film è cattivo”, l’algoritmo creerebbe un dizionario di frasi uniche come:

Poi, basandoci sul testo di input, calcoliamo il numero di volte di ciascuna parola nel dizionario.

[“questo”, “film”, “è”, “un”, “buono”, “il”, “cattivo”].

Ad esempio, l’input “Questo film è un buon film” produrrebbe un vettore di [1, 2, 1, 1, 1, 0, 0]

Infine, inseriamo il vettore generato nel nostro modello Random Forest.

Possiamo prevedere il sentimento delle recensioni e classificare il film come ‘Fresh’ o ‘Rotten’ addestrando il nostro classificatore Random Forest sui dati di testo vettorizzati.

Il codice seguente istanzia una classe CountVectorizer che trasforma i dati di testo in vettori numerici e specifica che una parola deve comparire in almeno un documento per essere inclusa nel vocabolario.

# Istanza la classe vectorizer
vectorizer = CountVectorizer(min_df=1)

Successivamente, trasformeremo i dati di addestramento in vettori utilizzando l’oggetto CountVectorizer istanziato.

# Trasforma i nostri dati di testo in vettori
X_train_vec = vectorizer.fit_transform(X_train).toarray()

Quindi, istanziamo un oggetto RandomForestClassifier con uno stato casuale specificato e addestriamo il modello Random Forest utilizzando i dati di addestramento.

# Inizializza il random forest e addestralo
rf = RandomForestClassifier(random_state=2)
rf.fit(X_train_vec, y_train)

Ora è il momento di prevedere utilizzando il modello addestrato e i dati di test trasformati.

Poi stamperemo il resoconto di classificazione che contiene metriche di valutazione come precisione, richiamo e punteggio f1.

# Prevedi e stampa il report di classificazione
y_predicted = rf.predict(vectorizer.transform(X_test).toarray())

print(classification_report(y_test, y_predicted))

Infine, creiamo una nuova figura con una dimensione specificata per il grafico della matrice di confusione e plottiamo la matrice di confusione.

fig, ax = plt.subplots(figsize=(12, 9))
plot_confusion_matrix(rf, vectorizer.transform(X_test).toarray(), y_test, cmap ='cividis', ax=ax

Ecco l’intero codice.

# Istanza della classe vectorizer
vectorizer = CountVectorizer(min_df=1)

# Trasformiamo i nostri dati di testo in vettori
X_train_vec = vectorizer.fit_transform(X_train).toarray()

# Inizializziamo il random forest e lo addestriamo
rf = RandomForestClassifier(random_state=2)
rf.fit(X_train_vec, y_train)

# Prevedi e stampa il report di classificazione
y_predicted = rf.predict(vectorizer.transform(X_test).toarray())

print(classification_report(y_test, y_predicted))

fig, ax = plt.subplots(figsize=(12, 9))
plot_confusion_matrix(rf, vectorizer.transform(X_test).toarray(), y_test, cmap ='cividis', ax=ax

Ecco l’output.

Random Forest Ponderato

Come possiamo vedere dalla nostra ultima matrice di confusione, le prestazioni del nostro modello non sono abbastanza buone.

Tuttavia, ciò potrebbe essere previsto a causa del lavoro con un numero limitato di punti dati (5000 invece di 100000).

Vediamo se possiamo aumentare le prestazioni risolvendo il problema dell’imbilanciamento con i pesi delle classi.

Ecco il codice.

class_weight = compute_class_weight(class_weight= 'balanced', classes= np.unique(df_feature_critics.review_type), 
                      y = df_feature_critics.review_type.values)

class_weight_dict = dict(zip(range(len(class_weight.tolist())), class_weight.tolist()))
class_weight_dict

Ecco l’output.

Ora addestriamo il nostro classificatore Random Forest sui dati di testo vettorizzati, ma questa volta includendo le informazioni sul peso delle classi per aumentare le metriche di valutazione.

Prima creiamo la classe CountVectorizer e, come prima, trasformiamo l’input di testo in vettori.

E trasformiamo i nostri dati di testo in un vettore.

vectorizer = CountVectorizer(min_df=1)
X_train_vec = vectorizer.fit_transform(X_train).toarray()

Poi definiamo un random forest con il peso delle classi calcolato e lo addestriamo.

# Inizializziamo il random forest e lo addestriamo
rf_weighted = RandomForestClassifier(random_state=2, class_weight=class_weight_dict)
rf_weighted.fit(X_train_vec, y_train)

Ora è il momento di fare una previsione usando i dati di test e stampare il report di classificazione.

# Prevedi e stampa il report di classificazione
y_predicted = rf_weighted.predict(vectorizer.transform(X_test).toarray())

print(classification_report(y_test, y_predicted))

Nella fase finale, impostiamo la dimensione della figura e plottiamo la matrice di confusione.

fig, ax = plt.subplots(figsize=(12, 9))
plot_confusion_matrix(rf_weighted, vectorizer.transform(X_test).toarray(), y_test, cmap ='cividis', ax=ax)

Ecco l’intero codice.

# Istanza della classe vectorizer
vectorizer = CountVectorizer(min_df=1)

# Trasformiamo i nostri dati di testo in vettori
X_train_vec = vectorizer.fit_transform(X_train).toarray()

# Inizializziamo il random forest e lo addestriamo
rf_weighted = RandomForestClassifier(random_state=2, class_weight=class_weight_dict)
rf_weighted.fit(X_train_vec, y_train)

# Prevedi e stampa il report di classificazione
y_predicted = rf_weighted.predict(vectorizer.transform(X_test).toarray())

print(classification_report(y_test, y_predicted))

fig, ax = plt.subplots(figsize=(12, 9))
plot_confusion_matrix(rf_weighted, vectorizer.transform(X_test).toarray(), y_test, cmap ='cividis', ax=ax)

Ecco l’output.

Ora la precisione del nostro modello è leggermente migliore rispetto a quello senza pesi di classe.

Inoltre, poiché il peso della classe 0 (‘Rotten’) è maggiore del peso della classe 1 (‘Fresh’), il modello ora si comporta meglio nella previsione delle recensioni di film ‘Rotten’ ma peggio nella previsione delle recensioni di film ‘Fresh’.

Questo perché il modello presta più attenzione ai dati classificati come ‘Rotten’.

Predizione dello stato del film

Utilizziamo il nostro modello Random Forest per prevedere lo stato del film ora che lo abbiamo addestrato a prevedere il sentimento di una recensione di un film. Seguiremo le seguenti fasi per determinare lo stato di un film:

  • Raccogliere tutte le recensioni per un determinato film.
  • Utilizzare il nostro modello Random Forest per stimare lo stato di ogni recensione (ad esempio, ‘Fresh’ o ‘Rotten’).
  • Per classificare lo stato finale di un film in base allo stato complessivo delle recensioni, utilizzare l’approccio basato sulle regole fornite sul sito web di Rotten Tomatoes.

Qui nel codice seguente, creiamo prima una funzione chiamata predict_movie_statust, che prende una previsione come argomento.

Quindi, a seconda del valore di positive_percentage, identifichiamo lo stato del film, assegnando alla variabile di previsione ‘Fresh’ o ‘Rotten’.

Infine, verrà restituito il percentuale di recensioni positive con lo stato del film.

Ecco il codice.

def predict_movie_status(prediction):
    """Assegna etichetta (Fresh/Rotten) in base alla previsione"""
    positive_percentage = (prediction == 1).sum()/len(prediction)*100
    
    prediction = 'Fresh' if positive_percentage >= 60 else 'Rotten'
    
    print(f'Recensione positiva: {positive_percentage:.2f}%')
    print(f'Stato del film: {prediction}')

In questo esempio, prevedremo lo stato di tre film: Body of Lies, Angel Heart e The Duchess. Iniziamo con Body of Lies.

Previsione su ‘Body of Lies’

Come indicato sopra, raccogliamo prima tutte le recensioni del film Body of Lies.

Ecco il codice.

# Raccogli tutte le recensioni del film Body of Lies
df_bol = df_merged.loc[df_merged['movie_title'] == 'Body of Lies']

df_bol.head()

Ecco il risultato.

Perfetto, a questo punto applichiamo un algoritmo random forest pesato per prevedere lo stato. Quindi utilizziamo questa funzione personalizzata che abbiamo definito in precedenza, che prende una previsione come argomento.

Ecco il codice.

y_predicted_bol = rf_weighted.predict(vectorizer.transform(df_bol['review_content']).toarray())
predict_movie_status(y_predicted_bol)

Ecco il risultato.

E qui abbiamo il nostro risultato, verifichiamo se è valido confrontandolo con lo stato ground_truth.

Ecco il codice.

df_merged['tomatometer_status'].loc[df_merged['movie_title'] == 'Body of Lies'].unique()

Ecco il risultato.

Sembra che la nostra previsione sia abbastanza valida perché lo stato di questo film è ‘Rotten’ come prevediamo.

Previsione su ‘Angel Heart’

Ripeteremo tutti i passaggi qui.

  • Raccogliere tutte le recensioni
  • Fare la previsione
  • Confronto

Iniziamo raccogliendo tutte le recensioni del film Angel Heart.

Ecco il codice.

df_ah = df_merged.loc[df_merged['movie_title'] == 'Angel Heart']
df_ah.head()

Ecco il risultato.

Ora è il momento di fare una previsione utilizzando il random forest e la nostra funzione personalizzata.

Ecco il codice.

y_predicted_ah = rf_weighted.predict(vectorizer.transform(df_ah['review_content']).toarray())
predict_movie_status(y_predicted_ah)

Ecco l’output.

Facciamo un confronto.

Ecco il codice.

df_merged['tomatometer_status'].loc[df_merged['movie_title'] == 'Angel Heart'].unique()

Ecco l’output.

Il nostro modello predice correttamente ancora una volta.

Ora proviamo ancora una volta.

‘The Duchess’ Prediction

Per prima cosa raccogliamo tutte le recensioni.

Ecco il codice.

df_duchess = df_merged.loc[df_merged['movie_title'] == 'The Duchess']
df_duchess.head()

Ecco l’output.

Ora è il momento di fare una previsione.

Ecco il codice.

y_predicted_duchess = rf_weighted.predict(vectorizer.transform(df_duchess['review_content']).toarray())
predict_movie_status(y_predicted_duchess)

Ecco l’output.

Confrontiamo la nostra previsione con la verità di fondo.

Ecco il codice.

df_merged['tomatometer_status'].loc[df_merged['movie_title'] == 'The Duchess'].unique()

Ecco l’output.

E l’etichetta di verità di fondo del film è ‘Fresh’, indicando che la previsione del nostro modello è errata.

Tuttavia, si può notare che la previsione del nostro modello è molto vicina alla soglia del 60%, indicando che una piccola modifica al modello potrebbe alterare la sua previsione da ‘Rotten’ a ‘Fresh’.

Ovviamente, il modello Random Forest che abbiamo addestrato sopra non è il migliore, poiché c’è ancora margine di miglioramento. Nella prossima parte, forniremo molte suggerimenti per migliorare le prestazioni del nostro modello.

Suggerimenti per il miglioramento delle prestazioni

  1. Aumentare la quantità di dati a disposizione.
  2. Impostare diversi iperparametri del modello random forest.
  3. Applicare diversi modelli di apprendimento automatico per trovare il migliore.
  4. Regolare il metodo utilizzato per rappresentare i dati testuali.

Conclusioni

In questo articolo, abbiamo esplorato due approcci diversi per prevedere lo stato di un film basandoci su caratteristiche numeriche e categoriche.

Abbiamo prima eseguito la pre-elaborazione dei dati e poi applicato un classificatore ad albero decisionale e un classificatore random forest per addestrare il nostro modello.

Abbiamo anche sperimentato la selezione delle caratteristiche e un classificatore random forest pesato.

Nel secondo approccio, abbiamo utilizzato il random forest di default e il random forest pesato per prevedere lo stato del film di tre film diversi.

Abbiamo fornito suggerimenti per migliorare le prestazioni dei nostri modelli. Speriamo che questo articolo sia stato informativo e utile.

Se desideri alcuni progetti di livello principiante, dai un’occhiata al nostro post “Ide per progetti di data science per principianti”. Nate Rosidi è un data scientist e si occupa di strategia dei prodotti. È anche professore a contratto che insegna analytics ed è il fondatore di StrataScratch, una piattaforma che aiuta i data scientist a prepararsi per i loro colloqui con domande di intervista reali dalle migliori aziende. Collegati con lui su Twitter: StrataScratch o LinkedIn.