Kurs
Tutorial für Anfänger: Empfehlungssysteme in Python
Das Ziel dieses Tutorials ist es nicht, dich zu einem Experten für die Erstellung von Empfehlungssystemen zu machen. Stattdessen geht es darum, dir einen Überblick über die verschiedenen Arten von Empfehlungssystemen zu verschaffen und dir zu zeigen, wie du selbst eines aufbauen kannst.
In diesem Lernprogramm lernst du, wie du ein grundlegendes Modell für einfache und inhaltsbasierte Empfehlungssysteme erstellst. Auch wenn diese Modelle in Bezug auf Komplexität, Qualität und Genauigkeit nicht an den Industriestandard heranreichen, helfen sie dir, komplexere Modelle zu erstellen, die noch bessere Ergebnisse liefern.
Empfehlungssysteme gehören heute zu den beliebtesten Anwendungen der Datenwissenschaft. Sie werden verwendet, um die "Bewertung" oder "Präferenz" vorherzusagen, die ein Nutzer für einen Artikel abgeben würde. Fast jedes große Technologieunternehmen hat sie in irgendeiner Form eingesetzt. Amazon nutzt sie, um Kunden Produkte vorzuschlagen, YouTube nutzt sie, um zu entscheiden, welches Video als Nächstes abgespielt werden soll, und Facebook nutzt sie, um Seiten zu empfehlen, die man mögen und denen man folgen sollte.
Für einige Unternehmen wie Netflix, Amazon Prime, Hulu und Hotstar dreht sich das Geschäftsmodell und ihr Erfolg um die Wirksamkeit ihrer Empfehlungen. Netflix hat 2009 sogar eine Million Dollar für denjenigen ausgelobt, der sein System um 10% verbessern kann.
Es gibt auch beliebte Empfehlungssysteme für Bereiche wie Restaurants, Filme und Online-Dating. Empfehlungssysteme wurden auch entwickelt, um Forschungsartikel und Experten, Mitarbeiter und Finanzdienstleistungen zu finden. YouTube nutzt das Empfehlungssystem in großem Umfang, um dir Videos auf der Grundlage deines Verlaufs vorzuschlagen. Wenn du dir zum Beispiel viele Lehrvideos ansiehst, schlägt es dir diese Art von Videos vor.
Aber was sind diese Empfehlungssysteme?
Im Großen und Ganzen lassen sich Empfehlungssysteme in 3 Typen einteilen:
- Einfache Empfehlungsprogramme: bieten jedem Nutzer allgemeine Empfehlungen, die auf der Popularität und/oder dem Genre des Films basieren. Die Grundidee hinter diesem System ist, dass Filme, die beliebter sind und von der Kritik gelobt werden, eine höhere Wahrscheinlichkeit haben, vom Durchschnittspublikum gemocht zu werden. Ein Beispiel wäre die IMDB Top 250.
- Inhaltsbasierte Empfehlungssysteme: schlagen ähnliche Artikel auf der Grundlage eines bestimmten Artikels vor. Dieses System verwendet Metadaten wie Genre, Regisseur, Beschreibung, Schauspieler usw. für Filme, um diese Empfehlungen auszusprechen. Der Grundgedanke hinter diesen Empfehlungssystemen ist, dass eine Person, die ein bestimmtes Produkt mag, auch ein ähnliches Produkt mögen wird. Und um das zu empfehlen, nutzt es die Metadaten der vergangenen Artikel des Nutzers. Ein gutes Beispiel dafür ist YouTube, das dir anhand deines Verlaufs neue Videos vorschlägt, die du dir möglicherweise ansehen könntest.
- Collaborative Filtering Engines: Diese Systeme sind weit verbreitet und versuchen, die Bewertung oder Präferenz, die ein Nutzer einem Artikel geben würde, auf der Grundlage früherer Bewertungen und Präferenzen anderer Nutzer vorherzusagen. Kollaborative Filter benötigen keine Objekt-Metadaten wie ihre inhaltsbasierten Gegenstücke.
Einfache Empfehlungsgeber
Wie im vorigen Abschnitt beschrieben, sind einfache Empfehlungssysteme grundlegende Systeme, die die besten Artikel auf der Grundlage einer bestimmten Metrik oder Punktzahl empfehlen. In diesem Abschnitt erstellst du einen vereinfachten Klon der IMDB Top 250 Movies mit Metadaten aus der IMDB.
Im Folgenden werden die einzelnen Schritte beschrieben:
-
Entscheide dich für eine Metrik oder Punktzahl, nach der du Filme bewerten willst.
-
Berechne die Punktzahl für jeden Film.
-
Sortiere die Filme nach der Punktzahl und gib die besten Ergebnisse aus.
Über den Datensatz
Die Datensatzdateien enthalten Metadaten für alle 45.000 Filme, die in der Full MovieLens Dataset
aufgeführt sind. Der Datensatz besteht aus Filmen, die im Juli 2017 oder früher veröffentlicht wurden. Dieser Datensatz enthält Merkmale wie Besetzung, Crew, Stichwörter zur Handlung, Budget, Einnahmen, Plakate, Veröffentlichungstermine, Sprachen, Produktionsfirmen, Länder, TMDB-Stimmenzahlen und Stimmendurchschnitte.
Mit diesen Merkmalen kannst du deine Machine-Learning-Modelle für Content und Collaborative Filtering trainieren.
Dieser Datensatz besteht aus den folgenden Dateien:
- movies_metadata.csv: Diese Datei enthält Informationen zu ~45.000 Filmen aus dem Full MovieLens-Datensatz. Zu den Funktionen gehören Plakate, Hintergründe, Budget, Genre, Einnahmen, Erscheinungsdaten, Sprachen, Produktionsländer und Unternehmen.
- keywords.csv: Enthält die Schlüsselwörter für die Filmhandlung unserer MovieLens-Filme. Verfügbar in Form eines stringifizierten JSON-Objekts.
- credits.csv: Hier findest du Informationen zu den Darstellern und der Crew für alle Filme. Verfügbar in Form eines stringifizierten JSON-Objekts.
- links.csv: Diese Datei enthält die TMDB- und IMDB-IDs aller Filme, die im vollständigen MovieLens-Datensatz enthalten sind.
- links_small.csv: Enthält die TMDB- und IMDB-IDs einer kleinen Teilmenge von 9.000 Filmen des vollständigen Datensatzes.
- ratings_small.csv: Die Teilmenge von 100.000 Bewertungen von 700 Nutzern zu 9.000 Filmen.
Der vollständige MovieLens-Datensatz umfasst 26 Millionen Bewertungen und 750.000 Tag-Anwendungen von 270.000 Nutzern für alle 45.000 Filme in diesem Datensatz. Du kannst sie auf der offiziellen GroupLens-Website aufrufen.
Hinweis: Der Datensatz, der im heutigen Tutorial verwendet wird, kann hier heruntergeladen werden.
Um deinen Datensatz zu laden, verwendest du die pandas
DataFrame-Bibliothek. pandas
wird hauptsächlich für die Datenbearbeitung und -analyse verwendet. Sie stellt deine Daten in einem Zeilen-/Spaltenformat dar. Die Pandas-Bibliothek stützt sich auf das Array NumPy
für die Implementierung von Pandas-Datenobjekten. pandas
bietet Standard-Datenstrukturen und -Operationen für die Bearbeitung von numerischen Tabellen, Zeitreihen, Bildern und Datensätzen zur Verarbeitung natürlicher Sprache. Grundsätzlich ist pandas
für Datensätze geeignet, die sich leicht in Tabellenform darstellen lassen.
Bevor du die oben genannten Schritte durchführst, laden wir deinen Film-Metadatensatz in einen pandas
DataFrame:
# 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)
Erwachsene | belongs_to_collection | budget | Genres | Homepage | id | imdb_id | original_language | original_title | Übersicht | ... | release_date | Umsatz | Laufzeit | spoken_languages | status | Tagline | title | video | vote_average | vote_count | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Falsch | {'id': 10194, "Name": Toy Story Collection", ... | 30000000 | [{'id': 16, "Name": 'Animation'}, {'id': 35, '... | http://toystory.disney.com/toy-story | 862 | tt0114709 | en | Toy Story | Angeführt von Woody, leben Andys Spielzeuge glücklich in seinem ... | ... | 1995-10-30 | 373554033.0 | 81.0 | [{'iso_639_1': 'en', 'name': 'Englisch'}] | Freigegeben | NaN | Toy Story | Falsch | 7.7 | 5415.0 |
1 | Falsch | NaN | 65000000 | [{'id': 12, "Name": 'Abenteuer'}, {'id': 14, '... | NaN | 8844 | tt0113497 | en | Jumanji | Als die Geschwister Judy und Peter eine Encha... | ... | 1995-12-15 | 262797249.0 | 104.0 | [{'iso_639_1': 'en', 'name': 'Englisch'}, {'iso... | Freigegeben | Lass die Würfel rollen und entfache die Aufregung! | Jumanji | Falsch | 6.9 | 2413.0 |
2 | Falsch | {'id': 119050, "Name": 'Grumpy Old Men Collect... | 0 | [{'id': 10749, "Name": "Romantik"}, {"id": 35, ... | NaN | 15602 | tt0113228 | en | Mürrische alte Männer | Eine Familienhochzeit entfacht die alte Fehde zwischen... | ... | 1995-12-22 | 0.0 | 101.0 | [{'iso_639_1': 'en', 'name': 'Englisch'}] | Freigegeben | Ich schreie immer noch. Still Fighting. Immer noch bereit für... | Mürrische alte Männer | Falsch | 6.5 | 92.0 |
3 Zeilen × 24 Spalten
Eine der grundlegendsten Metriken, die du dir vorstellen kannst, ist die Rangliste, um zu entscheiden, welche Filme in den Top 250 sind, basierend auf ihren jeweiligen Bewertungen.
Die Verwendung einer Bewertung als Maßstab hat jedoch ein paar Vorbehalte:
-
Zum einen berücksichtigt sie nicht die Popularität eines Films. Daher wird ein Film mit einer Bewertung von 9 von 10 Wählern als "besser" angesehen als ein Film mit einer Bewertung von 8,9 von 10.000 Wählern.
Stell dir zum Beispiel vor, du willst chinesisch essen gehen und hast mehrere Möglichkeiten. Das eine Restaurant hat nur 5 Sterne, das andere Restaurant hat 4,5 Sterne von 1000 Leuten. Welches Restaurant würdest du bevorzugen? Die zweite, richtig?
Natürlich kann es eine Ausnahme sein, dass das erste Restaurant erst vor ein paar Tagen eröffnet wurde und daher weniger Leute dafür gestimmt haben, während das zweite Restaurant bereits seit einem Jahr in Betrieb ist.
- Außerdem begünstigt diese Kennzahl Filme mit einer geringeren Anzahl von Wählern, die eine verzerrte und/oder extrem hohe Bewertung haben. Mit zunehmender Anzahl der Wählerinnen und Wähler reguliert sich die Bewertung eines Films und nähert sich einem Wert an, der die Qualität des Films widerspiegelt und dem Nutzer/der Nutzerin eine viel bessere Vorstellung davon gibt, welchen Film er/sie wählen sollte. Es ist zwar schwierig, die Qualität eines Films mit extrem wenigen Wählern zu beurteilen, aber du musst vielleicht externe Quellen heranziehen, um zu einem Schluss zu kommen.
Unter Berücksichtigung dieser Unzulänglichkeiten musst du eine gewichtete Bewertung erstellen, die die durchschnittliche Bewertung und die Anzahl der Stimmen, die sie angesammelt hat, berücksichtigt. Ein solches System stellt sicher, dass ein Film mit einer Bewertung von 9 von 100.000 Wählern eine (weit) höhere Punktzahl erhält als ein Film mit der gleichen Bewertung, aber nur ein paar hundert Wählern.
Da du versuchst, einen Klon der Top 250 von IMDB zu erstellen, lass uns die gewichtete Bewertungsformel als Maßstab/Score verwenden. Mathematisch lässt sich das wie folgt darstellen:
\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 der obigen Gleichung,
-
v ist die Anzahl der Stimmen für den Film;
-
m ist die Mindestanzahl an Stimmen, die erforderlich ist, um in der Tabelle aufgeführt zu werden;
-
R ist die durchschnittliche Bewertung des Films;
-
C ist der Mittelwert der Stimmen für den gesamten Bericht.
Du hast bereits die Werte für v (vote_count)
und R (vote_average)
für jeden Film im Datensatz. Es ist auch möglich, C direkt aus diesen Daten zu berechnen.
Die Bestimmung eines geeigneten Wertes für m
ist ein Hyperparameter, den du entsprechend wählen kannst, da es keinen richtigen Wert für m
gibt. Du kannst ihn als vorläufigen Negativfilter betrachten, der einfach die Filme entfernt, deren Stimmenzahl unter einem bestimmten Schwellenwert m
liegt. Die Selektivität deines Filters liegt in deinem Ermessen.
In diesem Lernprogramm verwendest du den Cutoff m
als 90ste Perzentile. Mit anderen Worten: Damit ein Film in die Charts kommt, muss er mehr Stimmen haben als mindestens 90% der Filme auf der Liste. (Hättest du hingegen das 75. Perzentil gewählt, hättest du die besten 25 % der Filme in Bezug auf die Anzahl der erhaltenen Stimmen berücksichtigt. Je niedriger der Prozentsatz ist, desto mehr Filme werden berücksichtigt).
Als ersten Schritt berechnen wir den Wert C, den Mittelwert der Bewertung aller Filme, mit der Funktion .mean()
:
# Calculate mean of vote average column
C = metadata['vote_average'].mean()
print(C)
5.618207215133889
Aus der obigen Ausgabe kannst du ersehen, dass die durchschnittliche Bewertung eines Films auf IMDB etwa 5,6 auf einer Skala von 10 beträgt.
Als Nächstes berechnen wir die Anzahl der Stimmen, m, die ein Film im 90sten Perzentil erhält. Die pandas
Bibliothek macht diese Aufgabe mit der .quantile()
Methode von Pandas extrem trivial:
# Calculate the minimum number of votes required to be in the chart, m
m = metadata['vote_count'].quantile(0.90)
print(m)
160.0
Da du jetzt die m
hast, kannst du einfach eine Bedingung verwenden, die größer als gleich ist, um Filme herauszufiltern, deren Stimmenzahl größer als 160 ist:
Du kannst die Methode .copy()
verwenden, um sicherzustellen, dass der neu erstellte q_movies
DataFrame unabhängig von deinem ursprünglichen Metadaten-DataFrame ist. Mit anderen Worten: Alle Änderungen am q_movies DataFrame haben keine Auswirkungen auf den ursprünglichen Metadaten-DataFrame.
# 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)
Aus der obigen Ausgabe geht hervor, dass etwa 10 % der Filme mehr als 160 Stimmen haben und sich für diese Liste qualifizieren.
Der nächste und wichtigste Schritt ist die Berechnung der gewichteten Bewertung für jeden qualifizierten Film. Um dies zu tun, musst du:
- Definiere eine Funktion,
weighted_rating()
; - Da du
m
undC
bereits berechnet hast, gibst du sie einfach als Argument an die Funktion weiter; - Dann wählst du die Spalten
vote_count
(v) undvote_average
(R) aus dem Datenrahmenq_movies
aus; - Schließlich berechnest du den gewichteten Durchschnitt und gibst das Ergebnis zurück.
Du definierst ein neues Merkmal score
, dessen Wert du berechnest, indem du diese Funktion auf deinen DataFrame mit qualifizierten Filmen anwendest:
# 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)
Zum Schluss sortieren wir den DataFrame in absteigender Reihenfolge nach der Spalte score
und geben den Titel, die Anzahl der Stimmen, den Durchschnitt der Stimmen und die gewichtete Bewertung (Score) der 20 besten Filme aus.
#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 | punkten | |
---|---|---|---|---|
314 | Die Verurteilten - Die Erlösung | 8358.0 | 8.5 | 8.445869 |
834 | Der Pate | 6024.0 | 8.5 | 8.425439 |
10309 | Dilwale Dulhania Le Jayenge | 661.0 | 9.1 | 8.421453 |
12481 | Der dunkle Ritter | 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 | Schindlers Liste | 4436.0 | 8.3 | 8.206639 |
23673 | Schleudertrauma | 4376.0 | 8.3 | 8.205404 |
5481 | Weggeistert | 3968.0 | 8.3 | 8.196055 |
2211 | Das Leben ist schön | 3643.0 | 8.3 | 8.187171 |
1178 | Der Pate: Teil II | 3418.0 | 8.3 | 8.180076 |
1152 | Einer flog über das Kuckucksnest | 3001.0 | 8.3 | 8.164256 |
351 | Forrest Gump | 8147.0 | 8.2 | 8.150272 |
1154 | Das Imperium schlägt zurück | 5998.0 | 8.2 | 8.132919 |
1176 | Psycho | 2405.0 | 8.3 | 8.132715 |
18465 | Die Berührbaren | 5410.0 | 8.2 | 8.125837 |
40251 | Dein Name. | 1030.0 | 8.5 | 8.112532 |
289 | Leon: Der Profi | 4293.0 | 8.2 | 8.107234 |
3030 | Die grüne Meile | 4166.0 | 8.2 | 8.104511 |
1170 | GoodFellas | 3211.0 | 8.2 | 8.077459 |
Anhand der obigen Ausgabe kannst du sehen, dass die simple recommender
gute Arbeit geleistet hat!
Da die Tabelle viele Filme mit der IMDB-Top-250-Liste gemeinsam hat: Deine beiden Top-Filme, "Die Verurteilten" und "Der Pate", sind zum Beispiel die gleichen wie bei IMDB, und wir alle wissen, dass sie in der Tat tolle Filme sind, verdienen es eigentlich alle Top-20-Filme, in dieser Liste zu stehen, oder?
Inhaltsbasierter Empfehlungsgeber
Plotbeschreibung basierte Empfehlung
In diesem Teil des Tutorials lernst du, wie du ein System aufbaust, das Filme empfiehlt, die einem bestimmten Film ähnlich sind. Um dies zu erreichen, berechnest du paarweise cosine
Ähnlichkeitswerte für alle Filme auf der Grundlage ihrer Handlungsbeschreibungen und empfiehlst Filme auf der Grundlage dieses Ähnlichkeitsschwellenwerts.
Die Grundstücksbeschreibung steht dir als Merkmal overview
in deinem metadata
Datensatz zur Verfügung. Schauen wir uns die Handlungen einiger Filme an:
#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
Bei dem vorliegenden Problem handelt es sich um ein Problem der natürlichen Sprachverarbeitung. Daher musst du aus den oben genannten Textdaten eine Art von Merkmalen extrahieren, bevor du die Ähnlichkeit und/oder Unähnlichkeit zwischen ihnen berechnen kannst. Einfach ausgedrückt: Es ist nicht möglich, die Ähnlichkeit zwischen zwei Übersichten in ihrer Rohform zu berechnen. Dazu musst du die Wortvektoren jeder Übersicht oder jedes Dokuments, wie es von nun an heißen wird, berechnen.
Wie der Name schon sagt, sind Wortvektoren vektorisierte Darstellungen von Wörtern in einem Dokument. Die Vektoren tragen eine semantische Bedeutung mit sich. Zum Beispiel haben Mann und König Vektordarstellungen, die nahe beieinander liegen, während Mann und Frau weit voneinander entfernt dargestellt werden.
Du berechnest Term Frequency-Inverse Document Frequency
(TF-IDF) Vektoren für jedes Dokument. So erhältst du eine Matrix, in der jede Spalte ein Wort aus dem Übersichtsvokabular darstellt (alle Wörter, die in mindestens einem Dokument vorkommen), und jede Spalte steht für einen Film, wie zuvor.
Im Wesentlichen ist der TF-IDF-Score die Häufigkeit, mit der ein Wort in einem Dokument vorkommt, gewichtet nach der Anzahl der Dokumente, in denen es vorkommt. Dies geschieht, um die Bedeutung von Wörtern, die häufig in Handlungsübersichten vorkommen, und damit auch ihre Bedeutung für die Berechnung der endgültigen Ähnlichkeitsbewertung zu verringern.
Zum Glück gibt es in scikit-learn eine eingebaute TfIdfVectorizer
Klasse, die die TF-IDF-Matrix in ein paar Zeilen erstellt.
- Importiere das Tfidf-Modul mit scikit-learn;
- Entferne Stoppwörter wie "der", "eine" usw., da sie keine nützlichen Informationen über das Thema liefern;
- Ersetze Nicht-eine-Zahl-Werte durch eine leere Zeichenkette;
- Erstelle schließlich die TF-IDF-Matrix für die Daten.
#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']
Anhand der obigen Ausgabe siehst du, dass 75.827 verschiedene Vokabulare oder Wörter in deinem Datensatz 45.000 Filme haben.
Mit dieser Matrix in der Hand kannst du nun eine Ähnlichkeitsbewertung errechnen. Es gibt verschiedene Ähnlichkeitsmetriken, die du dafür verwenden kannst, wie z.B. die Manhattan-, Euklidische-, Pearson und Cosinus-Ähnlichkeitsbewertung. Auch hier gibt es keine richtige Antwort darauf, welche Punktzahl die beste ist. Unterschiedliche Werte sind in verschiedenen Szenarien gut, und es ist oft eine gute Idee, mit verschiedenen Metriken zu experimentieren und die Ergebnisse zu beobachten.
Du wirst die cosine similarity
benutzen, um eine numerische Größe zu berechnen, die die Ähnlichkeit zwischen zwei Filmen angibt. Du verwendest den Cosinus-Ähnlichkeitsscore, da er unabhängig von der Größe ist und sich relativ einfach und schnell berechnen lässt (vor allem, wenn er in Verbindung mit dem TF-IDF-Score verwendet wird, der später erklärt wird). Mathematisch wird sie wie folgt definiert:
Da du den TF-IDF-Vektorisierer verwendet hast, ergibt die Berechnung des Punktprodukts zwischen den einzelnen Vektoren direkt den Cosinus-Ähnlichkeitswert. Deshalb verwendest du sklearn's linear_kernel()
anstelle von cosine_similarities()
, da es schneller ist.
Dies würde eine Matrix der Form 45466x45466 ergeben, was bedeutet, dass jeder Film overview
eine Kosinusähnlichkeit mit jedem anderen Film overview
hat. Jeder Film ist also ein 1x45466-Spalten-Vektor, bei dem jede Spalte einen Ähnlichkeitswert mit jedem Film darstellt.
# 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])
Du wirst eine Funktion definieren, die einen Filmtitel als Eingabe erhält und eine Liste mit den 10 ähnlichsten Filmen ausgibt. Dafür brauchst du erstens eine umgekehrte Zuordnung von Filmtiteln und DataFrame-Indizes. Mit anderen Worten: Du brauchst einen Mechanismus, um den Index eines Films in deinem metadata
DataFrame anhand seines Titels zu ermitteln.
#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
Jetzt bist du gut gerüstet, um deine Empfehlungsfunktion zu definieren. Das sind die folgenden Schritte, die du befolgen wirst:
-
Ermittelt den Index des Films anhand seines Titels.
-
Erhalte die Liste der Cosinus-Ähnlichkeitswerte für diesen bestimmten Film mit allen Filmen. Wandle sie in eine Liste von Tupeln um, in der das erste Element die Position und das zweite die Ähnlichkeitsbewertung ist.
-
Sortiere die oben genannte Liste von Tupeln nach den Ähnlichkeitswerten, d.h. nach dem zweiten Element.
-
Hol dir die 10 wichtigsten Elemente dieser Liste. Ignoriere das erste Element, da es sich auf dich selbst bezieht (der Film, der einem bestimmten Film am ähnlichsten ist, ist der Film selbst).
-
Gibt die Titel zurück, die den Indizes der obersten Elemente entsprechen.
# 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
Du siehst, dass dein System zwar eine gute Arbeit geleistet hat, um Filme mit ähnlichen Handlungsbeschreibungen zu finden, aber die Qualität der Empfehlungen ist nicht so gut. "The Dark Knight Rises" bringt alle Batman-Filme zurück, während es wahrscheinlicher ist, dass die Leute, die diesen Film mochten, eher geneigt sind, andere Christopher Nolan-Filme zu mögen. Das ist etwas, das dein derzeitiges System nicht erfassen kann.
Auf Credits, Genres und Schlagwörtern basierender Recommender
Die Qualität deines Empfehlungsprogramms wird durch die Verwendung besserer Metadaten und die Erfassung von mehr Details erhöht. Das ist genau das, was du in diesem Abschnitt tun wirst. Du baust ein Empfehlungssystem auf, das auf den folgenden Metadaten basiert: den 3 Top-Schauspielern, dem Regisseur, verwandten Genres und den Schlüsselwörtern zur Filmhandlung.
Die Daten zu den Stichwörtern, der Besetzung und der Crew sind in deinem aktuellen Datensatz nicht vorhanden. Der erste Schritt wäre also, sie in deinen Haupt-DataFrame metadata
zu laden und zusammenzuführen.
# 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)
Erwachsene | belongs_to_collection | budget | Genres | Homepage | id | imdb_id | original_language | original_title | Übersicht | ... | spoken_languages | status | Tagline | title | video | vote_average | vote_count | gießen | Mannschaft | keywords | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Falsch | {'id': 10194, "Name": Toy Story Collection", ... | 30000000 | [{'id': 16, "Name": 'Animation'}, {'id': 35, '... | http://toystory.disney.com/toy-story | 862 | tt0114709 | en | Toy Story | Angeführt von Woody, leben Andys Spielzeuge glücklich in seinem ... | ... | [{'iso_639_1': 'en', 'name': 'Englisch'}] | Freigegeben | NaN | Toy Story | Falsch | 7.7 | 5415.0 | [{'cast_id': 14, "Zeichen": 'Woody (Stimme)',... | [{'credit_id': '52fe4284c3a36847f8024f49', 'de... | [{'id': 931, "Name": "Eifersucht"}, {"id": 4290,... |
1 | Falsch | NaN | 65000000 | [{'id': 12, "Name": 'Abenteuer'}, {'id': 14, '... | NaN | 8844 | tt0113497 | en | Jumanji | Als die Geschwister Judy und Peter eine Encha... | ... | [{'iso_639_1': 'en', 'name': 'Englisch'}, {'iso... | Freigegeben | Lass die Würfel rollen und entfache die Aufregung! | Jumanji | Falsch | 6.9 | 2413.0 | [{'cast_id': 1, "Zeichen": "Alan Parrish", "... | [{'credit_id': '52fe44bfc3a36847f80a7cd1', 'de... | [{'id': 10090, 'name': 'Brettspiel'}, {'id': 1... |
2 Zeilen × 27 Spalten
Aus deinen neuen Merkmalen, der Besetzung, der Crew und den Schlüsselwörtern, musst du die drei wichtigsten Schauspieler, den Regisseur und die mit dem Film verbundenen Schlüsselwörter herausfiltern.
Aber das Wichtigste zuerst: Deine Daten liegen in Form von "stringifizierten" Listen vor. Du musst sie so umwandeln, dass sie für dich nutzbar sind.
# 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)
Als Nächstes schreibst du Funktionen, die dir helfen, die benötigten Informationen aus jedem Merkmal zu extrahieren.
Zunächst importierst du das NumPy-Paket, um Zugriff auf die Konstante NaN
zu erhalten. Anschließend kannst du damit die Funktion get_director()
schreiben:
# Import Numpy
import numpy as np
Hol dir den Namen des Regisseurs aus dem Crew-Feature. Wenn der Direktor nicht aufgeführt ist, schicke NaN
def get_director(x):
for i in x:
if i['job'] == 'Director':
return i['name']
return np.nan
Als nächstes schreibst du eine Funktion, die die obersten 3 Elemente oder die gesamte Liste zurückgibt, je nachdem, was mehr ist. Hier bezieht sich die Liste auf die cast
, keywords
, und 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 | gießen | director | keywords | Genres | |
---|---|---|---|---|---|
0 | Toy Story | (Tom Hanks, Tim Allen, Don Rickles) | John Lasseter | [Eifersucht, Spielzeug, Junge] | [Animation, Komödie, Familie] |
1 | Jumanji | [Robin Williams, Jonathan Hyde, Kirsten Dunst] | Joe Johnston | [Brettspiel, Verschwinden, basierend auf Kinder... | [Abenteuer, Fantasy, Familie] |
2 | Mürrische alte Männer | (Walter Matthau, Jack Lemmon, Ann-Margret) | Howard Deutch | (Angeln, bester Freund, während des Abspanns) | [Romanze, Komödie] |
Der nächste Schritt wäre, die Namen und Schlüsselwortinstanzen in Kleinbuchstaben umzuwandeln und alle Leerzeichen zwischen ihnen zu entfernen.
Das Entfernen der Leerzeichen zwischen den Wörtern ist ein wichtiger Vorverarbeitungsschritt. Das wird gemacht, damit dein Vektorisierer den Johnny von "Johnny Depp" und "Johnny Galecki" nicht als denselben zählt. Nach diesem Verarbeitungsschritt werden die oben genannten Akteure als "johnnydepp" und "johnnygalecki" dargestellt und sind für deinen Vektorisierer eindeutig.
Ein weiteres gutes Beispiel, bei dem das Modell dieselbe Vektordarstellung ausgeben könnte, sind "Brotstau" und "Verkehrsstau". Daher ist es besser, alle vorhandenen Lücken zu beseitigen.
Die folgende Funktion wird genau das für dich tun:
# 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)
Jetzt kannst du deine "Metadatensuppe" erstellen. Das ist eine Zeichenkette, die alle Metadaten enthält, die du an deinen Vektorisierer weitergeben willst (nämlich Schauspieler, Regisseur und Schlüsselwörter).
Die Funktion create_soup
verbindet einfach alle benötigten Spalten durch ein Leerzeichen. Dies ist der letzte Vorverarbeitungsschritt, und die Ausgabe dieser Funktion wird in das Wortvektormodell eingespeist.
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)
Suppe | |
---|---|
0 | Eifersucht toy boy tomhanks timallen donrickles ... |
1 | Brettspiel "Verschwinden" nach dem Kinderbuch ... |
Die nächsten Schritte sind die gleichen wie bei deinem plot description based recommender
. Ein wesentlicher Unterschied besteht darin, dass du die CountVectorizer()
anstelle von TF-IDF
verwendest. Das liegt daran, dass du die Präsenz des Schauspielers/Regisseurs nicht abwerten willst, wenn er oder sie in relativ vielen Filmen mitgespielt oder Regie geführt hat. Es macht intuitiv nicht viel Sinn, sie in diesem Zusammenhang herunterzugewichten.
Der Hauptunterschied zwischen CountVectorizer()
und TF-IDF
ist die inverse Dokumentenhäufigkeit (IDF), die in der letzteren Komponente enthalten ist, in der ersteren jedoch nicht.
# 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)
Anhand der obigen Ausgabe kannst du sehen, dass die von dir eingegebenen Metadaten 73.881 Vokabulare enthalten.
Als Nächstes verwendest du die cosine_similarity
, um den Abstand zwischen den Einbettungen zu messen.
# 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'])
Du kannst jetzt deine get_recommendations()
Funktion wiederverwenden, indem du die neue cosine_sim2
Matrix als zweites Argument angibst.
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
Toll! Du siehst, dass dein Empfehlungsprogramm aufgrund von mehr Metadaten mehr Informationen erfassen konnte und dir bessere Empfehlungen gegeben hat. Es gibt natürlich zahlreiche Möglichkeiten, mit diesem System zu experimentieren, um die Empfehlungen zu verbessern.
Einige Vorschläge:
-
Führe einen Beliebtheitsfilter ein: Dieser Empfehlungsgeber würde die 30 ähnlichsten Filme nehmen, die gewichteten Bewertungen berechnen (mit der IMDB-Formel von oben), die Filme nach dieser Bewertung sortieren und die 10 besten Filme zurückgeben.
-
Andere Teammitglieder: Die Namen anderer Teammitglieder, wie Drehbuchautoren und Produzenten, können ebenfalls aufgenommen werden.
-
Das zunehmende Gewicht des Regisseurs: Um dem Regisseur oder der Regisseurin mehr Gewicht zu verleihen, kann er oder sie mehrfach in der Suppe erwähnt werden, um die Ähnlichkeitswerte von Filmen mit demselben Regisseur zu erhöhen.
Kollaborative Filterung mit Python
In diesem Tutorial hast du gelernt, wie du dein eigenes einfaches und inhaltsbasiertes Filmempfehlungssystem erstellen kannst. Es gibt noch eine andere, sehr beliebte Art von Empfehlungssystemen, die sogenannten kollaborativen Filter.
Kollaborative Filter können in zwei Arten unterteilt werden:
User-based Filtering
: Diese Systeme empfehlen einem Nutzer Produkte, die ähnlichen Nutzern gefallen haben. Nehmen wir zum Beispiel an, Alice und Bob haben ein ähnliches Interesse an Büchern (d.h. sie mögen und mögen nicht dieselben Bücher). Nehmen wir an, ein neues Buch ist auf den Markt gekommen, und Alice hat es gelesen und geliebt. Es ist also sehr wahrscheinlich, dass es auch Bob gefallen wird, und deshalb empfiehlt das System dieses Buch für Bob.
Item-based Filtering
: Diese Systeme sind der von dir entwickelten Content Recommendation Engine sehr ähnlich. Diese Systeme erkennen ähnliche Gegenstände anhand der Bewertungen, die andere in der Vergangenheit abgegeben haben. Wenn Alice, Bob und Eva zum Beispiel 5 Sterne für Der Herr der Ringe und Der Hobbit vergeben haben, erkennt das System die Artikel als ähnlich. Wenn also jemand Der Herr der Ringe kauft, empfiehlt das System ihm oder ihr auch den Hobbit.
Ein Beispiel für kollaboratives Filtern auf der Grundlage eines Bewertungssystems:
Du wirst diese Systeme in diesem Lehrgang nicht bauen, aber du bist bereits mit den meisten Ideen vertraut, die du dafür brauchst. Ein guter Anfang für kollaborative Filter ist die Untersuchung des MovieLens-Datensatzes, den du hier findest.
Fazit
Herzlichen Glückwunsch zur Fertigstellung dieses Tutorials!
Du hast unser Tutorial über Empfehlungssysteme in Python erfolgreich durchlaufen. Du hast gelernt, wie du einfache und inhaltsbasierte Empfehlungsprogramme erstellen kannst.
Eine gute Übung für euch wäre es, kollaboratives Filtern in Python zu implementieren und dabei die Teilmenge des MovieLens-Datensatzes zu verwenden, die ihr für die Erstellung einfacher und inhaltsbasierter Empfehlungsprogramme verwendet habt.
Wenn du gerade erst mit Python anfängst und mehr darüber erfahren möchtest, besuche den DataCamp-Kurs Einführung in die Datenwissenschaft in Python.
Python-Kurse
Kurs
Einführung in die Datenwissenschaft in Python
Kurs