Warum dieser Schritt jetzt kommt
Nach der Theorie braucht ihr eine vernuenftige Projekt-Routine. Ein Modell ist erst dann hilfreich, wenn es auf neuen Daten stabil ist, transparent bewertet wird und ihr die Grenzen benennen koennt.
1) Train/Test-Split: warum wir fast nie "alle Wahrheit" sehen
In der Praxis arbeiten wir mit Stichproben. Darum sind Metriken immer Schaetzungen. Mit Train/Test trennt ihr Lernen und Pruefen sauber.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
def make_demo_df(seed=42, n_samples=320):
rng = np.random.default_rng(seed)
df = pd.DataFrame({
"marketing_spend": rng.normal(120, 25, size=n_samples).clip(40, 220),
"price_index": rng.normal(1.0, 0.12, size=n_samples).clip(0.7, 1.3),
"season_index": rng.integers(0, 4, size=n_samples),
"web_traffic": rng.normal(3200, 700, size=n_samples).clip(1200, 6000)
})
noise = rng.normal(0, 9, size=n_samples)
df["target_demand"] = (
55
+ 0.22 * df["marketing_spend"]
- 28 * df["price_index"]
+ 5.5 * df["season_index"]
+ 0.012 * df["web_traffic"]
+ noise
)
return df
# Standalone-Block
df = make_demo_df()
X = df.drop(columns=["target_demand"])
y = df["target_demand"]
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
shuffle=True
)
print("Train shape:", X_train.shape)
print("Test shape:", X_test.shape)
Der Trainingsfehler allein ist keine Aussage ueber echte Prognosefaehigkeit. Wir brauchen den Fehler auf bisher ungesehenen Daten.
Mini-Check: Warum reicht ein einziger Split manchmal nicht?
Ein einzelner Split kann zufaellig guenstig oder unguenstig sein. Fuer stabilere Aussagen nutzt man oft Cross-Validation oder mehrere Splits.
2) Baseline und Metriken: MSE vor R² fuer Modellvergleich
Beim Vergleich von Vorhersagemodellen ist MSE/MAE oft robuster interpretierbar. R² kann negativ werden und in der Kommunikation missverstanden werden.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
def make_demo_df(seed=42, n_samples=320):
rng = np.random.default_rng(seed)
df = pd.DataFrame({
"marketing_spend": rng.normal(120, 25, size=n_samples).clip(40, 220),
"price_index": rng.normal(1.0, 0.12, size=n_samples).clip(0.7, 1.3),
"season_index": rng.integers(0, 4, size=n_samples),
"web_traffic": rng.normal(3200, 700, size=n_samples).clip(1200, 6000)
})
noise = rng.normal(0, 9, size=n_samples)
df["target_demand"] = (
55
+ 0.22 * df["marketing_spend"]
- 28 * df["price_index"]
+ 5.5 * df["season_index"]
+ 0.012 * df["web_traffic"]
+ noise
)
return df
# Standalone-Block
df = make_demo_df()
X = df.drop(columns=["target_demand"])
y = df["target_demand"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, shuffle=True
)
baseline = LinearRegression()
baseline.fit(X_train, y_train)
pred_train = baseline.predict(X_train)
pred_test = baseline.predict(X_test)
print("Train MSE:", round(mean_squared_error(y_train, pred_train), 3))
print("Test MSE:", round(mean_squared_error(y_test, pred_test), 3))
print("Test MAE:", round(mean_absolute_error(y_test, pred_test), 3))
print("Test R2 :", round(r2_score(y_test, pred_test), 3))
Mini-Check: Warum nicht nur den kleinsten Trainingsfehler nehmen?
Weil ein Modell Trainingsdaten auswendig lernen kann. Entscheidend ist der Testfehler als Naeherung fuer Generalisierung.
3) L1/L2 in Praxis: Lasso vs Ridge mit Parameter-Tuning
Jetzt wird die Modellkomplexitaet bewusst gesteuert: Ridge (L2) schrumpft Gewichte weich, Lasso (L1) kann Features auf 0 setzen.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge, Lasso
from sklearn.metrics import mean_squared_error
def make_demo_df(seed=42, n_samples=320):
rng = np.random.default_rng(seed)
df = pd.DataFrame({
"marketing_spend": rng.normal(120, 25, size=n_samples).clip(40, 220),
"price_index": rng.normal(1.0, 0.12, size=n_samples).clip(0.7, 1.3),
"season_index": rng.integers(0, 4, size=n_samples),
"web_traffic": rng.normal(3200, 700, size=n_samples).clip(1200, 6000)
})
noise = rng.normal(0, 9, size=n_samples)
df["target_demand"] = (
55
+ 0.22 * df["marketing_spend"]
- 28 * df["price_index"]
+ 5.5 * df["season_index"]
+ 0.012 * df["web_traffic"]
+ noise
)
return df
# Standalone-Block
df = make_demo_df()
X = df.drop(columns=["target_demand"])
y = df["target_demand"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, shuffle=True
)
ridge_pipe = Pipeline([
("scale", StandardScaler()),
("model", Ridge())
])
lasso_pipe = Pipeline([
("scale", StandardScaler()),
("model", Lasso(max_iter=10000))
])
param_grid = {"model__alpha": [0.01, 0.1, 1.0, 3.0, 10.0]}
ridge_cv = GridSearchCV(ridge_pipe, param_grid=param_grid, scoring="neg_mean_squared_error", cv=5)
lasso_cv = GridSearchCV(lasso_pipe, param_grid=param_grid, scoring="neg_mean_squared_error", cv=5)
ridge_cv.fit(X_train, y_train)
lasso_cv.fit(X_train, y_train)
print("Best Ridge alpha:", ridge_cv.best_params_["model__alpha"])
print("Best Lasso alpha:", lasso_cv.best_params_["model__alpha"])
print("Ridge Test MSE:", round(mean_squared_error(y_test, ridge_cv.predict(X_test)), 3))
print("Lasso Test MSE:", round(mean_squared_error(y_test, lasso_cv.predict(X_test)), 3))
Ridge, wenn viele schwache Signale gemeinsam nuetzlich sind. Lasso, wenn ihr Feature-Selektion und ein kompakteres Modell wollt.
Mini-Check: Warum braucht Lasso oft Standardisierung?
L1-Strafe haengt von der Koeffizientengroesse ab. Ohne vergleichbare Feature-Skalen wird die Strafe ungleich verteilt.
4) Residuen-Check: Muster erkennen statt nur Score lesen
Ein guter MSE ist wichtig, aber nicht genug. Residuen zeigen euch, ob systematische Fehler uebrig bleiben (z. B. gekruemmte Muster, heterogene Varianz).
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge
def make_demo_df(seed=42, n_samples=320):
rng = np.random.default_rng(seed)
df = pd.DataFrame({
"marketing_spend": rng.normal(120, 25, size=n_samples).clip(40, 220),
"price_index": rng.normal(1.0, 0.12, size=n_samples).clip(0.7, 1.3),
"season_index": rng.integers(0, 4, size=n_samples),
"web_traffic": rng.normal(3200, 700, size=n_samples).clip(1200, 6000)
})
noise = rng.normal(0, 9, size=n_samples)
df["target_demand"] = (
55
+ 0.22 * df["marketing_spend"]
- 28 * df["price_index"]
+ 5.5 * df["season_index"]
+ 0.012 * df["web_traffic"]
+ noise
)
return df
# Standalone-Block
df = make_demo_df()
X = df.drop(columns=["target_demand"])
y = df["target_demand"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, shuffle=True
)
ridge_pipe = Pipeline([
("scale", StandardScaler()),
("model", Ridge())
])
param_grid = {"model__alpha": [0.01, 0.1, 1.0, 3.0, 10.0]}
ridge_cv = GridSearchCV(ridge_pipe, param_grid=param_grid, scoring="neg_mean_squared_error", cv=5)
ridge_cv.fit(X_train, y_train)
best_model = ridge_cv.best_estimator_
y_pred = best_model.predict(X_test)
residuals = y_test - y_pred
plt.figure(figsize=(7, 4))
plt.axhline(0, color="black", linewidth=1)
plt.scatter(y_pred, residuals, alpha=0.7)
plt.xlabel("Vorhersage y-hat")
plt.ylabel("Residuum (y - y-hat)")
plt.title("Residualplot")
plt.show()
print("Residual-Mittelwert:", round(float(np.mean(residuals)), 4))
Mini-Check: Was bedeutet ein U-foermiges Residuenmuster?
Das lineare Modell erfasst den Zusammenhang nicht vollstaendig. Es fehlt oft eine nichtlineare Komponente oder ein wichtiges Feature.
5) Ausreisser mit z-Score erkennen (erste einfache Stufe)
Outlier-Kontrolle gehoert in jeden Predictive-Workflow. Als Einstieg hilft der z-Score pro numerischem Feature.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
def make_demo_df(seed=42, n_samples=320):
rng = np.random.default_rng(seed)
df = pd.DataFrame({
"marketing_spend": rng.normal(120, 25, size=n_samples).clip(40, 220),
"price_index": rng.normal(1.0, 0.12, size=n_samples).clip(0.7, 1.3),
"season_index": rng.integers(0, 4, size=n_samples),
"web_traffic": rng.normal(3200, 700, size=n_samples).clip(1200, 6000)
})
noise = rng.normal(0, 9, size=n_samples)
df["target_demand"] = (
55
+ 0.22 * df["marketing_spend"]
- 28 * df["price_index"]
+ 5.5 * df["season_index"]
+ 0.012 * df["web_traffic"]
+ noise
)
return df
# Standalone-Block
df = make_demo_df()
X = df.drop(columns=["target_demand"])
y = df["target_demand"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, shuffle=True
)
numeric_cols = X_train.select_dtypes(include=["number"]).columns
z = (X_train[numeric_cols] - X_train[numeric_cols].mean()) / X_train[numeric_cols].std(ddof=0)
# einfache Regel: |z| > 3 als potenzieller Ausreisser
outlier_mask = (np.abs(z) > 3).any(axis=1)
outlier_count = int(outlier_mask.sum())
print("Potenziell auffaellige Zeilen im Train-Set:", outlier_count)
Mini-Check: Welche Outlier-Typen solltet ihr unterscheiden?
Messfehler, seltene aber reale Ereignisse und Datenpunkte ausserhalb des gueltigen Einsatzbereichs. Die Behandlung ist je Typ unterschiedlich.
6) `fit`, `transform`, `fit_transform` richtig einsetzen
Viele Fehler in Einsteigerprojekten entstehen beim Preprocessing. Die goldene Regel: auf Train fitten, auf Test nur transformieren.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
def make_demo_df(seed=42, n_samples=320):
rng = np.random.default_rng(seed)
df = pd.DataFrame({
"marketing_spend": rng.normal(120, 25, size=n_samples).clip(40, 220),
"price_index": rng.normal(1.0, 0.12, size=n_samples).clip(0.7, 1.3),
"season_index": rng.integers(0, 4, size=n_samples),
"web_traffic": rng.normal(3200, 700, size=n_samples).clip(1200, 6000)
})
noise = rng.normal(0, 9, size=n_samples)
df["target_demand"] = (
55
+ 0.22 * df["marketing_spend"]
- 28 * df["price_index"]
+ 5.5 * df["season_index"]
+ 0.012 * df["web_traffic"]
+ noise
)
return df
# Standalone-Block
df = make_demo_df()
X = df.drop(columns=["target_demand"])
y = df["target_demand"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, shuffle=True
)
scaler = StandardScaler()
# richtig:
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print("Scaled train shape:", X_train_scaled.shape)
print("Scaled test shape:", X_test_scaled.shape)
# fit: lernt Parameter (z. B. Mittelwert/Std)
# transform: wendet gelernte Parameter an
# fit_transform: beides in einem Schritt
Wenn ihr auf Testdaten fitten wuerdet, fliesst Testinformation ins Training (Data Leakage). Dann wirken Metriken besser als sie real sind.
Mini-Check: Warum helfen Pipelines?
Sie erzwingen die richtige Reihenfolge von Preprocessing und Modell und reduzieren Leakage-Risiko in Training und Cross-Validation.