What is reinforcement learning?

The training method behind ChatGPT, AlphaGo, and self-driving cars

Most AI you hear about is trained by showing it millions of examples with the right answers attached. That is supervised learning -- you hand it the textbook and the answer key. Reinforcement learning is different. You give the AI a goal and a scoring system, then let it figure out how to get there on its own. No answer key. Just trial, error, and a score.

Think of it like learning a video game without reading the manual. You press buttons, things happen, sometimes you score points, sometimes you die. Over time, you learn which buttons to press in which situations. That loop -- try something, see what happens, adjust -- is RL.

stateDiagram-v2 direction LR Agent --> Environment: Action Environment --> Agent: State + Reward Agent --> Agent: Update policy

The agent is the learner. The environment is whatever world it operates in -- a game, a chatbot conversation, a robot arm. Every step, the agent picks an action, the environment responds with a new state and a reward signal (a number that says how well it did), and the agent adjusts its strategy.

Core vocabulary

TermWhat it meansExample
AgentThe thing that learns and makes decisionsA chess engine, a chatbot, a robot
EnvironmentThe world the agent interacts withA chess board, a conversation, a warehouse
StateA snapshot of where things stand right nowCurrent board position, current conversation history
ActionA choice the agent makesMove a piece, generate a sentence, pick up a box
RewardA score signal -- higher is better+1 for winning, -1 for an unsafe response
PolicyThe agent's learned strategy (state → action)"When losing, play aggressively"
EpisodeOne complete run from start to finishOne full chess game, one conversation

RL vs supervised vs unsupervised

flowchart LR A["Raw data"] --> B{"Labels available?"} B -->|"Yes"| C["Supervised Learning"] B -->|"No"| D{"Has reward signal?"} D -->|"Yes"| E["Reinforcement Learning"] D -->|"No"| F["Unsupervised Learning"] C --> G["Image classification, translation"] E --> H["Game playing, RLHF, robotics"] F --> I["Clustering, anomaly detection"]
SupervisedReinforcement
DataLabeled examples (input + correct output)A goal and a scoring function
LearningMemorize patterns from examplesExplore, fail, discover what works
CeilingLimited by the quality of training dataCan discover strategies no human designed
CostNeed expensive labeled dataNeed a reliable reward signal

How RL trains language models (RLHF / GRPO)

This is where RL gets really relevant today. Every major chatbot -- ChatGPT, Claude, Gemini -- uses RL after initial training to learn which responses humans actually prefer. The original method is called RLHF (Reinforcement Learning from Human Feedback). Here is how it works in three stages:

sequenceDiagram participant H as Humans participant R as Reward Model participant L as Language Model participant P as RL Optimizer H->>R: Rate thousands of response pairs R->>R: Learn to predict human preferences L->>P: Generate responses to prompts P->>R: Score each response R->>P: Return reward scores P->>L: Update weights (reinforce good, suppress bad) L->>P: Generate better responses next round

Stage 1: Supervised fine-tuning (SFT). Take a pretrained model and fine-tune it on high-quality conversation examples. This gives it a baseline ability to follow instructions.

Stage 2: Train a reward model. Humans look at pairs of responses and pick which one is better. A separate model learns to predict those preferences. Now you have an automated scoring function.

Stage 3: RL optimization. The language model generates responses, the reward model scores them, and an RL algorithm (PPO, GRPO, etc.) adjusts the model to produce higher-scoring responses more often.

The algorithm landscape

Different RL algorithms make different tradeoffs between complexity, memory, and data requirements. Here is what matters in 2026:

flowchart TD A["Need to train an LLM with RL?"] --> B{"Have paired preference data?"} B -->|"Yes"| C["DPO -- simplest, offline"] B -->|"No"| D{"Budget for critic model?"} D -->|"Yes"| E["PPO -- gold standard, expensive"] D -->|"No"| F{"Training MoE?"} F -->|"Yes"| G["CISPO -- MoE-safe variant"] F -->|"No"| H["GRPO -- best general choice"]
AlgorithmWhat it doesNeeds critic?MemoryBest for
PPOPolicy gradient with value baselineYes (separate model)~3x modelMaximum quality, big budgets
GRPOGroups outputs, uses mean as baselineNo~2x modelBest quality-per-dollar
DPOLearns from preference pairs directlyNo~2x modelWhen you have preference data
DAPOGRPO + dynamic samplingNo~2x modelBetter exploration
CISPOClipped importance, per-expert safetyNo~2x modelMoE models (prevents expert collapse)

How GRPO works, step by step

GRPO is the breakout algorithm of 2025-2026. DeepSeek used it for R1, and it became the default because it gets PPO-level results without needing a separate critic model (which halves memory cost). Here is the actual process:

sequenceDiagram participant M as Model participant E as Environment participant S as Scorer M->>E: Given prompt, generate G outputs (e.g. 8) E->>S: Send all G outputs to scorer S->>S: Score each output (0 or 1, or partial) S->>M: Compute advantage: A_i = (r_i - mean) / std M->>M: Positive advantage = reinforce that output M->>M: Negative advantage = suppress that output M->>M: Clip updates to stay close to original (KL penalty)
  1. Sample a prompt from your dataset
  2. Generate G completions (typically 8-16) from the current model
  3. Score each completion using a verifiable reward (math: did it get the right answer? code: did the tests pass?)
  4. Normalize scores within the group: advantage_i = (reward_i - group_mean) / group_std
  5. Update the model: outputs that scored above average get reinforced, below average get suppressed
  6. Repeat for thousands of prompts
Why groups instead of a critic? PPO trains a separate "critic" model that estimates how good each state is. This doubles your memory. GRPO skips the critic entirely and just compares each output to the average of its group. Simpler, cheaper, works just as well for most tasks.

Training cost comparison

GPT-4
~$100M
Llama 3 405B
~$25M
DeepSeek-V3
$5.6M
Qwen 2.5 72B
~$3M
SmolLM3 3B
<$500K

Key numbers to know

671B
DeepSeek-V3 total params
37B active per token (MoE)
$5.6M
V3 full training cost
14.8T tokens, FP8 mixed precision
456B
MiniMax-M2 total params
45.9B active, lightning MoE
80.2%
M2.5 SWE-Bench Verified
#1 on real GitHub issue fixing

Resources

RL in your work life

Where RL is already deployed and making money

RL is not just research. It runs in products you use every day. Every time ChatGPT gives you a helpful answer instead of a weird one, that is RLHF at work. Every time Google Maps finds a faster route, that is RL. Every time YouTube keeps you watching, that is an RL-like system optimizing for engagement.

The RL pipeline inside every major LLM

flowchart LR A["Pretrain on internet text"] --> B["SFT on curated examples"] B --> C["Collect human preferences"] C --> D["Train reward model"] D --> E["RL fine-tuning (PPO/GRPO)"] E --> F["Deploy to users"] F --> G["Collect more feedback"] G --> C

This loop runs continuously at every frontier lab. It is the reason models keep getting better at following instructions, refusing harmful requests, and writing useful code.

Real deployments

CompanyWhat they didResultRL method
MiniMaxTrained M2.5 with Forge RL framework to be a coding, search, and office agent80.2% SWE-Bench Verified (top score), 76.3% BrowseCompCISPO + Forge
DeepSeekTrained R1 reasoning model from scratch with pure RLFirst open model to match o1 on math reasoningGRPO
Google DeepMindData center cooling optimization30% energy savings across Google data centersDeep RL
AmazonWarehouse staffing and inventory placementReduced idle time, fewer late shipmentsOffline RL + LLM
OpenAIRLHF for ChatGPT alignmentMade GPT-4 actually follow instructions instead of ramblingPPO + RLHF
AnthropicConstitutional AI for ClaudeModel critiques its own responses, then improves them via RLRLHF + CAI

SWE-Bench: who can actually fix real code?

SWE-Bench Verified takes real GitHub issues from popular Python repos and asks models to write the fix. This is the hardest coding benchmark because it requires reading thousands of lines of existing code, understanding the bug, and writing a working patch.

M2.5
80.2%
Claude Opus 4.6
78.9%
GPT-5
~76%
Gemini 2.5 Pro
~74%

Cost per task

M2.5 costs roughly 10-20x less than Opus per task at similar quality. This is the power of efficient RL training -- you can get frontier performance without frontier prices.

Opus 4.6
$$$$$
GPT-5
$$$
M2.5
$

MiniMax Forge: how they got to #1

MiniMax built a custom RL framework called Forge that solves three problems at once: throughput (training fast enough to iterate), stability (RL training loves to collapse), and flexibility (agents need to use tools, browse the web, write code, not just generate text).

flowchart TD A["Forge Framework"] --> B["Problem: RL for agents is slow"] A --> C["Problem: RL training collapses"] A --> D["Problem: Agents need diverse tools"] B --> E["Solution: 40x throughput vs TRL"] C --> F["Solution: CISPO algorithm -- clips per-expert to prevent collapse"] D --> G["Solution: 200K+ diverse training environments"] E --> H["M2.5: 80.2% SWE-Bench"] F --> H G --> H
Key insight from MiniMax: They found that standard GRPO causes expert collapse in MoE models -- some experts get all the training signal while others go dormant. CISPO (Clipped Importance Sampling Policy Optimization) clips the training signal per-expert to keep them all active. This is why M2.5 beats much larger models.

RL inside products you already use

ProductWhat RL doesWhy it matters
ChatGPT / Claude / GeminiRLHF picks helpful, safe responsesWithout it, models just autocomplete -- often badly
GitHub CopilotRL reduces hallucinated codeSuggests code that actually compiles
Google MapsRL optimizes routes in real-timeAdapts to traffic, construction, accidents
YouTube / TikTokRL-like systems optimize engagementLearns what keeps you watching
Tesla AutopilotRL for decision-making at intersectionsWhen to brake, accelerate, change lanes

Should you build or buy?

flowchart TD A["Do existing APIs solve your problem?"] -->|"Yes"| B["Use the API. Done."] A -->|"No"| C["Is your data domain-specific?"] C -->|"Yes"| D["Fine-tune with GRPO on 1 GPU"] C -->|"No"| E["Try prompt engineering first"] D --> F["Tools: TRL, Unsloth, simple_grpo"] E --> G["If it still doesn't work, then fine-tune"]

Most people do not need to train from scratch. If an existing model gets you 80% of the way there, prompt engineering or a small GRPO fine-tune on your specific domain data will close the gap.


Resources

Data for RL

What data you need depends entirely on what kind of RL you are doing

Here is the thing that confuses most people: classic RL does not need a dataset at all. The agent generates its own data by interacting with the environment. Play a game a million times and you have a million games of training data, generated for free.

LLM RL is different. You need prompts (questions to ask the model) and a reward signal (a way to score the answers). The reward signal is the hard part.

Two types of RL data

flowchart LR A["Classic RL"] --> B["No dataset needed"] B --> C["Agent plays the environment"] C --> D["Data = trajectories generated on the fly"] E["LLM RL"] --> F["Need prompts + reward"] F --> G["Prompts: questions, tasks, problems"] F --> H["Reward: human ratings, verifiers, test suites"]

What each task requires

TaskPrompts / InputReward signalExample datasets
Game playingEnvironment (built in)Score / win-lossGymnasium CartPole, Atari, Chess
Math reasoningMath problems with known answersBinary: correct or wrongDeepMath-103K, OpenR1-Math-220K
Code generationCoding tasks with test casesTest suite pass/failSWE-Bench, MBPP, HumanEval
General helpfulnessUser questionsHuman preference ratingsUltraFeedback, Anthropic HH
Safety alignmentAdversarial promptsHuman safety ratingsRed-teaming datasets

The reward signal matters more than the data

stateDiagram-v2 direction LR [*] --> Verifiable: Math, code, games [*] --> Learned: Chat, creativity Verifiable --> BinaryReward: Right/wrong Verifiable --> PartialCredit: Step-by-step scoring Learned --> RewardModel: Trained on human preferences Learned --> ConstitutionalAI: Model critiques itself

Verifiable rewards are the gold standard. If you can automatically check whether an answer is correct (math, code tests, game outcomes), your RL training will be much more stable and effective. This is why math and code are the dominant GRPO training domains -- you get free, noise-free reward signals.

Learned rewards (reward models trained on human preferences) are noisier. The reward model can have blind spots, and the RL agent will exploit them -- a problem called reward hacking.

The 30-70% rule: Your training prompts should be hard enough that the model gets them right 30-70% of the time before training. Too easy (>90% already correct) means the model learns nothing. Too hard (<10% correct) means it never gets positive reward and cannot improve. DeepSeek found this sweet spot was critical for R1's reasoning breakthrough.

Dataset sizes

OpenR1-Math
220K prompts
DeepMath-103K
103K prompts
UltraFeedback
64K preference pairs
Lichess Elite
~3M games

Where to find datasets

DatasetTypeSizeLink
DeepMath-103KMath for GRPO103KHF
OpenR1-Math-220KVerified math220KHF
UltraFeedbackPreference pairs64KHF
GymnasiumRL environmentsN/Afarama.org
MinariOffline RL trajectoriesVariousfarama.org
Lichess EliteChess games 2200+ ELO~3Mlichess.org

Data format for GRPO training

At minimum, you need a prompt and a way to verify the answer. TRL expects this format:

{"prompt": "What is 15 * 23?", "answer": "345"}
{"prompt": "Solve: 2x + 5 = 17", "answer": "6"}
{"prompt": "What is the derivative of x^3?", "answer": "3x^2"}

Quick start: load and inspect a dataset

pip install trl datasets transformers

from datasets import load_dataset
ds = load_dataset("trl-lib/DeepMath-103K", split="train")
print(f"Loaded {len(ds)} prompts")
print(f"Example: {ds[0]['prompt'][:100]}...")
print(f"Answer:  {ds[0]['answer']}")

Tooling: from solo to lab

The right tool depends on your budget, your hardware, and your model size

Pick your tier

flowchart TD A["What hardware do you have?"] --> B{"Just a laptop?"} B -->|"Yes"| C["Tier: Solo -- Gymnasium, SB3, CleanRL"] A --> D{"1 GPU (16-24GB)?"} D -->|"Yes"| E["Tier: 1 GPU -- TRL, Unsloth, simple_grpo"] A --> F{"2-8 GPUs?"} F -->|"Yes"| G["Tier: Team -- OpenRLHF, veRL, NeMo-RL"] A --> H{"16+ GPUs?"} H -->|"Yes"| I["Tier: Lab -- Forge, DAPO, Nanotron"]
TierCostHardwareToolsWhat you can train
SoloFreeLaptop / ColabGymnasium, CleanRL, SB3CartPole, Atari, FrozenLake, tabular RL
1 GPU$0-50/moRTX 4090 / L4TRL, Unsloth, simple_grpoGRPO on 0.5B-3B models
Team$200-2K/mo2-8x A100OpenRLHF, veRL, NeMo-RLGRPO on 7B-70B
Lab$5K+/mo16+ H100Forge, DAPO, NanotronFoundation models, frontier agents

GRPO frameworks compared

If you want to run GRPO on a language model, these are your options as of April 2026:

FrameworkMin hardwareDifficultyWhat makes it special
TRL1 GPUEasiestHuggingFace native. GRPOTrainer class, works out of the box. Best docs.
Unsloth1 GPU 16GBEasy2x speed, 60% less VRAM. Great for 3B models on consumer GPUs.
simple_grpo1 GPUEasySingle file. Read the code, understand the algorithm.
NeMo-RL1-8 GPUMediumNVIDIA native. Best integration with TensorRT, Triton.
OpenRLHF2+ GPUMediumRay + vLLM. Scales to 70B+ models.
veRL2+ GPUMediumFSDP + vLLM. Clean architecture.
Forge (MiniMax)16+ GPUInternal40x throughput vs TRL. CISPO algorithm. Not publicly available.

Throughput comparison

TRL (1 GPU)
1x
Unsloth (1 GPU)
2x
OpenRLHF (4x)
~5x
veRL (8x)
~8x
Forge (MiniMax)
40x

The GRPO training loop in code

This is what a minimal GRPO training run looks like with TRL. About 15 lines of actual code:

from trl import GRPOTrainer, GRPOConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-0.5B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B")
ds = load_dataset("trl-lib/DeepMath-103K", split="train[:1000]")

def reward_fn(completions, prompts):
    # return list of floats -- your scoring logic here
    return [1.0 if "answer" in c else 0.0 for c in completions]

config = GRPOConfig(
    output_dir="grpo-output",
    num_generations=8,         # G: outputs per prompt
    per_device_train_batch_size=2,
    learning_rate=5e-6,
    num_train_epochs=1,
)
trainer = GRPOTrainer(model=model, config=config,
    train_dataset=ds, reward_funcs=[reward_fn], tokenizer=tokenizer)
trainer.train()

Classic RL setup

pip install gymnasium stable-baselines3

from stable_baselines3 import PPO
import gymnasium as gym

env = gym.make("CartPole-v1")
model = PPO("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=50_000)
# Done. Model can balance a pole.

Supporting tools

vLLM
2-4x faster inference
Speeds up the "generate G outputs" step
W&B
Experiment tracking
Monitor reward curves, KL divergence, loss
DeepSpeed
ZeRO memory optimization
Fit bigger models on fewer GPUs
HF Hub
Models + datasets + configs
One-line downloads for everything

Hardware

You can do real RL training on a single GPU. Here is exactly what fits where.

The most common question: "How much GPU do I need?" The answer depends on whether you are doing classic RL (almost nothing) or LLM RL (depends on model size).

What fits on what

flowchart LR A["Classic RL"] --> B["CPU only. Laptop is fine."] C["GRPO 0.5B"] --> D["1x GPU 16GB"] E["GRPO 1.7B"] --> F["1x GPU 24GB"] G["GRPO 3B + LoRA"] --> H["1x GPU 24GB"] I["GRPO 7B"] --> J["1x A100 80GB"] K["GRPO 14B+"] --> L["4-8x A100/H100"]
TaskHardwareCostTime
CartPole / FrozenLake / TaxiLaptop CPU$0Seconds
Atari (DQN/PPO)CPU or GPU$01-4 hours
Chess CNN 23M params1x L4 24GB~$5~6 hours
GRPO 0.5B (Qwen2.5-0.5B)1x GPU 16GB+$5-152-8 hours
GRPO 1.7B (SmolLM3)1x GPU 24GB$10-304-24 hours
GRPO 3B + Unsloth + LoRA1x GPU 24GB$15-5012-48 hours
GRPO 7B full fine-tune1x A100 80GB$50-2001-3 days
GRPO 14B+4-8x A100/H100$500+2-7 days

GPU cloud pricing (April 2026)

RTX 3090 24GB
$0.25-0.40/hr
RTX 4090 24GB
$0.35-0.55/hr
L4 24GB
$0.30-0.50/hr
A100 40GB
$1.00-1.50/hr
A100 80GB
$1.50-2.00/hr
H100 80GB
$2.50-3.50/hr

VRAM math: why GRPO is cheaper than PPO

flowchart LR A["PPO"] --> B["Policy model"] A --> C["Critic model (same size!)"] A --> D["Reference model"] A --> E["Total: ~3x model params in VRAM"] F["GRPO"] --> G["Policy model"] F --> H["Reference model"] F --> I["Total: ~2x model params in VRAM"] J["GRPO + LoRA"] --> K["Policy model"] J --> L["LoRA adapters (tiny)"] J --> M["Total: ~1.1x model params in VRAM"]
~3x
PPO VRAM
Policy + Critic + Reference
~2x
GRPO VRAM
Policy + Reference
~1.1x
GRPO + LoRA VRAM
Policy + tiny adapters

Memory estimation formula

VRAM (GRPO full) = params_in_billions x 2 bytes x 2 copies + 3 GB overhead

0.5B model: 0.5 x 2 x 2 + 3 =  5 GB  -> fits 16GB GPU
1.7B model: 1.7 x 2 x 2 + 3 = 10 GB  -> fits 16GB GPU
3B model:   3.0 x 2 x 2 + 3 = 15 GB  -> fits 24GB GPU
3B + LoRA:  3.0 x 2 x 1 + 3 =  9 GB  -> fits 16GB GPU
7B model:   7.0 x 2 x 2 + 3 = 31 GB  -> needs A100 40GB+

Where to rent GPUs

ProviderCheapest optionBest GPUNotes
Google ColabFreeT4 16GBTime limited, can disconnect
Vast.ai$0.15/hr3090Cheapest marketplace, variable quality
RunPod$0.35/hr4090Good spot pricing
Lambda$1.10/hrA100Reliable, good for multi-day runs
NebiusVariesH100Nebius, competitive H100 pricing

Own vs rent

$9/wk
Own a 4090 (electricity only)
450W x 24h x 7d x $0.12/kWh
$60-90/wk
Rent a 4090 (cloud)
$0.35-0.55/hr x 168 hours

If you are training more than a few times per month, buying a used 3090 or 4090 pays for itself fast.

Benchmarking

How to measure whether your RL training actually worked

Training without evaluation is just burning money. You need to measure performance before training (baseline), during training (are things improving?), and after training (did it actually get better?).

The eval loop

sequenceDiagram participant B as Baseline Model participant T as Training participant E as Eval Suite participant D as Decision B->>E: Run evals before training E->>T: Baseline scores recorded T->>T: RL training (GRPO, PPO, etc.) T->>E: Run evals after training E->>D: Compare before vs after D->>D: Better? Ship it. Worse? Debug.

LLM benchmarks you should know

BenchmarkWhat it testsBest score (Apr 2026)Why it matters
SWE-Bench VerifiedFix real GitHub issues80.2% (M2.5)Hardest coding test. Requires reading real repos.
MMLUGeneral knowledge, 57 subjects~90%Broad competence check
MATH / AIMECompetition math~80%Tests reasoning, not just recall
HumanEvalCode generation from docstrings~95%Can the model write working code?
BrowseCompFind information on the web76.3% (M2.5)Tests agent web browsing ability
GPQAGraduate-level science questions~65%PhD-level reasoning
IFEvalInstruction following accuracy~90%Does it do what you asked?

Coding benchmark comparison

M2.5 (Droid)
79.7%
Opus 4.6 (Droid)
78.9%
M2.5 (OpenCode)
76.1%
Opus 4.6 (OC)
75.9%

What to watch during training

stateDiagram-v2 direction LR [*] --> Healthy [*] --> Danger Healthy --> RewardUp: Avg reward trending up Healthy --> StdDown: Reward std decreasing Healthy --> KLStable: KL divergence < 10 Danger --> RewardFlat: Reward plateau Danger --> KLSpike: KL spike = model collapse Danger --> LengthExplode: Completions getting very long
MetricWhat it tells youHealthyDanger sign
Average rewardIs the model improving?Trends upwardFlat or declining
Reward stdIs it converging?Gradually decreasingChaotic / increasing
KL divergenceHow far from the original model?<10-15Spike = reward hacking or collapse
Policy entropyStill exploring?Slow decreaseDrops to 0 = degenerate policy
Completion lengthIs it gaming the reward?StableExploding length = exploitation

Eval pipeline (from HF Smol Playbook)

flowchart LR A["Pretrain"] -->|"Eval"| B["MMLU, ARC, HellaSwag"] B --> C["SFT"] C -->|"Eval"| D["IFEval, MT-Bench"] D --> E["GRPO"] E -->|"Eval"| F["MATH, HumanEval, GSM8K"] F --> G["Deploy"]

Each training stage targets different capabilities. Pretraining builds knowledge (tested by MMLU). SFT teaches instruction following (tested by IFEval). RL training improves reasoning and correctness (tested by MATH and code benchmarks).

Running evals yourself

pip install lm-eval

# General knowledge
lm_eval --model hf --model_args pretrained=your-model --tasks mmlu --num_fewshot 5

# Math reasoning
lm_eval --model hf --model_args pretrained=your-model --tasks gsm8k --num_fewshot 8

# Code generation
pip install evalplus
evalplus.evaluate --model your-model --dataset humaneval

Leaderboards

Demo scripts

7 scripts you can run right now on your laptop. All CPU. Copy, paste, run.

#ScriptWhat it teachesTimeDependencies
1CartPole PPOBalance a pole using PPO. The "hello world" of RL.30sgymnasium stable-baselines3
2FrozenLake Q-learningTabular Q-learning. See the Q-table update in real time.5sgymnasium numpy
3Chess CNNTrain a tiny CNN to predict chess moves from board states.1mpython-chess torch
4GRPO simulationSee the GRPO algorithm work on a math task. No GPU needed.2snone
5Multi-armed banditThe explore-vs-exploit tradeoff. The oldest RL problem.1snumpy
6Taxi + action maskingQ-learning with illegal action masking.10sgymnasium numpy
7Reward shapingSame algo, same env, different rewards = different behavior.3sgymnasium numpy
1. CartPole PPO -- balance a pole on a cart
import gymnasium as gym
from stable_baselines3 import PPO

env = gym.make("CartPole-v1")
model = PPO("MlpPolicy", env, verbose=1, n_steps=1024, batch_size=64)
model.learn(total_timesteps=50_000)

scores = []
for _ in range(10):
    obs, _ = env.reset(); total = 0; done = False
    while not done:
        action, _ = model.predict(obs, deterministic=True)
        obs, r, term, trunc, _ = env.step(action); total += r; done = term or trunc
    scores.append(total)
print(f"Trained agent: {sum(scores)/len(scores):.0f}/500 avg score")
2. FrozenLake -- learn a path across a frozen lake
import gymnasium as gym
import numpy as np

env = gym.make("FrozenLake-v1", map_name="4x4", is_slippery=False)
q = np.zeros((16, 4)); lr, gamma, eps = 0.8, 0.95, 1.0

for ep in range(20_000):
    s, _ = env.reset(); done = False
    while not done:
        a = env.action_space.sample() if np.random.random() < eps else np.argmax(q[s])
        s2, r, t, tr, _ = env.step(a)
        q[s, a] += lr * (r + gamma * np.max(q[s2]) - q[s, a])
        s, done = s2, t or tr
    eps = max(0.05, eps * 0.999)

arrows = ["←", "↓", "→", "↑"]; holes = {5,7,11,12}
for row in range(4):
    line = ""
    for c in range(4):
        idx = row*4+c
        if idx in holes: line += " H "
        elif idx == 15: line += " G "
        else: line += f" {['L','D','R','U'][np.argmax(q[idx])]} "
    print(line)

wins = 0
for _ in range(1000):
    s, _ = env.reset()
    for _ in range(100):
        s, r, t, tr, _ = env.step(np.argmax(q[s]))
        if t or tr: break
    wins += r
print(f"Win rate: {wins/10:.1f}%")
3. Chess CNN -- predict moves from board positions
import random, numpy as np, chess, torch, torch.nn as nn

def board_to_tensor(b):
    p = np.zeros((12,8,8), dtype=np.float32)
    for sq, piece in b.piece_map().items():
        r,c = divmod(sq,8)
        p[(piece.piece_type-1)+(6 if piece.color==chess.BLACK else 0)][r][c]=1.
    return torch.tensor(p)

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Conv2d(12,32,3,padding=1),nn.ReLU(),
            nn.Conv2d(32,32,3,padding=1),nn.ReLU(),nn.Flatten(),
            nn.Linear(32*64,256),nn.ReLU(),nn.Linear(256,4096))
    def forward(self,x): return self.net(x)

data = []
for _ in range(500):
    b = chess.Board()
    for _ in range(40):
        if b.is_game_over(): break
        m = random.choice(list(b.legal_moves)); data.append((b.copy(),m)); b.push(m)
print(f"{len(data)} positions")

model = Net(); opt = torch.optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(5):
    random.shuffle(data); correct = total = 0
    for i in range(0,len(data)-64,64):
        batch = data[i:i+64]
        X = torch.stack([board_to_tensor(b) for b,_ in batch])
        Y = torch.tensor([m.from_square*64+m.to_square for _,m in batch])
        opt.zero_grad(); loss = nn.CrossEntropyLoss()(model(X),Y)
        loss.backward(); opt.step()
        correct += (model(X).argmax(1)==Y).sum().item(); total += len(Y)
    print(f"Epoch {epoch+1}: {correct/total*100:.1f}% accuracy")
4. GRPO simulation -- watch the algorithm learn math
import random

G, skill = 8, 0.3
prompts = [f"{a}+{b}" for a in range(1,20) for b in range(1,20)]
random.shuffle(prompts)

print("Simulating GRPO on simple addition...")
print(f"Starting accuracy: {skill:.0%}")
print()

for epoch in range(10):
    rewards = []
    for p in prompts[:50]:
        correct_answer = eval(p)
        group_rewards = []
        for _ in range(G):
            if random.random() < skill:
                group_rewards.append(1.0)  # correct
            else:
                group_rewards.append(0.0)  # wrong
        rewards.extend(group_rewards)
    skill = min(0.99, skill + (1-skill) * 0.15)
    avg_r = sum(rewards) / len(rewards)
    print(f"Epoch {epoch+1:2d} | avg reward: {avg_r:.3f} | model accuracy: {skill:.1%}")

print(f"\nFinal accuracy: {skill:.1%} (started at 30%)")
5. Multi-armed bandit -- explore vs exploit
import numpy as np
np.random.seed(42)

true_rewards = np.random.normal([1.0, 1.5, 0.8, 2.0, 1.2], 0.3)
Q = np.zeros(5)  # estimated value of each arm
N = np.zeros(5)  # times each arm was pulled

for t in range(1, 2001):
    if np.random.random() < 0.1:
        arm = np.random.randint(5)  # explore: random arm
    else:
        arm = np.argmax(Q)          # exploit: best known arm

    reward = np.random.normal(true_rewards[arm], 0.5)
    N[arm] += 1
    Q[arm] += (reward - Q[arm]) / N[arm]  # incremental mean update

    if t in [100, 500, 1000, 2000]:
        print(f"t={t:4d} | Q={np.round(Q,2)} | pulls={N.astype(int)}")

print(f"\nBest arm: {np.argmax(Q)} (true best: {np.argmax(true_rewards)})")
6. Taxi -- Q-learning with action masking
import gymnasium as gym
import numpy as np

env = gym.make("Taxi-v3")
q = np.zeros((env.observation_space.n, env.action_space.n))
lr, gamma, eps = 0.1, 0.99, 1.0

for _ in range(10_000):
    s, info = env.reset(); done = False
    while not done:
        mask = info.get("action_mask", np.ones(env.action_space.n))
        if np.random.random() < eps:
            a = np.random.choice(np.where(mask)[0])  # explore legal actions
        else:
            a = np.argmax(np.where(mask, q[s], -1e9))  # exploit best legal
        s2, r, t, tr, info = env.step(a)
        q[s, a] += lr * (r + gamma * np.max(q[s2]) - q[s, a])
        s, done = s2, t or tr
    eps = max(0.01, eps * 0.9995)

results = []
for _ in range(100):
    s, info = env.reset(); tot = 0
    for _ in range(200):
        mask = info.get("action_mask", np.ones(6))
        s, r, t, tr, info = env.step(np.argmax(np.where(mask, q[s], -1e9)))
        tot += r
        if t or tr: break
    results.append(tot)
print(f"Avg reward: {np.mean(results):.1f} (optimal ~8)")
7. Reward shaping -- same algo, different rewards, different behavior
import gymnasium as gym
import numpy as np

env = gym.make("MountainCar-v0")

def train(env, reward_fn, episodes=3000):
    bins = [20, 20]
    lo, hi = env.observation_space.low, env.observation_space.high
    sz = (hi - lo) / bins
    q = np.zeros(bins + [env.action_space.n])
    def discretize(obs):
        return tuple(np.clip(((obs - lo) / sz).astype(int), 0, np.array(bins) - 1))
    lr, gamma, ep = 0.1, 0.99, 1.0
    for _ in range(episodes):
        obs, _ = env.reset(); s = discretize(obs)
        for _ in range(200):
            a = env.action_space.sample() if np.random.random() < ep else np.argmax(q[s])
            obs2, r, t, tr, _ = env.step(a)
            shaped_r = reward_fn(obs, obs2, r, t)
            s2 = discretize(obs2)
            q[s][a] += lr * (shaped_r + gamma * np.max(q[s2]) - q[s][a])
            s, obs = s2, obs2
            if t or tr: break
        ep = max(0.01, ep * 0.999)
    return q, discretize

for name, fn in [
    ("Default reward", lambda o, o2, r, t: r),
    ("Shaped reward",  lambda o, o2, r, t: r + (o2[0] - o[0]) * 10 + abs(o2[1]) * 5),
]:
    q, disc = train(env, fn)
    wins = 0
    for _ in range(100):
        obs, _ = env.reset()
        for _ in range(200):
            obs, r, t, tr, _ = env.step(np.argmax(q[disc(obs)]))
            if t: wins += 1; break
            if tr: break
    print(f"{name}: {wins}/100 reached the goal")

print("\nSame algorithm, same environment.")
print("Different reward function = completely different behavior.")