Dotika MLOps · Bloc 5 — La mémoireBlock 5 — The memory
▶ Slides▶ Slides ← Sommaire← Contents
Bloc 5 · 4 h · théorie 0h30 · lab 3h30Block 5 · 4 h · theory 0h30 · lab 3h30

Le carnet
de bord
The ship's
logbook

Le système est en boîte — et amnésique. On lui donne une mémoire : chaque expérience tracée, chaque modèle versionné, et une API qui sert toujours la bonne version — en le prouvant. The system is boxed — and amnesic. We give it a memory: every experiment traced, every model versioned, and an API that always serves the right version — provably.

Où on en estWhere we stand

En boîte, portable — et amnésiqueBoxed, portable — and amnesic

Bloc 4 : le système vit dans une image Docker, identique partout. Mais demandez-lui d'où vient son modèle : silence. Combien d'essais avant celui-là ? Quels réglages ? Quel score ? Rien, nulle part, ne s'en souvient. Block 4: the system lives in a Docker image, identical everywhere. But ask it where its model came from: silence. How many attempts before this one? Which settings? Which score? Nothing, anywhere, remembers.

Aujourd'huiToday

Le carnet de bord apparaîtThe logbook appears

MLflow se branche sous le cerveau : chaque entraînement écrit sa page tout seul — paramètres, métriques, artefacts, commit. Le tableau markdown du bloc 2 est mort ; l'expérience laisse une trace, à chaque fois, sans discipline requise. MLflow gets wired under the brain: every training run writes its own page — parameters, metrics, artifacts, commit. Block 2's markdown table is dead; every experiment leaves a trace, every time, no discipline required.

Et surtout…And above all…

v3 · prod — servi depuis le registrev3 · prod — served from the registry

Regardez la puce de version sur le cerveau : l'API ne charge plus un fichier balladeur, elle demande au registre « la version en Production ». Promotion tracée, rollback en trente secondes — et l'auditeur a sa réponse. Look at the version chip on the brain: the API no longer loads a wandering file, it asks the registry for "the version in Production". Traced promotion, thirty-second rollback — and the auditor gets an answer.

Acte 1 · l'amnésieAct 1 · amnesia

Pas écrit, pas arrivéNot written, never happened

Mardi, bloc 2 : huit modèles, des scores griffonnés, model_final_v2_VRAI_celui_la.pkl. Maintenant l'histoire qui fait mal : le 3 mars, le régulateur demande « quel modèle a bloqué cette transaction — entraîné sur quelles données, avec quels réglages ? » Dans la salle : silence. Le modèle a changé quatre fois depuis. Tuesday, block 2: eight models, scribbled scores, model_final_v2_REAL_this_one.pkl. Now the story that stings: on March 3rd, the regulator asks "which model blocked this transaction — trained on which data, with which settings?" In the room: silence. The model has changed four times since.

INGRÉDIENT 1INGREDIENT 1
Le codeThe code
La version exacte du script — un commit Git, pas « à peu près la branche main ».The exact script version — a Git commit, not "roughly the main branch".
INGRÉDIENT 2INGREDIENT 2
Les donnéesThe data
Quel dataset, quelle version, quel split. Mêmes réglages, autres données : autre modèle.Which dataset, which version, which split. Same settings, different data: different model.
INGRÉDIENT 3INGREDIENT 3
Les réglagesThe settings
Hyperparamètres, features, seuil — tout ce que vous avez choisi à la main au bloc 2.Hyperparameters, features, threshold — everything you hand-picked in block 2.
INGRÉDIENT 4INGREDIENT 4
L'environnementThe environment
Bibliothèques et versions — figées au bloc 4 avec Docker. Déjà sous contrôle.Libraries and versions — frozen in block 4 with Docker. Already under control.
La règle du blocThe block's rule

Changez un seul des quatre ingrédients : ce n'est plus le même modèle — et un .pkl seul ne raconte aucun des quatre. Une expérience non enregistrée n'a jamais eu lieu, et la mémoire humaine ne compte pas : la mémoire doit être automatique. C'est le travail de MLflow. Change any one of the four ingredients: it's no longer the same model — and a .pkl alone tells you about none of the four. An unrecorded experiment never happened, and human memory doesn't count: memory must be automatic. That's MLflow's job.

Acte 2 · le trackingAct 2 · tracking

Le carnet de labo qui s'écrit tout seulThe lab notebook that writes itself

Les chimistes le font depuis deux siècles : une manipulation, une page. MLflow fait pareil pour vos modèles, sans stylo — quatre lignes dans votre train.py, et chaque entraînement écrit sa page : un run = paramètres + métriques + artefacts + commit Git. Chemists have done it for two centuries: one experiment, one page. MLflow does the same for your models, without a pen — four lines in your train.py, and every training run writes its page: a run = parameters + metrics + artifacts + Git commit.

mlflow.start_run()
log_param, log_metric, log_model : le coût d'entrée tient en quatre lignes. Le carnet vit dans mlruns/, se lit dans le navigateur (mlflow ui) — aucune infra pour démarrer.log_param, log_metric, log_model: the entry cost is four lines. The logbook lives in mlruns/, reads in the browser (mlflow ui) — no infrastructure to start.
Trois motsThree words
Un run est une page, une expérience est un classeur, le tracking server est la bibliothèque de l'équipe. Aujourd'hui sur votre machine ; en entreprise, un serveur partagé.A run is a page, an experiment is a binder, the tracking server is the team's library. Today on your machine; in a company, a shared server.
L'UI : trier, filtrer, comparerThe UI: sort, filter, compare
Trier par PR-AUC, filtrer par paramètre, comparer trois runs côte à côte : « qu'est-ce qui a fait gagner le run 7 ? » a enfin une réponse visuelle. Essayez ci-dessous.Sort by PR-AUC, filter by parameter, compare three runs side by side: "what made run 7 win?" finally has a visual answer. Try it below.
L'expérience · fraud-detectorThe experiment · fraud-detector
Le registre · fraud-detectorThe registry · fraud-detector
La discipline du carnetThe logbook's discipline

Toujours : params, métriques, modèle, commit. Souvent : courbe PR, matrice, csv des pires erreurs. Jamais : les données brutes des clients — le carnet trace un pointeur et un hash, pas les transactions. Prouver sans exposer. Always: params, metrics, model, commit. Often: PR curve, matrix, worst-errors csv. Never: raw customer data — the logbook records a pointer and a hash, not the transactions. Prove without exposing.

Acte 3 · au-delà de la moyenne — théorie 0h30Act 3 · beyond the average — theory 0h30

Bon en moyenne, mauvais là où ça coûteGood on average, bad where it costs

La PR-AUC globale résume 57 000 transactions en un chiffre. Mais la banque ne perd pas d'argent « en moyenne » — elle le perd sur des segments précis. La règle d'honnêteté : segments définis avant de regarder les résultats, les mêmes pour tous les modèles. The global PR-AUC compresses 57,000 transactions into one number. But the bank doesn't lose money "on average" — it loses it on specific segments. The honesty rule: segments defined before looking at results, the same for every model.

SEGMENT 1
Par montantBy amount
Rater 4 800 € ou 12 €, ce n'est pas la même perte. Le recall sur gros montants parle au métier.Missing €4,800 or €12 is not the same loss. Large-amount recall speaks the business's language.
SEGMENT 2
Par heureBy hour
La nuit : moins de transactions, plus de fraudes, moins d'analystes éveillés.At night: fewer transactions, more fraud, fewer analysts awake.
SEGMENT 3
Par profil de VBy V profile
Les familles de comportement : un modèle peut briller sur l'une et rater l'autre.Families of behaviour: a model can shine on one and miss the other.
OUTILLAGETOOLING
Les erreurs deviennent artefactsErrors become artifacts
worst_errors.csv loggé dans chaque run, recall_seg en métriques : l'analyse d'erreurs du bloc 2, avec une trace permanente.worst_errors.csv logged in every run, recall_seg as metrics: block 2's error analysis, with a permanent trace.
Le duelThe duel

Modèle A : PR-AUC 0,86 mais 31 % des fraudes > 1 000 € ratées. Modèle B : 0,83 mais seulement 9 % ratées. La moyenne dit A, le coût métier dit B — sans métriques par segment, vous auriez promu A sans même voir le débat. Verdict en trois regards : PR-AUC pour trier, segments pour voir où ça pèche, euros pour trancher. Model A: PR-AUC 0.86 but 31% of frauds > €1,000 missed. Model B: 0.83 but only 9% missed. The average says A, business cost says B — without per-segment metrics you'd have promoted A without even seeing the debate. Verdict in three looks: PR-AUC to rank, segments to spot the sins, euros to decide.

Acte 4 · le registreAct 4 · the registry

La production est une adresse, pas un fichierProduction is an address, not a file

Fini le .pkl qu'on se passe par mail : le Model Registry donne au modèle un nom officiel — fraud-detector — des versions numérotées, et un cycle de vie standard. Chaque version reste reliée à son run : params, métriques, erreurs, tout. No more .pkl passed around by email: the Model Registry gives the model an official name — fraud-detector — numbered versions, and a standard lifecycle. Each version stays linked to its run: params, metrics, errors, everything.

None Staging Production Archived enregistré candidat sert les clients jamais supprimé rollback : repointer, 30 srollback: repoint, 30 s
LA PROMOTIONPROMOTION
Une décision tracéeA traced decision
Staging → Production laisse une trace horodatée, signée, justifiée : qui, quand, pourquoi. La ligne qui répond à l'auditeur.Staging → Production leaves a timestamped, signed, justified trace: who, when, why. The line that answers the auditor.
L'APITHE API
models:/fraud-detector/Production
L'API du bloc 3 charge cette adresse — plus de nom de fichier. Changer de modèle ne touche ni au code ni à l'image Docker.Block 3's API loads this address — no filename anymore. Changing models touches neither the code nor the Docker image.
ROLLBACK
Se tromper devient réversibleBeing wrong becomes reversible
v3 déçoit ? Repointer v2 : trente secondes, zéro redéploiement. On l'automatisera au bloc 7.v3 disappoints? Repoint v2: thirty seconds, zero redeployment. We'll automate it in block 7.
GOUVERNANCEGOVERNANCE
Qui a le droit de promouvoir ?Who is allowed to promote?
Pas n'importe qui, pas n'importe quand, pas sans raison écrite. Au bloc 6, ces règles deviennent des barrières automatiques.Not just anyone, not just anytime, not without a written reason. In block 6 these rules become automatic gates.
La mission du blocThe block's mission

« Chaque expérience tracée, chaque modèle versionné — et l'API sert toujours la bonne version, en le prouvant. » "Every experiment traced, every model versioned — and the API always serves the right version, provably."

Le test final : « quel modèle tournait le 3 mars, entraîné comment, promu par qui ? » — vous répondez en trente secondes. Le silence de l'audit est devenu une réponse. The final test: "which model was running on March 3rd, trained how, promoted by whom?" — you answer in thirty seconds. The audit's silence has become an answer.

Lab · 3h30 · jours 2–3Lab · 3h30 · days 2–3

À vous de jouerYour turn

Quatre missions, en binômes — 3 h le jour 2, 1 h le jour 3. Les durées viennent du plan de cours : Four missions, in pairs — 3 h on day 2, 1 h on day 3. Durations come straight from the course plan:

1h20
Tracker — instrumenter train.py (params, métriques, artefacts), rejouer 5 runs minimum, comparer dans l'UITrack — instrument train.py (params, metrics, artifacts), replay at least 5 runs, compare in the UI
pratiquehands-on
1h10
Segments — analyse d'erreurs outillée : segments fixés à l'avance, recall_seg loggé, worst_errors.csv en artefact, verdict en eurosSegments — tooled error analysis: segments fixed up front, recall_seg logged, worst_errors.csv as artifact, verdict in euros
théo 0h30 + prat 0h40theory 0h30 + lab 0h40
0h50
Registre — enregistrer fraud-detector, v1 et v2, staging → production → archivage, justification écrite, rollback chronométréRegistry — register fraud-detector, v1 and v2, staging → production → archiving, written justification, timed rollback
pratiquehands-on
0h40
Intégration — FastAPI charge models:/fraud-detector/Production ; changer de stage change le modèle servi, sans toucher au codeIntegration — FastAPI loads models:/fraud-detector/Production; changing stage changes the served model, without touching the code
pratiquehands-on
Definition of done

Cinq runs tracés et comparés dans l'UI, des métriques par segment avec worst_errors.csv attaché, un registre à deux versions dont une seule en Production (justifiée par écrit), et POST /predict servi depuis le registre — preuve du changement de version à l'appui. Au bloc 6, la CI viendra garder tout ça automatiquement. Five runs traced and compared in the UI, per-segment metrics with worst_errors.csv attached, a registry with two versions and only one in Production (justified in writing), and POST /predict served from the registry — with proof of the version switch. In block 6, CI will start guarding all of this automatically.

Pas à pas · en autonomieStep by step · self-paced

Faites-le vous-même, de A à ZDo it yourself, start to finish

Tout le lab, rejouable seul : chaque commande, chaque fichier en entier, un point de contrôle par étape. Prérequis : le repo fraud-detection/ des blocs 2–3 — src/train/train.py, src/serve/app.py, data/creditcard.csv — et le venv .venv. The whole lab, replayable on your own: every command, every file in full, one checkpoint per step. Prerequisites: the fraud-detection/ repo from blocks 2–3 — src/train/train.py, src/serve/app.py, data/creditcard.csv — and the .venv virtualenv.

Mission 1 — Tracker (1h20)Mission 1 — Track (1h20)

1Installer MLflow et lancer le serveurInstall MLflow and start the server

Dans un terminal dédié — il reste ouvert pendant tout le lab. Alternative minimale : mlflow ui (même interface, lit le dossier local ./mlruns). In a dedicated terminal — it stays open for the whole lab. Minimal alternative: mlflow ui (same interface, reads the local ./mlruns folder).

cd fraud-detection
source .venv/bin/activate
pip install mlflow matplotlib

mlflow server --host 127.0.0.1 --port 5000
Vous devriez voir : l'UI MLflow sur http://127.0.0.1:5000, avec une expérience Default vide.You should see: the MLflow UI at http://127.0.0.1:5000, with an empty Default experiment.
2Instrumenter src/train/train.pyInstrument src/train/train.py

Remplacez src/train/train.py par cette version : les mêmes choix qu'au bloc 2 (split stratifié, scaler dans un Pipeline — jamais le classifieur seul —, class_weight, coût en euros), plus le carnet de bord : params, métriques, artefact, modèle. Replace src/train/train.py with this version: the same choices as block 2 (stratified split, scaler inside a Pipeline — never the bare classifier —, class_weight, cost in euros), plus the logbook: params, metrics, artifact, model.

import argparse

import joblib
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import mlflow
import mlflow.sklearn
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import average_precision_score, precision_recall_curve
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler


def main(c, class_weight, threshold):
    df = pd.read_csv("data/creditcard.csv")
    X = df.drop(columns=["Class"])
    y = df["Class"]
    X_tr, X_te, y_tr, y_te = train_test_split(
        X, y, test_size=0.2, stratify=y, random_state=42)

    cw = None if class_weight == "none" else class_weight
    model = Pipeline([
        ("scaler", StandardScaler()),
        ("clf", LogisticRegression(C=c, class_weight=cw, max_iter=1000)),
    ])

    mlflow.set_tracking_uri("http://127.0.0.1:5000")
    mlflow.set_experiment("fraud-detector")
    with mlflow.start_run():
        mlflow.log_params({"C": c, "class_weight": class_weight,
                           "threshold": threshold,
                           "features": "time_v1_v28_amount"})

        model.fit(X_tr, y_tr)
        proba = model.predict_proba(X_te)[:, 1]
        pred = (proba >= threshold).astype(int)

        pr_auc = average_precision_score(y_te, proba)
        missed = (y_te == 1) & (pred == 0)
        false_alert = (y_te == 0) & (pred == 1)
        cost = X_te.loc[missed, "Amount"].sum() + 15 * false_alert.sum()
        mlflow.log_metrics({"pr_auc": pr_auc, "cost_eur": float(cost)})

        prec, rec, _ = precision_recall_curve(y_te, proba)
        plt.plot(rec, prec)
        plt.xlabel("recall")
        plt.ylabel("precision")
        plt.savefig("pr_curve.png")
        mlflow.log_artifact("pr_curve.png")

        mlflow.sklearn.log_model(model, "model")
        joblib.dump(model, "models/model.joblib")
        print(f"pr_auc={pr_auc:.4f}  cost_eur={cost:.0f}")


if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("--c", type=float, default=1.0)
    p.add_argument("--class-weight", default="balanced")
    p.add_argument("--threshold", type=float, default=0.5)
    a = p.parse_args()
    main(a.c, a.class_weight, a.threshold)
Vous devriez voir : le fichier sauvegardé — l'exécution vient à l'étape 3.You should see: the file saved — running it comes in step 3.
3Lancer 3 runs variés et les comparerLaunch 3 varied runs and compare them

Un changement par run — la boucle scientifique du bloc 2, mais chaque page s'écrit toute seule. Dans un second terminal (venv activé) : One change per run — block 2's scientific loop, but every page now writes itself. In a second terminal (venv activated):

mkdir -p models
python src/train/train.py --c 1.0 --class-weight balanced --threshold 0.5
python src/train/train.py --c 0.1 --class-weight balanced --threshold 0.3
python src/train/train.py --c 1.0 --class-weight none --threshold 0.5
Vous devriez voir : 3 runs dans l'expérience fraud-detector de l'UI ; cochez-les puis Compare — params et PR-AUC côte à côte, pr_curve.png dans les artefacts.You should see: 3 runs in the UI's fraud-detector experiment; tick them then Compare — params and PR-AUC side by side, pr_curve.png in the artifacts.

Mission 2 — Segments (0h40)Mission 2 — Segments (0h40)

4Logger la PR-AUC par segmentLog per-segment PR-AUC

Segments fixés à l'avance, les mêmes pour tous les modèles : gros montants et nuit. Ajoutez ce bloc dans train.py, à l'intérieur du with mlflow.start_run():, après le calcul de proba — puis relancez un run. Segments fixed up front, the same for every model: large amounts and night. Add this block to train.py, inside the with mlflow.start_run():, after proba is computed — then launch one more run.

        y_true = y_te.to_numpy()
        segments = {
            "large_amounts": (X_te["Amount"] > 1000).to_numpy(),
            "night": ((X_te["Time"] % 86400) < 6 * 3600).to_numpy(),
        }
        for name, mask in segments.items():
            if y_true[mask].sum() > 0:
                seg_auc = average_precision_score(y_true[mask], proba[mask])
                mlflow.log_metric(f"pr_auc_{name}", seg_auc)
                print(f"pr_auc_{name}={seg_auc:.4f}  ({int(mask.sum())} tx)")
Vous devriez voir : le nouveau run porte pr_auc_large_amounts et pr_auc_night — souvent plus basses que la PR-AUC globale. Bon en moyenne, mauvais là où ça coûte : c'est exactement le point.You should see: the new run carries pr_auc_large_amounts and pr_auc_night — often lower than the global PR-AUC. Good on average, bad where it costs: that is exactly the point.

Mission 3 — Registre (0h50)Mission 3 — Registry (0h50)

5Enregistrer le modèle : fraud-detectorRegister the model: fraud-detector

Deux voies. Voie UI : ouvrez votre meilleur run → artefact model → bouton Register model → nom fraud-detector. Voie code : copiez le Run ID depuis l'UI, puis exécutez ceci (une fois pour un run moyen → version 1, une fois pour votre meilleur run → version 2) : Two routes. UI route: open your best run → model artifact → Register model button → name fraud-detector. Code route: copy the Run ID from the UI, then run this (once for an average run → version 1, once for your best run → version 2):

import mlflow

mlflow.set_tracking_uri("http://127.0.0.1:5000")
result = mlflow.register_model("runs:/<RUN_ID>/model", "fraud-detector")
print(f"registered: version {result.version}")
Vous devriez voir : onglet Models de l'UI — fraud-detector avec Version 1 et Version 2, chacune reliée à son run.You should see: the UI's Models tab — fraud-detector with Version 1 and Version 2, each linked to its run.
6Poser l'alias productionSet the production alias

Dans MLflow récent, les alias remplacent les stages (Staging/Production) : un alias est un pointeur nommé, déplaçable en une ligne. Créez scripts/promote.py puis exécutez-le : In recent MLflow, aliases replace stages (Staging/Production): an alias is a named pointer you can move in one line. Create scripts/promote.py then run it:

from mlflow import MlflowClient

client = MlflowClient(tracking_uri="http://127.0.0.1:5000")
client.set_registered_model_alias("fraud-detector", "production", version=2)
print("alias @production -> version 2")
mkdir -p scripts
python scripts/promote.py
Vous devriez voir : dans l'onglet Models, la Version 2 porte le badge @production. Le rollback, c'est la même ligne avec version=1 — trente secondes, chronométrez-le.You should see: in the Models tab, Version 2 carries the @production badge. Rollback is the same line with version=1 — thirty seconds, time it.

Mission 4 — L'API au registre (0h40)Mission 4 — The API on the registry (0h40)

7Brancher le lifespan de src/serve/app.py sur le registreWire src/serve/app.py's lifespan to the registry

Remplacez le haut de src/serve/app.py (imports, lifespan, /health) par ceci — vos endpoints /predict et schémas Pydantic du bloc 3 restent inchangés dessous, ils lisent state["model"] : Replace the top of src/serve/app.py (imports, lifespan, /health) with this — your block 3 /predict endpoints and Pydantic schemas stay unchanged below, they read state["model"]:

import os
from contextlib import asynccontextmanager

import joblib
import mlflow
import mlflow.sklearn
from fastapi import FastAPI

state = {}


@asynccontextmanager
async def lifespan(app: FastAPI):
    mlflow.set_tracking_uri(
        os.getenv("MLFLOW_TRACKING_URI", "http://127.0.0.1:5000"))
    try:
        state["model"] = mlflow.sklearn.load_model(
            "models:/fraud-detector@production")
        state["model_source"] = "registry"
    except Exception as exc:
        print(f"registry unavailable ({exc}), falling back to joblib")
        state["model"] = joblib.load("models/model.joblib")
        state["model_source"] = "joblib_fallback"
    yield
    state.clear()


app = FastAPI(lifespan=lifespan)


@app.get("/health")
def health():
    return {"status": "ok", "model_source": state["model_source"]}
Vous devriez voir : le fichier sauvegardé — la preuve arrive à l'étape 8.You should see: the file saved — the proof comes in step 8.
8Relancer l'API et prouver la version servieRestart the API and prove the served version

Serveur MLflow toujours ouvert dans son terminal. Dans le second terminal, relancez uvicorn puis interrogez /health — ensuite, déplacez l'alias vers la version 1 (scripts/promote.py avec version=1), relancez : le modèle servi change sans toucher au code. MLflow server still open in its terminal. In the second terminal, restart uvicorn then query /health — next, move the alias to version 1 (scripts/promote.py with version=1) and restart: the served model changes without touching the code.

export MLFLOW_TRACKING_URI=http://127.0.0.1:5000
uvicorn src.serve.app:app --port 8000

curl http://127.0.0.1:8000/health
Vous devriez voir : {"status":"ok","model_source":"registry"} — et, serveur MLflow coupé, le fallback {"model_source":"joblib_fallback"}.You should see: {"status":"ok","model_source":"registry"} — and, with the MLflow server stopped, the fallback {"model_source":"joblib_fallback"}.
Si ça coinceWhen it breaks

Aucun run dans l'UI ? Le tracking URI n'est pas défini : sans set_tracking_uri, MLflow écrit dans un ./mlruns local — vérifiez la ligne dans train.py ou la variable MLFLOW_TRACKING_URI.
Alias introuvable (RestException) ? L'alias @production n'a jamais été posé, ou une faute de frappe : re-exécutez scripts/promote.py et vérifiez le badge dans l'onglet Models.
Le modèle du registre prédit n'importe quoi ? Vous avez loggé le classifieur sans son scaler : loggez toujours un Pipeline sklearn (scaler + modèle), comme à l'étape 2.
Port 5000 déjà pris ? Sur mac, AirPlay Receiver occupe le port 5000 : désactivez-le dans Réglages, ou lancez --port 5001 et adaptez l'URI partout.
No run in the UI? The tracking URI is not set: without set_tracking_uri, MLflow writes to a local ./mlruns — check the line in train.py or the MLFLOW_TRACKING_URI variable.
Alias not found (RestException)? The @production alias was never set, or a typo: rerun scripts/promote.py and check the badge in the Models tab.
The registry model predicts nonsense? You logged the classifier without its scaler: always log an sklearn Pipeline (scaler + model), as in step 2.
Port 5000 already taken? On mac, AirPlay Receiver squats port 5000: disable it in Settings, or run --port 5001 and adapt the URI everywhere.