Warum diese Seite jetzt wichtig ist
Viele Einsteiger verwechseln lineare Regression, Training und Regularisierung. Hier trennen wir die Schritte klar: Loss definieren, Optimierer auswählen, Parameter iterativ verbessern, Ergebnis auf neue Daten denken.
1) Recap: Was bedeutet "Modell trainieren"?
Für lineare Regression suchen wir Parameter, die den Fehler minimieren. Geschlossen geht das über die Normal Equation, iterativ über Gradient Descent.
In Python startet `model.fit(X_train, y_train)` den Trainingsprozess. Unter der Haube wird je nach Modell/Implementierung ein passender Solver genutzt.
# optionales Setup
python3.11 -m venv .venv
source .venv/bin/activate
pip install numpy matplotlib
Mini-Check: Warum Train/Test-Split vor dem finalen Bewerten?
Damit du nicht nur Trainingsanpassung misst, sondern Generalisierung auf ungesehenen Daten.
2) Gradient Descent für eine kleine Funktion
Wir starten mit einer 1D-Loss-Funktion. So wird sichtbar, wie Lernrate und Gradientenrichtung zusammenwirken.
import numpy as np
# synthetische Loss-Funktion
# J(w) = 0.1*(w-2.5)^4 + 0.95*(w-2.5)^2 + 4
def loss(w):
d = w - 2.5
return 0.1 * d**4 + 0.95 * d**2 + 4
def grad(w):
d = w - 2.5
return 0.4 * d**3 + 1.9 * d
w = 7.0
alpha = 0.25
history = [w]
for _ in range(25):
w = w - alpha * grad(w)
history.append(w)
print("final w:", round(w, 4))
print("final loss:", round(loss(w), 4))
Mini-Check: Warum laufen wir entgegen dem Gradienten?
Der Gradient zeigt Richtung stärkster Zunahme der Loss. Für Minimierung gehen wir in die entgegengesetzte Richtung.
3) Loss-Verbesserung über Iterationen visualisieren
Jetzt machen wir den Lernverlauf sichtbar. Das hilft didaktisch enorm, um Konvergenz zu erklären.
import numpy as np
import matplotlib.pyplot as plt
# Standalone-Block: baut den Verlauf lokal neu auf
def loss(w):
d = w - 2.5
return 0.1 * d**4 + 0.95 * d**2 + 4
def grad(w):
d = w - 2.5
return 0.4 * d**3 + 1.9 * d
w = 7.0
alpha = 0.25
history = [w]
for _ in range(25):
w = w - alpha * grad(w)
history.append(w)
loss_history = [loss(wi) for wi in history]
plt.figure(figsize=(7, 4))
plt.plot(loss_history, marker="o")
plt.xlabel("Iteration")
plt.ylabel("Loss J(w)")
plt.title("Gradient Descent: Loss sinkt über Iterationen")
plt.grid(alpha=0.3)
plt.show()
Wenn die Kurve sauber fällt und dann abflacht, verstehen Teilnehmende sofort "Training konvergiert" ohne tiefere Analysis.
Mini-Check: Was deutet auf zu hohe Lernrate hin?
Starkes Auf-und-Ab in der Loss-Kurve oder sogar steigende Loss statt stabiler Abnahme.
4) Batch vs Mini-Batch vs Stochastic GD (linear regression)
Hier sehen wir dieselbe Grundidee auf einem kleinen Regressionsproblem. Unterschiedlich ist nur, wie viele Beobachtungen pro Update eingehen.
import numpy as np
# synthetische Daten: y ~= 3 + 2*x + noise
rng = np.random.default_rng(42)
X = rng.uniform(-2, 2, size=200)
noise = rng.normal(0, 0.7, size=200)
y = 3 + 2 * X + noise
# lineares Modell: y_hat = b0 + b1*x
def mse(b0, b1, x_batch, y_batch):
y_hat = b0 + b1 * x_batch
return np.mean((y_batch - y_hat) ** 2)
def gradients(b0, b1, x_batch, y_batch):
y_hat = b0 + b1 * x_batch
err = y_hat - y_batch
db0 = 2 * np.mean(err)
db1 = 2 * np.mean(err * x_batch)
return db0, db1
def train_gd(mode="batch", lr=0.05, epochs=60, batch_size=32):
b0, b1 = 0.0, 0.0
losses = []
n = len(X)
for _ in range(epochs):
if mode == "batch":
idx_sets = [np.arange(n)]
elif mode == "mini":
perm = rng.permutation(n)
idx_sets = [perm[i:i+batch_size] for i in range(0, n, batch_size)]
else: # stochastic
perm = rng.permutation(n)
idx_sets = [[i] for i in perm]
for idx in idx_sets:
xb, yb = X[idx], y[idx]
db0, db1 = gradients(b0, b1, xb, yb)
b0 -= lr * db0
b1 -= lr * db1
losses.append(mse(b0, b1, X, y))
return b0, b1, losses
for mode in ["batch", "mini", "stochastic"]:
b0, b1, losses = train_gd(mode=mode)
print(mode, "-> b0:", round(b0, 3), "b1:", round(b1, 3), "final loss:", round(losses[-1], 3))
Mini-Check: Warum ist SGD oft "rauschiger"?
Weil jedes Update nur auf einer einzelnen Beobachtung basiert und damit stärker von Zufallsschwankungen beeinflusst wird.
5) Momentum einführen
Momentum führt Trägheit ein: statt nur den aktuellen Gradient zu nutzen, wird ein Anteil früherer Richtungen mitgenommen.
import numpy as np
# Standalone-Block: eigenes Mini-Dataset + Hilfsfunktionen
rng = np.random.default_rng(42)
X = rng.uniform(-2, 2, size=200)
noise = rng.normal(0, 0.7, size=200)
y = 3 + 2 * X + noise
def mse(b0, b1, x_batch, y_batch):
y_hat = b0 + b1 * x_batch
return np.mean((y_batch - y_hat) ** 2)
def gradients(b0, b1, x_batch, y_batch):
y_hat = b0 + b1 * x_batch
err = y_hat - y_batch
db0 = 2 * np.mean(err)
db1 = 2 * np.mean(err * x_batch)
return db0, db1
def train_with_momentum(mode="mini", lr=0.05, beta=0.9, epochs=60, batch_size=32):
b0, b1 = 0.0, 0.0
v0, v1 = 0.0, 0.0
losses = []
n = len(X)
for _ in range(epochs):
if mode == "batch":
idx_sets = [np.arange(n)]
elif mode == "mini":
perm = rng.permutation(n)
idx_sets = [perm[i:i+batch_size] for i in range(0, n, batch_size)]
else:
perm = rng.permutation(n)
idx_sets = [[i] for i in perm]
for idx in idx_sets:
xb, yb = X[idx], y[idx]
db0, db1 = gradients(b0, b1, xb, yb)
v0 = beta * v0 + db0
v1 = beta * v1 + db1
b0 -= lr * v0
b1 -= lr * v1
losses.append(mse(b0, b1, X, y))
return b0, b1, losses
b0_m, b1_m, losses_m = train_with_momentum(mode="mini", beta=0.9)
print("with momentum ->", round(b0_m, 3), round(b1_m, 3), round(losses_m[-1], 3))
Momentum kann bei verrauschten Gradienten den Lernpfad glätten und Konvergenz beschleunigen. Zu hoch eingestellt kann es aber auch überschießen.
Mini-Check: In welchem Fall hilft Momentum besonders?
Wenn Updates stark schwanken (z. B. Mini-Batch/SGD) und der Lernpfad sonst zickzackartig verläuft.
6) Wie erkenne ich, ob ein Modell GD "under the hood" nutzt?
Nicht jedes Modell in scikit-learn trainiert mit Gradient Descent. Prüfe immer die Solver-Dokumentation des konkreten Estimators.
# Beispiel: unterschiedliche Solver-Strategien
# LinearRegression() nutzt i. d. R. keine klassische iterative GD-Schleife.
# SGDRegressor() trainiert explizit stochastisch iterativ.
from sklearn.linear_model import LinearRegression, SGDRegressor
print(LinearRegression())
print(SGDRegressor(max_iter=1000, tol=1e-3, random_state=42))
Mini-Check: Wann würdest du GD bevorzugen?
Bei großen Datenmengen, vielen Parametern oder wenn eine geschlossene Lösung unpraktisch wird und iterative Optimierung besser skaliert.