Capstone — Bau dein eigenes RL-Projekt
← Ship Your Brain · Kursstartseite
Voraussetzungen
- Alle Phasen 1–3 — Units 0–4 und mindestens eine Theorie-Unit; du musst flüssig zwischen PPO / DQN / SAC wählen können
- Unit 10 — du kannst ONNX exportieren und ein Binary shippen
- Debugging-Guide in einem anderen Tab offen — du wirst ihn brauchen
- Eine konkrete Projektidee, wie roh auch immer — Abschnitt 8 hat eine Liste, falls du eine brauchst
Zeit
Lesen: ~20 min · Training: variiert
Drei Wege, deine KI zu beobachten
Deine eigene Godot-Umgebung — ein Agent, den du entworfen hast, in einer Szene, die du gebaut hast · TensorBoard, das eine Belohnungskurve verfolgt, die du geschrieben hast · die finale itch.io-Seite oder das Desktop-Build, das du in die Welt geshippt hast.
Du hast jede Unit abgeschlossen. Du weißt, wie Q-Learning Wert-Schätzungen bootstrapped, wie PPO seine eigenen Gradient-Updates clippt, warum SAC Entropie maximiert, wie Neugier Erkundung antreibt, wie Imitation Learning aus Demonstrationen bootstrapped und wie man einen Roboterarm physisch simuliert. Jetzt fallen die Stützräder weg.
Diese Unit ist ein Gerüst, kein Tutorial. Es gibt keine Starter-Szene, keine vorgeschriebene Reward-Funktion, keine Referenz-Implementierung, gegen die du prüfen kannst. Du wählst ein Problem, entwirfst das MDP, wählst den Algorithmus, fährst die Trainings-Pipeline, evaluierst das Ergebnis und shippst es. Die folgenden Abschnitte führen dich durch jede Entscheidung in der Reihenfolge, in der sie auf dich zukommt.
1 · Eine Umgebung wählen
Der häufigste Capstone-Fehler ist, einen zu großen Scope zu wählen. Ein zweiräumiges Labyrinth, das in einer Stunde gelernt wird, lehrt dich mehr über RL als ein halbfertiges Open-World-Spiel. Starte kleiner, als du denkst, shippe es und expandiere von dort.
Entscheidungsbaum
Is the task physically continuous (arms, drones, cars)?
├── Yes → Robotics / physics sim path
│ Recommended: unit-robotics.md, unit-sac.md
│ Action space: continuous (Box)
│ Consider: unit-sim-to-real.md if you want real hardware
└── No → Game / grid path
├── Turn-based or discrete movement?
│ └── Yes → Discrete actions (Discrete or MultiDiscrete)
│ Recommended: unit-q-learning.md, unit-03.md
└── Real-time, smooth motion?
└── Yes → Continuous or hybrid actions
Recommended: unit-ppo-deep.md, unit-sac.md
How many agents are there?
├── One → Single-agent (start here)
└── Multiple → Are they cooperative, competitive, or mixed?
├── Competitive (zero-sum) → self-play, unit-self-play.md
├── Cooperative → MAPPO / shared reward, unit-self-play.md
└── Mixed → contact the course forum before proceeding
How dense is your reward signal?
├── Agent gets feedback every step → Dense reward → standard PPO / SAC
└── Agent only finds out at the end → Sparse reward
├── Episode < 200 steps → try shaped reward first (unit-reward-engineering.md)
└── Episode > 200 steps → add curiosity (unit-curiosity.md) or HER (unit-her.md)
Empfohlene Startpunkte nach Ziel
| Dein Ziel | Umgebungs-Typ | Aktionsraum | Algorithmus | Schwierigkeit |
|---|---|---|---|---|
| Schnell die volle Pipeline lernen | 2D-Grid-Spiel | Diskret | PPO | Einsteiger |
| Glatte Fortbewegung | 2D- oder 3D-Physik | Continuous Box | SAC | Mittel |
| Adversarial-KI | Beliebiges 2-Spieler-Spiel | Diskret oder kontinuierlich | PPO + Self-Play | Mittel |
| Langhorizon-Planung | Puzzle / Strategie | Diskret | PPO + Neugier | Mittel |
| Echter Roboter | Physik-Sim | Continuous Box | SAC | Fortgeschritten |
| Emergentes Multi-Agent-Verhalten | Beliebig | Diskret oder kontinuierlich | MAPPO / Self-Play | Fortgeschritten |
Scope-Creep ist Capstone-Killer Nr. 1
Wähle eine Umgebung, einen Agententyp und ein Erfolgskriterium, bevor du eine Zeile GDScript schreibst. Wenn deine Projektbeschreibung das Wort „und" mehr als zweimal enthält, halbiere sie. Komplexität kannst du nach deiner ersten funktionierenden Policy immer noch ergänzen.
2 · Beobachtungs-Design
Der Beobachtungsvektor ist alles, was der Agent zu jedem Timestep über die Welt weiß. Mach ihn falsch und der Agent ignoriert entweder den Großteil seiner Eingaben (zu viel Info) oder kann die Aufgabe nie lösen, weil er nicht weiß, wo er ist (zu wenig Info).
Die Sensor-Checkliste
Bevor du eine Komponente zum Beobachtungsvektor hinzufügst, stelle für jeden Kandidatenwert diese Fragen:
- Ändert er sich sinnvoll mit dem Agentenzustand? Wenn der Wert über alle Episoden nahezu konstant ist, entferne ihn.
- Ist er in der Realität beobachtbar? Gib dem Agenten keine Information, die eine echte Entität nicht wahrnehmen könnte (z. B. das interne Ziel des Gegners).
- Ist er redundant zu einer anderen Komponente? Stark korrelierte Eingaben verschwenden Kapazität und verlangsamen das Lernen.
- Ist er normalisiert? Neuronale Netze erwarten Eingaben in einem konsistenten Bereich. Alles muss in
[-1, 1]oder[0, 1]liegen.
Normalisierung in GDScript
# BAD — raw world coordinates (can be thousands of units)
obs.append(global_position.x)
obs.append(global_position.y)
# GOOD — normalised relative position (always -1 to 1 if arena is 200 units wide)
const ARENA_HALF_WIDTH := 100.0
const ARENA_HALF_HEIGHT := 100.0
obs.append(clamp(global_position.x / ARENA_HALF_WIDTH, -1.0, 1.0))
obs.append(clamp(global_position.y / ARENA_HALF_HEIGHT, -1.0, 1.0))
# BAD — unnormalised velocity (unbounded)
obs.append(linear_velocity.x)
# GOOD — velocity normalised by maximum expected speed
const MAX_SPEED := 500.0
obs.append(clamp(linear_velocity.x / MAX_SPEED, -1.0, 1.0))
obs.append(clamp(linear_velocity.y / MAX_SPEED, -1.0, 1.0))
Typische Beobachtungs-Fehler
| Fehler | Symptom | Fix |
|---|---|---|
| Pixelpositionen in einer großen Welt einbeziehen | Policy lernt nichts, Value-Loss explodiert | Auf arena-relative Koordinaten normalisieren |
| Zielposition exponieren, aber nicht den relativen Vektor zum Ziel | Policy konvergiert langsam | (goal_pos - agent_pos).normalized() als Obs hinzufügen |
| Verbleibende Episodenzeit einbeziehen | Policy ignoriert sie früh, übergewichtet sie spät | Entfernen, außer Zeitdruck ist eine Kernmechanik |
| Beobachtungen am Episodenende nicht zurücksetzen | Veraltete Werte aus voriger Episode bluten durch | Allen mutablen Obs-State in _reset() zurücksetzen |
| Partielle Beobachtbarkeit ohne Gedächtnis | Policy oszilliert, konvergiert nie | RecurrentPPO hinzufügen (siehe unit-08) |
Minimal starten
Beginne mit dem kleinsten Beobachtungsvektor, der die Aufgabe theoretisch lösen könnte. Füge Komponenten nur hinzu, wenn das Training stagniert und du identifizieren kannst, welche Information fehlt. Eine 6-Element-Obs, die funktioniert, schlägt eine 60-Element-Obs, die das Netz verwirrt.
3 · Reward-Design
Die Reward-Funktion ist das Wichtigste, was du schreiben wirst. Verbringe mehr Zeit damit als mit der Algorithmus-Wahl.
Der strukturierte Prozess
Befolge diese Schritte der Reihe nach. Überspringe nichts.
Schritt 1 — Erfolg in einfachen Worten definieren. Schreibe einen Satz: „Der Agent ist erfolgreich, wenn ___." Das wird deine terminale Reward-Bedingung. Sei konkret. „Schießt ein Tor" ist konkret. „Spielt gut" nicht.
Schritt 2 — Zuerst den terminalen Reward schreiben.
func _get_reward() -> float:
# Terminal reward only — shaped components come later
if _goal_reached():
return 1.0
if _episode_timeout():
return -0.1 # mild penalty for timing out, not failing
return 0.0
Trainiere nur mit diesem Reward. Findet der Agent das Ziel innerhalb einer sinnvollen Schrittzahl (versuche 1–5 Millionen), bist du fertig. Springe zu Schritt 5.
Schritt 3 — Den Random-Policy-Sanity-Check laufen lassen.
Lasse vor dem Training 100 Episoden mit zufälliger Policy laufen und logge das Return:
# Python / SB3 sanity check before training
from stable_baselines3 import PPO
from stable_baselines3.common.env_checker import check_env
env = your_env # your GodotEnv or wrapped env
check_env(env)
# Random rollout
obs, _ = env.reset()
total_reward = 0.0
for _ in range(500):
action = env.action_space.sample()
obs, reward, terminated, truncated, info = env.step(action)
total_reward += reward
if terminated or truncated:
obs, _ = env.reset()
print(f"Random policy return: {total_reward:.3f}")
Bekommt eine zufällige Policy in 100 Episoden nie einen positiven Reward, wird auch eine trainierte Policy das wahrscheinlich nicht. Füge vor dem Training eine kleine geformte Komponente hinzu.
Schritt 4 — Geformte Komponenten nur ergänzen, wenn das Training stagniert.
func _get_reward() -> float:
var reward := 0.0
# Terminal reward (primary signal — keep this weight at 1.0)
if _goal_reached():
reward += 1.0
return reward # early return, don't add shaping on terminal step
if _episode_timeout():
reward -= 0.1
return reward
# Shaped components (secondary — keep weights small, << 1.0)
var dist_to_goal: float = global_position.distance_to(_goal_position)
var dist_delta: float = _prev_dist_to_goal - dist_to_goal # positive = getting closer
reward += 0.01 * dist_delta # potential-based shaping: safe and consistent
_prev_dist_to_goal = dist_to_goal
return reward
Schritt 5 — Auf Reward-Hacking prüfen. Nach jedem Trainingslauf den Agenten in Godot beobachten. Ein hoher TensorBoard-Score bei bizarr aussehendem Verhalten heißt: dein Reward wurde gehackt. Siehe unit-reward-engineering für die volle Theorie und weitere Anti-Hacking-Techniken.
Reward-Skalierung ist wichtig
Terminale Rewards sollten dominieren. Summieren sich deine geformten Komponenten über eine Episode zu mehr als dein terminaler Reward, optimiert der Agent stattdessen das geformte Signal. Faustregel: shaped_total_per_episode ≤ 0,5 × terminal_reward.
Die vollständige Reward-Engineering-Referenz findest du in unit-reward-engineering.md.
4 · Einen Algorithmus wählen
Nutze dieses Flussdiagramm, um den Algorithmus zu wählen. Überdenke es nicht — die Algorithmuswahl zählt viel weniger als das Reward-Design.
Are your actions continuous (Box action space)?
├── Yes → SAC (preferred for robotics / smooth control)
│ or PPO (simpler, works well for game-like tasks)
│ → see unit-sac.md
└── No → PPO with Discrete or MultiDiscrete action space
→ see unit-ppo-deep.md
Is your reward sparse (agent rarely sees a positive reward)?
├── Yes → Add curiosity (RND or ICM) on top of your chosen algorithm
│ → see unit-curiosity.md
│ or add HER if your task is goal-conditioned
│ → see unit-her.md
└── No → Standard PPO or SAC without modification
Does the agent need memory (partial observability, sequence-dependent decisions)?
├── Yes → RecurrentPPO (LSTM-augmented PPO)
│ → see unit-08.md
└── No → Standard PPO or SAC
Is there more than one agent?
├── Competitive (zero-sum) → PPO + self-play
│ → see unit-self-play.md
├── Cooperative → MAPPO with shared reward
│ → see unit-self-play.md
└── Single-agent → any of the above
Schnellreferenz
| Szenario | Algorithmus | Config-Key |
|---|---|---|
| Diskretes Spiel, dichter Reward | PPO | algorithm: ppo |
| Kontinuierliche Steuerung, dichter Reward | SAC | algorithm: sac |
| Beliebige Aufgabe, spärlicher Reward | PPO + Neugier | use_curiosity: true |
| Goal-conditioned-Robotik | SAC + HER | use_her: true |
| Partielle Obs / Gedächtnis nötig | RecurrentPPO | algorithm: rppo |
| Kompetitiver Multi-Agent | PPO + Self-Play | self_play: true |
Im Zweifel mit PPO starten
PPO ist der robusteste Algorithmus in diesem Kurs. Er bewältigt diskrete und kontinuierliche Aktionen, ist tolerant gegenüber Hyperparameter-Wahl und trainiert stabil bei nahezu jedem Reward-Design. Wechsle nur dann zu SAC, wenn du speziell Sample-Effizienz auf einer Continuous-Control-Aufgabe brauchst.
5 · Trainings-Pipeline
Headless-Export
Trainiere immer mit einem Headless-Godot-Build — der Renderer kostet ~30 % deiner Wall-Clock-Zeit beim Training. Aus dem Godot-Editor exportieren:
Projekt → Exportieren → Linux/macOS (Headless) → Projekt exportieren
Nenne das Binary game.x86_64 (Linux) oder game (macOS). Platziere es im Projekt-Root.
Launch-Command-Template
Kopiere und passe diesen Befehl an. Stelle n_parallel auf die Anzahl physischer CPU-Kerne ein (nicht Hyper-Threads):
# Minimum viable training run (3 seeds, 4 parallel envs each)
for SEED in 1 2 3; do
gdrl train \
--config-name=ppo \
env.path=./game.x86_64 \
env.n_parallel=4 \
train.timesteps=3_000_000 \
train.seed=$SEED \
train.checkpoint_freq=100_000 \
hydra.run.dir=runs/seed_${SEED} \
&
done
wait
echo "All seeds finished"
Für SAC (Single-Process, keine vektorisierten Envs):
for SEED in 1 2 3; do
gdrl train \
--config-name=sac \
env.path=./game.x86_64 \
env.n_parallel=1 \
train.timesteps=1_000_000 \
train.seed=$SEED \
hydra.run.dir=runs/sac_seed_${SEED} \
&
done
wait
TensorBoard-Setup
# In a separate terminal — keep this running throughout training
tensorboard --logdir=runs/ --port=6006
# Open http://localhost:6006 in your browser
Schlüssel-Metriken zum Beobachten:
| Metrik | Was sie dir sagt |
|---|---|
rollout/ep_rew_mean |
Lernt der Agent überhaupt? Sollte ansteigen |
rollout/ep_len_mean |
Episodenlänge — stabil, sobald der Agent die Aufgabe löst |
train/value_loss |
Critic-Genauigkeit — sollte sinken, dann plateauen |
train/entropy_loss |
Erkundung — fällt sie auf null, steckt der Agent fest |
train/approx_kl |
PPO-Update-Größe — durchgehend > 0,05 → lr senken |
Checkpoint-Frequenz
Setze checkpoint_freq für Aufgaben unter 3 M Schritten auf alle 100 000 Schritte und für längere Läufe auf alle 250 000 Schritte. Checkpoints lassen dich eine gute Policy zurückgewinnen, auch wenn das Training später divergiert.
Immer mindestens 3 Seeds laufen lassen
RL-Training hat hohe Varianz. Ein einzelner Seed, der gut performt, kann ein Ausreißer sein. Drei Seeds geben dir aussagekräftige Mittelwerte und Standardabweichungen. Weniger und du kannst nicht entscheiden, ob dein Design funktioniert oder du nur Glück hattest.
6 · Evaluation
Das Training endet. Jetzt beweise, dass dein Agent tatsächlich etwas gelernt hat.
Baselines zuerst
Vergleiche immer gegen eine zufällige Policy. Die Random-Baseline ist dein Boden. Schlägt dein trainierter Agent sie nur knapp, stimmt etwas nicht.
# Compute random baseline before any training
import numpy as np
from godot_rl.wrappers.stable_baselines_wrapper import StableBaselinesGodotEnv
env = StableBaselinesGodotEnv(env_path="./game.x86_64", show_window=False)
random_returns = []
for episode in range(50):
obs, _ = env.reset()
ep_ret = 0.0
done = False
while not done:
action = env.action_space.sample()
obs, reward, terminated, truncated, _ = env.step(action)
ep_ret += reward
done = terminated or truncated
random_returns.append(ep_ret)
env.close()
print(f"Random baseline — mean: {np.mean(random_returns):.3f} std: {np.std(random_returns):.3f}")
Multi-Seed-Zusammenfassung
Nach Abschluss des Trainings über alle Seeds, berechne:
import numpy as np
# Load per-seed final episode returns (e.g. from TensorBoard CSV export)
seed_returns = [
[final_ep_rets_from_seed_1],
[final_ep_rets_from_seed_2],
[final_ep_rets_from_seed_3],
]
all_returns = np.concatenate(seed_returns)
print(f"Trained agent — mean: {np.mean(all_returns):.3f} std: {np.std(all_returns):.3f}")
# Interquartile mean (IQM) — more robust than plain mean for RL
q25, q75 = np.percentile(all_returns, [25, 75])
iqm = np.mean(all_returns[(all_returns >= q25) & (all_returns <= q75)])
print(f"IQM: {iqm:.3f}")
Das Interquartile Mean (IQM) schneidet die oberen und unteren 25 % der Episoden vor der Mittelung ab. Es ist robuster als der einfache Mittelwert, wenn Episoden-Returns Ausreißer haben, was in RL üblich ist. Verwende es in deinem Report.
Visualisierungs-Checkpoint-Video
# Run a trained checkpoint in render mode and record with OBS or ffmpeg
gdrl enjoy \
--config-name=ppo \
enjoy.checkpoint=runs/seed_1/best_model.zip \
env.path=./game.x86_64 \
env.show_window=true \
enjoy.n_episodes=5
# Alternatively, pipe Godot window to ffmpeg (Linux)
ffmpeg -video_size 1280x720 -framerate 30 -f x11grab -i :0.0 \
-c:v libx264 -preset fast capstone_demo.mp4
Wie „gut" aussieht
„Gut" ist immer relativ zu deiner Aufgabe. Definiere es, bevor du mit dem Training beginnst:
- Ist Erfolg binär (Ziel erreicht / nicht), sollte ein „guter" Agent das Ziel in > 80 % der Eval-Episoden erreichen.
- Ist Erfolg ein Score, sollte ein „guter" Agent die Random-Baseline deutlich übertreffen (mindestens 2× Mittelwert-Return).
- Ist Erfolg qualitativ (glatte Fortbewegung, kohärente Strategie), nimm ein Video auf und schau es kritisch an.
7 · Shipping
Wenn du mit deinem Agenten zufrieden bist, exportiere das ONNX-Modell und führe es ohne Python in Godot aus.
ONNX-Export
# Export from SB3 checkpoint to ONNX
from godot_rl.wrappers.onnx.stable_baselines_export import export_ppo_model_as_onnx
export_ppo_model_as_onnx(
model_path="runs/seed_1/best_model.zip",
onnx_model_path="exported/agent.onnx",
)
print("ONNX export complete → exported/agent.onnx")
Für SAC:
from godot_rl.wrappers.onnx.stable_baselines_export import export_sac_model_as_onnx
export_sac_model_as_onnx(
model_path="runs/sac_seed_1/best_model.zip",
onnx_model_path="exported/agent.onnx",
)
Godot-Inferenz-Modus
Setze in deinem AIController-Node inference_mode = true im Editor-Inspector und zeige onnx_model_path auf deine exportierte Datei:
# AIController.gd — inference mode snippet
extends AIController3D # or AIController2D
func _ready() -> void:
# These are set in the Inspector — shown here for reference
# inference_mode = true
# onnx_model_path = "res://exported/agent.onnx"
pass
func get_obs() -> Array:
# Must match exactly what you returned during training
var obs: Array = []
obs.append(clamp(global_position.x / ARENA_HALF_WIDTH, -1.0, 1.0))
obs.append(clamp(global_position.y / ARENA_HALF_HEIGHT, -1.0, 1.0))
obs.append(clamp(linear_velocity.x / MAX_SPEED, -1.0, 1.0))
obs.append(clamp(linear_velocity.y / MAX_SPEED, -1.0, 1.0))
return obs
Reihenfolge der Beobachtungen muss identisch sein
Die ONNX-Runtime füttert deinen Obs-Vektor in der Reihenfolge ins Netz, in der du ihn aus get_obs() zurückgibst. Weicht die Reihenfolge vom Training ab, produziert die Policy Müll. Halte get_obs() unter Versionskontrolle und ordne sie zwischen Training und Export nie um.
Für HTML5-Export und volle Shipping-Details siehe unit-10.md.
8 · Projektideen
Wähle eine, die dich interessiert. Die Schwierigkeits-Ratings nehmen an, dass du alle Kurs-Units abgeschlossen hast.
| Projekt | Schwierigkeit | Umgebungs-Typ | Aktionsraum | Relevanteste Units |
|---|---|---|---|---|
| Fußball-KI — 1-gegen-1-Agent schießt Tore gegen einen Gegner | Einsteiger | 2D-Physik | Kontinuierlich | unit-04, unit-self-play |
| Plattformer-NPC — Agent lernt, durch ein handgebautes Level zu navigieren | Einsteiger | 2D-Physik | Diskret | unit-01, unit-ppo-deep |
| Tower Defense — Agent platziert Türme, um Wellen zu überleben | Mittel | 2D-Grid | Diskret (MultiDiscrete) | unit-03, unit-reward-engineering |
| Koch-Spiel — Agent kombiniert Zutaten vor Ablauf des Timers | Mittel | 2D-Grid | Diskret | unit-08, unit-curiosity |
| Verkehrs-Simulation — Agenten an einer Kreuzung minimieren Wartezeit | Mittel | 2D-Physik | Diskret | unit-self-play, unit-reward-engineering |
| Drohnen-Rennen — Quadrotor-Agent absolviert einen Checkpoint-Parcours | Mittel | 3D-Physik | Continuous Box | unit-sac, unit-robotics |
| Lagerhaus-Roboter — Arm platziert Kisten der Reihe nach auf Paletten | Mittel | 3D-Physik | Continuous Box | unit-robotics, unit-her |
| Verstecken-Spielen — Versteck-Agenten vs. Sucher mit emergenter Strategie | Fortgeschritten | 3D-Physik | Kontinuierlich | unit-self-play, unit-curiosity |
| Schach-Variante Self-Play — Agent lernt modifiziertes Schach | Fortgeschritten | 2D-Grid | Diskret (groß) | unit-self-play, unit-03 |
| Musik-Rhythmus-Agent — Agent trifft Beats mit präzisem Timing | Fortgeschritten | 2D | Kontinuierlich | unit-sac, unit-reward-engineering |
Empfehlung für Einsteiger
Starte mit Plattformer-NPC oder Fußball-KI. Beide haben einfache, prüfbare Erfolgsbedingungen, kurze Episoden und dichte genug Rewards, sodass eine zufällige Policy gelegentlich Erfolg hat — genau die Bedingungen, unter denen RL zuverlässig funktioniert.
Multi-Agent-Projekte
Verstecken-Spielen, Verkehrs-Simulation und Schach-Varianten-Self-Play brauchen alle Multi-Agent-Infrastruktur. Schließe unit-self-play.md ab, bevor du eines davon versuchst. Unterschätze nicht die zusätzliche Komplexität von Multi-Agent-Debugging.
9 · Häufige Fehlermodi
Das sind Probleme, die speziell bei eigenen Projekten auftauchen — sie kommen in den geführten Beispielen nicht vor, weil der Starter-Code sie bereits behandelt. Sie stehen nicht im Standard-Debugging-Guide (unit-debugging.md), weil sie sich auf Design-Entscheidungen beziehen, nicht auf Trainings-Pathologien.
1 · Falscher Aktionsraum-Typ
Symptom: Agent macht in jedem Schritt scheinbar dasselbe, oder Aktionen haben keinen sichtbaren Effekt.
Diagnose: Du hast einen Box (kontinuierlich) Aktionsraum definiert, aber dein Spiel erwartet einen Integer-Index, oder umgekehrt. Prüfe die Aktionsraum-Deklaration in deinem AIController und den set_action()-Handler.
# WRONG — Box action when you want discrete
func get_action_space() -> Dictionary:
return {"move": {"size": 4, "action_type": "continuous"}}
# CORRECT — Discrete action for 4 directions
func get_action_space() -> Dictionary:
return {"move": {"size": 4, "action_type": "discrete"}}
2 · Beobachtungen werden am Episodenende nicht zurückgesetzt
Symptom: Agent performt in der ersten Episode gut, degradiert dann oder verhält sich in folgenden Episoden erratisch.
Diagnose: Mutabler State, der in get_obs() verwendet wird (z. B. _prev_dist_to_goal, gestapelte Frame-Buffer), wird beim Episodenende nicht zurückgesetzt.
func _reset() -> void:
# ALWAYS reset all mutable observation state here
_prev_dist_to_goal = global_position.distance_to(_goal_position)
_prev_velocity = Vector2.ZERO
_step_count = 0
# ... any other stateful obs components
3 · Reward-Skalen-Mismatch zwischen geformten und terminalen Komponenten
Symptom: TensorBoard zeigt hohen ep_rew_mean, aber der Agent erreicht das Ziel nie — er erntet geformten Reward unendlich.
Diagnose: Dein geformter Reward pro Episode übersteigt deinen terminalen Reward. Der Agent vermeidet rational das Beenden der Episode.
# BAD — shaped reward dwarfs terminal reward over a 500-step episode
# Step reward: +0.01 × 500 steps = +5.0 total shaped
# Terminal reward: +1.0
# Agent prefers staying alive and collecting +5.0 over ending the episode
# GOOD — keep shaped total << terminal
# Step reward: +0.001 × 500 steps = +0.5 total shaped
# Terminal reward: +1.0
# Agent prefers ending the episode (efficiency) over harvesting shaped reward
4 · Vergessen, kontinuierliche Beobachtungen zu normalisieren
Symptom: Value-Loss oszilliert wild, Policy-Loss divergiert, oder der Agent lernt nach Millionen Schritten nichts trotz vernünftigem Reward.
Diagnose: Eine oder mehrere Obs-Komponenten haben eine sehr andere Skala als der Rest. Die Gewichte des neuronalen Netzes können sich nicht stabilisieren, wenn Eingaben im selben Vektor von 0,001 bis 10 000 reichen.
Fix: Audite jede von get_obs() zurückgegebene Komponente. Kann ein Wert irgendwo 1,0 übersteigen oder unter -1,0 fallen, normalisiere ihn wie in Abschnitt 2 gezeigt.
5 · Nur einen Seed laufen lassen und Sieg erklären
Symptom: Dein Agent „funktioniert großartig", aber ein Kommilitone kann deine Ergebnisse nicht reproduzieren, oder dein Agent performt an einem Eval-Tag gut und eine Woche später auf demselben Checkpoint schlecht.
Diagnose: Du hast einen Seed gelaufen. RL hat hohe Varianz. Ein einzelner glücklicher Seed kann scheinbar eine Aufgabe lösen, die dein Design eigentlich nicht zuverlässig löst.
Fix: Lasse immer mindestens drei Seeds laufen (Abschnitt 5). Berichte Mittelwert ± Std oder IQM. Variiert die Performance enorm über die Seeds, ist dein Reward-Design oder Beobachtungs-Design zu sensibel — untersuche das, bevor du shippst.
6 · Ein 3D-Problem als 2D-Problem behandeln (oder umgekehrt)
Symptom: Agent löst die Aufgabe in 2D-Testszenarien, scheitert aber in der vollen 3D-Umgebung. Oder: der AIController des Agenten erbt von AIController2D in einer 3D-Szene (oder umgekehrt), was Physik-Queries falsche Werte zurückgeben lässt.
Diagnose: Prüfe, von welcher Basisklasse dein AIController erbt. Prüfe, dass Raycasts, Kollisions-Shapes und Positions-Queries das richtige Koordinatensystem nutzen.
# For a 2D scene
extends AIController2D
# For a 3D scene
extends AIController3D
# Do NOT mix them — the parent class determines which physics server is queried
10 · Stretch Goals
Du hast einen funktionierenden Agenten geshippt. Hier sind drei Wege, weiterzugehen.
Auf ein Leaderboard oder itch.io einreichen
Exportiere dein Spiel als HTML5-Build (unit-10.md) und veröffentliche es auf itch.io. Schreibe eine kurze Beschreibung, was der Agent gelernt hat und wie du ihn trainiert hast. Teile den Link im Kurs-Discord.
Passt dein Projekt zu einem existierenden Benchmark (z. B. eine Fußball-Variante), erwäge eine Einreichung beim Godot-RL-Agents-Community-Leaderboard.
Einen 1-seitigen Technical Report schreiben
Struktur: 1. Aufgabe (2 Sätze) — was ist die Umgebung und Erfolgsbedingung? 2. MDP-Design (3–4 Sätze) — Beobachtungsraum, Aktionsraum, Reward-Funktion. 3. Algorithmus (1 Satz) — welcher Algorithmus und warum? 4. Ergebnisse (3–4 Sätze) — Multi-Seed-Mittelwert ± Std, IQM, Vergleich zur Random-Baseline. 5. Was nicht funktioniert hat (2–3 Sätze) — mindestens ein gescheiterter Ansatz. Das ist der wertvollste Abschnitt. 6. Zukünftige Arbeit (1–2 Sätze) — was würdest du als Nächstes versuchen?
Über Misserfolge zu schreiben ist wertvoller als Erfolge zu melden. Misserfolge enthalten die echten Lehren.
Ein 2-minütiges Video-Demo aufnehmen
Zeige: 1. Die zufällige Policy (erste 30 Sekunden) — damit Zuschauer einschätzen können, wie schwer die Aufgabe ist. 2. Einen Mid-Training-Checkpoint (30 Sekunden) — zeige den Agenten beim Lernen. 3. Die finale trainierte Policy (60 Sekunden) — bestes Verhalten, das du beobachtet hast.
Kommentiere jeden Abschnitt. „Der Agent hat gelernt, Wänden auszuweichen, weiß aber noch nicht, wie er Tore schießt" ist informativer als Schweigen.
Du bist jetzt ein RL-Practitioner
Jedes Projekt, das du von hier an baust, wird schneller sein als das letzte. Das Schwere — das MDP entwerfen, Reward-Hacking debuggen, TensorBoard interpretieren, Multi-Seed-Evaluationen fahren — ist jetzt vertraut. Algorithmen und Frameworks werden sich ändern; dieser Prozess nicht.
Selbstcheck, bevor du das Projekt startest
Kannst du diese in eigenen Worten zu deiner Capstone-Idee beantworten?
- Formuliere deine Aufgabe in einem Satz, dann schreibe ihr MDP: Beobachtungsraum, Aktionsraum, Reward, terminale Bedingung.
- Begründe deine Algorithmuswahl (PPO / DQN / SAC / RecurrentPPO), indem du die spezifische Eigenschaft deiner Aufgabe nennst, die ihn passend macht.
- Was ist dein Minimum Viable Result — die kleinste Version davon, die du in 4–8 Stunden Training shippen kannst?
- Was ist dein Evaluations-Protokoll — wie viele Seeds, wie viele Eval-Episoden, deterministische oder stochastische Policy?
- Liste die drei wahrscheinlichsten Reward-Hacking-Fehlermodi auf, die dein Design ermöglichen könnte, und jeweils eine Gegenmaßnahme.
Wenn du alle fünf beantworten kannst — leg los. Wenn nicht, liegt die Antwort in den Abschnitten 1–4 oben.
Antworten zum Selbstcheck
- Durchgerechnetes Beispiel (Platformer NPC) — „Der Agent ist erfolgreich, wenn er den Level-Ausgang erreicht." Sein MDP: Beobachtungen = normalisierter Relativvektor zum Ausgang plus normalisierte Geschwindigkeit (Abschnitt 2); Aktionen =
Discrete(4)-Bewegung; Reward =+1.0terminal beim Erreichen des Ausgangs,-0.1bei Timeout, Shaping nur, falls das Training stagniert (Abschnitt 3); terminale Bedingung = Ausgang erreicht oder Schrittlimit. Egal welche Aufgabe: Deine Antwort muss alle vier Teile explizit benennen — ist einer unscharf, geh zurück zu den Abschnitten 2–3, bevor du eine Zeile GDScript schreibst. - Eine echte Begründung nennt eine konkrete Aufgaben-Eigenschaft aus dem Flowchart in Abschnitt 4: z. B. „diskrete Aktionen, dichter Reward → PPO", „kontinuierliche Box-Aktionen, bei denen Sample-Effizienz zählt → SAC", „partielle Beobachtbarkeit → RecurrentPPO". „PPO, weil ich es kenne" ist ebenfalls akzeptabel — die Algorithmuswahl zählt weit weniger als das Reward-Design, und PPO ist der robuste Standard des Kurses.
- Das Minimum Viable Result ist die kleinste Version mit einer Umgebung, einem Agenten und einem Erfolgskriterium, deren trainierte Policy die Random-Baseline klar schlägt — ein einzelnes kleines Level, nicht das ganze Spiel. Die Regel aus Abschnitt 1 gilt: Scope Creep ist der Capstone-Killer Nr. 1, und Komplexität kannst du nach der ersten funktionierenden Policy jederzeit ergänzen.
- Das Standard-Protokoll des Kurses: mindestens 3 Seeds (Abschnitt 5), berichtet als Mittelwert ± Std plus IQM (Abschnitt 6), mit den Returns pro Seed aus den letzten Trainingsepisoden (TensorBoard-Export aus Abschnitt 6) und der 50-Episoden-Random-Policy-Baseline als Maßstab. Deterministisch oder stochastisch evaluieren ist deine Entscheidung — nenne sie nur im Report und halte sie über alle Seeds konsistent.
- Die drei klassischen Muster aus den Abschnitten 3 und 9: (a) Shaped-Reward-Harvesting — der Agent erntet Shaping, statt fertig zu werden → halte die Shaping-Summe pro Episode ≤ 0,5 × terminaler Reward; (b) Hin- und Herpendeln zum Ziel, um Distanz-Delta-Reward erneut einzusammeln → nutze potenzialbasiertes Shaping und setze
_prev_dist_to_goalin_reset()zurück; (c) Vermeiden des Episodenendes, weil Überleben sich mehr lohnt → lass den terminalen Reward dominieren und bestrafe Timeouts. Eine Gegenmaßnahme deckt alle drei ab: Schau dir den Agenten nach jedem Run in Godot an — eine hohe Kurve bei bizarrem Verhalten heißt, der Reward wurde ausgetrickst.