139 lines
5.6 KiB
Python
139 lines
5.6 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
|
||
|
|
# erzeuge_predict_vs_optimize.py
|
||
|
|
"""
|
||
|
|
Erzeugt das Diagramm zum Predict-then-Optimize-Kapitel:
|
||
|
|
|
||
|
|
bilder_04/kap_prognose_entscheidungsguete.svg
|
||
|
|
|
||
|
|
Vier Verfahren, zwei Massstaebe: links der Prognosefehler (MSE), rechts die
|
||
|
|
Kosten je Tag. Die Balken sind absichtlich nebeneinander gestellt, denn der
|
||
|
|
Befund des Kapitels ist genau ihr Auseinanderlaufen: Das Verfahren mit dem
|
||
|
|
BESTEN MSE hat die HOECHSTEN Kosten.
|
||
|
|
|
||
|
|
Ohne Formel gesagt: Der Prognostiker optimiert seinen Fehler, der Planer traegt
|
||
|
|
die Kosten - und beide messen etwas anderes. Wer die Prognose verbessert, ohne
|
||
|
|
zu fragen, wofuer sie gebraucht wird, verbessert die falsche Zahl.
|
||
|
|
|
||
|
|
Alle Werte kommen aus Predict_then_Optimize.py: dieselben Daten, dieselbe Saat,
|
||
|
|
dieselben vier Verfahren. pruefe_befund() stellt sicher, dass die Aussage des
|
||
|
|
Kapitels im Bild auch wirklich zu sehen ist - sonst waere das Bild ein
|
||
|
|
Widerspruch zum Text daneben.
|
||
|
|
|
||
|
|
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||
|
|
python3 bilder_04/erzeuge_predict_vs_optimize.py
|
||
|
|
|
||
|
|
Benoetigt: numpy, scipy, scikit-learn, matplotlib
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
HIER = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
BASIS = os.path.dirname(HIER)
|
||
|
|
sys.path.insert(0, HIER)
|
||
|
|
sys.path.insert(0, os.path.join(
|
||
|
|
BASIS, "Operations_Research_mit_Python_Version_04_Programme"))
|
||
|
|
|
||
|
|
from stil_04 import FARBEN, speichere # noqa: E402
|
||
|
|
import matplotlib.pyplot as plt # noqa: E402
|
||
|
|
from scipy.stats import norm # noqa: E402
|
||
|
|
from sklearn.linear_model import LinearRegression, QuantileRegressor # noqa: E402
|
||
|
|
from Predict_then_Optimize import (KRITISCHES_VERHAELTNIS, TAGE, # noqa: E402
|
||
|
|
TRAINING, erzeuge_daten, tageskosten)
|
||
|
|
|
||
|
|
KURZ = ["Punkt-\nprognose", "+ Zuschlag aus der\nNormalverteilung",
|
||
|
|
"+ Zuschlag auf\nKosten trainiert", "Quantil-\nregression"]
|
||
|
|
|
||
|
|
|
||
|
|
def rechne() -> list[tuple[str, float, float]]:
|
||
|
|
"""Dieselben vier Verfahren wie im Buchprogramm."""
|
||
|
|
merkmale, nachfrage = erzeuge_daten()[:2]
|
||
|
|
# Dieselbe Aufteilung wie im Buchprogramm - eine selbst gewaehlte Grenze
|
||
|
|
# ergaebe andere Zahlen als die abgedruckte Tabelle.
|
||
|
|
lernen = slice(0, TRAINING)
|
||
|
|
pruefen = slice(TRAINING, TAGE)
|
||
|
|
|
||
|
|
kq = LinearRegression().fit(merkmale[lernen], nachfrage[lernen])
|
||
|
|
punkt = kq.predict(merkmale[pruefen])
|
||
|
|
rest = nachfrage[lernen] - kq.predict(merkmale[lernen])
|
||
|
|
pauschal = norm.ppf(KRITISCHES_VERHAELTNIS) * rest.std()
|
||
|
|
|
||
|
|
kandidaten = np.linspace(-10.0, 30.0, 401)
|
||
|
|
trainingsprognose = kq.predict(merkmale[lernen])
|
||
|
|
kostenzuschlag = float(kandidaten[np.argmin(
|
||
|
|
[tageskosten(trainingsprognose + z, nachfrage[lernen])
|
||
|
|
for z in kandidaten])])
|
||
|
|
|
||
|
|
quantil = QuantileRegressor(quantile=KRITISCHES_VERHAELTNIS, alpha=0.0,
|
||
|
|
solver="highs").fit(merkmale[lernen],
|
||
|
|
nachfrage[lernen])
|
||
|
|
quantilprognose = quantil.predict(merkmale[pruefen])
|
||
|
|
|
||
|
|
verfahren = [
|
||
|
|
(KURZ[0], punkt, punkt),
|
||
|
|
(KURZ[1], punkt, punkt + pauschal),
|
||
|
|
(KURZ[2], punkt, punkt + kostenzuschlag),
|
||
|
|
(KURZ[3], quantilprognose, quantilprognose),
|
||
|
|
]
|
||
|
|
aus = []
|
||
|
|
for name, prognose, bestellung in verfahren:
|
||
|
|
mse = float(((prognose - nachfrage[pruefen]) ** 2).mean())
|
||
|
|
aus.append((name, mse, tageskosten(bestellung, nachfrage[pruefen])))
|
||
|
|
return aus
|
||
|
|
|
||
|
|
|
||
|
|
def pruefe_befund(werte) -> None:
|
||
|
|
"""Die Aussage des Kapitels muss im Bild sichtbar sein."""
|
||
|
|
bester_mse = min(werte, key=lambda z: z[1])
|
||
|
|
teuerster = max(werte, key=lambda z: z[2])
|
||
|
|
if bester_mse[0] != teuerster[0]:
|
||
|
|
raise SystemExit(
|
||
|
|
f"Predict-then-Optimize-Bild: Das Verfahren mit dem besten MSE "
|
||
|
|
f"({bester_mse[0].replace(chr(10), ' ')}) ist nicht das teuerste "
|
||
|
|
f"({teuerster[0].replace(chr(10), ' ')}). Das Kapitel behauptet "
|
||
|
|
f"genau das - Bild und Text wuerden sich widersprechen.")
|
||
|
|
|
||
|
|
|
||
|
|
def zeichne(werte) -> None:
|
||
|
|
figur, achsen = plt.subplots(1, 2, figsize=(9.4, 4.4))
|
||
|
|
namen = [n for n, _, _ in werte]
|
||
|
|
y = np.arange(len(namen))[::-1]
|
||
|
|
|
||
|
|
for achse, index, titel, einheit, farbe in (
|
||
|
|
(achsen[0], 1, "Prognosefehler (MSE)", "", FARBEN["zweit"]),
|
||
|
|
(achsen[1], 2, "Kosten je Tag", " €", FARBEN["warnung"])):
|
||
|
|
wert = [w[index] for w in werte]
|
||
|
|
bester = min(wert)
|
||
|
|
farben = [FARBEN["gut"] if abs(w - bester) < 1e-9 else farbe
|
||
|
|
for w in wert]
|
||
|
|
achse.barh(y, wert, height=0.62, color=farben, zorder=3)
|
||
|
|
for stelle, w in enumerate(wert):
|
||
|
|
achse.text(w * 1.01, y[stelle], f"{w:,.2f}{einheit}"
|
||
|
|
.replace(",", "."), va="center", fontsize=9,
|
||
|
|
color=FARBEN["text"])
|
||
|
|
achse.set_yticks(y)
|
||
|
|
achse.set_yticklabels(namen if index == 1 else [], fontsize=8.5,
|
||
|
|
color=FARBEN["text"])
|
||
|
|
achse.set_xlim(0, max(wert) * 1.22)
|
||
|
|
achse.set_title(titel, fontsize=10.5, color=FARBEN["text"])
|
||
|
|
achse.grid(axis="y", visible=False)
|
||
|
|
|
||
|
|
figur.suptitle("Grün ist jeweils der beste Wert — und es ist nicht "
|
||
|
|
"dasselbe Verfahren",
|
||
|
|
fontsize=11, color=FARBEN["text"], y=1.02)
|
||
|
|
speichere(figur, "kap_prognose_entscheidungsguete")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
werte = rechne()
|
||
|
|
for name, mse, kosten in werte:
|
||
|
|
print(f" {name.replace(chr(10), ' '):<40} MSE {mse:>8.2f} "
|
||
|
|
f"Kosten {kosten:>7.2f} EUR")
|
||
|
|
pruefe_befund(werte)
|
||
|
|
print(" Befund bestätigt: bester MSE = höchste Kosten.")
|
||
|
|
zeichne(werte)
|