Apache Spark und Python für Big Data und Machine Learning
Apache Spark gilt als schnelle, benutzerfreundliche und vielseitige Engine für die Big-Data-Verarbeitung mit integrierten Modulen für Streaming, SQL, Machine Learning (ML) und Graphverarbeitung. Diese Technologie ist eine gefragte Kompetenz für Data Engineers, aber auch Data Scientists profitieren von Spark bei Exploratory Data Analysis (EDA), Merkmalsextraktion und natürlich ML.
In diesem Tutorial nutzt du Spark mit Python über PySpark, die Spark-Python-API, die das Spark-Programmierungsmodell in Python verfügbar macht. Konkret konzentrierst du dich auf:
Die Installation von Spark und die Inbetriebnahme können eine Herausforderung sein. In diesem Abschnitt gehst du die Schritte durch, um Spark auf deinem PC zu installieren.
Als Erstes prüfst du die Voraussetzungen. Spark ist in der Programmiersprache Scala geschrieben und läuft in der Java Virtual Machine (JVM). Deshalb solltest du kontrollieren, ob ein Java Development Kit (JDK) installiert ist. Das JDK stellt eine oder mehrere Implementierungen der JVM bereit. Am besten nimmst du die neueste Version, zum Zeitpunkt der Erstellung war das JDK8.
Als Nächstes kannst du Spark herunterladen!
Herunterladen von pyspark mit pip
Du kannst PySpark ganz einfach mit pip herunterladen und installieren. Das funktioniert wie bei jedem anderen Paket: Du führst den bekannten Befehl aus und der Rest passiert automatisch:
Alternativ kannst du auch die Spark-Download-Seite nutzen. Lass die Standardoptionen in den ersten drei Schritten unverändert; in Schritt 4 findest du dann einen Download-Link. Klicke darauf, um den Download zu starten. Für dieses Tutorial lädst du Spark in der Version 2.2.0 und den Pakettyp „Pre-built for Apache Hadoop 2.7 and later“ herunter.
Hinweis: Der Download kann etwas dauern!
Spark mit Homebrew installieren
Du kannst Spark auch mit Homebrew, einem freien Open-Source-Paketmanager, installieren. Das ist besonders praktisch, wenn du mit macOS arbeitest.
Führe einfach die folgenden Befehle aus, um nach Spark zu suchen, mehr Infos zu erhalten und es schließlich auf deinem Rechner zu installieren:
# Search for spark
$ brew search spark
# Get more information on apache-spark
$ brew info apache-spark
# Install apache-spark
$ brew install apache-sparkBereitgestellt von
Spark herunterladen und einrichten
Entpacke nun das Archiv, das in deinem Ordner Downloads auftaucht. Das geht automatisch per Doppelklick auf spark-2.2.0-bin-hadoop2.7.tgz oder über das Terminal mit folgendem Befehl:
Hinweis: Wenn du eine Fehlermeldung zur fehlenden Berechtigung bekommst, füge sudo vor dem Befehl ein. Die Zeile lautet dann $ sudo mv spark-2.1.0-bin-hadoop2.7 /usr/local/spark. Du wirst nach deinem Passwort gefragt, in der Regel das gleiche, mit dem du deinen PC entsperrst :)
Jetzt öffne die README-Datei im Pfad /usr/local/spark. Das machst du mit
Damit wechselst du in den benötigten Ordner. Dort kannst du dir die Dateien ansehen und die enthaltene README lesen.
Nutze zuerst $ ls, um dir Dateien und Ordner im spark-Verzeichnis auflisten zu lassen. Du siehst die Datei README.md. Öffne sie mit einem der folgenden Befehle:
# Open and edit the file
$ nano README.md
# Just read the file
$ cat README.mdBereitgestellt von
Tipp: Nutze die Tabulatortaste, um Dateinamen beim Tippen automatisch zu vervollständigen :) Das spart Zeit.
Die README enthält allgemeine Infos zu Spark, Online-Dokumentation, Build-Hinweise, die interaktiven Scala- und Python-Shells, Beispielprogramme und mehr.
Besonders interessant ist der Abschnitt zum Bauen von Spark – aber nur, wenn du keine vorgebaute Version geladen hast. Für dieses Tutorial hast du eine vorgebaute Version heruntergeladen. Beende die README mit STRG + X, um zurück in den Spark-Ordner zu gelangen.
Falls du doch eine Version gewählt hast, die noch nicht gebaut wurde, führe den in der README genannten Befehl aus. Zum Zeitpunkt der Erstellung war das:
Beachte, dass dieser Befehl eine Weile laufen kann.
PySpark-Grundlagen: RDDs
Nachdem Spark und PySpark installiert sind, schauen wir uns zunächst die interaktive Spark-Shell an und klären die Grundlagen, die du für den Start brauchst. Im weiteren Verlauf des Tutorials arbeitest du jedoch mit PySpark in einem Jupyter-Notebook.
Spark-Anwendungen vs. Spark-Shell
Die interaktive Shell ist eine Read-Eval(uate)-Print-Loop-Umgebung (REPL); Das heißt, Eingaben werden gelesen, ausgewertet und das Ergebnis ausgegeben, damit du direkt weitermachen kannst. Das erinnert an IPython, eine leistungsfähige interaktive Python-Shell, die du aus Jupyter kennst. Mehr dazu im DataCamp-Artikel IPython or Jupyter.
Du kannst die Shell, die es für Python und Scala gibt, für alle interaktiven Aufgaben nutzen.
Neben der Shell kannst du auch Spark-Anwendungen schreiben und deployen. Im Gegensatz dazu ist in der Shell die SparkSession bereits erstellt, sodass du direkt loslegen kannst, ohne Zeit für die Initialisierung zu verlieren.
Du fragst dich: Was ist die SparkSession?
Sie ist der zentrale Einstiegspunkt in die Spark-Funktionalität: Sie stellt die Verbindung zu einem Spark-Cluster dar, mit der du RDDs erzeugst und Variablen im Cluster broadcastest. In Spark beginnt und endet alles mit dieser SparkSession. Hinweis: Vor Spark 2.0.0 waren die drei zentralen Objekte SparkContext, SqlContext und HiveContext.
Mehr dazu später. Konzentrieren wir uns zunächst auf die Shell.
Die Python-Spark-Shell
Im Ordner spark unter /usr/local/spark startest du mit
Erst erscheinen einige Ausgaben, dann siehst du „Spark“, etwa so:
Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 172016, 12:39:47)[GCC 4.2.1 (Apple Inc. build 5666)(dot 3)] on darwin
Type "help", "copyright", "credits" or "license"formore information.
Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).
17/07/26 11:41:26 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
17/07/26 11:41:47 WARN ObjectStore: Failed to get database global_temp, returning NoSuchObjectException
Welcome to
____ __
/ __/__ ___ _____/ /__
_\ \/ _ \/ _ `/ __/ '_/
/__ / .__/\_,_/_/ /_/\_\ version 2.2.0
/_/
Using Python version 2.7.13 (v2.7.13:a06454b1afa1, Dec 17201612:39:47)
SparkSession available as 'spark'.>>>Bereitgestellt von
Wenn du diese Ausgabe siehst, kannst du in der interaktiven Shell loslegen!
Tipp: Falls du lieber die IPython-Shell statt der Spark-Shell nutzt, setze diese Umgebungsvariable:
exportPYSPARK_DRIVER_PYTHON="/usr/local/ipython/bin/ipython"Bereitgestellt von
RDDs erstellen
Starten wir klein und erzeugen ein RDD, den grundlegendsten Baustein von Spark. Ein RDD repräsentiert Daten, ist aber nicht einfach ein einzelnes Objekt, eine Sammlung von Records, ein Result-Set oder ein Datensatz. Denn es ist für Daten gedacht, die auf mehreren Rechnern liegen: Ein RDD kann über Tausende JVMs verteilt sein, da Spark die Daten intern automatisch partitioniert, um Parallelität zu erreichen. Natürlich kannst du die Parallelität anpassen, um mehr Partitionen zu erhalten. Ein RDD ist daher eine Sammlung von Partitionen.
Ein einfaches RDD erstellst du mit der Funktion parallelize(), der du einfach Daten (ein Iterable wie eine Liste oder eine Collection) übergibst:
Hinweis: Das SparkSession-Objekt enthält das SparkContext-Objekt, das du über spark.sparkContext erreichst. Aus Gründen der Abwärtskompatibilität kannst du SparkContext auch weiterhin mit sc ansprechen, etwa rdd1 = sc.parallelize(['a',7),('a',2),('b',2)]).
RDD-Operationen
Mit den erstellten RDDs kannst du die verteilten Daten in rdd1 und rdd2 parallel verarbeiten. Es gibt zwei Typen von Operationen: Transformations und Actions.
Zur Einordnung: Häufige Transformations sind map(), filter(), flatMap(), sample(), randomSplit(), coalesce() und repartition(); gängige Actions sind reduce(), collect(), first(), take(), count(), saveAsHadoopFile().
Transformations sind „faule“ Operationen auf einem RDD, die ein oder mehrere neue RDDs erzeugen, während Actions Nicht-RDD-Werte liefern: ein Result-Set, eine Zahl, eine Datei, …
Du kannst z. B. alle Elemente von rdd1 mit folgender einfachen Lambda-Funktion aggregieren und das Ergebnis an das Treiberprogramm zurückgeben:
Die Ausführung ergibt: ('a', 7, 'a', 2, 'b', 2). Ein weiteres Beispiel für eine Transformation ist flatMapValues() auf Key-Value-RDDs wie rdd2. Dabei werden die Werte per FlatMap-Funktion abgeflacht, die Keys bleiben unverändert. Anschließend sammelst du die Ergebnisse mit collect() ein:
Nachdem du die Grundlagen in der interaktiven Shell abgedeckt hast, wird es Zeit für echte Daten. In diesem Tutorial nutzt du den California Housing Datensatz. Natürlich ist das eigentlich „Small Data“, und Spark ist hier überdimensioniert; Dieses Tutorial dient Lernzwecken und zeigt dir, wie du mit PySpark ein Machine-Learning-Modell aufsetzt.
Daten laden und erkunden
Auch wenn du schon etwas über deine Daten weißt, solltest du sie gründlich erkunden. Bevor es losgeht, richtest du dein Jupyter Notebook für Spark ein und definierst erste Schritte für den SparkContext.
PySpark im Jupyter Notebook
Für diesen Teil nutzt du nicht die Shell, sondern baust eine eigene Anwendung – in einem Jupyter Notebook. Alles Nötige ist bereits installiert, daher ist keine große Einrichtung erforderlich.
Starte das Notebook wie gewohnt mit $ jupyter notebook. Erstelle ein neues Notebook, importiere die Bibliothek findspark und rufe die Funktion init() auf. In diesem Beispiel gibst du /usr/local/spark an, weil du sicher bist, dass dort deine Spark-Installation liegt.
# Import findspark import findspark
# Initialize and provide path
findspark.init("/usr/local/spark")# Or use this alternative#findspark.init()Bereitgestellt von
Tipp: Wenn du unsicher bist, ob der Pfad stimmt oder wo Spark installiert ist, nutze findspark.find(), um den Installationsort automatisch zu erkennen.
Jetzt kannst du endlich dein erstes Spark-Programm erstellen!
Dein erstes Spark-Programm
Als Erstes importierst du den SparkContext aus dem Paket pyspark und initialisierst ihn. Denke daran, dass die interaktive Spark-Shell dies automatisch für dich erledigt hat! Hier musst du etwas mehr selbst tun :)
Importiere das Modul SparkSession aus pyspark.sql und baue mit der Methode builder() eine SparkSession. Danach setzt du die Master-URL, den Applikationsnamen, optional zusätzliche Konfiguration wie den Executor-Speicher und rufst zum Schluss getOrCreate() auf, um eine laufende Session zu erhalten oder eine neue zu erstellen.
Hinweis: Falls ein FileNotFoundError auftritt wie „No such file or directory: ‘/User/YourName/Downloads/spark-2.1.0-bin-hadoop2.7/./bin/spark-submit’“, musst du deinen Spark-PATH (neu) setzen. Wechsle mit $ cd in dein Home-Verzeichnis und bearbeite die Datei .bash_profile mit $ nano .bash_profile.
Beende die Datei mit STRG + X und bestätige das Speichern mit Y. Vergiss nicht, die Änderungen mit source .bash_profile zu aktivieren.
Tipp: Du kannst bei Bedarf weitere Umgebungsvariablen setzen. Meist brauchst du sie nicht, aber gut zu wissen. Etwa so:
# Set a fixed value for the hash seed secretexportPYTHONHASHSEED=0# Set an alternate Python executableexportPYSPARK_PYTHON=/usr/local/ipython/bin/ipython
# Augment the default search path for shared librariesexportLD_LIBRARY_PATH=/usr/local/ipython/bin/ipython
# Augment the default search path for private libraries exportPYTHONPATH=$SPARK_HOME/python/lib/py4j-*-src.zip:$PYTHONPATH:$SPARK_HOME/python/Bereitgestellt von
Hinweis: Du hast nun eine Standard-SparkSession initialisiert. In den meisten Fällen wirst du sie weiter konfigurieren wollen – vor allem bei echten Big-Data-Workloads. Mehr dazu hier.
Daten laden
Dieses Tutorial nutzt den California-Housing-Datensatz. Er erschien 1997 im Paper Sparse Spatial Autoregressions von Pace, R. Kelley und Ronald Barry in den Statistics and Probability Letters. Die Forschenden bauten den Datensatz auf Basis der Volkszählung in Kalifornien von 1990.
Die Daten enthalten eine Zeile pro „Census Block Group“. Eine Block Group ist die kleinste geografische Einheit, für die das U.S. Census Bureau Stichprobendaten veröffentlicht (typischerweise 600 bis 3.000 Personen). In dieser Stichprobe umfasst eine Block Group im Schnitt 1425,5 Personen in einem geografisch kompakten Gebiet. Diese Infos findest du auf dieser Seite oder im oben erwähnten Paper hier.
Die räumlichen Daten umfassen 20.640 Beobachtungen zu Immobilienpreisen mit 9 ökonomischen Variablen:
Longitude bezeichnet die geografische Länge je Block Group;
Latitude bezeichnet die geografische Breite je Block Group;
Housing median age ist das Medianalter der Personen in einer Block Group. Hinweis: Der Median ist der Wert in der Mitte der Verteilung;
Total rooms ist die Gesamtzahl der Räume in den Häusern je Block Group;
Total bedrooms ist die Gesamtzahl der Schlafzimmer in den Häusern je Block Group;
Population ist die Einwohnerzahl einer Block Group;
Households bezeichnet die Anzahl der Haushalte je Block Group;
Median income ist das Medianeinkommen der Personen in einer Block Group; und
Median house value ist die Zielvariable und bezeichnet den medianen Hauswert je Block Group.
Zudem wurden alle Block Groups mit Nulleinträgen in unabhängigen und abhängigen Variablen aus den Daten ausgeschlossen.
Der Median house value ist die abhängige Variable und dient in deinem ML-Modell als Zielvariable.
Du kannst die Daten hier herunterladen. Suche den Ordner houses.zip, lade ihn herunter und entpacke ihn, um an die Datenordner zu kommen.
Als Nächstes liest du die Daten mit der Methode textFile() aus dem heruntergeladenen Ordner als RDDs ein. Diese Methode erwartet eine URI zur Datei (hier der lokale Pfad) und liest sie als Sammlung von Zeilen. Zur Kontrolle lädst du nicht nur die .data-Datei, sondern auch die .domain-Datei mit dem Header. So kannst du die Reihenfolge der Variablen gegenprüfen.
# Load in the data
rdd = sc.textFile('/Users/yourName/Downloads/CaliforniaHousing/cal_housing.data')# Load in the header
header = sc.textFile('/Users/yourName/Downloads/CaliforniaHousing/cal_housing.domain')Bereitgestellt von
Daten erkunden
Du hast bereits viele Informationen von der Webseite mit dem Datensatz gesammelt, aber praktische Inspektionen mit Spark in Python sind unerlässlich.
Wichtig: Da die Ausführung in Spark „lazy“ ist, wurde noch nichts ausgeführt. Deine Daten wurden noch nicht wirklich gelesen. Die Variablen rdd und header sind bisher nur Konzepte. Du musst Spark „anschubsen“. Schau dir zunächst den header mit collect() an:
Tipp: Sei vorsichtig mit collect()! Das kann den Treiber aus dem Speicher laufen lassen. take() ist sicherer, wenn du nur einen Ausschnitt sehen willst. Generell solltest du Ergebnissets begrenzen, so wie bei SQL.
Die Reihenfolge der Variablen entspricht der oben beschriebenen, und alle Spalten sind kontinuierlich. Lass Spark mehr arbeiten und sieh dir die California-Housing-Daten an, um das zu bestätigen.
Damit holst du die ersten 2 Elemente des RDD. Wie erwartet: Da du mit textFile() eingelesen hast, liegen die Zeilen als Strings vor. Die Einträge sind durch Komma getrennt, ebenso die Zeilen:
[u'-122.230000,37.880000,41.000000,880.000000,129.000000,322.000000,126.000000,8.325200,452600.000000',u'-122.220000,37.860000,21.000000,7099.000000,1106.000000,2401.000000,1138.000000,8.301400,358500.000000']Bereitgestellt von
Das musst du aufbereiten. Du musst die Einträge nicht einzeln aufspalten, aber die Zeilen sollten separate Elemente sein. Dafür verwendest du map() mit einer Lambda-Funktion, die an jedem Komma trennt. Prüfe das Ergebnis wieder mit take():
Erinnerung: Lambda-Funktionen sind anonyme Funktionen, die zur Laufzeit erzeugt werden.
# Split lines on commas
rdd = rdd.map(lambda line: line.split(","))# Inspect the first 2 lines
rdd.take(2)Bereitgestellt von
Du erhältst Folgendes:
[[u'-122.230000',u'37.880000',u'41.000000',u'880.000000',u'129.000000',u'322.000000',u'126.000000',u'8.325200',u'452600.000000'],[u'-122.220000',u'37.860000',u'21.000000',u'7099.000000',u'1106.000000',u'2401.000000',u'1138.000000',u'8.301400',u'358500.000000']]Bereitgestellt von
Alternativ kannst du auch Folgendes nutzen:
# Inspect the first line
rdd.first()# Take top elements
rdd.top(2)Bereitgestellt von
Wenn du Pandas oder Data Frames in R gewohnt bist, erwartest du einen Header – den gibt es hier nicht. Um dir das Leben zu erleichtern, wechselst du vom RDD zu einem DataFrame. DataFrames sind RDDs vorzuziehen, wann immer möglich. Besonders in Python sind sie performanter.
Was ist der Unterschied?
RDDs nutzt du, wenn du Low-Level-Transformationen und -Actions auf unstrukturierten Daten brauchst, also kein Schema erzwingen oder Spaltennamen verwenden willst. Performancevorteile von DataFrames für (semi-)strukturierte Daten spielen dann weniger eine Rolle. RDDs sind sinnvoll, wenn du lieber mit funktionalen Konstrukten statt domänenspezifischen Ausdrücken arbeitest.
Wir wechseln jetzt zu DataFrames, um High-Level-Ausdrücke, SQL-Abfragen zur weiteren Exploration und spaltenweisen Zugriff zu nutzen.
Los geht’s.
Schritt eins: Erzeuge eine SchemaRDD bzw. ein RDD aus Row-Objekten mit Schema. Ziel ist, wie bei einem DataFrame, Zeilen und Spalten mit Datentypen zu haben.
Verwende erneut map() mit einer Lambda-Funktion, die jedes Element einer Zeile einem Feld in einer Row zuordnet. Beispiel für die erste Zeile:
[u'-122.230000',u'37.880000',u'41.000000',u'880.000000',u'129.000000',u'322.000000',u'126.000000',u'8.325200',u'452600.000000']Bereitgestellt von
Die Lambda-Funktion konstruiert eine Row der SchemaRDD: Index 0 heißt „longitude“, und so weiter.
Mit dieser SchemaRDD kannst du das RDD mit toDF() in ein DataFrame umwandeln.
# Import the necessary modules from pyspark.sql import Row
# Map the RDD to a DF
df = rdd.map(lambda line: Row(longitude=line[0],
latitude=line[1],
housingMedianAge=line[2],
totalRooms=line[3],
totalBedRooms=line[4],
population=line[5],
households=line[6],
medianIncome=line[7],
medianHouseValue=line[8])).toDF()Bereitgestellt von
Jetzt kannst du df mit Methoden wie first(), take(), head() und show() inspizieren:
Tipp: df.columns gibt dir die Spaltennamen zurück.
Die Daten sind sauber in Spalten organisiert, aber wie sieht es mit den Datentypen aus? Spark versucht beim Einlesen ein Schema zu erkennen – hat das geklappt? Nutze df.dtypes oder df.printSchema(), um mehr über die Datentypen zu erfahren.
# Print the data types of all `df` columns# df.dtypes# Print the schema of `df`
df.printSchema()Bereitgestellt von
Da du die erste Zeile nicht ausführst, bekommst du Folgendes zurück:
Alle Spalten sind noch vom Typ String … Ernüchternd!
Wenn du mit diesem DataFrame weiterarbeiten willst, solltest du passendere Datentypen vergeben. Das verbessert auch die Performance. Intuitiv könntest du alle Spalten von df auf FloatType() casten:
Diese Wiederholungen sind jedoch fehleranfällig und nicht schön zu lesen. Warum nicht eine Funktion schreiben, die das sauber für dich erledigt?
Die folgende Funktion nimmt ein DataFrame, Spaltennamen und den neuen Datentyp. Für jeden Spaltennamen castest du die Spalte auf den neuen Typ und gibst das DataFrame zurück:
# Import all from `sql.types`from pyspark.sql.types import*# Write a custom function to convert the data type of DataFrame columnsdefconvertColumn(df, names, newType):for name in names:
df = df.withColumn(name, df[name].cast(newType))return df
# Assign all column names to `columns`
columns =['households','housingMedianAge','latitude','longitude','medianHouseValue','medianIncome','population','totalBedRooms','totalRooms']# Conver the `df` columns to `FloatType()`
df = convertColumn(df, columns, FloatType())Bereitgestellt von
So wirkt es deutlich aufgeräumter! Prüfe die Datentypen von df erneut mit printSchema().
Jetzt geht es an die eigentliche Exploration. Spaltenweiser Zugriff und SQL-Abfragen sind zwei große Vorteile von DataFrames. Starte klein und wähle zwei Spalten aus df, von denen du 10 Zeilen sehen möchtest:
+----------+-------------+|population|totalBedRooms|+----------+-------------+|322.0|129.0||2401.0|1106.0||496.0|190.0||558.0|235.0||565.0|280.0||413.0|213.0||1094.0|489.0||1157.0|687.0||1206.0|665.0||1551.0|707.0|+----------+-------------+
only showing top 10 rowsBereitgestellt von
Du kannst die Abfragen natürlich komplexer gestalten, z. B. so:
df.groupBy("housingMedianAge").count().sort("housingMedianAge",ascending=False).show()Bereitgestellt von
Ergebnis:
+----------------+-----+|housingMedianAge|count|+----------------+-----+|52.0|1273||51.0|48||50.0|136||49.0|134||48.0|177||47.0|198||46.0|245||45.0|294||44.0|356||43.0|353||42.0|368||41.0|296||40.0|304||39.0|369||38.0|394||37.0|537||36.0|862||35.0|824||34.0|689||33.0|615|+----------------+-----+
only showing top 20 rowsBereitgestellt von
Neben Abfragen kannst du auch Zusammenfassungsstatistiken ausgeben. Das hilft dir im nächsten Schritt:
Sieh dir Minimum und Maximum der numerischen Attribute an. Mehrere Spalten haben große Wertebereiche – du solltest deinen Datensatz normalisieren.
Datenvorverarbeitung
Auf Basis deiner kurzen explorativen Analyse weißt du genug, um die Daten für das Modell aufzubereiten.
Um fehlende Werte musst du dich nicht kümmern; alle Nullwerte wurden ausgeschlossen.
Du solltest die Daten standardisieren, da die Spannweiten groß sind.
Zusätzliche Merkmale sind sinnvoll, z. B. Schlafzimmer pro Raum oder Räume pro Haushalt.
Auch die Zielvariable ist recht groß; zur einfacheren Handhabung passt du ihre Werte etwas an.
Zielvariable vorverarbeiten
Beginnen wir mit medianHouseValue, deiner abhängigen Variable. Um die Arbeit mit den Zielwerten zu erleichtern, drückst du Hauswerte in Einheiten von 100.000 aus. Aus 452600.000000 wird also 4.526:
# Import all from `sql.functions` from pyspark.sql.functions import*# Adjust the values of `medianHouseValue`
df = df.withColumn("medianHouseValue", col("medianHouseValue")/100000)# Show the first 2 lines of `df`
df.take(2)Bereitgestellt von
Im Ergebnis von take() siehst du die korrekt angepassten Werte:
Nachdem du medianHouseValue angepasst hast, fügst du zusätzliche Merkmale hinzu:
Rooms per household: Anzahl der Räume pro Haushalt je Block Group;
Population per household: wie viele Personen pro Haushalt je Block Group;
Bedrooms per room: Anteil der Schlafzimmer an allen Räumen je Block Group;
Mit DataFrames nutzt du am besten select(), um die Spalten auszuwählen, mit denen du arbeitest: totalRooms, households und population. Verwende außerdem col(), um Spaltenausdrücke zu bilden – nur so kannst du elementweise Operationen wie Division durchführen:
# Import all from `sql.functions` if you haven't yetfrom pyspark.sql.functions import*# Divide `totalRooms` by `households`
roomsPerHousehold = df.select(col("totalRooms")/col("households"))# Divide `population` by `households`
populationPerHousehold = df.select(col("population")/col("households"))# Divide `totalBedRooms` by `totalRooms`
bedroomsPerRoom = df.select(col("totalBedRooms")/col("totalRooms"))# Add the new columns to `df`
df = df.withColumn("roomsPerHousehold", col("totalRooms")/col("households")) \
.withColumn("populationPerHousehold", col("population")/col("households")) \
.withColumn("bedroomsPerRoom", col("totalBedRooms")/col("totalRooms"))# Inspect the result
df.first()Bereitgestellt von
Für die erste Zeile siehst du z. B. etwa 6,98 Räume pro Haushalt, ca. 2,56 Personen pro Haushalt und einen Schlafzimmeranteil von 0,14:
Als Nächstes – mit Blick auf die Standardisierung – ordnest du die Spalten neu. Da du die Zielvariable nicht standardisieren willst, isolierst du sie im DataFrame.
Das erreichst du über select() und gibst die Spalten in der gewünschten Reihenfolge an. medianHouseValue kommt zuerst, damit es von der Standardisierung unberührt bleibt.
Hinweis: Jetzt ist auch der richtige Zeitpunkt, um Variablen auszuschließen, die du nicht brauchst. In diesem Fall lassen wir z. B. longitude, latitude, housingMedianAge und totalRooms weg.
# Re-order and select columns
df = df.select("medianHouseValue","totalBedRooms","population","households","medianIncome","roomsPerHousehold","populationPerHousehold","bedroomsPerRoom")Bereitgestellt von
Standardisierung
Nach der Neuordnung kannst du die Daten normalisieren – fast. Ein Schritt fehlt noch: Trenne Features und Zielvariable. Im Kern bedeutet das, die erste Spalte vom Rest zu isolieren.
Hierfür nutzt du die map()-Funktion auf dem RDD des DataFrames. Außerdem verwendest du DenseVector(), einen lokalen Vektor, der die Einträge als Double-Array speichert – praktisch, um Wertearrays in PySpark zu halten.
Anschließend machst du wieder ein DataFrame aus input_data und benennst die Spalten in "label" und "features" um:
# Import `DenseVector`from pyspark.ml.linalg import DenseVector
# Define the `input_data`
input_data = df.rdd.map(lambda x:(x[0], DenseVector(x[1:])))# Replace `df` with the new DataFrame
df = spark.createDataFrame(input_data,["label","features"])Bereitgestellt von
Jetzt kannst du skalieren. Nutze dafür Spark ML: Die Bibliothek bietet ML-Algorithmen und alles für praxistaugliche ML-Pipelines. In diesem Fall brauchst du keine komplette Pipeline, aber wenn du mehr dazu wissen willst, lies hier.
Die Eingabespalte ist features, die Ausgabespalte der skalierten Werte in scaled_df heißt "features_scaled":
# Import `StandardScaler` from pyspark.ml.feature import StandardScaler
# Initialize the `standardScaler`
standardScaler = StandardScaler(inputCol="features", outputCol="features_scaled")# Fit the DataFrame to the scaler
scaler = standardScaler.fit(df)# Transform the data in `df` with the scaler
scaled_df = scaler.transform(df)# Inspect the result
scaled_df.take(2)Bereitgestellt von
Ein Blick ins DataFrame zeigt die neue Spalte features_scaled, die du mit features vergleichen kannst:
[Row(label=4.526, features=DenseVector([129.0,322.0,126.0,8.3252,6.9841,2.5556,0.1466]), features_scaled=DenseVector([0.3062,0.2843,0.3296,4.3821,2.8228,0.2461,2.5264])), Row(label=3.585, features=DenseVector([1106.0,2401.0,1138.0,8.3014,6.2381,2.1098,0.1558]), features_scaled=DenseVector([2.6255,2.1202,2.9765,4.3696,2.5213,0.2031,2.6851]))]Bereitgestellt von
Hinweis: Diese Schritte ähneln stark dem Vorgehen in Scikit-Learn.
Ein Machine-Learning-Modell mit Spark ML bauen
Nach der Vorverarbeitung baust du nun dein Lineares Regressionsmodell. Wie gewohnt teilst du zuerst in Trainings- und Testdaten. Mit randomSplit() ist das kein Problem:
# Split the data into train and test sets
train_data, test_data = scaled_df.randomSplit([.8,.2],seed=1234)Bereitgestellt von
Du übergibst eine Liste mit zwei Zahlen für die Größen von Training und Test sowie einen Seed für Reproduzierbarkeit. Mehr dazu im DataCamp-Python Machine Learning Tutorial.
Dann erstellst du das Modell:
Hinweis: Das Argument elasticNetParam entspricht α und regParam (Regularisierung) entspricht λ. Mehr Infos hier.
# Import `LinearRegression`from pyspark.ml.regression import LinearRegression
# Initialize `lr`
lr = LinearRegression(labelCol="label", maxIter=10, regParam=0.3, elasticNetParam=0.8)# Fit the data to the model
linearModel = lr.fit(train_data)Bereitgestellt von
Mit dem Modell kannst du nun Vorhersagen auf den Testdaten erzeugen: Verwende transform(), extrahiere per RDD-Operationen Vorhersagen und wahre Labels und führe beides als Liste predictionAndLabel zusammen.
Zum Schluss inspizierst du einige Paare einfach per Indexzugriff:
# Generate predictions
predicted = linearModel.transform(test_data)# Extract the predictions and the "known" correct labels
predictions = predicted.select("prediction").rdd.map(lambda x: x[0])
labels = predicted.select("label").rdd.map(lambda x: x[0])# Zip `predictions` and `labels` into a list
predictionAndLabel = predictions.zip(labels).collect()# Print out first 5 instances of `predictionAndLabel`
predictionAndLabel[:5]Bereitgestellt von
Du siehst in etwa Folgendes (zuerst Vorhersage, dann Ist-Wert):
[(1.4491508524918457,0.14999),(1.5705029404692372,0.14999),(2.148727956912464,0.14999),(1.5831547768979277,0.344),(1.5182107797955968,0.398)]Bereitgestellt von
Das Modell evaluieren
Ein paar Vorhersagen anzuschauen ist gut, Kennzahlen sind besser. Starte mit den Koeffizienten und dem Achsenabschnitt deines Modells:
# Coefficients for the model
linearModel.coefficients
# Intercept for the model
linearModel.interceptBereitgestellt von
Ergebnis:
# The coefficients[0.0,0.0,0.0,0.276239709215,0.0,0.0,0.0]# The intercept0.990399577462Bereitgestellt von
Über das Attribut summary bekommst du außerdem rootMeanSquaredError und r2:
# Get the RMSE
linearModel.summary.rootMeanSquaredError
# Get the R2
linearModel.summary.r2Bereitgestellt von
Der RMSE misst den Fehler zwischen Vorhersage und beobachtetem Wert. Je kleiner, desto näher liegen Vorhersage und Realität beieinander.
Das R² (Bestimmtheitsmaß) zeigt, wie gut die Daten zur Regressionsgeraden passen. Der Wert liegt zwischen 0 und 1, wobei 0% bedeutet, dass das Modell keine Varianz erklärt, und 100% das Gegenteil. Allgemein gilt: Je höher R², desto besser passt das Modell.
Da ist definitiv noch Luft nach oben! Wenn du weitermachen möchtest, experimentiere mit den Hyperparametern, den aufgenommenen Variablen im ursprünglichen DataFrame, … Für dieses Tutorial ist hier Schluss!
Erforsche Pythons match-case: eine Anleitung zu seiner Syntax, Anwendungen in Data Science und ML sowie eine vergleichende Analyse mit dem traditionellen switch-case.
Mach dich mit Python-Datenstrukturen vertraut: Lerne mehr über Datentypen und primitive sowie nicht-primitive Datenstrukturen wie Strings, Listen, Stapel usw.
Ein umfassendes Einführungs-Tutorial zu Python-Schleifen. Lerne und übe while- und for-Schleifen, verschachtelte Schleifen, die Schlüsselwörter break und continue, die Range-Funktion und vieles mehr!
Apache Spark und Python für Big Data und Machine Learning
Apache Spark gilt als schnelle, benutzerfreundliche und vielseitige Engine für die Big-Data-Verarbeitung mit integrierten Modulen für Streaming, SQL, Machine Learning (ML) und Graphverarbeitung. Diese Technologie ist eine gefragte Kompetenz für Data Engineers, aber auch Data Scientists profitieren von Spark bei Exploratory Data Analysis (EDA), Merkmalsextraktion und natürlich ML.
In diesem Tutorial nutzt du Spark mit Python über PySpark, die Spark-Python-API, die das Spark-Programmierungsmodell in Python verfügbar macht. Konkret konzentrierst du dich auf:
Wenn du Spark lieber mit R nutzen möchtest, wirf einen Blick auf DataCamps kostenlosen Kurs Introduction to Spark in R with sparklyr oder lade dir das PySpark SQL Cheat Sheet herunter.
Apache Spark installieren
Die Installation von Spark und die Inbetriebnahme können eine Herausforderung sein. In diesem Abschnitt gehst du die Schritte durch, um Spark auf deinem PC zu installieren.
Als Erstes prüfst du die Voraussetzungen. Spark ist in der Programmiersprache Scala geschrieben und läuft in der Java Virtual Machine (JVM). Deshalb solltest du kontrollieren, ob ein Java Development Kit (JDK) installiert ist. Das JDK stellt eine oder mehrere Implementierungen der JVM bereit. Am besten nimmst du die neueste Version, zum Zeitpunkt der Erstellung war das JDK8.
Als Nächstes kannst du Spark herunterladen!
Herunterladen von
pysparkmitpipDu kannst PySpark ganz einfach mit
pipherunterladen und installieren. Das funktioniert wie bei jedem anderen Paket: Du führst den bekannten Befehl aus und der Rest passiert automatisch:Alternativ kannst du auch die Spark-Download-Seite nutzen. Lass die Standardoptionen in den ersten drei Schritten unverändert; in Schritt 4 findest du dann einen Download-Link. Klicke darauf, um den Download zu starten. Für dieses Tutorial lädst du Spark in der Version 2.2.0 und den Pakettyp „Pre-built for Apache Hadoop 2.7 and later“ herunter.
Hinweis: Der Download kann etwas dauern!
Spark mit Homebrew installieren
Du kannst Spark auch mit Homebrew, einem freien Open-Source-Paketmanager, installieren. Das ist besonders praktisch, wenn du mit macOS arbeitest.
Führe einfach die folgenden Befehle aus, um nach Spark zu suchen, mehr Infos zu erhalten und es schließlich auf deinem Rechner zu installieren:
Spark herunterladen und einrichten
Entpacke nun das Archiv, das in deinem Ordner
Downloadsauftaucht. Das geht automatisch per Doppelklick aufspark-2.2.0-bin-hadoop2.7.tgzoder über das Terminal mit folgendem Befehl:Verschiebe anschließend den entpackten Ordner nach
/usr/local/sparkmit dieser Zeile:Hinweis: Wenn du eine Fehlermeldung zur fehlenden Berechtigung bekommst, füge
sudovor dem Befehl ein. Die Zeile lautet dann$ sudo mv spark-2.1.0-bin-hadoop2.7 /usr/local/spark. Du wirst nach deinem Passwort gefragt, in der Regel das gleiche, mit dem du deinen PC entsperrst :)Jetzt öffne die README-Datei im Pfad
/usr/local/spark. Das machst du mitDamit wechselst du in den benötigten Ordner. Dort kannst du dir die Dateien ansehen und die enthaltene README lesen.
Nutze zuerst
$ ls, um dir Dateien und Ordner imspark-Verzeichnis auflisten zu lassen. Du siehst die Datei README.md. Öffne sie mit einem der folgenden Befehle:Tipp: Nutze die Tabulatortaste, um Dateinamen beim Tippen automatisch zu vervollständigen :) Das spart Zeit.
Die README enthält allgemeine Infos zu Spark, Online-Dokumentation, Build-Hinweise, die interaktiven Scala- und Python-Shells, Beispielprogramme und mehr.
Besonders interessant ist der Abschnitt zum Bauen von Spark – aber nur, wenn du keine vorgebaute Version geladen hast. Für dieses Tutorial hast du eine vorgebaute Version heruntergeladen. Beende die README mit STRG + X, um zurück in den Spark-Ordner zu gelangen.
Falls du doch eine Version gewählt hast, die noch nicht gebaut wurde, führe den in der README genannten Befehl aus. Zum Zeitpunkt der Erstellung war das:
Beachte, dass dieser Befehl eine Weile laufen kann.
PySpark-Grundlagen: RDDs
Nachdem Spark und PySpark installiert sind, schauen wir uns zunächst die interaktive Spark-Shell an und klären die Grundlagen, die du für den Start brauchst. Im weiteren Verlauf des Tutorials arbeitest du jedoch mit PySpark in einem Jupyter-Notebook.
Spark-Anwendungen vs. Spark-Shell
Die interaktive Shell ist eine Read-Eval(uate)-Print-Loop-Umgebung (REPL); Das heißt, Eingaben werden gelesen, ausgewertet und das Ergebnis ausgegeben, damit du direkt weitermachen kannst. Das erinnert an IPython, eine leistungsfähige interaktive Python-Shell, die du aus Jupyter kennst. Mehr dazu im DataCamp-Artikel IPython or Jupyter.
Du kannst die Shell, die es für Python und Scala gibt, für alle interaktiven Aufgaben nutzen.
Neben der Shell kannst du auch Spark-Anwendungen schreiben und deployen. Im Gegensatz dazu ist in der Shell die SparkSession bereits erstellt, sodass du direkt loslegen kannst, ohne Zeit für die Initialisierung zu verlieren.
Du fragst dich: Was ist die SparkSession?
Sie ist der zentrale Einstiegspunkt in die Spark-Funktionalität: Sie stellt die Verbindung zu einem Spark-Cluster dar, mit der du RDDs erzeugst und Variablen im Cluster broadcastest. In Spark beginnt und endet alles mit dieser SparkSession. Hinweis: Vor Spark 2.0.0 waren die drei zentralen Objekte SparkContext, SqlContext und HiveContext.
Mehr dazu später. Konzentrieren wir uns zunächst auf die Shell.
Die Python-Spark-Shell
Im Ordner
sparkunter/usr/local/sparkstartest du mitErst erscheinen einige Ausgaben, dann siehst du „Spark“, etwa so:
Wenn du diese Ausgabe siehst, kannst du in der interaktiven Shell loslegen!
Tipp: Falls du lieber die IPython-Shell statt der Spark-Shell nutzt, setze diese Umgebungsvariable:
RDDs erstellen
Starten wir klein und erzeugen ein RDD, den grundlegendsten Baustein von Spark. Ein RDD repräsentiert Daten, ist aber nicht einfach ein einzelnes Objekt, eine Sammlung von Records, ein Result-Set oder ein Datensatz. Denn es ist für Daten gedacht, die auf mehreren Rechnern liegen: Ein RDD kann über Tausende JVMs verteilt sein, da Spark die Daten intern automatisch partitioniert, um Parallelität zu erreichen. Natürlich kannst du die Parallelität anpassen, um mehr Partitionen zu erhalten. Ein RDD ist daher eine Sammlung von Partitionen.
Ein einfaches RDD erstellst du mit der Funktion
parallelize(), der du einfach Daten (ein Iterable wie eine Liste oder eine Collection) übergibst:Hinweis: Das SparkSession-Objekt enthält das SparkContext-Objekt, das du über
spark.sparkContexterreichst. Aus Gründen der Abwärtskompatibilität kannst du SparkContext auch weiterhin mitscansprechen, etwardd1 = sc.parallelize(['a',7),('a',2),('b',2)]).RDD-Operationen
Mit den erstellten RDDs kannst du die verteilten Daten in
rdd1undrdd2parallel verarbeiten. Es gibt zwei Typen von Operationen: Transformations und Actions.Zur Einordnung: Häufige Transformations sind
map(),filter(),flatMap(),sample(),randomSplit(),coalesce()undrepartition(); gängige Actions sindreduce(),collect(),first(),take(),count(),saveAsHadoopFile().Transformations sind „faule“ Operationen auf einem RDD, die ein oder mehrere neue RDDs erzeugen, während Actions Nicht-RDD-Werte liefern: ein Result-Set, eine Zahl, eine Datei, …
Du kannst z. B. alle Elemente von
rdd1mit folgender einfachen Lambda-Funktion aggregieren und das Ergebnis an das Treiberprogramm zurückgeben:Die Ausführung ergibt:
('a', 7, 'a', 2, 'b', 2). Ein weiteres Beispiel für eine Transformation istflatMapValues()auf Key-Value-RDDs wierdd2. Dabei werden die Werte per FlatMap-Funktion abgeflacht, die Keys bleiben unverändert. Anschließend sammelst du die Ergebnisse mitcollect()ein:Die Daten
Nachdem du die Grundlagen in der interaktiven Shell abgedeckt hast, wird es Zeit für echte Daten. In diesem Tutorial nutzt du den California Housing Datensatz. Natürlich ist das eigentlich „Small Data“, und Spark ist hier überdimensioniert; Dieses Tutorial dient Lernzwecken und zeigt dir, wie du mit PySpark ein Machine-Learning-Modell aufsetzt.
Daten laden und erkunden
Auch wenn du schon etwas über deine Daten weißt, solltest du sie gründlich erkunden. Bevor es losgeht, richtest du dein Jupyter Notebook für Spark ein und definierst erste Schritte für den SparkContext.
PySpark im Jupyter Notebook
Für diesen Teil nutzt du nicht die Shell, sondern baust eine eigene Anwendung – in einem Jupyter Notebook. Alles Nötige ist bereits installiert, daher ist keine große Einrichtung erforderlich.
Starte das Notebook wie gewohnt mit
$ jupyter notebook. Erstelle ein neues Notebook, importiere die Bibliothekfindsparkund rufe die Funktioninit()auf. In diesem Beispiel gibst du/usr/local/sparkan, weil du sicher bist, dass dort deine Spark-Installation liegt.Tipp: Wenn du unsicher bist, ob der Pfad stimmt oder wo Spark installiert ist, nutze
findspark.find(), um den Installationsort automatisch zu erkennen.Weitere Wege, Spark in Jupyter zu nutzen, findest du in unserer Apache Spark in Python: Beginner’s Guide.
Jetzt kannst du endlich dein erstes Spark-Programm erstellen!
Dein erstes Spark-Programm
Als Erstes importierst du den
SparkContextaus dem Paketpysparkund initialisierst ihn. Denke daran, dass die interaktive Spark-Shell dies automatisch für dich erledigt hat! Hier musst du etwas mehr selbst tun :)Importiere das Modul
SparkSessionauspyspark.sqlund baue mit der Methodebuilder()eine SparkSession. Danach setzt du die Master-URL, den Applikationsnamen, optional zusätzliche Konfiguration wie den Executor-Speicher und rufst zum SchlussgetOrCreate()auf, um eine laufende Session zu erhalten oder eine neue zu erstellen.Hinweis: Falls ein FileNotFoundError auftritt wie „No such file or directory: ‘/User/YourName/Downloads/spark-2.1.0-bin-hadoop2.7/./bin/spark-submit’“, musst du deinen Spark-PATH (neu) setzen. Wechsle mit
$ cdin dein Home-Verzeichnis und bearbeite die Datei.bash_profilemit$ nano .bash_profile.Füge am Ende der Datei etwas wie Folgendes ein
Beende die Datei mit STRG + X und bestätige das Speichern mit Y. Vergiss nicht, die Änderungen mit
source .bash_profilezu aktivieren.Tipp: Du kannst bei Bedarf weitere Umgebungsvariablen setzen. Meist brauchst du sie nicht, aber gut zu wissen. Etwa so:
Hinweis: Du hast nun eine Standard-SparkSession initialisiert. In den meisten Fällen wirst du sie weiter konfigurieren wollen – vor allem bei echten Big-Data-Workloads. Mehr dazu hier.
Daten laden
Dieses Tutorial nutzt den California-Housing-Datensatz. Er erschien 1997 im Paper Sparse Spatial Autoregressions von Pace, R. Kelley und Ronald Barry in den Statistics and Probability Letters. Die Forschenden bauten den Datensatz auf Basis der Volkszählung in Kalifornien von 1990.
Die Daten enthalten eine Zeile pro „Census Block Group“. Eine Block Group ist die kleinste geografische Einheit, für die das U.S. Census Bureau Stichprobendaten veröffentlicht (typischerweise 600 bis 3.000 Personen). In dieser Stichprobe umfasst eine Block Group im Schnitt 1425,5 Personen in einem geografisch kompakten Gebiet. Diese Infos findest du auf dieser Seite oder im oben erwähnten Paper hier.
Die räumlichen Daten umfassen 20.640 Beobachtungen zu Immobilienpreisen mit 9 ökonomischen Variablen:
Zudem wurden alle Block Groups mit Nulleinträgen in unabhängigen und abhängigen Variablen aus den Daten ausgeschlossen.
Der Median house value ist die abhängige Variable und dient in deinem ML-Modell als Zielvariable.
Du kannst die Daten hier herunterladen. Suche den Ordner houses.zip, lade ihn herunter und entpacke ihn, um an die Datenordner zu kommen.
Als Nächstes liest du die Daten mit der Methode
textFile()aus dem heruntergeladenen Ordner als RDDs ein. Diese Methode erwartet eine URI zur Datei (hier der lokale Pfad) und liest sie als Sammlung von Zeilen. Zur Kontrolle lädst du nicht nur die .data-Datei, sondern auch die .domain-Datei mit dem Header. So kannst du die Reihenfolge der Variablen gegenprüfen.Daten erkunden
Du hast bereits viele Informationen von der Webseite mit dem Datensatz gesammelt, aber praktische Inspektionen mit Spark in Python sind unerlässlich.
Wichtig: Da die Ausführung in Spark „lazy“ ist, wurde noch nichts ausgeführt. Deine Daten wurden noch nicht wirklich gelesen. Die Variablen
rddundheadersind bisher nur Konzepte. Du musst Spark „anschubsen“. Schau dir zunächst denheadermitcollect()an:collect()holt das gesamte RDD auf eine Maschine, und du siehst etwa Folgendes:Tipp: Sei vorsichtig mit
collect()! Das kann den Treiber aus dem Speicher laufen lassen.take()ist sicherer, wenn du nur einen Ausschnitt sehen willst. Generell solltest du Ergebnissets begrenzen, so wie bei SQL.Die Reihenfolge der Variablen entspricht der oben beschriebenen, und alle Spalten sind kontinuierlich. Lass Spark mehr arbeiten und sieh dir die California-Housing-Daten an, um das zu bestätigen.
Rufe
take()auf deinem RDD auf:Damit holst du die ersten 2 Elemente des RDD. Wie erwartet: Da du mit
textFile()eingelesen hast, liegen die Zeilen als Strings vor. Die Einträge sind durch Komma getrennt, ebenso die Zeilen:Das musst du aufbereiten. Du musst die Einträge nicht einzeln aufspalten, aber die Zeilen sollten separate Elemente sein. Dafür verwendest du
map()mit einer Lambda-Funktion, die an jedem Komma trennt. Prüfe das Ergebnis wieder mittake():Erinnerung: Lambda-Funktionen sind anonyme Funktionen, die zur Laufzeit erzeugt werden.
Du erhältst Folgendes:
Alternativ kannst du auch Folgendes nutzen:
Wenn du Pandas oder Data Frames in R gewohnt bist, erwartest du einen Header – den gibt es hier nicht. Um dir das Leben zu erleichtern, wechselst du vom RDD zu einem DataFrame. DataFrames sind RDDs vorzuziehen, wann immer möglich. Besonders in Python sind sie performanter.
Was ist der Unterschied?
RDDs nutzt du, wenn du Low-Level-Transformationen und -Actions auf unstrukturierten Daten brauchst, also kein Schema erzwingen oder Spaltennamen verwenden willst. Performancevorteile von DataFrames für (semi-)strukturierte Daten spielen dann weniger eine Rolle. RDDs sind sinnvoll, wenn du lieber mit funktionalen Konstrukten statt domänenspezifischen Ausdrücken arbeitest.
Wir wechseln jetzt zu DataFrames, um High-Level-Ausdrücke, SQL-Abfragen zur weiteren Exploration und spaltenweisen Zugriff zu nutzen.
Los geht’s.
Schritt eins: Erzeuge eine SchemaRDD bzw. ein RDD aus Row-Objekten mit Schema. Ziel ist, wie bei einem DataFrame, Zeilen und Spalten mit Datentypen zu haben.
Verwende erneut
map()mit einer Lambda-Funktion, die jedes Element einer Zeile einem Feld in einer Row zuordnet. Beispiel für die erste Zeile:Die Lambda-Funktion konstruiert eine Row der SchemaRDD: Index 0 heißt „longitude“, und so weiter.
Mit dieser SchemaRDD kannst du das RDD mit
toDF()in ein DataFrame umwandeln.Jetzt kannst du
dfmit Methoden wiefirst(),take(),head()undshow()inspizieren:Das sieht deutlich anders aus als das RDD zuvor:
Tipp:
df.columnsgibt dir die Spaltennamen zurück.Die Daten sind sauber in Spalten organisiert, aber wie sieht es mit den Datentypen aus? Spark versucht beim Einlesen ein Schema zu erkennen – hat das geklappt? Nutze
df.dtypesoderdf.printSchema(), um mehr über die Datentypen zu erfahren.Da du die erste Zeile nicht ausführst, bekommst du Folgendes zurück:
Alle Spalten sind noch vom Typ String … Ernüchternd!
Wenn du mit diesem DataFrame weiterarbeiten willst, solltest du passendere Datentypen vergeben. Das verbessert auch die Performance. Intuitiv könntest du alle Spalten von
dfaufFloatType()casten:Diese Wiederholungen sind jedoch fehleranfällig und nicht schön zu lesen. Warum nicht eine Funktion schreiben, die das sauber für dich erledigt?
Die folgende Funktion nimmt ein DataFrame, Spaltennamen und den neuen Datentyp. Für jeden Spaltennamen castest du die Spalte auf den neuen Typ und gibst das DataFrame zurück:
So wirkt es deutlich aufgeräumter! Prüfe die Datentypen von
dferneut mitprintSchema().Jetzt geht es an die eigentliche Exploration. Spaltenweiser Zugriff und SQL-Abfragen sind zwei große Vorteile von DataFrames. Starte klein und wähle zwei Spalten aus
df, von denen du 10 Zeilen sehen möchtest:Das liefert dir:
Du kannst die Abfragen natürlich komplexer gestalten, z. B. so:
Ergebnis:
Neben Abfragen kannst du auch Zusammenfassungsstatistiken ausgeben. Das hilft dir im nächsten Schritt:
Sieh dir Minimum und Maximum der numerischen Attribute an. Mehrere Spalten haben große Wertebereiche – du solltest deinen Datensatz normalisieren.
Datenvorverarbeitung
Auf Basis deiner kurzen explorativen Analyse weißt du genug, um die Daten für das Modell aufzubereiten.
Zielvariable vorverarbeiten
Beginnen wir mit
medianHouseValue, deiner abhängigen Variable. Um die Arbeit mit den Zielwerten zu erleichtern, drückst du Hauswerte in Einheiten von 100.000 aus. Aus452600.000000wird also4.526:Im Ergebnis von
take()siehst du die korrekt angepassten Werte:Feature Engineering
Nachdem du
medianHouseValueangepasst hast, fügst du zusätzliche Merkmale hinzu:Mit DataFrames nutzt du am besten
select(), um die Spalten auszuwählen, mit denen du arbeitest:totalRooms,householdsundpopulation. Verwende außerdemcol(), um Spaltenausdrücke zu bilden – nur so kannst du elementweise Operationen wie Division durchführen:Für die erste Zeile siehst du z. B. etwa 6,98 Räume pro Haushalt, ca. 2,56 Personen pro Haushalt und einen Schlafzimmeranteil von 0,14:
Als Nächstes – mit Blick auf die Standardisierung – ordnest du die Spalten neu. Da du die Zielvariable nicht standardisieren willst, isolierst du sie im DataFrame.
Das erreichst du über
select()und gibst die Spalten in der gewünschten Reihenfolge an.medianHouseValuekommt zuerst, damit es von der Standardisierung unberührt bleibt.Hinweis: Jetzt ist auch der richtige Zeitpunkt, um Variablen auszuschließen, die du nicht brauchst. In diesem Fall lassen wir z. B.
longitude,latitude,housingMedianAgeundtotalRoomsweg.Standardisierung
Nach der Neuordnung kannst du die Daten normalisieren – fast. Ein Schritt fehlt noch: Trenne Features und Zielvariable. Im Kern bedeutet das, die erste Spalte vom Rest zu isolieren.
Hierfür nutzt du die
map()-Funktion auf dem RDD des DataFrames. Außerdem verwendest duDenseVector(), einen lokalen Vektor, der die Einträge als Double-Array speichert – praktisch, um Wertearrays in PySpark zu halten.Anschließend machst du wieder ein DataFrame aus
input_dataund benennst die Spalten in"label"und"features"um:Jetzt kannst du skalieren. Nutze dafür Spark ML: Die Bibliothek bietet ML-Algorithmen und alles für praxistaugliche ML-Pipelines. In diesem Fall brauchst du keine komplette Pipeline, aber wenn du mehr dazu wissen willst, lies hier.
Die Eingabespalte ist
features, die Ausgabespalte der skalierten Werte inscaled_dfheißt"features_scaled":Ein Blick ins DataFrame zeigt die neue Spalte
features_scaled, die du mitfeaturesvergleichen kannst:Hinweis: Diese Schritte ähneln stark dem Vorgehen in Scikit-Learn.
Ein Machine-Learning-Modell mit Spark ML bauen
Nach der Vorverarbeitung baust du nun dein Lineares Regressionsmodell. Wie gewohnt teilst du zuerst in Trainings- und Testdaten. Mit
randomSplit()ist das kein Problem:Du übergibst eine Liste mit zwei Zahlen für die Größen von Training und Test sowie einen Seed für Reproduzierbarkeit. Mehr dazu im DataCamp-Python Machine Learning Tutorial.
Dann erstellst du das Modell:
Hinweis: Das Argument
elasticNetParamentspricht α undregParam(Regularisierung) entspricht λ. Mehr Infos hier.Mit dem Modell kannst du nun Vorhersagen auf den Testdaten erzeugen: Verwende
transform(), extrahiere per RDD-Operationen Vorhersagen und wahre Labels und führe beides als ListepredictionAndLabelzusammen.Zum Schluss inspizierst du einige Paare einfach per Indexzugriff:
Du siehst in etwa Folgendes (zuerst Vorhersage, dann Ist-Wert):
Das Modell evaluieren
Ein paar Vorhersagen anzuschauen ist gut, Kennzahlen sind besser. Starte mit den Koeffizienten und dem Achsenabschnitt deines Modells:
Ergebnis:
Über das Attribut
summarybekommst du außerdemrootMeanSquaredErrorundr2:Der RMSE misst den Fehler zwischen Vorhersage und beobachtetem Wert. Je kleiner, desto näher liegen Vorhersage und Realität beieinander.
Das R² (Bestimmtheitsmaß) zeigt, wie gut die Daten zur Regressionsgeraden passen. Der Wert liegt zwischen 0 und 1, wobei 0% bedeutet, dass das Modell keine Varianz erklärt, und 100% das Gegenteil. Allgemein gilt: Je höher R², desto besser passt das Modell.
Du erhältst etwa Folgendes:
Da ist definitiv noch Luft nach oben! Wenn du weitermachen möchtest, experimentiere mit den Hyperparametern, den aufgenommenen Variablen im ursprünglichen DataFrame, … Für dieses Tutorial ist hier Schluss!
Bevor du gehst …
Beende die SparkSession mit folgender Zeile:
Big Data weiterdenken
Glückwunsch! Du hast es bis zum Ende geschafft und gelernt, wie du mit Spark ML ein Lineares Regressionsmodell aufbaust.
Wenn du mehr über PySpark lernen willst, starte mit DataCamps Kurs Introduction to PySpark und wirf einen Blick auf das Apache Spark Tutorial: ML with PySpark.