Immer mehr geschäftliche Entscheidungen werden auf Basis automatisch generierter Daten gewonnen. Nur die Korrektheit des Quellcodes der Transformationen zu testen, reicht nicht mehr aus. Die Korrektheit der Daten, die typischerweise aus unterschiedlichsten, sich schnell ändernden Quellen zusammengeführt werden, muss ebenfalls geprüft werden. Schlimmstenfalls droht eine Fehlentscheidung aufgrund mangelnder Datenqualität. Das ist nicht unbedingt eine neue Erkenntnis, die Toolunterstützung für Datenqualitätsprüfungen im Open-Source-Umfeld war allerdings bisher wenig zufriedenstellend. Dieser Blog zeigt eine Methodik und ein Python-Tool zu sogenannten Pipeline Tests, die genau diese Lücke füllen.
Was ist Pipeline Debt?
Dass Daten als Rohstoff in jedem Unternehmensbereich eine große Bedeutung zukommt, ist bereits mehr als bekannt. Data-Warehouse-Strukturen werden aus allen Unternehmensbereichen mit Daten, von Sensoren bis Planzahlen, gefüttert. Ein Machine-Learning-Modell arbeitet auf Basis der Daten, die es im Trainingsprozess gesehen hat. Daten, die sehr von allem, was das Modell jemals gesehen hat, weit entfernt sind, können zu falschem Verhalten führen. Durch fehlerhafte Quellen oder Verarbeitungsschritte verfälschte Daten werden mit Sicherheit zu falschen Ergebnissen führen. Von der Rohdatenquelle bis zum Dashboard durchläuft ein Datensatz normalerweise mehrere Prozesse (Transformationen, Ladeprozesse, Machine Learning Modelle, meist über mehrere Datenmodelle hinweg). Oft wird das Monitoring der Datenqualität dabei vernachlässigt. Im Folgenden sehen wir uns ein neues Open Source Tool namens Great Expectations genauer an. Es validiert Daten, bevor ein Machine-Learning-Modell ausgeführt oder die Zahlen anderweitig genutzt werden.
Data Scientists kennen das Problem: Wir haben ein Dashboard mit Kennzahlen, die sich aus verschiedenen Datenquellen ableiten. Hinter manchen der Kennzahlen verbirgt sich ein Machine-Learning-Modell, z.B. ein Scoring. Plötzlich ändert sich eine Kennzahl so drastisch, dass sie keinen Sinn mehr ergibt oder die Kennzahl wird NaN, weil sich der Wert nicht mehr berechnen lässt.
Warum brauche ich Pipeline Tests?
Im Bereich der Software Entwicklung ist Test Driven Development mittlerweile ein Standard-Vorgehen. Unit Tests zu schreiben, gehört zum guten Stil und hilft, die Code Qualität hochzuhalten und Fehler zu vermeiden.
Mittlerweile liegt die Komplexität aber oft nicht mehr nur im Code selbst, sondern auch in den Daten. Wenn es bei der Ausführung eines Machine-Learning-Modells zu unerwartetem Verhalten kommt, kann es sein, dass tatsächlich eine ungewöhnliche Situation aufgetreten ist, die die Daten nur widerspiegeln. Oder aber die Daten selbst sind fehlerhaft. Oft ist eine nicht kommunizierte Änderung im Datenmodell des Quellsystems die Ursache.
Ein Machine-Learning-Modell besteht mittlerweile selbst aus einer Pipeline mit mehreren Transformationsschritten:
- Laden der Daten (aus Rohdaten, aus einem Data Warehouse oder einer anderen Datenquelle)
- Cleaning und Preparation
- Aggregationen
- Bilden von Features
- Dimensionality Reduction
- Normalisierung oder Skalierung der Daten
Die Notwendigkeit eines Tools zur Überprüfung der Datenqualität bei all diesen Schritten ist offensichtlich. Im Gegensatz zu Unit Tests, die ausführbaren Code prüfen (meist zur compile oder build/deploy Zeit), nennt man diese Datentests pipeline tests, welche Batch-artig ausgeführt werden.
Great Expectations zur Rettung!
Im Machine-Learning-Bereich hat sich Python als Entwicklungssprache durchgesetzt. Das Tool Great Expectations ist ein Python-Paket und lässt sich über pip oder conda installieren:
pip install great-expectations
conda install conda-forge::great-expectations
Das Tool ist sehr abstrakt und generisch aufgebaut. Dies mag eine etwas höhere Einstiegshürde darstellen; was sich aber relativiert, sobald man sich mit dem ersten Anwendungsfall beschäftigt. Die Entwickler von Great Expectations haben ein Discus Forum eingerichtet, in denen anschauliche Einstiegsbeispiele dargestellt werden. Es gibt auch einen Slack Channel zur Diskussion.
Prinzipiell beginnt jedes Projekt nach der Installation des Python Pakets mit:
great-expectations init
Dieser Befehl legt den Grundordner eines Great Expectations (GE) Projekts an (im GE Jargon auch DataContext genannt) und kann auch gleich von einem
git init
gefolgt werden, um das Projekt mit git zu versionieren.
Exemplarische Ordnerstruktur, die Great Expectations nach einem Initialisierungsaufruf anlegt.
Im Initialisierungsprozess wird man auch gleich nach den Zugängen zu den Datenquellen gefragt. Diesen Schritt kann man auch überspringen und manuell durchführen. Die möglichen Datenquellen sind vielfältig, vom lokalen CSV, über SQLAlchemy-Konnektoren für relationale Datenbanken bis hin zu Spark und Hadoop. Ich habe zum Beispiel ein partitioniertes Parquetfile auf HDFS erfolgreich eingebunden.
Die grundlegenden Begriffe von Great Expectations sind Expectations und Validations. Wenn das Projekt einmal initial (das entspricht einem DataContext) angelegt ist und die Datenquellen eingetragen sind, kann man sofort loslegen, einen Batch (Ausschnitt aus den Daten) laden und Expectations definieren. Einige exemplarische Expectations:
- Spalte "ID" darf nicht Null sein
- Spalte "ID" muss monoton steigen
- Spalte "ID" muss ein Integer sein
- Spalte "A" muss immer größer als Spalte "B" sein
- Spalte "B" muss zwischen 0 und 1000 liegen
- ....
Es steht eine große Anzahl von Vorlagen für Expectations zur Verfügung, unter anderem auch statistische (Verteilungs-)Tests. Um Expectations explorativ in einem neuen Datensatz zu entwickeln, stehen im Ordner great_expectations/notebooks/ beispielhafte Notebooks für die verschiedenen Konnektoren (csv, SQL, pySpark) mit Beispielen zur Verfügung.
Hat man einmal einen Satz an Expectations definiert, geht es darum, die Expectations mit einem neuen Batch der Daten (zum Beispiel nach dem Eintreffen neuer Daten des letzten Tages) zu validieren.
Bei der Validierung ist es auch möglich, Expectation Variablen zu parametrisieren. Angenommen, man hat eine Streaming-Datenquelle mit Zeitspalte mit unixtime Werten. Die Daten werden persistiert und einmal am Tag von einem Batch des vorangegangenen Tages validiert. Dass die Zeitstempel nun innerhalb des Tages liegen, kann man einfach validieren, indem man beim Setzen der Expectations
batch.expect_column_values_to_be_between('timestamp', {'$PARAMETER': 'start_time'}, {'$PARAMETER': 'end_time'})
und beim Validieren
batch.validate(evaluation_parameters={'start_time': unixtime_start, 'end_time': unixtime_end})
angibt.
Ein großer Vorteil an diesem Tool sind die DataDocs, die angelegt und im Verlauf von Pipeline-Tests gepflegt werden. Dort wird ein Katalog der vom Great-Expectations-Projekt verfolgten Datenquellen, Expectations und Validations in einem übersichtlichen Webseiten-Format abgelegt und versioniert (ebenfalls im Ordner great_expectations/uncommitted/data_docs). So kann ich mich jederzeit informieren, welche Datenquellen im Monitoring sind, welche Tests (Expectations) definiert sind, und ob die Tests positiv waren oder fehlgeschlagen sind.
Konfigurationsschlüssel, die man nicht ins GitHub einchecken möchte, kann man einfach in eine yml-Datei im uncommited Ordner ablegen, sodass Passwörter und Zugangsschlüssel sicher verwahrt bleiben.
Lifecycle und Versionierung eines DataContexts
Hat man sich einen Satz an Expectations zugelegt, mit denen man zukünftig seine Datenquelle validieren möchte, speichert man seine Expectation Suite ab. Diese wird dann in Form einer JSON Datei im Projektordner abgelegt. Jetzt ist wieder ein guter Zeitpunkt, ein git commit auszulösen, um die Expectation Suite versioniert abzulegen.
Great Expectations versteht sich nicht als Pipeline-Automatisierungstool (Pipeline Execution), lässt sich aber in ein solches integrieren (Airflow, Oozie, ...), um die Validierungen nach einem zeitlichen Muster, z.B. täglich, durchzuführen.
Hat man das Muster einmal verstanden, nach dem Great Expectations vorgeht, kann man das Tool sehr leicht ausweiten. Entweder möchte man zusätzliche Expectations definieren oder man möchte eine neue Datenquelle einbinden. Sinnvoll ist es auch, dieselbe Datenquelle auf ihrem Weg durch ihre Aufbereitungspipeline mehrfach zu monitoren, um ggfs. einen Zwischenschritt zu identifizieren, der die Datenqualität beeinträchtigt oder Prüfungen durchzuführen, die erst nach komplexeren Transformationen sinnvoll und möglich sind.
Ein End-to-end-Beispiel eines Beladungsprozesses einer Datenbank mit einer Datenquelle kann man sich in einem GitHub Repository zu Gemüte führen. Dort wird erst ein täglich neu generiertes CSV als Rohdaten validiert und dann nach einem Beladeprozess in eine MySQL Datenbank ein weiteres Mal validiert.
Fazit
Great Expectations ist ein Tool, um die Datenqualität für den Betrieb einer Ladestrecke oder eines Machine-Learning-Modells abzusichern. Dabei ist es den Entwicklern des Tools besonders wichtig, das Framework möglichst generisch zu halten und den Benutzern viele Schnittstellen anzubieten, mit denen sie Great Expectations an ihr eigenes Projekt anpassen und für eigene Bedürfnisse erweitern können. Beispielsweise steht ein Action Operator zur Verfügung, der nach einer Validierung eine automatische Slack-Benachrichtigung erzeugen kann. Dieses oder andere Templates können benutzt werden, um Schnittstellen zu anderen Systemen zu entwerfen.
Die Entwicklungsgeschwindigkeit und die Tatsache, dass dieses Tool von Größen wie Databricks auf Konferenzen beworben wird, lässt auf die Ernsthaftigkeit und Qualität dieses Tools schließen. Gleichzeitig ist der Bedarf groß, das alte Thema Datenqualitätsprüfung mit Hilfe eines modernen Tools in die Welt moderner Datenquellen und Entwicklungsansätze zu transportieren. Es lohnt sich also, Great Expectations kennenzulernen und in die eigenen Datenpipelines zu integrieren - das Tool wird uns sicher noch länger begleiten.