Zum Inhalt springen

Einführung in maschinelles Lernen für Backend-Entwickler

Eine praktische Einführung in die Konzepte des maschinellen Lernens, die Backend-Entwickler brauchen, um ML-Modelle in Produktivanwendungen zu integrieren.

4 Min. Lesezeit
Diagramm, das eine ML-Pipeline von den Daten über das Modell bis zum API-Endpunkt zeigt

Für die Integration von maschinellem Lernen in deine Anwendungen brauchst du keinen Doktortitel. Was du aber brauchst, ist ein Verständnis dafür, was ML-Modelle können und was nicht, wie sie versagen und wie man sie in Produktion betreibt. Die meisten Backend-Entwickler kommen nicht durchs Trainieren von Modellen mit ML in Berührung, sondern durchs Deployen, Überwachen und Warten.

Die Lücke zwischen „das Modell funktioniert im Jupyter-Notebook" und „das Modell funktioniert in Produktion" ist genau dort, wo Backend-Engineering-Fähigkeiten entscheidend werden. Hier kommst du ins Spiel.

ML-Modelle sind einfach nur Funktionen

Entfernt man den Fachjargon, ist ein trainiertes ML-Modell eine Funktion: Eingabedaten gehen hinein, eine Vorhersage kommt heraus. Der Trainingsprozess findet die Parameter, mit denen diese Funktion nützliche Ergebnisse liefert.

pypython
# A trained model is conceptually this simple
def predict_churn(customer: dict) -> float:
    """Returns probability (0-1) that customer will cancel."""
    # Internally: multiply features by learned weights,
    # apply activation functions, return result
    return model.predict(preprocess(customer))
 
# In production, you're wrapping this function in an API
@app.post("/predict/churn")
async def churn_endpoint(customer: CustomerInput):
    probability = predict_churn(customer.dict())
    return {"churn_probability": probability}

Die Komplexität steckt in drei Bereichen: die Daten in das richtige Format bringen, Vorhersagen mit akzeptabler Latenz ausliefern und überwachen, ob das Modell noch funktioniert.

Feature Engineering: die eigentliche Arbeit

Modelle verarbeiten Zahlen, keine Objekte. Das Umwandeln roher Anwendungsdaten in die numerischen Merkmale, die ein Modell erwartet, nennt man Feature Engineering – und das ist meist der anfälligste Teil der Pipeline.

pypython
# ❌ Passing raw data directly — model can't use this
raw_customer = {
    "signup_date": "2020-03-15",
    "plan": "premium",
    "last_login": "2021-01-20",
    "support_tickets": 3,
}
 
# ✅ Engineered features — numeric values the model expects
def extract_features(customer: dict) -> list[float]:
    now = datetime.now()
    signup = datetime.fromisoformat(customer["signup_date"])
    last_login = datetime.fromisoformat(customer["last_login"])
 
    return [
        (now - signup).days,                    # account_age_days
        (now - last_login).days,                # days_since_login
        customer["support_tickets"],            # support_ticket_count
        1.0 if customer["plan"] == "premium" else 0.0,  # is_premium
        customer["support_tickets"] / max((now - signup).days, 1),  # tickets_per_day
    ]

Die entscheidende Regel: Die in Produktion verwendeten Merkmale müssen exakt mit den beim Training verwendeten Merkmalen übereinstimmen, in derselben Reihenfolge und mit denselben Transformationen. Eine Abweichung hier führt zu Vorhersagen, die plausibel aussehen, aber völlig falsch sind.

tstypescript
// Feature pipeline validation
interface FeatureSchema {
  name: string;
  type: 'numeric' | 'categorical';
  range?: [number, number];
  required: boolean;
}
 
const expectedFeatures: FeatureSchema[] = [
  { name: 'account_age_days', type: 'numeric', range: [0, 3650], required: true },
  { name: 'days_since_login', type: 'numeric', range: [0, 365], required: true },
  { name: 'support_tickets', type: 'numeric', range: [0, 100], required: true },
  { name: 'is_premium', type: 'categorical', required: true },
  { name: 'tickets_per_day', type: 'numeric', range: [0, 10], required: true },
];

Muster zum Ausliefern von Modellen

Drei gängige Muster, um ML-Vorhersagen auszuliefern, jedes mit eigenen Kompromissen.

Synchrone API

pypython
# Direct inference — model loaded in the API process
from fastapi import FastAPI
import joblib
 
app = FastAPI()
model = joblib.load("models/churn_v3.pkl")
 
@app.post("/predict")
async def predict(features: FeatureInput):
    prediction = model.predict([features.to_array()])
    return {"prediction": float(prediction[0])}

Einfach, aber das Modell belegt in jedem API-Worker-Prozess Arbeitsspeicher, und die Inferenzlatenz schlägt sich direkt in der Antwortzeit nieder.

Asynchron mit Warteschlange

pypython
# Decouple prediction from request/response
import aio_pika
 
async def request_prediction(customer_id: str, features: list[float]):
    message = aio_pika.Message(
        body=json.dumps({
            "customer_id": customer_id,
            "features": features,
            "callback_url": "/webhooks/prediction-complete",
        }).encode()
    )
    await channel.default_exchange.publish(
        message,
        routing_key="predictions",
    )
 
# Worker process — separate from API
async def prediction_worker(message):
    data = json.loads(message.body)
    prediction = model.predict([data["features"]])
    await notify_callback(data["callback_url"], {
        "customer_id": data["customer_id"],
        "prediction": float(prediction[0]),
    })

Besser geeignet für teure Modelle (Inferenz über 100 ms) oder Batch-Vorhersagen. Die API bleibt reaktionsfähig, während die Vorhersage asynchron abläuft.

Vorab berechnete Vorhersagen

sqlsql
-- Batch job runs nightly, stores predictions in database
INSERT INTO churn_predictions (customer_id, probability, computed_at)
SELECT
  c.id,
  predict_churn(c.features),
  NOW()
FROM customers c
ON CONFLICT (customer_id)
DO UPDATE SET probability = EXCLUDED.probability,
              computed_at = EXCLUDED.computed_at;
tstypescript
// API reads pre-computed predictions — zero inference latency
async function getChurnRisk(customerId: string): Promise<number> {
  const result = await db.query(
    'SELECT probability FROM churn_predictions WHERE customer_id = $1',
    [customerId]
  );
  return result?.probability ?? 0.5; // default for new customers
}

Das funktioniert, wenn Vorhersagen nicht in Echtzeit vorliegen müssen. Die meisten Empfehlungssysteme, Risiko-Scores und Personalisierungsfunktionen verwenden vorab berechnete Vorhersagen.

Modell-Monitoring

Ein Modell, das beim Training zu 95 % genau war, kann in Produktion ohne jede Codeänderung auf 60 % abfallen. Das passiert, wenn sich die Verteilung der realen Daten von der Verteilung der Trainingsdaten entfernt.

tstypescript
interface PredictionLog {
  modelVersion: string;
  inputFeatures: Record<string, number>;
  prediction: number;
  confidence: number;
  latencyMs: number;
  timestamp: Date;
}
 
async function logPrediction(log: PredictionLog): Promise<void> {
  // Store for monitoring and analysis
  await db.insert('prediction_logs', log);
 
  // Alert on anomalies
  if (log.confidence < 0.3) {
    await alerting.warn('low-confidence-prediction', {
      model: log.modelVersion,
      confidence: log.confidence,
    });
  }
 
  if (log.latencyMs > 200) {
    await alerting.warn('slow-prediction', {
      model: log.modelVersion,
      latency: log.latencyMs,
    });
  }
}

Überwache drei Dinge:

  1. Verteilung der Vorhersagen – wenn sich die Ausgabeverteilung verschiebt, sieht das Modell möglicherweise Daten, mit denen es nicht trainiert wurde
  2. Verteilung der Merkmale – wenn Eingabemerkmale von den Trainingsbereichen abweichen, werden Vorhersagen unzuverlässig
  3. Latenz – die Inferenzzeit des Modells kann bei bestimmten Eingabeformen stark ansteigen

A/B-Tests für Modelle

Beim Ausrollen einer neuen Modellversion muss man sie mit echtem Traffic gegen die aktuelle Version vergleichen.

tstypescript
function selectModel(userId: string): ModelVersion {
  // Deterministic assignment based on user ID
  const hash = hashCode(userId) % 100;
 
  if (hash < 10) {
    return 'v4-candidate'; // 10% traffic
  }
  return 'v3-production'; // 90% traffic
}
 
async function predict(userId: string, features: number[]): Promise<Prediction> {
  const version = selectModel(userId);
  const model = models.get(version);
  const result = model.predict(features);
 
  await logPrediction({
    modelVersion: version,
    prediction: result,
    userId,
  });
 
  return result;
}

Vergleiche das Kandidatenmodell anhand von Geschäftskennzahlen (Konversionsrate, Umsatzwirkung) mit dem Produktionsmodell, nicht nur anhand von Genauigkeitswerten. Ein Modell mit 2 % geringerer Genauigkeit, aber 10 % höherer Konversionsrate ist die bessere Wahl.

Die wichtigsten Erkenntnisse

  1. ML-Modelle sind Funktionen – Daten gehen hinein, eine Vorhersage kommt heraus. Deine Aufgabe ist es, diese Funktion zuverlässig auszuliefern.
  2. Merkmalsparität ist entscheidend – Produktionsmerkmale müssen exakt mit den Trainingsmerkmalen übereinstimmen, sonst sind die Vorhersagen bedeutungslos
  3. Wähle das Ausliefermuster passend zu deinen Latenzanforderungen – synchrone API für Echtzeit, Warteschlange für teure Modelle, vorab berechnet für Batch-Verarbeitung
  4. Überwache Vorhersagen, nicht nur die Infrastruktur – die Modellqualität verschlechtert sich ohne Codeänderungen, wenn sich die Datenverteilung ändert
  5. Führe A/B-Tests anhand von Geschäftskennzahlen durch – Genauigkeit allein sagt dir nicht, ob das Modell die Ergebnisse verbessert
Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX