operations_research/OR_HTML_04/prognose.html

761 lines
94 KiB
HTML
Raw Permalink Normal View History

Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kapitel 15: Predict-then-Optimize{idx:Predict-then-Optimize} — die bessere Prognose, die schlechtere Entscheidung · Optimierte Entscheidungsfindung mit Python</title>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<script>
(function () {
try {
var t = localStorage.getItem("or-theme");
if (t) document.documentElement.setAttribute("data-theme", t);
} catch (e) {}
})();
</script>
<link rel="stylesheet" href="assets/highlight.css" />
<link rel="stylesheet" href="katex/katex.min.css" />
<script defer="" src="katex/katex.min.js"></script>
<script>document.addEventListener("DOMContentLoaded", function () {
var mathElements = document.getElementsByClassName("math");
var macros = [];
for (var i = 0; i < mathElements.length; i++) {
var texText = mathElements[i].firstChild;
if (mathElements[i].tagName == "SPAN") {
katex.render(texText.data, mathElements[i], {
displayMode: mathElements[i].classList.contains('display'),
throwOnError: false,
macros: macros,
fleqn: false
});
}}
// Der Browser springt zu einem #anker in der URL schon beim ersten Rendern
// an, BEVOR die KaTeX-Formeln oben im Text ihre finale Hoehe bekommen -
// durch den Reflow landet der Anker danach zu weit unten. Nach dem
// Formel-Rendering hier erneut zum Anker springen, das behebt es.
if (location.hash) {
var ziel = document.getElementById(decodeURIComponent(location.hash.slice(1)));
if (ziel) ziel.scrollIntoView({behavior: "instant", block: "start"});
}
});
</script>
<link rel="stylesheet" href="assets/site.css" />
</head>
<body>
<svg style="display:none" aria-hidden="true"><symbol id="icon-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.2" y2="16.2"/>
</symbol>
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4.5"/>
<line x1="12" y1="1.5" x2="12" y2="4"/><line x1="12" y1="20" x2="12" y2="22.5"/>
<line x1="1.5" y1="12" x2="4" y2="12"/><line x1="20" y1="12" x2="22.5" y2="12"/>
<line x1="4.5" y1="4.5" x2="6.2" y2="6.2"/><line x1="17.8" y1="17.8" x2="19.5" y2="19.5"/>
<line x1="19.5" y1="4.5" x2="17.8" y2="6.2"/><line x1="6.2" y1="17.8" x2="4.5" y2="19.5"/>
</symbol>
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a6.8 6.8 0 0 0 10.5 10.5z"/>
</symbol>
<symbol id="icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 4 7 12 15 20"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 4 17 12 9 20"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 13 9.5 18.5 20 6"/>
</symbol>
<symbol id="icon-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19V7A1.5 1.5 0 0 1 5 5.5h5.5"/>
<polyline points="14.5 3.5 20.5 3.5 20.5 9.5"/><line x1="11" y1="13" x2="20" y2="4"/>
</symbol>
<symbol id="icon-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 5.5A2 2 0 0 1 6 4h6v16H6a2 2 0 0 0-2 1.5z"/>
<path d="M20 5.5A2 2 0 0 0 18 4h-6v16h6a2 2 0 0 1 2 1.5z"/>
</symbol>
<symbol id="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3v12"/><polyline points="7 10 12 15 17 10"/><path d="M4 19.5h16"/>
</symbol></svg>
<header class="site-header">
<button type="button" class="icon-btn" id="sidebar-toggle" aria-label="Menü öffnen"><svg class="icon" aria-hidden="true"><use href="#icon-menu"></use></svg></button>
<a class="brand" href="index.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> <span>Optimierte Entscheidungsfindung mit Python</span></a>
<div class="site-search">
<input id="suche-eingabe" type="search" placeholder="Suchen …" aria-label="Suche" autocomplete="off" />
<svg class="icon such-icon" aria-hidden="true"><use href="#icon-search"></use></svg>
<div id="suche-ergebnisse" class="suche-ergebnisse" hidden></div>
</div>
<button type="button" class="icon-btn" id="theme-toggle" aria-label="Darstellung umschalten">
<svg class="icon icon-sun" aria-hidden="true"><use href="#icon-sun"></use></svg><svg class="icon icon-moon" aria-hidden="true"><use href="#icon-moon"></use></svg>
</button>
</header>
<div class="site-body">
<div class="sidebar-overlay" id="sidebar-overlay" hidden></div>
<nav class="sidebar" id="sidebar" aria-label="Kapitelnavigation"><div class="sidebar-inhalt"><details class="sidebar-gruppe"><summary>Einstieg</summary><ul><li data-kapitel="vorwort.html"><a href="vorwort.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Vorwort & Lesehilfe</span></a></li><li data-kapitel="notation.html"><a href="notation.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Notation & Abkürzungen</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil I: Grundlagen des Operations Research</summary><ul><li data-kapitel="einfuehrung.html"><a href="einfuehrung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></a></li><li data-kapitel="fundament.html"><a href="fundament.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität{idx:Konvexität}</span></a></li><li data-kapitel="oekosystem.html"><a href="oekosystem.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></a></li><li data-kapitel="modellierung.html"><a href="modellierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 4: Vom Management-Wunsch zum Modell</span></a></li><li data-kapitel="synthese-grundlagen.html"><a href="synthese-grundlagen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil I</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><li data-kapitel="lp.html"><a href="lp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></a></li><li data-kapitel="milp.html"><a href="milp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound{idx:Branch-and-Bound}</span></a></li><li data-kapitel="cpsat.html"><a href="cpsat.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 7: Constraint Programming mit CP-SAT{idx:CP-SAT} — Logik, Scheduling und Zuweisung</span></a></li><li data-kapitel="graphen.html"><a href="graphen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a></li><li data-kapitel="metaheuristiken.html"><a href="metaheuristiken.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></a></li><li data-kapitel="dekomposition.html"><a href="dekomposition.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span></a></li><li data-kapitel="synthese-kernverfahren.html"><a href="synthese-kernverfahren.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil II</span></a></li></ul></details><details class="sidebar-gruppe" open><summary>Teil
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<main class="content">
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> <span>Teil III</span> <span>Kapitel 15: Predict-then-Optimize{idx:Predict-then-Optimize} — die bessere Prognose, die schlechtere Entscheidung</span></nav>
Phase 8.1: Synthese-Seiten je Teil - und die gebrochene Lesekette Setzt Paket 5 aus Verbesserungen_02.md um (den Teil, der nicht zurueckgestellt wurde). Fuenf neue Dateien 19_/29_/39_/49_/52_Synthese_*.md, je eine am Ende eines Teils, mit eigener Website-Seite ueber SONDERSEITEN - sie tragen bewusst keine "# Kapitel:"-Ueberschrift, weil sie keine Kapitel sind, sondern der Rueckblick auf einen Teil. Der Entwurf musste sich abgrenzen: Die Teil-Einleitungen haben bereits Entscheidungsdiagramme. Eine zweite Matrix am Teil-Ende waere eine Dopplung gewesen. Die Synthesen leisten deshalb, was eine Einleitung nicht kann - den Vergleich ueber die Kapitel hinweg (Verfahren nebeneinander, mit der Spalte "wo es aufhoert"), eine Tabelle "was dieser Teil gemessen hat" (Behauptung gegen Messung gegen Fundstelle) und drei Fehler, die der Teil verhindert. Zitiert wird ausschliesslich, was im Buch tatsaechlich gerechnet wird. Drei Funde beim Einbau: * Teil III sagte "die drei Kapitel dieses Teils", hat aber fuenf. Phase 3 hatte Mehrziel und Predict-then-Optimize hinzugefuegt, die Einleitung blieb stehen. * 50_Praxis.md verwies auf die Projektwerkstatt mit "acht eigene Anwendungen" - sie hat elf. * Und der eigentliche Fund: Die Lesekette der Quelldateien fuehrte an ACHT Kapiteln vorbei. 12_Python_Oekosystem zeigte direkt auf 20_Lineare_Programmierung, 23_Graphen direkt auf 30_QP, 32_Dynamische direkt auf 40_Finanzdaten, 50_Praxis direkt auf die Projektwerkstatt. Wer der Kette folgte, uebersprang acht von 23 Kapiteln - darunter Metaheuristiken, Spaltengenerierung, Strukturbruecke, Supply-Chain und das ganze Testing-Kapitel. Zehn weitere Dateien hatten gar keine Navigationszeile. Zur Reichweite, damit sie nicht ueberschaetzt wird: Diese Zeilen stehen nur in den Quelldateien. entferne_navigation() streicht sie aus dem Gesamtdokument, und die Website baut ihre Vor/Zurueck-Knoepfe selbst aus DATEIEN. PDF und Website waren nie betroffen - wohl aber jeder, der die Markdown-Dateien im Repository liest, und das wird nach der Veroeffentlichung der Normalfall sein. Die Kette ist jetzt ueber alle 35 Uebergaenge geschlossen, und --check bewacht sie: Fehlt eine Zeile oder zeigt sie an der in DATEIEN folgenden Datei vorbei, ist der Lauf rot. Gegengetestet mit beiden Bruchformen. Stand: 36 Dateien, 296 Abschnitte, 815 Querverweise, 328 Indexmarken, 76 Programme (unveraendert), 33 pytest-Tests, PDF 758 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:10:02 +02:00
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="mehrziel.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 14: Mehrere Ziele — Pareto-Fronten statt Gewichte</span></a><a class="prev-next-knopf prev-next-next" href="synthese-nichtlinear.html"><span><small>Weiter</small>Synthese Teil III</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<article>
<h1 id="kap-prognose">Kapitel 15: Predict-then-Optimize — die bessere Prognose, die schlechtere Entscheidung</h1>
<div class="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Um die Naht zwischen zwei Welten, die in getrennten Abteilungen sitzen: Ein Modell <strong>prognostiziert</strong>, ein anderes <strong>entscheidet</strong>. Beide arbeiten sauber — und genau an der Naht entsteht ein Fehler, den keine der beiden Seiten sieht.</p>
<p><strong>Voraussetzungen:</strong> <a href="unsicherheit.html#kap-unsicherheit">Kapitel 12</a>, insbesondere das <a href="anhang-glossar.md#gloss:newsvendor-problem" class="glossar-link">Newsvendor-Problem</a> und das kritische Verhältnis. Etwas Regression hilft, ist aber nicht nötig.</p>
<p><strong>Danach können Sie:</strong> begründen, warum ein Prognosemodell mit kleinerem <a href="anhang-glossar.md#gloss:mse" class="glossar-link">MSE</a> teurere Entscheidungen erzeugen kann; die richtige <strong>Zielgröße</strong> einer Prognose bestimmen; und Prognosemodelle an Entscheidungskosten statt an Fehlermaßen bewerten.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p><strong>Zeitbedarf:</strong> ca. 3,5 Stunden.</p>
<p><strong>Programme:</strong><br />
<code>Predict_then_Optimize.py</code></p>
Veroeffentlichung: Lizenzen, README, Colab-Entscheidung, JupyterLab im Image Sechs Dinge, alle fuer das jetzt oeffentliche Repository. LIZENZEN. LICENSE traegt die MIT-Lizenz fuer den Programmcode (alle .py, Notebooks, Dockerfile, pyproject.toml), LICENSE-TEXT.md die CC BY-SA 4.0 fuer Buchtext, PDF, Website und Diagramme. Beide Dateien nennen ausdruecklich, was sie abdecken und was nicht - ein Codeblock im Buchtext bleibt Programmcode und steht unter MIT. Vorher hatte das Repository gar keine Lizenz, womit standardmaessig "alle Rechte vorbehalten" galt und niemand die 76 Programme haette weiterverwenden duerfen. COLAB. COLAB_BASIS_URL steht jetzt auf "". Der Platzhalter zeigte auf GitHub, das Repository liegt auf einer eigenen Forgejo-Instanz - und Colab oeffnet Notebooks NUR aus GitHub, Google Drive oder einem Upload. Die URL-Form colab.research.google.com/github/... ist fest auf GitHub verdrahtet; eine selbstgehostete Adresse dort einzusetzen ergaebe keinen Link zum eigenen Server, sondern einen toten GitHub-Link. Der Kommentar im Quelltext ging von GitHub aus und war damit selbst irrefuehrend; er ist ersetzt. Verloren geht nichts: Die 25 Notebooks liegen neben der Website und bekommen einen echten Download-Link, jetzt mit dem Hinweis, was man damit tut - "herunterladen und in Jupyter oeffnen, in Colab hochladen oder mit dem Kurs-Image starten". JUPYTERLAB IM KURS-IMAGE. Neue pyproject-Gruppe [notebook] mit jupyterlab, die Notebooks werden ins Image kopiert, und ein kleiner Startbefehl macht beide Betriebsarten ohne --entrypoint moeglich: ohne Argument JupyterLab, mit Argument ein einzelnes Programm. Gebaut und geprueft - Rucksack.py laeuft, JupyterLab antwortet mit HTTP 200 und zeigt alle 25 Notebooks. Image 1,31 -> 1,46 GB. Es laeuft ohne Token, deshalb im README die Portfreigabe an 127.0.0.1 gebunden. README KOMPLETT NEU. Es war die Bau-Anleitung eines privaten Verzeichnisses und ist jetzt die Visitenkarte eines oeffentlichen Repositorys: was das Buch ist, wo man es liest, drei Wege die Beispiele auszufuehren (Container, schlanke Installation, alles auf einmal), was hier liegt, wie man baut, die Colab-Frage, die Lizenzen und wie man mitwirkt. Alle relativen Links geprueft: 0 tot. .gitattributes. Ohne die Datei entschied core.autocrlf des jeweiligen Rechners, was beim Klonen passiert - ein Windows-Leser bekam CRLF-Rauschen in jedem Diff. Jetzt: im Repository immer LF, im Arbeitsverzeichnis passend zum System, Binaerdateien ausdruecklich ausgenommen. Der Bestand war bereits sauber (git add --renormalize aendert null Dateien). Zusaetzlich sind die erzeugten Verzeichnisse als linguist-generated markiert, sonst zaehlt die Sprachstatistik das Repository als HTML-Projekt. PROGRESS.md: Remote-Repository als erledigt markiert, der Colab-Befund festgehalten. CLAUDE.md um Veroeffentlichung, Lizenzen und die neuen Dateien ergaenzt. Geprueft: --check ohne Fehler, 0 tote README-Links, 76 Programme unveraendert, 33 pytest-Tests, pyproject baut mit acht Gruppen, PDF 759 Seiten (eine weniger - die Colab-Zeile entfaellt in 25 Kapiteln). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:54:04 +02:00
<p><strong>Notebook:</strong> <a href="Notebooks_04/prognose.ipynb">prognose.ipynb</a> — herunterladen und in Jupyter öffnen, in Colab hochladen oder mit dem Kurs-Image starten</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</blockquote>
</div>
<hr />
<h2 id="sec:prognose-schnellstart">15.1 In 5 Minuten gelöst</h2>
<div class="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Der Durchschnitt ist die falsche Zahl</strong></p>
<p>Eine Bäckerei kennt die Nachfrage der letzten 20 Tage. Ein Brot bringt 6 € Marge, ein übriges kostet 3 € Einkauf. Wie viele soll sie ansetzen?</p>
<p>Die naheliegende Antwort — den Durchschnitt — ist nachweislich falsch:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>nachfrage <span class="op">=</span> np.array([<span class="dv">104</span>, <span class="dv">138</span>, <span class="dv">96</span>, <span class="dv">151</span>, <span class="dv">118</span>, <span class="dv">127</span>, <span class="dv">143</span>, <span class="dv">109</span>, <span class="dv">162</span>, <span class="dv">121</span>, <span class="co"># 20 Tage</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="dv">133</span>, <span class="dv">115</span>, <span class="dv">148</span>, <span class="dv">102</span>, <span class="dv">129</span>, <span class="dv">156</span>, <span class="dv">111</span>, <span class="dv">140</span>, <span class="dv">124</span>, <span class="dv">135</span>])</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>preis, einkauf <span class="op">=</span> <span class="fl">9.0</span>, <span class="fl">3.0</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>fehl, ueber <span class="op">=</span> preis <span class="op">-</span> einkauf, einkauf <span class="co"># 6 EUR zu wenig, 3 EUR zu viel</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>kosten <span class="op">=</span> <span class="kw">lambda</span> menge: (fehl <span class="op">*</span> np.maximum(<span class="dv">0</span>, nachfrage <span class="op">-</span> menge)</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> ueber <span class="op">*</span> np.maximum(<span class="dv">0</span>, menge <span class="op">-</span> nachfrage)).mean()</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>erwartungswert <span class="op">=</span> <span class="bu">round</span>(nachfrage.mean())</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>kritisch <span class="op">=</span> <span class="bu">round</span>(np.quantile(nachfrage, fehl <span class="op">/</span> (fehl <span class="op">+</span> ueber)))</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Erwartungswert bestellen: </span><span class="sc">{</span>erwartungswert<span class="sc">}</span><span class="ss"> Stueck -&gt; </span><span class="sc">{</span>kosten(erwartungswert)<span class="sc">:5.2f}</span><span class="ss"> EUR/Tag&quot;</span>)</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;kritisches Quantil : </span><span class="sc">{</span>kritisch<span class="sc">}</span><span class="ss"> Stueck -&gt; </span><span class="sc">{</span>kosten(kritisch)<span class="sc">:5.2f}</span><span class="ss"> EUR/Tag&quot;</span>)</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;bestmoegliche feste Menge: </span><span class="sc">{</span><span class="bu">int</span>(np.argmin([kosten(m) <span class="cf">for</span> m <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">200</span>)]))<span class="sc">}</span><span class="ss"> Stueck&quot;</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot; -&gt; </span><span class="sc">{</span><span class="bu">min</span>(kosten(m) <span class="cf">for</span> m <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">200</span>))<span class="sc">:5.2f}</span><span class="ss"> EUR/Tag&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>Erwartungswert bestellen: 128 Stueck -&gt; 69.45 EUR/Tag
kritisches Quantil : 137 Stueck -&gt; 62.25 EUR/Tag
bestmoegliche feste Menge: 138 Stueck -&gt; 62.10 EUR/Tag</code></pre>
</blockquote>
</div>
<p><strong>Und jetzt der Punkt.</strong> Der Durchschnitt ist perfekt bestimmt — es ist der Mittelwert derselben Daten, aus denen auch das Quantil kommt. Trotzdem kostet er <strong>7,20 € je Tag mehr</strong>, gut 10 %.</p>
<p>Der Grund steht in den Preisen: Ein fehlendes Brot kostet doppelt so viel wie ein übriges. Bei asymmetrischen Kosten liegt die optimale Menge nicht in der Mitte der Verteilung, sondern beim <strong>kritischen Verhältnis</strong> <span class="math inline">c_-/(c_- + c_+) = 6/9 = 66{,}7\,\%</span> — das kennen Sie aus <a href="unsicherheit.html#kap-unsicherheit">Kapitel 12</a>.</p>
<p>Bemerkenswert ist die dritte Zeile: Die Quantilregel trifft mit 137 fast genau die beste überhaupt mögliche feste Menge (138). Sie ist nicht ungefähr richtig, sie ist richtig.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Eine Prognose ist kein Selbstzweck. Sie liefert eine Zahl, die in eine Entscheidung eingeht — und <strong>welche</strong> Zahl das sein muss, bestimmt die Entscheidung, nicht die Statistik.</p>
</blockquote>
<p><strong>Warum funktioniert das?</strong> Weil die Kostenfunktion des Newsvendors geknickt und unsymmetrisch ist. Der Mittelwert minimiert den <em>quadratischen</em> Fehler; die Kosten sind aber stückweise linear mit verschiedenen Steigungen nach oben und unten. Zwei verschiedene Zielfunktionen haben zwei verschiedene Optima — das ist kein Paradox, sondern Arithmetik.</p>
<hr />
<h2 id="sec:prognose-lernziele">15.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="1">
<li>… die Zweiteilung <em>predict</em> / <em>optimize</em> benennen und sagen, wo dabei Information verloren geht.</li>
<li>… begründen, warum das MSE-beste Modell nicht das kostenbeste sein muss.</li>
<li>… die richtige Prognosegröße aus der Entscheidung ableiten statt aus Gewohnheit.</li>
<li>… erklären, warum ein pauschaler Sicherheitszuschlag schwächer ist als ein Modell, das die Unsicherheit aus den Merkmalen liest.</li>
<li>… Prognosemodelle an <strong>Entscheidungskosten</strong> messen — und wissen, wie viele Testdaten ein solcher Vergleich braucht.</li>
</ol>
<hr />
<h2 id="sec:prognose-naht">15.3 Die Naht zwischen zwei Abteilungen</h2>
<p>In fast jedem Unternehmen sieht der Ablauf so aus:</p>
<pre><code> Verkaufsdaten -&gt; [ PROGNOSE ] -&gt; Nachfrageschätzung -&gt; [ PLANUNG ] -&gt; Bestellung
Data Science Disposition</code></pre>
<p>Beide Seiten arbeiten sorgfältig. Die Prognoseabteilung optimiert ihr Modell auf ein <strong>Fehlermaß</strong> — meist den mittleren quadratischen Fehler (MSE) oder <a href="anhang-glossar.md#gloss:mape" class="glossar-link">MAPE</a> — und berichtet stolz eine Verbesserung von 12 %. Die Disposition nimmt die Zahl entgegen und rechnet ihre Bestellmenge aus.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Der Fehler steckt nicht in einer der beiden Hälften, sondern in der Naht:</p>
<blockquote>
<p><strong>📖 Definition: <a href="anhang-glossar.md#gloss:predict-then-optimize" class="glossar-link">Predict-then-Optimize</a></strong></p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Das übliche zweistufige Vorgehen: erst eine unbekannte Größe schätzen, dann mit der Schätzung optimieren, als wäre sie die Wahrheit.</p>
<p>Das Problem: Die erste Stufe wird auf ein <strong>statistisches</strong> Maß trainiert, die zweite erzeugt <strong>ökonomische</strong> Kosten. Niemand garantiert, dass ein besseres statistisches Maß zu geringeren Kosten führt — und in diesem Kapitel ist es nachweislich umgekehrt.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ Warum das nicht auffällt</strong></p>
<p>Beide Abteilungen erfüllen ihre Kennzahl. Die Prognose wird besser (MSE sinkt), die Disposition arbeitet korrekt (sie wendet die Formel richtig an). Es gibt keinen Ort, an dem der Verlust sichtbar würde — außer man misst die Entscheidungskosten, und das ist niemandes Kennzahl.</p>
<p>Das ist dieselbe Struktur wie in <a href="testing.html#sec:testing-denkfehler">Abschnitt 23.9</a>: Der Fehler entsteht zwischen zwei Zuständigkeiten und wird deshalb von keiner Prüfung gefunden, die innerhalb einer der beiden liegt.</p>
</blockquote>
<hr />
<h2 id="sec:prognose-programm">15.4 Das Programm</h2>
<p>Dieselbe Bäckerei, aber mit Merkmalen: Wochentag, Temperatur, Aktionstage. Die Nachfrage muss jetzt <strong>modelliert</strong> werden.</p>
<p>Eine Eigenschaft der Daten ist dabei entscheidend und in der Praxis der Normalfall: An Aktionstagen ist die Nachfrage nicht nur höher, sondern auch <strong>viel unsicherer</strong> (heteroskedastisch).</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Predict_then_Optimize.py</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Prognose: Die bessere Prognose trifft die schlechtere Entscheidung.</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="co">Eine Baeckerei muss jeden Abend entscheiden, wie viel sie fuer den naechsten Tag</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="co">ansetzt. Zu wenig kostet die Marge des entgangenen Verkaufs, zu viel kostet den</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="co">Einkaufspreis der Retoure. Das ist das Newsvendor-Problem aus dem Kapitel</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="co">Unsicherheit - nur dass die Nachfrage diesmal nicht aus einer Verteilung kommt,</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a><span class="co">sondern PROGNOSTIZIERT werden muss: aus Wochentag, Temperatur und Aktionstagen.</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a><span class="co">Damit zerfaellt die Aufgabe in zwei Schritte, und genau an der Naht entsteht der</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a><span class="co">Fehler, um den es hier geht:</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a><span class="co"> PREDICT ein Modell schaetzt die Nachfrage</span></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a><span class="co"> OPTIMIZE daraus wird eine Bestellmenge</span></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a><span class="co">Der Prognostiker optimiert seinen Modellfehler, meist den MSE. Der Planer traegt</span></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a><span class="co">die Kosten. Beide messen etwas anderes - und die beiden Masse widersprechen</span></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a><span class="co">einander. Das Programm zeigt:</span></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a><span class="co"> 1. Vier Verfahren, verglichen nach MSE UND nach Entscheidungskosten. Das</span></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a><span class="co"> Verfahren mit dem BESTEN MSE hat die HOECHSTEN Kosten.</span></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a><span class="co"> 2. Warum ein pauschaler Sicherheitszuschlag zu kurz greift - die Streuung der</span></span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a><span class="co"> Nachfrage haengt selbst von den Merkmalen ab.</span></span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a><span class="co"> 3. Eine Messfalle, in die der Autor dieses Programms zuerst selbst getappt</span></span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a><span class="co"> ist: Bei kurzen Testzeitraeumen ist der MSE-Vergleich nicht stabil.</span></span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: numpy, scipy, scikit-learn</span></span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.stats <span class="im">import</span> norm</span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> sklearn.linear_model <span class="im">import</span> LinearRegression, QuantileRegressor</span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true" tabindex="-1"></a>VERKAUFSPREIS <span class="op">=</span> <span class="fl">9.0</span></span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true" tabindex="-1"></a>EINKAUFSPREIS <span class="op">=</span> <span class="fl">3.0</span></span>
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true" tabindex="-1"></a>KOSTEN_FEHLMENGE <span class="op">=</span> VERKAUFSPREIS <span class="op">-</span> EINKAUFSPREIS <span class="co"># entgangene Marge: 6 EUR</span></span>
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true" tabindex="-1"></a>KOSTEN_UEBERHANG <span class="op">=</span> EINKAUFSPREIS <span class="co"># Retoure: 3 EUR</span></span>
<span id="cb4-43"><a href="#cb4-43" aria-hidden="true" tabindex="-1"></a>KRITISCHES_VERHAELTNIS <span class="op">=</span> KOSTEN_FEHLMENGE <span class="op">/</span> (KOSTEN_FEHLMENGE <span class="op">+</span> KOSTEN_UEBERHANG)</span>
<span id="cb4-44"><a href="#cb4-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true" tabindex="-1"></a>TAGE <span class="op">=</span> <span class="dv">5000</span> <span class="co"># Simulation, siehe Hinweis unten</span></span>
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true" tabindex="-1"></a>TRAINING <span class="op">=</span> <span class="dv">1000</span></span>
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true" tabindex="-1"></a>SAAT <span class="op">=</span> <span class="dv">11</span></span>
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-49"><a href="#cb4-49" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-50"><a href="#cb4-50" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_daten(tage: <span class="bu">int</span> <span class="op">=</span> TAGE, saat: <span class="bu">int</span> <span class="op">=</span> SAAT):</span>
<span id="cb4-51"><a href="#cb4-51" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Taegliche Nachfrage mit Wochentag, Temperatur und Aktionstagen.</span></span>
<span id="cb4-52"><a href="#cb4-52" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-53"><a href="#cb4-53" aria-hidden="true" tabindex="-1"></a><span class="co"> Die entscheidende Eigenschaft steckt in &#39;streuung&#39;: An Aktionstagen ist die</span></span>
<span id="cb4-54"><a href="#cb4-54" aria-hidden="true" tabindex="-1"></a><span class="co"> Nachfrage nicht nur hoeher, sondern auch viel UNSICHERER. Solche</span></span>
<span id="cb4-55"><a href="#cb4-55" aria-hidden="true" tabindex="-1"></a><span class="co"> heteroskedastischen Daten sind der Normalfall - und der Grund, warum ein</span></span>
<span id="cb4-56"><a href="#cb4-56" aria-hidden="true" tabindex="-1"></a><span class="co"> pauschaler Sicherheitszuschlag nicht genuegt.</span></span>
<span id="cb4-57"><a href="#cb4-57" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb4-58"><a href="#cb4-58" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(saat)</span>
<span id="cb4-59"><a href="#cb4-59" aria-hidden="true" tabindex="-1"></a> wochentag <span class="op">=</span> np.arange(tage) <span class="op">%</span> <span class="dv">7</span></span>
<span id="cb4-60"><a href="#cb4-60" aria-hidden="true" tabindex="-1"></a> temperatur <span class="op">=</span> (<span class="dv">12</span> <span class="op">+</span> <span class="dv">10</span> <span class="op">*</span> np.sin(<span class="dv">2</span> <span class="op">*</span> np.pi <span class="op">*</span> np.arange(tage) <span class="op">/</span> <span class="dv">365</span>)</span>
<span id="cb4-61"><a href="#cb4-61" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> rng.normal(<span class="dv">0</span>, <span class="dv">3</span>, tage))</span>
<span id="cb4-62"><a href="#cb4-62" aria-hidden="true" tabindex="-1"></a> aktion <span class="op">=</span> (rng.random(tage) <span class="op">&lt;</span> <span class="fl">0.15</span>).astype(<span class="bu">float</span>)</span>
<span id="cb4-63"><a href="#cb4-63" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-64"><a href="#cb4-64" aria-hidden="true" tabindex="-1"></a> merkmale <span class="op">=</span> np.column_stack([np.eye(<span class="dv">7</span>)[wochentag][:, <span class="dv">1</span>:], temperatur, aktion])</span>
<span id="cb4-65"><a href="#cb4-65" aria-hidden="true" tabindex="-1"></a> erwartung <span class="op">=</span> (<span class="dv">120</span></span>
<span id="cb4-66"><a href="#cb4-66" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> np.eye(<span class="dv">7</span>)[wochentag] <span class="op">@</span> np.array([<span class="dv">0</span>, <span class="dv">10</span>, <span class="dv">12</span>, <span class="dv">14</span>, <span class="dv">18</span>, <span class="dv">35</span>, <span class="op">-</span><span class="dv">40</span>])</span>
<span id="cb4-67"><a href="#cb4-67" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="fl">1.8</span> <span class="op">*</span> temperatur <span class="op">+</span> <span class="dv">45</span> <span class="op">*</span> aktion)</span>
<span id="cb4-68"><a href="#cb4-68" aria-hidden="true" tabindex="-1"></a> streuung <span class="op">=</span> <span class="dv">8</span> <span class="op">+</span> <span class="dv">22</span> <span class="op">*</span> aktion</span>
<span id="cb4-69"><a href="#cb4-69" aria-hidden="true" tabindex="-1"></a> nachfrage <span class="op">=</span> np.maximum(<span class="fl">0.0</span>, erwartung <span class="op">+</span> rng.normal(<span class="dv">0</span>, <span class="dv">1</span>, tage) <span class="op">*</span> streuung)</span>
<span id="cb4-70"><a href="#cb4-70" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> merkmale, nachfrage, aktion</span>
<span id="cb4-71"><a href="#cb4-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-72"><a href="#cb4-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-73"><a href="#cb4-73" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> tageskosten(bestellung: np.ndarray, nachfrage: np.ndarray) <span class="op">-&gt;</span> <span class="bu">float</span>:</span>
<span id="cb4-74"><a href="#cb4-74" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Die Zahl, auf die es ankommt - und die kein Prognosemass kennt.&quot;&quot;&quot;</span></span>
<span id="cb4-75"><a href="#cb4-75" aria-hidden="true" tabindex="-1"></a> fehlmenge <span class="op">=</span> np.maximum(<span class="fl">0.0</span>, nachfrage <span class="op">-</span> bestellung)</span>
<span id="cb4-76"><a href="#cb4-76" aria-hidden="true" tabindex="-1"></a> ueberhang <span class="op">=</span> np.maximum(<span class="fl">0.0</span>, bestellung <span class="op">-</span> nachfrage)</span>
<span id="cb4-77"><a href="#cb4-77" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>((KOSTEN_FEHLMENGE <span class="op">*</span> fehlmenge</span>
<span id="cb4-78"><a href="#cb4-78" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> KOSTEN_UEBERHANG <span class="op">*</span> ueberhang).mean())</span>
<span id="cb4-79"><a href="#cb4-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-80"><a href="#cb4-80" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-81"><a href="#cb4-81" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb4-82"><a href="#cb4-82" aria-hidden="true" tabindex="-1"></a> merkmale, nachfrage, aktion <span class="op">=</span> erzeuge_daten()</span>
<span id="cb4-83"><a href="#cb4-83" aria-hidden="true" tabindex="-1"></a> lernen <span class="op">=</span> <span class="bu">slice</span>(<span class="dv">0</span>, TRAINING)</span>
<span id="cb4-84"><a href="#cb4-84" aria-hidden="true" tabindex="-1"></a> pruefen <span class="op">=</span> <span class="bu">slice</span>(TRAINING, TAGE)</span>
<span id="cb4-85"><a href="#cb4-85" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-86"><a href="#cb4-86" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb4-87"><a href="#cb4-87" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DIE BESSERE PROGNOSE TRIFFT DIE SCHLECHTERE ENTSCHEIDUNG&quot;</span>)</span>
<span id="cb4-88"><a href="#cb4-88" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb4-89"><a href="#cb4-89" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Verkaufspreis </span><span class="sc">{</span>VERKAUFSPREIS<span class="sc">:.0f}</span><span class="ss"> EUR, Einkauf </span><span class="sc">{</span>EINKAUFSPREIS<span class="sc">:.0f}</span><span class="ss"> EUR.&quot;</span>)</span>
<span id="cb4-90"><a href="#cb4-90" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Fehlmenge kostet </span><span class="sc">{</span>KOSTEN_FEHLMENGE<span class="sc">:.0f}</span><span class="ss"> EUR, Ueberhang &quot;</span></span>
<span id="cb4-91"><a href="#cb4-91" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>KOSTEN_UEBERHANG<span class="sc">:.0f}</span><span class="ss"> EUR je Stueck.&quot;</span>)</span>
<span id="cb4-92"><a href="#cb4-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kritisches Verhaeltnis: </span><span class="sc">{</span>KRITISCHES_VERHAELTNIS<span class="sc">:.3f}</span><span class="ss"> - der Planer sollte &quot;</span></span>
<span id="cb4-93"><a href="#cb4-93" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;also das&quot;</span>)</span>
<span id="cb4-94"><a href="#cb4-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>KRITISCHES_VERHAELTNIS<span class="sc">:.1%}</span><span class="ss">-Quantil der Nachfrage bestellen, nicht ihren &quot;</span></span>
<span id="cb4-95"><a href="#cb4-95" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Erwartungswert.&quot;</span>)</span>
<span id="cb4-96"><a href="#cb4-96" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss">Training: Tag 1 bis </span><span class="sc">{</span>TRAINING<span class="sc">}</span><span class="ss">. Bewertung: die restlichen &quot;</span></span>
<span id="cb4-97"><a href="#cb4-97" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>TAGE <span class="op">-</span> TRAINING<span class="sc">}</span><span class="ss"> Tage.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb4-98"><a href="#cb4-98" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-99"><a href="#cb4-99" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Die vier Verfahren ----------------------------------------------</span></span>
<span id="cb4-100"><a href="#cb4-100" aria-hidden="true" tabindex="-1"></a> kleinste_quadrate <span class="op">=</span> LinearRegression().fit(merkmale[lernen], nachfrage[lernen])</span>
<span id="cb4-101"><a href="#cb4-101" aria-hidden="true" tabindex="-1"></a> punktprognose <span class="op">=</span> kleinste_quadrate.predict(merkmale[pruefen])</span>
<span id="cb4-102"><a href="#cb4-102" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-103"><a href="#cb4-103" aria-hidden="true" tabindex="-1"></a> restfehler <span class="op">=</span> nachfrage[lernen] <span class="op">-</span> kleinste_quadrate.predict(merkmale[lernen])</span>
<span id="cb4-104"><a href="#cb4-104" aria-hidden="true" tabindex="-1"></a> pauschalzuschlag <span class="op">=</span> norm.ppf(KRITISCHES_VERHAELTNIS) <span class="op">*</span> restfehler.std()</span>
<span id="cb4-105"><a href="#cb4-105" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-106"><a href="#cb4-106" aria-hidden="true" tabindex="-1"></a> <span class="co"># Ein Zuschlag, der nicht aus der Normalverteilung kommt, sondern direkt</span></span>
<span id="cb4-107"><a href="#cb4-107" aria-hidden="true" tabindex="-1"></a> <span class="co"># auf den Trainingsdaten die Kosten minimiert.</span></span>
<span id="cb4-108"><a href="#cb4-108" aria-hidden="true" tabindex="-1"></a> kandidaten <span class="op">=</span> np.linspace(<span class="op">-</span><span class="fl">10.0</span>, <span class="fl">30.0</span>, <span class="dv">401</span>)</span>
<span id="cb4-109"><a href="#cb4-109" aria-hidden="true" tabindex="-1"></a> trainingsprognose <span class="op">=</span> kleinste_quadrate.predict(merkmale[lernen])</span>
<span id="cb4-110"><a href="#cb4-110" aria-hidden="true" tabindex="-1"></a> kostenzuschlag <span class="op">=</span> <span class="bu">float</span>(kandidaten[np.argmin(</span>
<span id="cb4-111"><a href="#cb4-111" aria-hidden="true" tabindex="-1"></a> [tageskosten(trainingsprognose <span class="op">+</span> z, nachfrage[lernen]) <span class="cf">for</span> z <span class="kw">in</span> kandidaten])])</span>
<span id="cb4-112"><a href="#cb4-112" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-113"><a href="#cb4-113" aria-hidden="true" tabindex="-1"></a> <span class="co"># Und das Verfahren, das von vornherein das richtige Quantil schaetzt.</span></span>
<span id="cb4-114"><a href="#cb4-114" aria-hidden="true" tabindex="-1"></a> quantilmodell <span class="op">=</span> QuantileRegressor(quantile<span class="op">=</span>KRITISCHES_VERHAELTNIS,</span>
<span id="cb4-115"><a href="#cb4-115" aria-hidden="true" tabindex="-1"></a> alpha<span class="op">=</span><span class="fl">0.0</span>, solver<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb4-116"><a href="#cb4-116" aria-hidden="true" tabindex="-1"></a> quantilmodell.fit(merkmale[lernen], nachfrage[lernen])</span>
<span id="cb4-117"><a href="#cb4-117" aria-hidden="true" tabindex="-1"></a> quantilprognose <span class="op">=</span> quantilmodell.predict(merkmale[pruefen])</span>
<span id="cb4-118"><a href="#cb4-118" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-119"><a href="#cb4-119" aria-hidden="true" tabindex="-1"></a> verfahren <span class="op">=</span> [</span>
<span id="cb4-120"><a href="#cb4-120" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;bestelle die Punktprognose&quot;</span>, punktprognose, punktprognose),</span>
<span id="cb4-121"><a href="#cb4-121" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;+ Zuschlag aus der Normalverteilung&quot;</span>,</span>
<span id="cb4-122"><a href="#cb4-122" aria-hidden="true" tabindex="-1"></a> punktprognose, punktprognose <span class="op">+</span> pauschalzuschlag),</span>
<span id="cb4-123"><a href="#cb4-123" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;+ Zuschlag auf Kosten trainiert&quot;</span>,</span>
<span id="cb4-124"><a href="#cb4-124" aria-hidden="true" tabindex="-1"></a> punktprognose, punktprognose <span class="op">+</span> kostenzuschlag),</span>
<span id="cb4-125"><a href="#cb4-125" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Quantilregression aufs kritische Quantil&quot;</span>,</span>
<span id="cb4-126"><a href="#cb4-126" aria-hidden="true" tabindex="-1"></a> quantilprognose, quantilprognose),</span>
<span id="cb4-127"><a href="#cb4-127" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb4-128"><a href="#cb4-128" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-129"><a href="#cb4-129" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Verfahren&#39;</span><span class="sc">:&lt;42}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;MSE&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Kosten/Tag&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;gegen Zeile 1&#39;</span><span class="sc">:&gt;14}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-130"><a href="#cb4-130" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">80</span>)</span>
<span id="cb4-131"><a href="#cb4-131" aria-hidden="true" tabindex="-1"></a> ergebnisse <span class="op">=</span> {}</span>
<span id="cb4-132"><a href="#cb4-132" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, prognose, bestellung <span class="kw">in</span> verfahren:</span>
<span id="cb4-133"><a href="#cb4-133" aria-hidden="true" tabindex="-1"></a> mse <span class="op">=</span> <span class="bu">float</span>(((prognose <span class="op">-</span> nachfrage[pruefen]) <span class="op">**</span> <span class="dv">2</span>).mean())</span>
<span id="cb4-134"><a href="#cb4-134" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> tageskosten(bestellung, nachfrage[pruefen])</span>
<span id="cb4-135"><a href="#cb4-135" aria-hidden="true" tabindex="-1"></a> ergebnisse[name] <span class="op">=</span> (mse, kosten)</span>
<span id="cb4-136"><a href="#cb4-136" aria-hidden="true" tabindex="-1"></a> basis <span class="op">=</span> ergebnisse[verfahren[<span class="dv">0</span>][<span class="dv">0</span>]][<span class="dv">1</span>]</span>
<span id="cb4-137"><a href="#cb4-137" aria-hidden="true" tabindex="-1"></a> vergleich <span class="op">=</span> <span class="st">&quot;&quot;</span> <span class="cf">if</span> name <span class="op">==</span> verfahren[<span class="dv">0</span>][<span class="dv">0</span>] <span class="cf">else</span> <span class="ss">f&quot;</span><span class="sc">{</span>(kosten <span class="op">-</span> basis) <span class="op">/</span> basis<span class="sc">:+13.1%}</span><span class="ss">&quot;</span></span>
<span id="cb4-138"><a href="#cb4-138" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;42}</span><span class="ss"> </span><span class="sc">{</span>mse<span class="sc">:&gt;9.1f}</span><span class="ss"> </span><span class="sc">{</span>kosten<span class="sc">:&gt;10.2f}</span><span class="ss"> EUR </span><span class="sc">{</span>vergleich<span class="sc">:&gt;14}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-139"><a href="#cb4-139" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-140"><a href="#cb4-140" aria-hidden="true" tabindex="-1"></a> bester_mse <span class="op">=</span> <span class="bu">min</span>(ergebnisse, key<span class="op">=</span><span class="kw">lambda</span> k: ergebnisse[k][<span class="dv">0</span>])</span>
<span id="cb4-141"><a href="#cb4-141" aria-hidden="true" tabindex="-1"></a> beste_kosten <span class="op">=</span> <span class="bu">min</span>(ergebnisse, key<span class="op">=</span><span class="kw">lambda</span> k: ergebnisse[k][<span class="dv">1</span>])</span>
<span id="cb4-142"><a href="#cb4-142" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> bester MSE: </span><span class="sc">{</span>bester_mse<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-143"><a href="#cb4-143" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; beste Kosten: </span><span class="sc">{</span>beste_kosten<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-144"><a href="#cb4-144" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Das Verfahren mit dem besten MSE hat die HOECHSTEN Kosten, und das&quot;</span>)</span>
<span id="cb4-145"><a href="#cb4-145" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Verfahren mit den besten Kosten hat einen um &quot;</span></span>
<span id="cb4-146"><a href="#cb4-146" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>(ergebnisse[beste_kosten][<span class="dv">0</span>] <span class="op">/</span> ergebnisse[bester_mse][<span class="dv">0</span>] <span class="op">-</span> <span class="dv">1</span>)<span class="sc">:.0%}</span><span class="ss"> SCHLECHTEREN MSE.&quot;</span>)</span>
<span id="cb4-147"><a href="#cb4-147" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Wer Prognosemodelle nach MSE auswaehlt, waehlt hier das falsche.&quot;</span>)</span>
<span id="cb4-148"><a href="#cb4-148" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-149"><a href="#cb4-149" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Warum der pauschale Zuschlag zu kurz greift ---------------------</span></span>
<span id="cb4-150"><a href="#cb4-150" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb4-151"><a href="#cb4-151" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Warum ein pauschaler Zuschlag nicht genuegt</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb4-152"><a href="#cb4-152" aria-hidden="true" tabindex="-1"></a> ist_aktion <span class="op">=</span> aktion[pruefen] <span class="op">&gt;</span> <span class="fl">0.5</span></span>
<span id="cb4-153"><a href="#cb4-153" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Verfahren&#39;</span><span class="sc">:&lt;42}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;normale Tage&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Aktionstage&#39;</span><span class="sc">:&gt;14}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-154"><a href="#cb4-154" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">74</span>)</span>
<span id="cb4-155"><a href="#cb4-155" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, _, bestellung <span class="kw">in</span> verfahren:</span>
<span id="cb4-156"><a href="#cb4-156" aria-hidden="true" tabindex="-1"></a> normal <span class="op">=</span> tageskosten(bestellung[<span class="op">~</span>ist_aktion], nachfrage[pruefen][<span class="op">~</span>ist_aktion])</span>
<span id="cb4-157"><a href="#cb4-157" aria-hidden="true" tabindex="-1"></a> aktionstag <span class="op">=</span> tageskosten(bestellung[ist_aktion], nachfrage[pruefen][ist_aktion])</span>
<span id="cb4-158"><a href="#cb4-158" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;42}</span><span class="ss"> </span><span class="sc">{</span>normal<span class="sc">:&gt;10.2f}</span><span class="ss"> EUR </span><span class="sc">{</span>aktionstag<span class="sc">:&gt;10.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb4-159"><a href="#cb4-159" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-160"><a href="#cb4-160" aria-hidden="true" tabindex="-1"></a> <span class="co"># Nachgerechnet statt behauptet: Welcher Zuschlag waere je Tagesart richtig?</span></span>
<span id="cb4-161"><a href="#cb4-161" aria-hidden="true" tabindex="-1"></a> aktion_training <span class="op">=</span> aktion[lernen] <span class="op">&gt;</span> <span class="fl">0.5</span></span>
<span id="cb4-162"><a href="#cb4-162" aria-hidden="true" tabindex="-1"></a> z <span class="op">=</span> norm.ppf(KRITISCHES_VERHAELTNIS)</span>
<span id="cb4-163"><a href="#cb4-163" aria-hidden="true" tabindex="-1"></a> richtig_normal <span class="op">=</span> z <span class="op">*</span> restfehler[<span class="op">~</span>aktion_training].std()</span>
<span id="cb4-164"><a href="#cb4-164" aria-hidden="true" tabindex="-1"></a> richtig_aktion <span class="op">=</span> z <span class="op">*</span> restfehler[aktion_training].std()</span>
<span id="cb4-165"><a href="#cb4-165" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Der pauschale Zuschlag betraegt </span><span class="sc">{</span>pauschalzuschlag<span class="sc">:.1f}</span><span class="ss"> Stueck. Aus den&quot;</span>)</span>
<span id="cb4-166"><a href="#cb4-166" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Trainingsresten getrennt nach Tagesart waere richtig:&quot;</span>)</span>
<span id="cb4-167"><a href="#cb4-167" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; normale Tage : </span><span class="sc">{</span>richtig_normal<span class="sc">:5.1f}</span><span class="ss"> Stueck&quot;</span>)</span>
<span id="cb4-168"><a href="#cb4-168" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Aktionstage : </span><span class="sc">{</span>richtig_aktion<span class="sc">:5.1f}</span><span class="ss"> Stueck&quot;</span>)</span>
<span id="cb4-169"><a href="#cb4-169" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Ein Zuschlag fuer alle Tage kann nur einen Mittelweg treffen - hier&quot;</span>)</span>
<span id="cb4-170"><a href="#cb4-170" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; ist er an normalen Tagen </span><span class="sc">{</span>pauschalzuschlag <span class="op">/</span> richtig_normal<span class="sc">:.1f}</span><span class="ss">-mal zu gross und an&quot;</span>)</span>
<span id="cb4-171"><a href="#cb4-171" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Aktionstagen nur </span><span class="sc">{</span>pauschalzuschlag <span class="op">/</span> richtig_aktion<span class="sc">:.0%}</span><span class="ss"> dessen, was noetig waere.&quot;</span>)</span>
<span id="cb4-172"><a href="#cb4-172" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Die Quantilregression schaetzt das </span><span class="sc">{</span>KRITISCHES_VERHAELTNIS<span class="sc">:.1%}</span><span class="ss">-Quantil &quot;</span></span>
<span id="cb4-173"><a href="#cb4-173" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;direkt aus den&quot;</span>)</span>
<span id="cb4-174"><a href="#cb4-174" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Merkmalen und darf deshalb an verschiedenen Tagen verschieden weit&quot;</span>)</span>
<span id="cb4-175"><a href="#cb4-175" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; ueber dem Erwartungswert liegen. Genau das ist der Unterschied&quot;</span>)</span>
<span id="cb4-176"><a href="#cb4-176" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; zwischen &#39;ein Modell und danach eine Formel&#39; und &#39;ein Modell, das&quot;</span>)</span>
<span id="cb4-177"><a href="#cb4-177" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; weiss, wofuer es gebraucht wird&#39;.&quot;</span>)</span>
<span id="cb4-178"><a href="#cb4-178" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-179"><a href="#cb4-179" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Die Messfalle ---------------------------------------------------</span></span>
<span id="cb4-180"><a href="#cb4-180" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb4-181"><a href="#cb4-181" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Eine Messfalle, in die der Autor zuerst selbst getappt ist</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb4-182"><a href="#cb4-182" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Der erste Entwurf dieses Programms bewertete auf 230 Testtagen - ein&quot;</span>)</span>
<span id="cb4-183"><a href="#cb4-183" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; realistischer Zeitraum. Dort hatte die Quantilregression den BESSEREN&quot;</span>)</span>
<span id="cb4-184"><a href="#cb4-184" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; MSE, und die ganze Aussage des Kapitels stand auf dem Kopf.&quot;</span>)</span>
<span id="cb4-185"><a href="#cb4-185" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Wie oft das passiert, laesst sich ausmessen:</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb4-186"><a href="#cb4-186" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(<span class="dv">0</span>)</span>
<span id="cb4-187"><a href="#cb4-187" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Testfenster&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;QR sieht MSE-besser aus&#39;</span><span class="sc">:&gt;26}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-188"><a href="#cb4-188" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">42</span>)</span>
<span id="cb4-189"><a href="#cb4-189" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> fenster <span class="kw">in</span> (<span class="dv">180</span>, <span class="dv">365</span>, <span class="dv">730</span>, <span class="dv">2000</span>):</span>
<span id="cb4-190"><a href="#cb4-190" aria-hidden="true" tabindex="-1"></a> treffer <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb4-191"><a href="#cb4-191" aria-hidden="true" tabindex="-1"></a> versuche <span class="op">=</span> <span class="dv">400</span></span>
<span id="cb4-192"><a href="#cb4-192" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> _ <span class="kw">in</span> <span class="bu">range</span>(versuche):</span>
<span id="cb4-193"><a href="#cb4-193" aria-hidden="true" tabindex="-1"></a> start <span class="op">=</span> <span class="bu">int</span>(rng.integers(TRAINING, TAGE <span class="op">-</span> fenster))</span>
<span id="cb4-194"><a href="#cb4-194" aria-hidden="true" tabindex="-1"></a> ausschnitt <span class="op">=</span> <span class="bu">slice</span>(start, start <span class="op">+</span> fenster)</span>
<span id="cb4-195"><a href="#cb4-195" aria-hidden="true" tabindex="-1"></a> mse_punkt <span class="op">=</span> ((kleinste_quadrate.predict(merkmale[ausschnitt])</span>
<span id="cb4-196"><a href="#cb4-196" aria-hidden="true" tabindex="-1"></a> <span class="op">-</span> nachfrage[ausschnitt]) <span class="op">**</span> <span class="dv">2</span>).mean()</span>
<span id="cb4-197"><a href="#cb4-197" aria-hidden="true" tabindex="-1"></a> mse_quantil <span class="op">=</span> ((quantilmodell.predict(merkmale[ausschnitt])</span>
<span id="cb4-198"><a href="#cb4-198" aria-hidden="true" tabindex="-1"></a> <span class="op">-</span> nachfrage[ausschnitt]) <span class="op">**</span> <span class="dv">2</span>).mean()</span>
<span id="cb4-199"><a href="#cb4-199" aria-hidden="true" tabindex="-1"></a> treffer <span class="op">+=</span> mse_quantil <span class="op">&lt;</span> mse_punkt</span>
<span id="cb4-200"><a href="#cb4-200" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>fenster<span class="sc">:&gt;10}</span><span class="ss"> Tage </span><span class="sc">{</span>treffer <span class="op">/</span> versuche<span class="sc">:&gt;24.1%}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-201"><a href="#cb4-201" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-202"><a href="#cb4-202" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Bei einem halben Jahr Testdaten sieht das schlechtere Modell in gut&quot;</span>)</span>
<span id="cb4-203"><a href="#cb4-203" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; jedem zehnten Fall besser aus. Das ist keine grosse Zahl - aber wer&quot;</span>)</span>
<span id="cb4-204"><a href="#cb4-204" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; EINMAL misst, hat genau eine Ziehung aus dieser Verteilung.&quot;</span>)</span>
<span id="cb4-205"><a href="#cb4-205" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Die Lehre ist nicht &#39;nimm 4.000 Testtage&#39; - die hat niemand. Sie&quot;</span>)</span>
<span id="cb4-206"><a href="#cb4-206" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; lautet: Ein Kennzahlenvergleich ohne Angabe seiner Streuung ist keine&quot;</span>)</span>
<span id="cb4-207"><a href="#cb4-207" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Aussage. Bei kurzen Zeitraeumen gehoert eine Kreuzvalidierung dazu.&quot;</span>)</span>
<span id="cb4-208"><a href="#cb4-208" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-209"><a href="#cb4-209" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb4-210"><a href="#cb4-210" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS MAN DARAUS MITNIMMT&quot;</span>)</span>
<span id="cb4-211"><a href="#cb4-211" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb4-212"><a href="#cb4-212" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Der Prognostiker optimiert den MSE, der Planer traegt die Kosten - und&quot;</span>)</span>
<span id="cb4-213"><a href="#cb4-213" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;die beiden Masse zeigen hier in verschiedene Richtungen. Drei Saetze:&quot;</span>)</span>
<span id="cb4-214"><a href="#cb4-214" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb4-215"><a href="#cb4-215" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; 1. Sagen Sie nicht den Erwartungswert vorher, sondern die Groesse, die&quot;</span>)</span>
<span id="cb4-216"><a href="#cb4-216" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; in die Entscheidung eingeht. Beim Newsvendor ist das das kritische&quot;</span>)</span>
<span id="cb4-217"><a href="#cb4-217" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Quantil - und das kann man direkt schaetzen.&quot;</span>)</span>
<span id="cb4-218"><a href="#cb4-218" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; 2. Bewerten Sie Prognosemodelle an den ENTSCHEIDUNGSKOSTEN. Die sind&quot;</span>)</span>
<span id="cb4-219"><a href="#cb4-219" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; in Euro und damit vergleichbar; ein MSE ist es nicht.&quot;</span>)</span>
<span id="cb4-220"><a href="#cb4-220" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; 3. Ein pauschaler Sicherheitszuschlag ist besser als nichts und&quot;</span>)</span>
<span id="cb4-221"><a href="#cb4-221" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; schlechter als ein Modell, das die Unsicherheit selbst aus den&quot;</span>)</span>
<span id="cb4-222"><a href="#cb4-222" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Merkmalen liest.&quot;</span>)</span>
<span id="cb4-223"><a href="#cb4-223" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb4-224"><a href="#cb4-224" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Der naechste Schritt - Prognosemodelle so zu trainieren, dass sie die&quot;</span>)</span>
<span id="cb4-225"><a href="#cb4-225" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Entscheidungskosten direkt minimieren (Smart Predict-then-Optimize,&quot;</span>)</span>
<span id="cb4-226"><a href="#cb4-226" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;differenzierbare Optimierungsschichten) - ist Forschungsstand und&quot;</span>)</span>
<span id="cb4-227"><a href="#cb4-227" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;erfordert Bibliotheken wie cvxpylayers. Die dritte Zeile der Tabelle&quot;</span>)</span>
<span id="cb4-228"><a href="#cb4-228" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;oben ist seine einfachste denkbare Form: ein einziger Parameter, auf&quot;</span>)</span>
<span id="cb4-229"><a href="#cb4-229" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Kosten statt auf Fehler trainiert.&quot;</span>)</span>
<span id="cb4-230"><a href="#cb4-230" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>====================================================================================
DIE BESSERE PROGNOSE TRIFFT DIE SCHLECHTERE ENTSCHEIDUNG
====================================================================================
Verkaufspreis 9 EUR, Einkauf 3 EUR.
Fehlmenge kostet 6 EUR, Ueberhang 3 EUR je Stueck.
Kritisches Verhaeltnis: 0.667 - der Planer sollte also das
66.7%-Quantil der Nachfrage bestellen, nicht ihren Erwartungswert.
Training: Tag 1 bis 1000. Bewertung: die restlichen 4000 Tage.
Verfahren MSE Kosten/Tag gegen Zeile 1
--------------------------------------------------------------------------------
bestelle die Punktprognose 201.0 42.63 EUR
+ Zuschlag aus der Normalverteilung 201.0 39.51 EUR -7.3%
+ Zuschlag auf Kosten trainiert 201.0 39.14 EUR -8.2%
Quantilregression aufs kritische Quantil 227.6 38.08 EUR -10.7%
bester MSE: bestelle die Punktprognose
beste Kosten: Quantilregression aufs kritische Quantil
Grafik-Upgrade Stufe 1: elf neue Diagramme Grafik_Upgrade.md verlangt "37 didaktisch wertvolle Visualisierungen". Es sind 19: Die Abschnitte "Modul 1-4" wiederholen die Grafiken 1-18 unter den Nummern 19-36, nur Grafik 37 ist zusaetzlich. Belegt an den Skriptnamen - 38 Nennungen, 19 verschiedene Dateien, erzeuge_kombinatorik_wand.py steht dreimal darin. Von den 19 betreffen neun bereits vorhandene Diagramme. Umgesetzt sind die zehn wirklich neuen plus eine elfte, die aus einem Sachfehler des Auftrags entstand: Grafik 14 sollte in Abschnitt 12.3 stehen, nannte aber Zahlen aus Kapitel 17. Beide Stellen haben jetzt ihre eigene Grafik mit ihren eigenen Zahlen. Vier Vorgaben des Auftrags wurden bewusst nicht befolgt: PNG bei 300 dpi (das Buch kennt nur SVG - die 33 verwaisten PNGs waren gerade erst geloescht worden), Matplotlib-Standardfarben (die Buchpalette steht in 33 Diagrammen), harte Kapitelnummern im Fliesstext ({ref:sec:...} stattdessen) und die fehlende Byte-Reproduzierbarkeit. Der eigentliche Ertrag waren die Selbsttests. Jeder Generator prueft seine Zahlen gegen das, was im Kapitel steht, und bricht bei Abweichung ab. Vier haben angeschlagen: * Kombinatorik-Wand: 25! ergibt 491,5 Mio. Jahre, die Tabelle nennt 490 Mio. - Rundung auf zwei signifikante Stellen, nicht Fehler des Buchs. * Laufzeit-Diagramm: Die abgedruckte Prozentspalte laesst sich aus den abgedruckten Zeiten nicht exakt nachrechnen (0,001/0,008 ergibt 12 %, gedruckt sind 14 %) - die Zeiten sind gerundet, die Prozente nicht. * Predict-then-Optimize: geratene Datenaufteilung ergab 197,35 statt 200,95 MSE. Mit TRAINING aus dem Buchprogramm stimmen alle vier Kostenwerte. * Almgren-Chriss: ein echter Fehler im Buchprogramm. DER FUND: analytische_loesung() minimiert die eigene Kostenfunktion nicht. Ihr Pfad kostet 10.860,40 EUR, der DP-Pfad 10.266,24 EUR - ein Gitterverfahren kann das kontinuierliche Optimum aber nicht unterbieten. periodenkosten() rechnet das Risiko mit lambda/2, die geschlossene Formel setzt kappa~^2 = lambda*sigma^2*P0^2/eta ohne dieses Halbe. Mit lambda/2 liefert die Formel 10.264,85 EUR und denselben Pfad wie die DP, knapp UNTER der Gitterloesung. Die Grafik zeigt bis zur Entscheidung darueber den DP-Pfad; er ist der, den das Kapitel abdruckt. Nachweise: Alle elf Diagramme sind ueber zwei Laeufe byte-identisch. Kein vorhandenes Diagramm wurde veraendert. Die Lastabwurf-Grafik reproduziert die Befundtabelle exakt (874.870/2.504.154/28 von 40 gegen 351.356/415.924/0). Zwei Abbildungen landeten beim automatischen Einfuegen im Codeblock (ihre Anker standen in abgedruckten Ausgaben) und wurden dahinter verschoben; eine Pruefung ueber alle elf zeigt null Fehlplatzierungen. Neu: bilder_04/stil_04.py buendelt Palette, rcParams und speichere(). networkx wurde entgegen der Planung nicht gebraucht - pyproject.toml bleibt unveraendert. PDF von 781 auf 788 Seiten, 44 Diagramme, 27 Generatoren. Veroeffentlicht: 25 Dateien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 01:11:33 +02:00
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
Das Verfahren mit dem besten MSE hat die HOECHSTEN Kosten, und das
Verfahren mit den besten Kosten hat einen um 13% SCHLECHTEREN MSE.
Wer Prognosemodelle nach MSE auswaehlt, waehlt hier das falsche.
------------------------------------------------------------------------------------
Warum ein pauschaler Zuschlag nicht genuegt
Verfahren normale Tage Aktionstage
--------------------------------------------------------------------------
bestelle die Punktprognose 29.67 EUR 108.59 EUR
+ Zuschlag aus der Normalverteilung 27.53 EUR 100.49 EUR
+ Zuschlag auf Kosten trainiert 26.67 EUR 102.58 EUR
Quantilregression aufs kritische Quantil 26.49 EUR 97.07 EUR
Der pauschale Zuschlag betraegt 5.9 Stueck. Aus den
Trainingsresten getrennt nach Tagesart waere richtig:
normale Tage : 3.5 Stueck
Aktionstage : 12.3 Stueck
Ein Zuschlag fuer alle Tage kann nur einen Mittelweg treffen - hier
ist er an normalen Tagen 1.7-mal zu gross und an
Aktionstagen nur 48% dessen, was noetig waere.
Die Quantilregression schaetzt das 66.7%-Quantil direkt aus den
Merkmalen und darf deshalb an verschiedenen Tagen verschieden weit
ueber dem Erwartungswert liegen. Genau das ist der Unterschied
zwischen &#39;ein Modell und danach eine Formel&#39; und &#39;ein Modell, das
weiss, wofuer es gebraucht wird&#39;.
------------------------------------------------------------------------------------
Eine Messfalle, in die der Autor zuerst selbst getappt ist
Der erste Entwurf dieses Programms bewertete auf 230 Testtagen - ein
realistischer Zeitraum. Dort hatte die Quantilregression den BESSEREN
MSE, und die ganze Aussage des Kapitels stand auf dem Kopf.
Wie oft das passiert, laesst sich ausmessen:
Testfenster QR sieht MSE-besser aus
------------------------------------------
180 Tage 11.8%
365 Tage 5.5%
730 Tage 0.0%
2000 Tage 0.0%
Bei einem halben Jahr Testdaten sieht das schlechtere Modell in gut
jedem zehnten Fall besser aus. Das ist keine grosse Zahl - aber wer
EINMAL misst, hat genau eine Ziehung aus dieser Verteilung.
Die Lehre ist nicht &#39;nimm 4.000 Testtage&#39; - die hat niemand. Sie
lautet: Ein Kennzahlenvergleich ohne Angabe seiner Streuung ist keine
Aussage. Bei kurzen Zeitraeumen gehoert eine Kreuzvalidierung dazu.
====================================================================================
WAS MAN DARAUS MITNIMMT
====================================================================================
Der Prognostiker optimiert den MSE, der Planer traegt die Kosten - und
die beiden Masse zeigen hier in verschiedene Richtungen. Drei Saetze:
1. Sagen Sie nicht den Erwartungswert vorher, sondern die Groesse, die
in die Entscheidung eingeht. Beim Newsvendor ist das das kritische
Quantil - und das kann man direkt schaetzen.
2. Bewerten Sie Prognosemodelle an den ENTSCHEIDUNGSKOSTEN. Die sind
in Euro und damit vergleichbar; ein MSE ist es nicht.
3. Ein pauschaler Sicherheitszuschlag ist besser als nichts und
schlechter als ein Modell, das die Unsicherheit selbst aus den
Merkmalen liest.
Der naechste Schritt - Prognosemodelle so zu trainieren, dass sie die
Entscheidungskosten direkt minimieren (Smart Predict-then-Optimize,
differenzierbare Optimierungsschichten) - ist Forschungsstand und
erfordert Bibliotheken wie cvxpylayers. Die dritte Zeile der Tabelle
oben ist seine einfachste denkbare Form: ein einziger Parameter, auf
Kosten statt auf Fehler trainiert.
====================================================================================</code></pre>
Grafik-Upgrade Stufe 1: elf neue Diagramme Grafik_Upgrade.md verlangt "37 didaktisch wertvolle Visualisierungen". Es sind 19: Die Abschnitte "Modul 1-4" wiederholen die Grafiken 1-18 unter den Nummern 19-36, nur Grafik 37 ist zusaetzlich. Belegt an den Skriptnamen - 38 Nennungen, 19 verschiedene Dateien, erzeuge_kombinatorik_wand.py steht dreimal darin. Von den 19 betreffen neun bereits vorhandene Diagramme. Umgesetzt sind die zehn wirklich neuen plus eine elfte, die aus einem Sachfehler des Auftrags entstand: Grafik 14 sollte in Abschnitt 12.3 stehen, nannte aber Zahlen aus Kapitel 17. Beide Stellen haben jetzt ihre eigene Grafik mit ihren eigenen Zahlen. Vier Vorgaben des Auftrags wurden bewusst nicht befolgt: PNG bei 300 dpi (das Buch kennt nur SVG - die 33 verwaisten PNGs waren gerade erst geloescht worden), Matplotlib-Standardfarben (die Buchpalette steht in 33 Diagrammen), harte Kapitelnummern im Fliesstext ({ref:sec:...} stattdessen) und die fehlende Byte-Reproduzierbarkeit. Der eigentliche Ertrag waren die Selbsttests. Jeder Generator prueft seine Zahlen gegen das, was im Kapitel steht, und bricht bei Abweichung ab. Vier haben angeschlagen: * Kombinatorik-Wand: 25! ergibt 491,5 Mio. Jahre, die Tabelle nennt 490 Mio. - Rundung auf zwei signifikante Stellen, nicht Fehler des Buchs. * Laufzeit-Diagramm: Die abgedruckte Prozentspalte laesst sich aus den abgedruckten Zeiten nicht exakt nachrechnen (0,001/0,008 ergibt 12 %, gedruckt sind 14 %) - die Zeiten sind gerundet, die Prozente nicht. * Predict-then-Optimize: geratene Datenaufteilung ergab 197,35 statt 200,95 MSE. Mit TRAINING aus dem Buchprogramm stimmen alle vier Kostenwerte. * Almgren-Chriss: ein echter Fehler im Buchprogramm. DER FUND: analytische_loesung() minimiert die eigene Kostenfunktion nicht. Ihr Pfad kostet 10.860,40 EUR, der DP-Pfad 10.266,24 EUR - ein Gitterverfahren kann das kontinuierliche Optimum aber nicht unterbieten. periodenkosten() rechnet das Risiko mit lambda/2, die geschlossene Formel setzt kappa~^2 = lambda*sigma^2*P0^2/eta ohne dieses Halbe. Mit lambda/2 liefert die Formel 10.264,85 EUR und denselben Pfad wie die DP, knapp UNTER der Gitterloesung. Die Grafik zeigt bis zur Entscheidung darueber den DP-Pfad; er ist der, den das Kapitel abdruckt. Nachweise: Alle elf Diagramme sind ueber zwei Laeufe byte-identisch. Kein vorhandenes Diagramm wurde veraendert. Die Lastabwurf-Grafik reproduziert die Befundtabelle exakt (874.870/2.504.154/28 von 40 gegen 351.356/415.924/0). Zwei Abbildungen landeten beim automatischen Einfuegen im Codeblock (ihre Anker standen in abgedruckten Ausgaben) und wurden dahinter verschoben; eine Pruefung ueber alle elf zeigt null Fehlplatzierungen. Neu: bilder_04/stil_04.py buendelt Palette, rcParams und speichere(). networkx wurde entgegen der Planung nicht gebraucht - pyproject.toml bleibt unveraendert. PDF von 781 auf 788 Seiten, 44 Diagramme, 27 Generatoren. Veroeffentlicht: 25 Dateien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 01:11:33 +02:00
<figure>
<img src="bilder_04/kap_prognose_entscheidungsguete.svg" alt="Abb. 15.1: Dieselben vier Verfahren, zwei Maßstäbe. Grün ist jeweils der beste Wert. Erzeugt von bilder_04/erzeuge_predict_vs_optimize.py." />
<figcaption aria-hidden="true">Abb. 15.1: Dieselben vier Verfahren, zwei Maßstäbe. Grün ist jeweils der beste Wert. Erzeugt von <code>bilder_04/erzeuge_predict_vs_optimize.py</code>.</figcaption>
</figure>
<h2 id="was-sie-in-der-abbildung-sehen.-links-und-rechts-gewinnt-nicht-dasselbe-verfahren.-die-punktprognose-hat-den-besten-prognosefehler-und-die-höchsten-kosten-beide-maßstäbe-messen-etwas-anderes-und-nur-einer-davon-steht-am-ende-in-der-bilanz."><strong>Was Sie in der Abbildung sehen.</strong> Links und rechts gewinnt nicht dasselbe Verfahren. Die Punktprognose hat den besten Prognosefehler und die höchsten Kosten — beide Maßstäbe messen etwas anderes, und nur einer davon steht am Ende in der Bilanz.</h2>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<h2 id="sec:prognose-befund">15.5 Der Befund</h2>
<table>
<colgroup>
<col style="width: 27%" />
<col style="width: 36%" />
<col style="width: 36%" />
</colgroup>
<thead>
<tr class="header">
<th>Verfahren</th>
<th style="text-align: right;">MSE</th>
<th style="text-align: right;">Kosten je Tag</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>bestelle die Punktprognose</td>
<td style="text-align: right;"><strong>201,0</strong> (bester)</td>
<td style="text-align: right;">42,63 € (schlechteste)</td>
</tr>
<tr class="even">
<td>+ Zuschlag aus der Normalverteilung</td>
<td style="text-align: right;">201,0</td>
<td style="text-align: right;">39,51 €</td>
</tr>
<tr class="odd">
<td>+ Zuschlag auf Kosten trainiert</td>
<td style="text-align: right;">201,0</td>
<td style="text-align: right;">39,14 €</td>
</tr>
<tr class="even">
<td>Quantilregression aufs kritische Quantil</td>
<td style="text-align: right;">227,6 (schlechtester)</td>
<td style="text-align: right;"><strong>38,08 €</strong> (beste)</td>
</tr>
</tbody>
</table>
<p><strong>Das Modell mit dem besten MSE hat die höchsten Kosten.</strong> Das kostenbeste Modell hat einen um 13 % schlechteren MSE — und spart 10,7 % der Kosten.</p>
<p>Wer Prognosemodelle nach MSE auswählt (und das ist die Voreinstellung jeder Modellauswahl-Bibliothek), wählt hier das falsche.</p>
<div class="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer: zwei Zielfunktionen</strong></p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Mathematik</th>
<th>Was sie belohnt</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">\min \sum_t (\hat{d}_t - d_t)^2</span></td>
<td>„Liege im Mittel richtig.” Abweichungen nach oben und unten zählen gleich, große Abweichungen überproportional.</td>
</tr>
<tr class="even">
<td><span class="math inline">\min \sum_t \big[c_-(d_t - q_t)^+ + c_+(q_t - d_t)^+\big]</span></td>
<td>„Sei lieber etwas zu großzügig als etwas zu knapp.” Beide Richtungen zählen <strong>verschieden</strong> und beide nur linear.</td>
</tr>
<tr class="odd">
<td><span class="math inline">(x)^+</span></td>
<td>„nur der positive Teil” — also <span class="math inline">\max(0, x)</span>.</td>
</tr>
<tr class="even">
<td><span class="math inline">c_- \ne c_+</span></td>
<td>Der ganze Grund, warum die beiden Optima auseinanderfallen.</td>
</tr>
</tbody>
</table>
<p>Wären <span class="math inline">c_-</span> und <span class="math inline">c_+</span> gleich groß, läge das Optimum beim <strong>Median</strong> — immer noch nicht beim Mittelwert, aber wenigstens in der Mitte. Erst die Asymmetrie schiebt es ans Quantil.</p>
</blockquote>
</div>
<h3 id="warum-ein-pauschaler-zuschlag-zu-kurz-greift">Warum ein pauschaler Zuschlag zu kurz greift</h3>
<p>Die zweite Zeile der Tabelle ist die verbreitete Praxis: Punktprognose plus Sicherheitsbestand. Sie hilft (7,3 %), bleibt aber hinter der Quantilregression zurück. Das Programm rechnet nach, warum:</p>
<table>
<thead>
<tr class="header">
<th></th>
<th style="text-align: right;">benötigter Zuschlag</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>normale Tage</td>
<td style="text-align: right;">3,5 Stück</td>
</tr>
<tr class="even">
<td>Aktionstage</td>
<td style="text-align: right;">12,3 Stück</td>
</tr>
<tr class="odd">
<td><strong>pauschal verwendet</strong></td>
<td style="text-align: right;"><strong>5,9 Stück</strong></td>
</tr>
</tbody>
</table>
<p>Ein einziger Wert kann nur einen Mittelweg treffen: An normalen Tagen ist er 1,7-mal zu groß, an Aktionstagen erreicht er nur 48 % dessen, was nötig wäre.</p>
<p>Die Quantilregression schätzt das 66,7-%-Quantil <strong>direkt aus den Merkmalen</strong> und darf deshalb an verschiedenen Tagen verschieden weit über dem Erwartungswert liegen. Das ist der Unterschied zwischen <em>„ein Modell und danach eine Formel”</em> und <em>„ein Modell, das weiß, wofür es gebraucht wird”</em>.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Sagen Sie nicht den Erwartungswert vorher, sondern <strong>die Größe, die in die Entscheidung eingeht</strong>. Beim Newsvendor ist das das kritische Quantil — und das lässt sich direkt schätzen, statt es nachträglich aus einer Punktprognose zu basteln.</p>
</blockquote>
<hr />
<h2 id="sec:prognose-messfalle">15.6 Eine Messfalle, in die der Autor selbst getappt ist</h2>
<p>Der erste Entwurf dieses Programms bewertete auf <strong>230 Testtagen</strong> — ein realistischer Zeitraum, gut sieben Monate. Dort hatte die Quantilregression den <em>besseren</em> MSE, und die ganze Aussage dieses Kapitels stand auf dem Kopf.</p>
<p>Das war kein Denkfehler, sondern Zufall. Wie viel Zufall, lässt sich ausmessen:</p>
<table>
<thead>
<tr class="header">
<th style="text-align: right;">Testfenster</th>
<th style="text-align: right;">Quantilregression sieht MSE-besser aus</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">180 Tage</td>
<td style="text-align: right;">11,8 %</td>
</tr>
<tr class="even">
<td style="text-align: right;">365 Tage</td>
<td style="text-align: right;">5,5 %</td>
</tr>
<tr class="odd">
<td style="text-align: right;">730 Tage</td>
<td style="text-align: right;">0,0 %</td>
</tr>
<tr class="even">
<td style="text-align: right;">2 000 Tage</td>
<td style="text-align: right;">0,0 %</td>
</tr>
</tbody>
</table>
<p>Bei einem halben Jahr Testdaten sieht das (bezogen auf den MSE) schlechtere Modell in gut jedem zehnten Fall besser aus. Das ist keine große Zahl — aber <strong>wer einmal misst, hat genau eine Ziehung aus dieser Verteilung.</strong></p>
<blockquote>
<p><strong>⚠️ Was daraus folgt — und was nicht</strong></p>
<p>Die Lehre ist <strong>nicht</strong> „nimm 4 000 Testtage”. Die hat niemand; das Programm arbeitet deshalb mit simulierten Daten und sagt das auch.</p>
<p>Die Lehre ist: <strong>Ein Kennzahlenvergleich ohne Angabe seiner Streuung ist keine Aussage.</strong> Bei kurzen Zeiträumen gehört eine Kreuzvalidierung dazu, und der Unterschied zwischen zwei Modellen muss größer sein als die Schwankung zwischen zwei Zeitfenstern.</p>
<p>Verwandt, aber nicht dasselbe: <a href="handelsmaschine.html#kap-handelsmaschine">Kapitel 21</a>, <code>Data_Snooping.py</code>. Dort geht es um die Zahl der <em>Versuche</em>, hier um die Zahl der <em>Beobachtungen</em>. Beide Male führt eine weggelassene Angabe zu einer Aussage, die nicht trägt.</p>
</blockquote>
<hr />
<h2 id="sec:prognose-ausblick">15.7 Wie weit das Verfahren reicht</h2>
<p>Die dritte Zeile der Ergebnistabelle — „Zuschlag auf Kosten trainiert” — ist die einfachste denkbare Form einer Idee, die derzeit erforscht wird: <strong>das Prognosemodell direkt auf die Entscheidungskosten zu trainieren</strong> statt auf ein Fehlermaß.</p>
<p>Hier war es ein einziger <a href="anhang-glossar.md#gloss:parameter" class="glossar-link">Parameter</a>, mit einer Rasterschleife bestimmt. Der allgemeine Fall ist schwieriger, weil die Optimierung zwischen Modell und Kosten steht: Um den Gradienten der Kosten nach den Modellparametern zu bilden, muss man <strong>durch das Optimierungsproblem hindurch ableiten</strong>.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Ansatz</th>
<th>Idee</th>
<th>Bibliothek</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Richtige Zielgröße</strong> (dieses Kapitel)</td>
<td>Schätze das Quantil, das die Entscheidung braucht</td>
<td><code>sklearn</code></td>
</tr>
<tr class="even">
<td><strong>SPO+</strong> (Elmachtoub/Grigas)</td>
<td>Ein Ersatzverlust, der die Entscheidungskosten nach oben abschätzt und konvex ist</td>
<td></td>
</tr>
<tr class="odd">
<td><strong>Differenzierbare Optimierung</strong></td>
<td>Das Optimierungsproblem wird eine Schicht im neuronalen Netz</td>
<td><code>cvxpylayers</code></td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>💡 Womit man anfängt</strong></p>
<p>Nicht mit <code>cvxpylayers</code>. Der Ertrag der ersten Zeile ist in der Praxis meist der größte und kostet einen Nachmittag: Fragen Sie, welche Größe die Entscheidung wirklich braucht, und schätzen Sie diese. In diesem Kapitel bringt das 10,7 % — die aufwendigen Verfahren ringen danach um die letzten Prozentpunkte.</p>
</blockquote>
<hr />
<h2 id="sec:prognose-uebungsaufgaben">15.8 Übungsaufgaben</h2>
<blockquote>
<p>Lösungen: <a href="anhang-loesungen.html#sec:loesungen-prognose">Abschnitt A.15</a>.</p>
</blockquote>
<p><strong>Aufgabe 15.1 ⭐ — Andere Preise.</strong> Die Bäckerei kann übrige Ware am Folgetag zum halben Preis abgeben; der Überhang kostet dann nur noch 1,50 € statt 3 €. Wie ändert sich das kritische Verhältnis, und in welche Richtung verschiebt sich die Bestellmenge?</p>
<p><strong>Aufgabe 15.2 ⭐ — Wann ist der Mittelwert richtig?</strong> Nennen Sie die Bedingung, unter der Erwartungswert und optimale Bestellmenge zusammenfallen. Wie realistisch ist sie?</p>
<p><strong>Aufgabe 15.3 ⭐⭐ — Die Kennzahl der Prognoseabteilung.</strong> Ersetzen Sie im Programm den MSE durch den MAPE (mittlerer absoluter prozentualer Fehler). Ändert sich die Rangfolge der vier Verfahren? Begründen Sie, warum das kein Zufall ist.</p>
<p><strong>Aufgabe 15.4 ⭐⭐ — Zwei Quantile.</strong> Schätzen Sie zusätzlich das 5-%- und das 95-%-Quantil und zeichnen Sie für 30 Testtage ein Band um die Punktprognose. An welchen Tagen ist es breit — und passt das zu Ihrer Erwartung?</p>
<p><strong>Aufgabe 15.5 ⭐⭐⭐ — Der Wert der Merkmale.</strong> Entfernen Sie das Merkmal „Aktion” aus dem Modell. Wie verschlechtern sich MSE und Kosten — und zwar <strong>in unterschiedlichem Ausmaß</strong>? Was sagt das über den Wert eines Merkmals aus?</p>
<hr />
<h2 id="sec:prognose-denkfehler">15.9 Finde den Denkfehler</h2>
<div class="card card-denkfehler">
<blockquote>
<p><strong>🐛 „Wir haben die Prognose um 18 % verbessert”</strong></p>
<p>Ein Data-Science-Team stellt sein Quartalsergebnis vor:</p>
<blockquote>
<p><em>„Wir haben das alte lineare Modell durch ein <a href="anhang-glossar.md#gloss:gradient" class="glossar-link">Gradient</a>-Boosting-Modell ersetzt. Der MSE &gt; auf den Testdaten ist um 18 % gesunken, der MAPE um 14 %. Das ist die größte &gt; Prognoseverbesserung, die wir je erreicht haben. Die Disposition bekommt ab nächstem &gt; Monat die neuen Werte.”</em></p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</blockquote>
<p>Sechs Wochen später meldet die Disposition, die Retouren seien gestiegen.</p>
<p>Das Team prüft alles nach: Die Modellgüte stimmt, die Testdaten waren sauber getrennt, es gibt kein Leck. Das neue Modell prognostiziert die Nachfrage tatsächlich deutlich besser als das alte.</p>
<p><strong>Wie kann eine bessere Prognose zu schlechteren Ergebnissen führen — und was hätte das Team messen müssen?</strong></p>
<p>Ein zweiter Hinweis für den zweiten Teil der Antwort: Die Disposition rechnet mit Punktprognose plus einem Sicherheitszuschlag, der aus den <strong>Residuen des alten Modells</strong> stammt und seit Jahren unverändert ist.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:prognose-quiz">15.10 Micro-Quiz</h2>
<div class="card card-quiz">
<blockquote>
<p><strong>❓ Drei Fragen</strong></p>
<p><strong>1. Warum ist der Erwartungswert beim Newsvendor die falsche Bestellmenge?</strong> a) Weil er statistisch unzuverlässig geschätzt wird. b) Weil die Kosten für Fehlmenge und Überhang verschieden hoch sind und das Optimum deshalb am kritischen Quantil liegt. c) Weil die Nachfrage nicht normalverteilt ist.</p>
<p><strong>2. Ein Kollege schlägt vor, den Sicherheitszuschlag jährlich aus den Residuen neu zu berechnen. Was löst das nicht?</strong> a) Nichts — das ist die richtige Lösung. b) Der Zuschlag bleibt für alle Tage gleich, obwohl die Unsicherheit von den Merkmalen abhängt. c) Residuen sind grundsätzlich ungeeignet, um Unsicherheit zu schätzen.</p>
<p><strong>3. Bei 180 Testtagen sieht das MSE-schlechtere Modell in 11,8 % der Fälle besser aus. Was folgt daraus für die Praxis?</strong> a) Man braucht mindestens 730 Testtage, sonst ist jeder Vergleich wertlos. b) Ein einzelner Kennzahlenvergleich ohne Streuungsangabe ist keine belastbare Aussage; bei kurzen Zeiträumen gehört eine Kreuzvalidierung dazu. c) Der MSE ist grundsätzlich unbrauchbar.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:prognose-selbsttest">15.11 Selbsttest</h2>
<ol type="1">
<li>Erklären Sie in zwei Sätzen, warum „predict then optimize” eine Naht hat, an der Information verlorengeht.</li>
<li>Welche Größe muss ein Prognosemodell liefern, wenn die Entscheidung ein Newsvendor ist — und woher kennen Sie den Wert?</li>
<li>Warum ist ein pauschaler Sicherheitszuschlag schwächer als eine Quantilregression? Nennen Sie die Dateneigenschaft, um die es geht.</li>
<li>Ihre Prognoseabteilung meldet 18 % besseren MSE. Welche eine Frage stellen Sie?</li>
<li>Wie viele Testdaten braucht ein Modellvergleich? Formulieren Sie die Antwort ohne eine konkrete Zahl.</li>
</ol>
<hr />
<h2 id="sec:prognose-zusammenfassung">15.12 Zusammenfassung</h2>
<ul>
<li><strong>Predict-then-Optimize</strong> trennt Schätzen und Entscheiden. Die erste Stufe wird auf ein statistisches Maß trainiert, die zweite erzeugt ökonomische Kosten — und die beiden Maße können in verschiedene Richtungen zeigen.</li>
<li>Im Beispiel hat das Verfahren mit dem <strong>besten MSE die höchsten Kosten</strong>; das kostenbeste Verfahren hat einen um 13 % schlechteren MSE und spart 10,7 %.</li>
<li>Der Grund ist die <strong>Asymmetrie</strong> der Kosten: Fehlmenge und Überhang kosten verschieden viel, deshalb liegt das Optimum am kritischen Quantil und nicht am Erwartungswert.</li>
<li>Ein <strong>pauschaler Sicherheitszuschlag</strong> hilft, greift aber zu kurz, wenn die Unsicherheit selbst von den Merkmalen abhängt: Hier wäre er an normalen Tagen 1,7-mal zu groß und an Aktionstagen halb so groß wie nötig.</li>
<li><strong>Bewerten Sie Prognosemodelle an den Entscheidungskosten.</strong> Sie sind in Euro und damit vergleichbar; ein MSE ist es nicht.</li>
<li>Ein Kennzahlenvergleich braucht eine <strong>Streuungsangabe</strong>. Bei 180 Testtagen sah das schlechtere Modell in gut jedem zehnten Fall besser aus — und wer einmal misst, hat genau eine Ziehung.</li>
</ul>
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="prognose.html"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg> <span>Als gelesen markieren</span></button>
Phase 8.1: Synthese-Seiten je Teil - und die gebrochene Lesekette Setzt Paket 5 aus Verbesserungen_02.md um (den Teil, der nicht zurueckgestellt wurde). Fuenf neue Dateien 19_/29_/39_/49_/52_Synthese_*.md, je eine am Ende eines Teils, mit eigener Website-Seite ueber SONDERSEITEN - sie tragen bewusst keine "# Kapitel:"-Ueberschrift, weil sie keine Kapitel sind, sondern der Rueckblick auf einen Teil. Der Entwurf musste sich abgrenzen: Die Teil-Einleitungen haben bereits Entscheidungsdiagramme. Eine zweite Matrix am Teil-Ende waere eine Dopplung gewesen. Die Synthesen leisten deshalb, was eine Einleitung nicht kann - den Vergleich ueber die Kapitel hinweg (Verfahren nebeneinander, mit der Spalte "wo es aufhoert"), eine Tabelle "was dieser Teil gemessen hat" (Behauptung gegen Messung gegen Fundstelle) und drei Fehler, die der Teil verhindert. Zitiert wird ausschliesslich, was im Buch tatsaechlich gerechnet wird. Drei Funde beim Einbau: * Teil III sagte "die drei Kapitel dieses Teils", hat aber fuenf. Phase 3 hatte Mehrziel und Predict-then-Optimize hinzugefuegt, die Einleitung blieb stehen. * 50_Praxis.md verwies auf die Projektwerkstatt mit "acht eigene Anwendungen" - sie hat elf. * Und der eigentliche Fund: Die Lesekette der Quelldateien fuehrte an ACHT Kapiteln vorbei. 12_Python_Oekosystem zeigte direkt auf 20_Lineare_Programmierung, 23_Graphen direkt auf 30_QP, 32_Dynamische direkt auf 40_Finanzdaten, 50_Praxis direkt auf die Projektwerkstatt. Wer der Kette folgte, uebersprang acht von 23 Kapiteln - darunter Metaheuristiken, Spaltengenerierung, Strukturbruecke, Supply-Chain und das ganze Testing-Kapitel. Zehn weitere Dateien hatten gar keine Navigationszeile. Zur Reichweite, damit sie nicht ueberschaetzt wird: Diese Zeilen stehen nur in den Quelldateien. entferne_navigation() streicht sie aus dem Gesamtdokument, und die Website baut ihre Vor/Zurueck-Knoepfe selbst aus DATEIEN. PDF und Website waren nie betroffen - wohl aber jeder, der die Markdown-Dateien im Repository liest, und das wird nach der Veroeffentlichung der Normalfall sein. Die Kette ist jetzt ueber alle 35 Uebergaenge geschlossen, und --check bewacht sie: Fehlt eine Zeile oder zeigt sie an der in DATEIEN folgenden Datei vorbei, ist der Lauf rot. Gegengetestet mit beiden Bruchformen. Stand: 36 Dateien, 296 Abschnitte, 815 Querverweise, 328 Indexmarken, 76 Programme (unveraendert), 33 pytest-Tests, PDF 758 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:10:02 +02:00
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="mehrziel.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 14: Mehrere Ziele — Pareto-Fronten statt Gewichte</span></a><a class="prev-next-knopf prev-next-next" href="synthese-nichtlinear.html"><span><small>Weiter</small>Synthese Teil III</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</main>
</div>
<footer class="site-footer">
<p>© Dieter Schlüter · <a href="gesamtdokument.html">Gesamtdokument</a> ·
<a href="programme.html">Beispielprogramme</a></p>
</footer>
<script defer src="assets/search-index.js"></script>
<script defer src="assets/programme.js"></script>
<script defer src="assets/site.js"></script>
</body>
</html>