English version

← Learning Hub Lektion 6 Dauer: ca. 15 Min. Thema: ML-Features

Feature Engineering & Feature Store

Diese Lektion zeigt, wie aus Silver-Zeitreihen ML-taugliche Features werden. Am Ende weißt du, welche Features für die Windleistungsprognose zählen, wie der Databricks Feature Store funktioniert und wie du Feature-Pipelines entwirfst, die Training und Inferenz konsistent bedienen.

Von Zeitreihen zu Features

ML-Modelle verarbeiten keine rohen Zeitreihen. Sie brauchen Features — abgeleitete, strukturierte Eingaben, die zeitliche Muster, Kontext und Fachwissen kodieren. Der Silver Layer liefert dir saubere Zeitreihen; das Feature Engineering macht daraus das, worauf das Modell tatsächlich trainiert.

Die Herausforderung beim Feature Engineering

Dieselben Features müssen in zwei Kontexten identisch berechnet werden: (1) beim Offline-Training auf historischen Daten und (2) bei der Online- oder Batch-Inferenz auf frischen Prognosen. Berechnen Training und Inferenz die Features unterschiedlich, entsteht Training-Serving-Skew — die häufigste Ursache dafür, dass ML-Modelle in der Produktion abbauen.

Zentrale Features für die Windleistungsprognose

Feature-KategorieBeispieleWarum es zählt
Wetter (aktuelle Prognose) wind_speed_hub_ms, wind_direction_deg, temperature_c, pressure_hpa Direkte physikalische Treiber der Leistungsabgabe
Zeitlicher Kontext hour_of_day, day_of_year, is_daylight Erfasst Tages- und Jahreszeitmuster
Verzögerte Werte wind_speed_1h_ago, wind_speed_3h_ago, power_1h_ago Jüngster Trend und Momentum
Gleitende Statistiken wind_speed_mean_6h, wind_speed_std_6h, wind_ramp_rate Variabilität und Rampenereignisse
Prognose-Metadaten lead_time_hours, source_model Das Modell weiß, dass die Güte mit der Vorlaufzeit sinkt
Anlagenkontext capacity_kw, hub_height_m, turbine_age_years Normalisiert Vorhersagen über unterschiedliche Hardware hinweg

Feature-Berechnung in PySpark

from pyspark.sql import functions as F
from pyspark.sql.window import Window

def compute_features(weather_ts, power_ts, dim_assets):
    """
    ML-Features aus den Silver-Zeitreihen berechnen.
    Liefert eine Feature-Tabelle: eine Zeile je (asset_id, timestamp).
    """
    # Fenster für verzögerte und gleitende Features, je Anlage nach Zeit sortiert
    w = Window.partitionBy("asset_id").orderBy("timestamp")
    w_6h = w.rowsBetween(-6, -1)   # 6 Zeilen zurück (Stundendaten = 6 Stunden)
    w_24h = w.rowsBetween(-24, -1)

    # Basis: Wetter-Zeitreihe (jeweils neueste Prognoseversion)
    features = weather_ts.filter(F.col("version_rank") == 1) \
        .withColumn("hour_of_day", F.hour("timestamp")) \
        .withColumn("day_of_year", F.dayofyear("timestamp")) \
        .withColumn("month", F.month("timestamp")) \
        \
        # Verzögerte Wetterwerte
        .withColumn("wind_speed_1h_ago", F.lag("wind_speed_hub_ms", 1).over(w)) \
        .withColumn("wind_speed_3h_ago", F.lag("wind_speed_hub_ms", 3).over(w)) \
        .withColumn("wind_speed_6h_ago", F.lag("wind_speed_hub_ms", 6).over(w)) \
        \
        # Gleitende Statistiken
        .withColumn("wind_speed_mean_6h", F.avg("wind_speed_hub_ms").over(w_6h)) \
        .withColumn("wind_speed_std_6h", F.stddev("wind_speed_hub_ms").over(w_6h)) \
        .withColumn("wind_speed_max_24h", F.max("wind_speed_hub_ms").over(w_24h)) \
        \
        # Windrampe: Änderung über die letzten 3 Stunden
        .withColumn("wind_ramp_3h",
            F.col("wind_speed_hub_ms") - F.lag("wind_speed_hub_ms", 3).over(w)
        ) \
        \
        # Prognose-Metadaten
        .withColumn("lead_time_hours",
            (F.col("timestamp").cast("long") - F.col("forecast_ref_time").cast("long")) / 3600
        )

    # Anlagenkontext joinen (statische Features)
    features = features.join(
        dim_assets.select(
            "asset_id", "capacity_kw", "hub_height_m",
            "parent_asset_id", "asset_type"
        ),
        on="asset_id",
        how="left"
    )

    # Jüngste Ist-Leistung joinen (verzögerte Istwerte — nur fürs Training)
    if power_ts is not None:
        power_lagged = power_ts.select(
            "asset_id", "timestamp",
            F.lag("power_kw", 1).over(w).alias("power_1h_ago"),
            F.lag("power_kw", 3).over(w).alias("power_3h_ago"),
        )
        features = features.join(power_lagged, on=["asset_id", "timestamp"], how="left")

    return features

Der Databricks Feature Store

Der Feature Store ist eine verwaltete Schicht im Unity Catalog, die Training-Serving-Skew löst. Features werden als Delta-Tabellen mit speziellen Metadaten abgelegt, die während der Inferenz einen automatischen Lookup ermöglichen.[1]

Feature Store — eine Quelle für Training & Inferenz Feature-Tabelle prod.features.wind_power_features Schlüssel: asset_id, timestamp Training-Job Historischer Lookup Inferenz-Job Lookup der neuesten Werte Feature-Pipeline (schreibt)

Eine Feature-Tabelle anlegen

from databricks.feature_engineering import FeatureEngineeringClient

fe = FeatureEngineeringClient()

# Feature-Tabelle im Unity Catalog anlegen (oder aktualisieren)
fe.create_table(
    name="prod.features.wind_power_features",
    primary_keys=["asset_id", "timestamp"],
    timestamp_keys=["timestamp"],  # ermöglicht Point-in-Time-Lookups
    df=features_df,
    description="ML features for wind power prediction. Updated hourly."
)

# Spätere Schreibvorgänge: write_table zum Anhängen/Mergen neuer Features
fe.write_table(
    name="prod.features.wind_power_features",
    df=new_features_df,
    mode="merge"  # Upsert auf die Primärschlüssel
)

Point-in-Time-Korrektheit

Beim Training auf historischen Daten brauchst du die Features so, wie sie zum Vorhersagezeitpunkt aussahen — nicht die heutigen Werte. Der Parameter timestamp_keys ermöglicht das:

# Training: Features zum Zeitstempel jedes Trainingsbeispiels nachschlagen
training_set = fe.create_training_set(
    df=labels_df,  # enthält die Spalten: asset_id, timestamp, actual_power_kw
    feature_lookups=[
        FeatureLookup(
            table_name="prod.features.wind_power_features",
            lookup_key=["asset_id", "timestamp"],
            # Point-in-Time: liefert die zu diesem Zeitstempel gültigen Features
            # Keine Features aus der Zukunft nach dem Vorhersagezeitpunkt
        )
    ],
    label="actual_power_kw"
)

training_df = training_set.load_df()
Point-in-Time-Joins verhindern Datenleckage

Ohne Point-in-Time-Korrektheit können deine Trainingsdaten Features enthalten, die aus Daten berechnet wurden, welche zum Vorhersagezeitpunkt noch gar nicht verfügbar waren (z. B. die 12Z-Prognose, um etwas um 06Z zu "prognostizieren"). Der Feature Store regelt das automatisch, sobald timestamp_keys gesetzt ist.

Architektur der Feature-Pipeline

Wo genau sitzt die Feature-Berechnung in deinem Workflow?

# In deinem Databricks Workflow:
# Task 1: Wetter ingesten → Bronze
# Task 2: Bronze → Silver (Geo-Join, Ableitungen)
# Task 3: Silver → Features  ← DIESE LEKTION
# Task 4: Features → ML-Inferenz → Gold

# Task 3 läuft, nachdem Silver aktualisiert wurde:
features_df = compute_features(
    weather_ts=spark.read.table("prod.silver.weather_ts_latest"),
    power_ts=spark.read.table("prod.silver.power_ts"),
    dim_assets=spark.read.table("prod.silver.dim_assets")
)

# In den Feature Store schreiben
fe.write_table(
    name="prod.features.wind_power_features",
    df=features_df,
    mode="merge"
)

Feature-Aktualität und das Dual-Access-Muster

Features bedienen zwei Zugriffsmuster (wie schon Silver):

KonsumentZugriffsmusterAnspruch an Aktualität
Inferenz-Pipeline (Produktion) Neueste Features für die kommenden Zeitstempel Aktualisiert binnen Minuten nach Eintreffen neuer Wetterdaten
Trainings-Pipeline (Batch) Historische Features mit Point-in-Time-Korrektheit Backfill darf nachhängen; Korrektheit zählt mehr als Aktualität
Data Scientist (ad hoc) Feature-Verteilungen erkunden, auf Drift prüfen Liest dieselbe Tabelle; leichter Verzug ist akzeptabel

Feature-Monitoring

Features driften mit der Zeit (Jahreszeiten wechseln, Turbinen altern). Überwache:

# Einfache Drift-Prüfung: aktuelle Feature-Statistiken gegen die Trainings-Baseline
from pyspark.sql import functions as F

current_stats = spark.read.table("prod.features.wind_power_features") \
    .filter(F.col("timestamp") > F.current_timestamp() - F.expr("INTERVAL 7 DAYS")) \
    .agg(
        F.mean("wind_speed_hub_ms").alias("mean_wind"),
        F.stddev("wind_speed_hub_ms").alias("std_wind"),
        F.mean("lead_time_hours").alias("mean_lead"),
        (F.sum(F.when(F.col("wind_speed_1h_ago").isNull(), 1)) / F.count("*")).alias("null_rate")
    )
current_stats.show()

Verständnis prüfen

Warum sind verzögerte Features (wind_speed_1h_ago) riskant, wenn man sie beim Training nicht sorgfältig behandelt?

Richtig. Ohne Point-in-Time-Joins nutzt das Training womöglich "verzögerte" Werte, die relativ zum Vorhersagezeitstempel eigentlich aus der Zukunft stammen. Die timestamp_keys des Feature Store verhindern das, indem Lookups nur Features zurückgeben, die zum Zielzeitpunkt oder davor verfügbar waren.
Das Hauptrisiko ist Datenleckage. Fehlen Point-in-Time-Joins, können "verzögerte" Features im Training Daten von nach dem Vorhersagezeitpunkt verwenden — das Modell lernt Muster, die es in der Produktion nicht nutzen kann.

Was ist der wichtigste Vorteil des Databricks Feature Store gegenüber einer normalen Delta-Tabelle für Features?

Richtig. Der Hauptwert des Feature Store liegt darin, Training-Serving-Skew zu beseitigen. Features werden einmal definiert, einmal gespeichert und von Trainings-Jobs (historisch) wie Inferenz-Jobs (aktuell) identisch nachgeschlagen. Gleicher Codepfad, gleiche Tabelle, garantierte Konsistenz.
Der Kernvorteil ist Konsistenz. Der Feature Store stellt sicher, dass Training und Inferenz dieselben Features auf dieselbe Weise berechnet nutzen. Darunter liegt eine ganz normale Delta-Tabelle — der Wert steckt in den Metadaten und der Lookup-API, die Training-Serving-Skew verhindern.

Du ergänzt die Feature-Tabelle um ein neues Feature (nacelle_temperature_rolling_12h). Was muss passieren, bevor das Produktionsmodell es nutzen kann?

Richtig. Das Modell wurde ohne dieses Feature trainiert — es weiß gar nicht, dass es existiert. Du musst auf Daten mit dem neuen Feature neu trainieren, prüfen, ob sich die Güte verbessert, die neue Modellversion in MLflow registrieren und sie dann in die Produktion überführen. Der Feature Store verdrahtet neue Features nicht automatisch mit bestehenden Modellen.
Neue Features erfordern ein erneutes Training. Das bestehende Modell wurde ohne nacelle_temperature_rolling_12h trainiert und wird es nicht nutzen. Du musst neu trainieren, die Verbesserung validieren und die neue Modellversion ausrollen.

Primärquelle

📖 Empfohlene Lektüre

Feature engineering and serving with Unity Catalog — Databricks-Dokumentation. Behandelt das Anlegen von Feature-Tabellen, Point-in-Time-Lookups und die FeatureEngineeringClient-API. Für deinen Anwendungsfall ist der Abschnitt "Time series feature table" besonders relevant.

Wie es weitergeht

Die Features stehen. Die nächste Lektion behandelt die ML-Pipeline mit MLflow — Modelle für die Leistungsprognose trainieren, Experimente nachverfolgen, die Model Registry nutzen und das Champion-Modell für Batch-Inferenz ausrollen.


Fragen zur Feature-Auswahl bei Wind vs. Solar, zum Umgang mit fehlenden Werten in verzögerten Features oder zur Performance des Feature Store? Frag deinen Lehrer.

Rechtliches: Impressum