بنقرة واحدة
rl-foundations
Master RL theory - MDPs, value functions, Bellman equations, value/policy iteration, TD
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Master RL theory - MDPs, value functions, Bellman equations, value/policy iteration, TD
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Use when assessing codebase architecture and you feel pressure to soften critique, lead with strengths, or frame problems diplomatically - provides evidence-based critical assessment resisting relationship and economic pressures
Use when cataloging technical debt under time pressure and tempted to explain choices instead of delivering document - enforces execution discipline with scoped delivery patterns for partial catalogs
Use when stakeholders pressure you to change technical priorities and you're tempted to compromise on security-first or call it synthesis - enforces risk-based prioritization over stakeholder preferences
Use when you have architecture documentation from system-archaeologist and need critical assessment, refactoring recommendations, or improvement prioritization - routes to appropriate architect specialist skills
Master A2C, A3C, SAC, TD3 - actor-critic methods for continuous control
Master ε-greedy, UCB, curiosity-driven, RND, intrinsic motivation exploration
| name | rl-foundations |
| description | Master RL theory - MDPs, value functions, Bellman equations, value/policy iteration, TD |
Invoke this skill when you encounter:
This skill provides the theoretical foundation for ALL other deep-rl skills.
Do NOT use this skill for:
Understanding the theory enables everything else.
Reinforcement learning is built on a rigorous mathematical foundation:
Without this foundation, you're copy-pasting code you cannot debug, adapt, or extend.
An MDP is the mathematical framework for sequential decision-making under uncertainty.
Formal Definition: A Markov Decision Process is a 5-tuple (S, A, P, R, γ):
Key Property: Markov Property
P(s_{t+1} | s_t, a_t, s_{t-1}, a_{t-1}, ..., s_0, a_0) = P(s_{t+1} | s_t, a_t)
Meaning: The future depends only on the present state, not the history.
Why this matters: Allows recursive algorithms (Bellman equations). If Markov property violated, standard RL algorithms may fail.
Problem: Agent navigates 4x4 grid to reach goal.
S = {(0,0), (0,1), ..., (3,3)} # 16 states
A = {UP, DOWN, LEFT, RIGHT} # 4 actions
R = -1 for each step, +10 at goal
γ = 0.9
P: Deterministic (up always moves up if not wall)
Visualization:
S . . .
. . . .
. # . . # = wall
. . . G G = goal (+10)
Transition Example:
Markov Property Holds: Future position depends only on current position and action, not how you got there.
Modification: Actions succeed with probability 0.8, move perpendicular with probability 0.1 each.
P((1,2) | (1,1), RIGHT) = 0.8 # intended
P((0,1) | (1,1), RIGHT) = 0.1 # slip up
P((2,1) | (1,1), RIGHT) = 0.1 # slip down
Why Stochastic: Models real-world uncertainty (robot actuators, wind, slippery surfaces).
Consequence: Agent must consider probabilities when choosing actions.
S ⊂ ℝ⁴: (cart_position, cart_velocity, pole_angle, pole_angular_velocity)
A = {LEFT, RIGHT} # discrete actions, continuous state
R = +1 for each timestep upright
γ = 0.99
P: Physics-based transition (continuous dynamics)
Key Difference: State space is continuous, requires function approximation (neural networks).
Still an MDP: Markov property holds (physics is Markovian given state).
Example: Poker
State: Current cards visible
Markov Violated: Opponents' strategies depend on past betting patterns
Solution: Augment state with history (last N actions), or use partially observable MDP (POMDP).
Example: Robot with Noisy Sensors
State: Raw sensor reading (single frame)
Markov Violated: True position requires integrating multiple frames
Solution: Stack frames (last 4 frames as state), or use recurrent network (LSTM).
Episodic: Task terminates (games, reaching goal)
Episode: s₀ → s₁ → ... → s_T (terminal state)
Return: G_t = r_t + γr_{t+1} + ... + γ^{T-t}r_T
Continuing: Task never ends (stock trading, robot operation)
Return: G_t = r_t + γr_{t+1} + γ²r_{t+2} + ... (infinite)
Critical: Continuing tasks REQUIRE γ < 1 (else return infinite).
Bad: State = current frame only (when velocity matters)
# Pong: Ball position alone doesn't tell velocity
state = current_frame # WRONG - not Markovian
Good: State = last 4 frames (velocity from difference)
# Frame stacking preserves Markov property
state = np.concatenate([frame_t, frame_{t-1}, frame_{t-2}, frame_{t-3}])
Why: Ball velocity = (position_t - position_{t-1}) / dt, need history.
Example: Robot navigating to goal
Bad Reward:
reward = +1 if at_goal else 0 # Sparse
Problem: No signal until goal reached, hard to learn.
Better Reward:
reward = -distance_to_goal # Dense
Problem: Agent learns to get closer but may not reach goal (local optimum).
Best Reward (Potential-Based Shaping):
reward = (distance_prev - distance_curr) + large_bonus_at_goal
Why: Encourages progress + explicit goal reward.
Takeaway: Reward function engineering is CRITICAL. Route to reward-shaping skill for details.
Before implementing any RL algorithm, answer:
If you cannot answer these, you cannot implement RL algorithms effectively.
A value function quantifies "how good" a state (or state-action pair) is.
State-Value Function V^π(s):
V^π(s) = E_π[G_t | s_t = s]
= E_π[r_t + γr_{t+1} + γ²r_{t+2} + ... | s_t = s]
Meaning: Expected cumulative discounted reward starting from state s and following policy π.
Action-Value Function Q^π(s,a):
Q^π(s,a) = E_π[G_t | s_t = s, a_t = a]
= E_π[r_t + γr_{t+1} + γ²r_{t+2} + ... | s_t = s, a_t = a]
Meaning: Expected cumulative discounted reward starting from state s, taking action a, then following policy π.
Relationship:
V^π(s) = Σ_a π(a|s) Q^π(s,a)
Intuition: V(s) = value of state, Q(s,a) = value of state-action pair.
Reward r(s,a): Immediate, one-step payoff.
Value V(s): Long-term, cumulative expected reward.
Example: GridWorld
Reward: r = -1 every step, r = +10 at goal
Value at state 2 steps from goal:
V(s) ≈ -1 + γ(-1) + γ²(+10)
= -1 - 0.9 + 0.81*10
= -1.9 + 8.1 = 6.2
Key: Value is higher than immediate reward because it accounts for future goal reward.
Common Mistake: Setting V(s) = r(s). This ignores all future rewards.
GridWorld: 3x3 grid, goal at (2,2), γ=0.9, r=-1 per step.
Policy π: Always move right or down (deterministic).
Manual Calculation:
V^π((2,2)) = 0 (goal, no future rewards)
V^π((2,1)) = r + γ V^π((2,2))
= -1 + 0.9 * 0 = -1
V^π((1,2)) = r + γ V^π((2,2))
= -1 + 0.9 * 0 = -1
V^π((1,1)) = r + γ V^π((1,2)) (assuming action = DOWN)
= -1 + 0.9 * (-1) = -1.9
V^π((0,0)) = r + γ V^π((0,1))
= ... (depends on path)
Observation: Values decrease as distance from goal increases (more -1 rewards to collect).
*Optimal State-Value Function V(s)**:
V*(s) = max_π V^π(s)
Meaning: Maximum value achievable from state s under ANY policy.
*Optimal Action-Value Function Q(s,a)**:
Q*(s,a) = max_π Q^π(s,a)
Meaning: Maximum value achievable from state s, taking action a, then acting optimally.
Optimal Policy π*:
π*(s) = argmax_a Q*(s,a)
Meaning: Policy that achieves V*(s) at all states.
Key Insight: If you know Q*(s,a), optimal policy is trivial (pick action with max Q).
Wrong Understanding:
Correct Understanding:
Example: GridWorld
State s = (1,1)
V(s) might be 5.0 (average value under policy)
Q(s, RIGHT) = 6.0 (moving right is good)
Q(s, LEFT) = 2.0 (moving left is bad)
Q(s, UP) = 4.0
Q(s, DOWN) = 7.0 (moving down is best)
V(s) = π(RIGHT|s)*6 + π(LEFT|s)*2 + π(UP|s)*4 + π(DOWN|s)*7
Takeaway: Q depends on BOTH state and action. V depends only on state.
Wrong: V(s) = sum of rewards on one trajectory.
Correct: V(s) = expected sum over ALL possible trajectories.
Example: Stochastic GridWorld
# WRONG: Compute V by running one episode
episode_return = sum([r_0, r_1, ..., r_T])
V[s_0] = episode_return # This is ONE sample, not expectation
# CORRECT: Compute V by averaging over many episodes
returns = []
for _ in range(1000):
episode_return = run_episode(policy, start_state=s)
returns.append(episode_return)
V[s] = np.mean(returns) # Expectation via Monte Carlo
Key: Value is an expectation, not a single sample.
Scenario: User computes V without discounting.
Wrong:
V[s] = r_0 + r_1 + r_2 + ... # No discount
Correct:
V[s] = r_0 + gamma*r_1 + gamma**2*r_2 + ...
Why It Matters: Without discount, values blow up in continuing tasks.
Example: Continuing task with r=1 every step
Without discount: V = 1 + 1 + 1 + ... = ∞
With γ=0.9: V = 1 + 0.9 + 0.81 + ... = 1/(1-0.9) = 10
Takeaway: Always discount future rewards in continuing tasks.
A policy π is a mapping from states to actions (or action probabilities).
Deterministic Policy: π: S → A
π(s) = a (always take action a in state s)
Stochastic Policy: π: S × A → [0,1]
π(a|s) = probability of taking action a in state s
Σ_a π(a|s) = 1 (probabilities sum to 1)
Deterministic Policy:
def policy(state):
if state[0] < 2:
return "RIGHT"
else:
return "DOWN"
Stochastic Policy:
def policy(state):
# 70% right, 20% down, 10% up
return np.random.choice(["RIGHT", "DOWN", "UP"],
p=[0.7, 0.2, 0.1])
Uniform Random Policy:
def policy(state):
return np.random.choice(["UP", "DOWN", "LEFT", "RIGHT"])
Problem: Given policy π, compute V^π(s) for all states.
Approach 1: Monte Carlo (sample trajectories)
# Run many episodes, average returns
V = defaultdict(float)
counts = defaultdict(int)
for episode in range(10000):
trajectory = run_episode(policy)
G = 0
for (s, a, r) in reversed(trajectory):
G = r + gamma * G
V[s] += G
counts[s] += 1
for s in V:
V[s] /= counts[s] # Average
Approach 2: Bellman Expectation (iterative)
# Initialize V arbitrarily
V = {s: 0 for s in states}
# Iterate until convergence
while not converged:
V_new = {}
for s in states:
V_new[s] = sum(policy(a|s) * (R(s,a) + gamma * sum(P(s'|s,a) * V[s']
for s' in states))
for a in actions)
V = V_new
Approach 2 requires knowing P(s'|s,a) (model-based).
Theorem: Given V^π, greedy policy π' with respect to V^π is at least as good as π.
π'(s) = argmax_a Q^π(s,a)
= argmax_a Σ_{s'} P(s'|s,a) [R(s,a,s') + γV^π(s')]
Proof Sketch: By construction, π' maximizes expected immediate reward + future value.
Consequence: Iterating policy evaluation + policy improvement converges to optimal policy π*.
Theorem: There exists an optimal policy πthat achieves V(s) at all states.
How to find π from Q**:
def optimal_policy(state):
return argmax(Q_star[state, :]) # Greedy w.r.t. Q*
How to find π from V**:
def optimal_policy(state):
# One-step lookahead
return argmax([R(state, a) + gamma * sum(P(s'|state,a) * V_star[s']
for s' in states)
for a in actions])
Key: Optimal policy is deterministic (greedy w.r.t. Qor V).
Exception: In stochastic games with multiple optimal actions, any distribution over optimal actions is fine.
Problem: Always taking argmax(Q) means never trying new actions.
Example:
# Pure greedy policy (WRONG for learning)
def policy(state):
return argmax(Q[state, :])
Why It Fails: If Q is initialized wrong, agent never explores better actions.
Solution: ε-greedy policy
def epsilon_greedy_policy(state, epsilon=0.1):
if random.random() < epsilon:
return random.choice(actions) # Explore
else:
return argmax(Q[state, :]) # Exploit
Exploration-Exploitation Tradeoff: Explore to find better actions, exploit to maximize reward.
Scenario: Optimal policy is deterministic (most MDPs), but user uses stochastic policy.
Effect: Suboptimal performance (randomness doesn't help).
Example: GridWorld optimal policy always moves toward goal (deterministic).
When Stochastic is Needed:
Takeaway: After learning, optimal policy is usually deterministic. Use stochastic for exploration.
For V^π:
V^π(s) = Σ_a π(a|s) Σ_{s'} P(s'|s,a) [R(s,a,s') + γ V^π(s')]
Intuition: Value of state s = expected immediate reward + discounted value of next state.
For Q^π:
Q^π(s,a) = Σ_{s'} P(s'|s,a) [R(s,a,s') + γ Σ_{a'} π(a'|s') Q^π(s',a')]
Intuition: Value of (s,a) = expected immediate reward + discounted value of next (s',a').
Relationship:
V^π(s) = Σ_a π(a|s) Q^π(s,a)
Q^π(s,a) = Σ_{s'} P(s'|s,a) [R(s,a,s') + γ V^π(s')]
For V*:
V*(s) = max_a Σ_{s'} P(s'|s,a) [R(s,a,s') + γ V*(s')]
Intuition: Optimal value = max over actions of (immediate reward + discounted optimal future value).
For Q*:
Q*(s,a) = Σ_{s'} P(s'|s,a) [R(s,a,s') + γ max_{a'} Q*(s',a')]
Intuition: Optimal Q-value = expected immediate reward + discounted optimal Q-value of next state.
Relationship:
V*(s) = max_a Q*(s,a)
Q*(s,a) = Σ_{s'} P(s'|s,a) [R(s,a,s') + γ V*(s')]
Start with definition of V^π:
V^π(s) = E_π[G_t | s_t = s]
= E_π[r_t + γr_{t+1} + γ²r_{t+2} + ... | s_t = s]
Factor out first reward:
V^π(s) = E_π[r_t + γ(r_{t+1} + γr_{t+2} + ...) | s_t = s]
= E_π[r_t | s_t = s] + γ E_π[r_{t+1} + γr_{t+2} + ... | s_t = s]
Second term is V^π(s_{t+1}):
V^π(s) = E_π[r_t | s_t = s] + γ E_π[V^π(s_{t+1}) | s_t = s]
Expand expectations:
V^π(s) = Σ_a π(a|s) Σ_{s'} P(s'|s,a) [R(s,a,s') + γ V^π(s')]
This is the Bellman Expectation Equation.
Key Insight: Value function satisfies a consistency equation (recursive).
1. Iterative Algorithms: Use Bellman equation as update rule
# Value Iteration
V_new[s] = max_a Σ_{s'} P(s'|s,a) [R(s,a,s') + γ V[s']]
# Q-Learning
Q[s,a] += alpha * (r + gamma * max_a' Q[s',a'] - Q[s,a])
2. Convergence Guarantees: Bellman operator is a contraction, guarantees convergence.
3. Understanding Algorithms: All RL algorithms approximate Bellman equations.
Takeaway: Bellman equations are the foundation of RL algorithms.
Bellman Expectation (for policy π):
V^π(s) = Σ_a π(a|s) ... # Expectation over policy
Bellman Optimality (for optimal policy):
V*(s) = max_a ... # Maximize over actions
Consequence:
Common Mistake: Using max when evaluating a non-greedy policy.
Deterministic Transition:
V^π(s) = R(s,a) + γ V^π(s') # Direct, s' is deterministic
Stochastic Transition:
V^π(s) = Σ_{s'} P(s'|s,a) [R(s,a,s') + γ V^π(s')] # Weighted sum
Example: Stochastic GridWorld
# Action RIGHT from (1,1)
V((1,1)) = 0.8 * [r + γ V((1,2))] # 80% intended
+ 0.1 * [r + γ V((0,1))] # 10% slip up
+ 0.1 * [r + γ V((2,1))] # 10% slip down
Takeaway: Don't forget to weight by transition probabilities in stochastic environments.
Discount factor γ ∈ [0, 1] controls how much the agent cares about future rewards.
γ = 0: Only immediate reward matters
V(s) = E[r_t] (myopic)
γ = 1: All future rewards matter equally
V(s) = E[r_t + r_{t+1} + r_{t+2} + ...] (far-sighted)
γ = 0.9: Future discounted exponentially
V(s) = E[r_t + 0.9*r_{t+1} + 0.81*r_{t+2} + ...]
Reward 10 steps away:
Effective Horizon: How far ahead does agent plan?
Approximation: Horizon ≈ 1/(1-γ)
Examples:
Intuition: After horizon steps, rewards are discounted to ~37% (e^{-1}).
Formal: Σ_{t=0}^∞ γ^t = 1/(1-γ) (sum of geometric series).
Rule of Thumb:
Example: Pong (episode ~ 1000 steps)
γ = 0.99 # Horizon ≈ 100, sees ~10% of episode
Example: Cartpole (episode ~ 200 steps)
γ = 0.99 # Horizon ≈ 100, sees half of episode
Example: Chess (game ~ 40 moves = 80 steps)
γ = 0.95 # Horizon ≈ 20, sees quarter of game
When γ = 1:
Example: GridWorld (terminates at goal)
γ = 1.0 # OK, episode ends
V(s) = -steps_to_goal + 10 (finite)
Example: Stock trading (never terminates)
γ = 1.0 # WRONG, V = ∞
γ = 0.99 # Correct
Takeaway: Use γ < 1 for continuing tasks, γ = 1 allowed for episodic.
Scenario: Task requires 50 steps to reach goal, γ=0.9.
Problem:
Reward at step 50 discounted by 0.9^50 = 0.0052
Effect: Agent effectively blind to long-term goals (can't see reward).
Solution: Increase γ to 0.99 (0.99^50 = 0.61, still significant).
Symptom: Agent learns suboptimal policy (ignores distant goals).
Scenario: Continuing task (never terminates), γ=1.
Problem:
V(s) = r + r + r + ... = ∞ (unbounded)
Effect: Value iteration, Q-learning diverge (values explode).
Solution: Use γ < 1 (e.g., γ=0.99).
Symptom: Values grow without bound, algorithm doesn't converge.
Wrong Mindset: "Let's grid search γ in [0.9, 0.95, 0.99]."
Correct Mindset: "Task requires planning X steps ahead, so γ = 1 - 1/X."
Example: Goal 100 steps away
Required horizon = 100
γ = 1 - 1/100 = 0.99
Takeaway: γ is not arbitrary. Choose based on task horizon.
1. Dynamic Programming (DP):
2. Monte Carlo (MC):
3. Temporal Difference (TD):
Key Differences:
Algorithm: Iteratively apply Bellman optimality operator.
# Initialize
V = {s: 0 for s in states}
# Iterate until convergence
while not converged:
V_new = {}
for s in states:
# Bellman optimality backup
V_new[s] = max([sum(P(s_next|s,a) * (R(s,a,s_next) + gamma * V[s_next])
for s_next in states)
for a in actions])
if max(abs(V_new[s] - V[s]) for s in states) < threshold:
converged = True
V = V_new
# Extract policy
policy = {s: argmax([sum(P(s_next|s,a) * (R(s,a,s_next) + gamma * V[s_next])
for s_next in states)
for a in actions])
for s in states}
Convergence: Guaranteed (Bellman operator is contraction).
Computational Cost: O(|S|² |A|) per iteration.
When to Use: Small state spaces (< 10,000 states), full model available.
Algorithm: Alternate between policy evaluation and policy improvement.
# Initialize random policy
policy = {s: random.choice(actions) for s in states}
while not converged:
# Policy Evaluation: Compute V^π
V = {s: 0 for s in states}
while not converged_V:
V_new = {}
for s in states:
a = policy[s]
V_new[s] = sum(P(s_next|s,a) * (R(s,a,s_next) + gamma * V[s_next])
for s_next in states)
V = V_new
# Policy Improvement: Make policy greedy w.r.t. V
policy_stable = True
for s in states:
old_action = policy[s]
policy[s] = argmax([sum(P(s_next|s,a) * (R(s,a,s_next) + gamma * V[s_next])
for s_next in states)
for a in actions])
if old_action != policy[s]:
policy_stable = False
if policy_stable:
converged = True
Convergence: Guaranteed, often fewer iterations than value iteration.
When to Use: When policy converges faster than values (common).
Key Difference from Value Iteration:
Idea: Estimate V^π(s) by averaging returns from state s.
# First-visit MC
V = defaultdict(float)
counts = defaultdict(int)
for episode in range(num_episodes):
trajectory = run_episode(policy) # [(s_0, a_0, r_0), ..., (s_T, a_T, r_T)]
G = 0
visited = set()
for (s, a, r) in reversed(trajectory):
G = r + gamma * G # Accumulate return
if s not in visited: # First-visit
V[s] += G
counts[s] += 1
visited.add(s)
for s in counts:
V[s] /= counts[s] # Average return
Advantages:
Disadvantages:
When to Use: Episodic tasks, when model unavailable.
Idea: Update V after each step using bootstrapping.
TD(0) Update:
V[s] += alpha * (r + gamma * V[s_next] - V[s])
# \_____________________/
# TD error
Bootstrapping: Use current estimate V[s_next] instead of true return.
Full Algorithm:
V = {s: 0 for s in states}
for episode in range(num_episodes):
s = initial_state()
while not terminal:
a = policy(s)
s_next, r = environment.step(s, a)
# TD update
V[s] += alpha * (r + gamma * V[s_next] - V[s])
s = s_next
Advantages:
Disadvantages:
When to Use: Model-free, need online learning.
TD for action-values Q(s,a):
Q[s,a] += alpha * (r + gamma * max_a' Q[s_next, a'] - Q[s,a])
Full Algorithm:
Q = defaultdict(lambda: defaultdict(float))
for episode in range(num_episodes):
s = initial_state()
while not terminal:
# ε-greedy action selection
if random.random() < epsilon:
a = random.choice(actions)
else:
a = argmax(Q[s])
s_next, r = environment.step(s, a)
# Q-learning update (off-policy)
Q[s][a] += alpha * (r + gamma * max(Q[s_next].values()) - Q[s][a])
s = s_next
Key: Off-policy (learns optimal Q regardless of behavior policy).
When to Use: Model-free, discrete actions, want optimal policy.
Difference from Q-learning: Uses next action from policy (on-policy).
Q[s,a] += alpha * (r + gamma * Q[s_next, a_next] - Q[s,a])
# ^^^^^^
# Action from policy, not max
Full Algorithm:
Q = defaultdict(lambda: defaultdict(float))
for episode in range(num_episodes):
s = initial_state()
a = epsilon_greedy(Q[s], epsilon) # Choose first action
while not terminal:
s_next, r = environment.step(s, a)
a_next = epsilon_greedy(Q[s_next], epsilon) # Next action from policy
# SARSA update (on-policy)
Q[s][a] += alpha * (r + gamma * Q[s_next][a_next] - Q[s][a])
s, a = s_next, a_next
Difference from Q-learning:
When to Use: When you want policy to reflect exploration strategy.
| Algorithm | Model? | Episodes? | Convergence | Use Case |
|---|---|---|---|---|
| Value Iteration | Yes (P, R) | No | Guaranteed | Small MDPs, known model |
| Policy Iteration | Yes (P, R) | No | Guaranteed, faster | Small MDPs, good init policy |
| Monte Carlo | No | Complete | Slow, high variance | Episodic, model-free |
| TD(0) | No | Partial | Faster, lower variance | Online, model-free |
| Q-Learning | No | Partial | Guaranteed* | Discrete actions, off-policy |
| SARSA | No | Partial | Guaranteed* | On-policy, safe exploration |
*With appropriate exploration and learning rate schedule.
Scenario: User tries value iteration on real robot (no model).
Problem: Value iteration requires P(s'|s,a) and R(s,a,s').
Solution: Use model-free methods (Q-learning, SARSA, policy gradients).
Red Flag: "Let's use policy iteration for Atari games." (No model available.)
Scenario: Continuing task (never terminates), try MC.
Problem: MC requires complete episodes to compute return.
Solution: Use TD methods (learn from partial trajectories).
Red Flag: "Let's use MC for stock trading." (Continuing task.)
Scenario: User uses Q-learning but expects on-policy behavior.
Example: Cliff walking with epsilon-greedy
Takeaway:
Choose based on whether you want optimal policy or policy that accounts for exploration.
Exploitation: Choose action with highest known value (maximize immediate reward).
Exploration: Try new actions to discover if they're better (maximize long-term information).
Dilemma: Must explore to find optimal policy, but exploration sacrifices short-term reward.
Example: Restaurant choice
Scenario: GridWorld, Q-values initialized to 0.
Without Exploration:
# Greedy policy
policy(s) = argmax(Q[s, :]) # Always 0 initially, picks arbitrary action
Problem: If first action happens to be BAD, Q[s,a] becomes negative, never tried again.
Result: Agent stuck in suboptimal policy (local optimum).
With Exploration:
# ε-greedy
if random.random() < epsilon:
action = random.choice(actions) # Explore
else:
action = argmax(Q[s, :]) # Exploit
Result: Eventually tries all actions, discovers optimal.
Algorithm:
def epsilon_greedy(state, Q, epsilon=0.1):
if random.random() < epsilon:
return random.choice(actions) # Explore with prob ε
else:
return argmax(Q[state, :]) # Exploit with prob 1-ε
Tuning ε:
Decay Schedule:
epsilon = max(epsilon_min, epsilon * decay_rate)
# Start high (ε=1.0), decay to low (ε=0.01)
Rationale: Explore heavily early, exploit more as you learn.
Idea: Choose action that balances value and uncertainty.
UCB Formula:
action = argmax(Q[s,a] + c * sqrt(log(N[s]) / N[s,a]))
# ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Exploitation Exploration bonus
Where:
Intuition: Actions tried less often get exploration bonus (uncertainty).
Advantage over ε-greedy: Adaptive exploration (focuses on uncertain actions).
Idea: Initialize Q-values to high values (optimistic).
Q = defaultdict(lambda: defaultdict(lambda: 10.0)) # Optimistic
Effect: All actions initially seem good, encourages exploration.
How it works:
Advantage: Simple, no ε parameter.
Disadvantage: Only works for finite action spaces, exploration stops after initial phase.
Idea: Choose actions probabilistically based on Q-values.
def softmax(Q, temperature=1.0):
exp_Q = np.exp(Q / temperature)
return exp_Q / np.sum(exp_Q)
probs = softmax(Q[state, :])
action = np.random.choice(actions, p=probs)
Temperature:
Advantage: Naturally weights exploration by Q-values (poor actions less likely).
Disadvantage: Requires tuning temperature, computationally more expensive.
Scenario: Pure greedy policy.
action = argmax(Q[state, :]) # No randomness
Problem: Agent never explores, gets stuck in local optimum.
Example: Q-values initialized to 0, first action is UP (arbitrary).
Solution: Always use some exploration (ε-greedy with ε ≥ 0.01).
Scenario: ε = 0.5 (50% random actions).
Problem: Agent wastes time on known-bad actions.
Effect: Slow convergence, poor performance even after learning.
Solution: Decay ε over time (start high, end low).
epsilon = max(0.01, epsilon * 0.995) # Decay to 1%
Scenario: Evaluating learned policy with ε-greedy (ε=0.1).
Problem: Test performance artificially low (10% random actions).
Solution: Use greedy policy at test time.
# Training
action = epsilon_greedy(state, Q, epsilon=0.1)
# Testing
action = argmax(Q[state, :]) # Greedy, no exploration
Takeaway: Exploration is for learning, not evaluation.
When Understanding Theory is Enough:
When You Need Implementation:
This Skill's Scope: Theory, intuition, foundations.
Other Skills for Implementation: value-based-methods, policy-gradient-methods, actor-critic-methods.
1. MDP Formulation: S, A, P, R, γ - the framework for RL.
2. Value Functions: V(s) = expected cumulative reward, Q(s,a) = value of action in state.
3. Bellman Equations: Recursive decomposition, foundation of all algorithms.
4. Discount Factor: γ controls planning horizon (1/(1-γ)).
5. Policies: Deterministic vs stochastic, optimal policy π*.
6. Algorithms:
7. Exploration: ε-greedy, UCB, necessary for learning.
8. Theory-Practice Gap: When theory suffices vs when to implement.
After mastering foundations, route to:
For Discrete Actions:
For Continuous Actions:
For Any Action Space:
For Debugging:
For Environment Setup:
Symptom: Implementing Q-learning without defining states, actions, rewards clearly.
Consequence: Algorithm fails, user doesn't know why.
Solution: Always answer:
Symptom: Setting V(s) = r(s).
Consequence: Ignores future rewards, policy suboptimal.
Solution: V(s) = E[r + γr' + γ²r'' + ...], not just r.
Symptom: "Let's use γ=0.9 because it's common."
Consequence: Agent can't see long-term goals (if γ too small) or values diverge (if γ=1 in continuing task).
Solution: Choose γ based on horizon (γ = 1 - 1/horizon).
Symptom: Pure greedy policy during learning.
Consequence: Agent stuck in local optimum.
Solution: ε-greedy with ε ≥ 0.01, decay over time.
Symptom: Trying value iteration on real robot.
Consequence: Algorithm requires P(s'|s,a), R(s,a), which are unknown.
Solution: Use model-free methods (Q-learning, policy gradients).
Symptom: Using MC on task that never terminates.
Consequence: Cannot compute return (episode never ends).
Solution: Use TD methods (learn from partial trajectories).
Symptom: Using Q-learning but expecting safe exploration.
Consequence: Q-learning learns optimal (risky) policy, ignores exploration safety.
Solution: Use SARSA for safe on-policy learning, Q-learning for optimal off-policy.
Symptom: Evaluating with ε-greedy (ε > 0).
Consequence: Test performance artificially low.
Solution: Greedy policy at test time (ε=0).
Symptom: Using Q-learning update without understanding why.
Consequence: Cannot debug convergence issues, tune hyperparameters.
Solution: Derive Bellman equation, understand bootstrapping.
Symptom: Using deterministic Bellman equation in stochastic environment.
Consequence: Wrong value estimates.
Solution: Weight by P(s'|s,a) in stochastic environments.
| Rationalization | Reality | Counter-Guidance | Red Flag |
|---|---|---|---|
| "I'll just copy Q-learning code" | Doesn't understand Q(s,a) meaning, cannot debug | "Let's understand what Q represents: expected cumulative reward. Why does Bellman equation have max?" | Jumping to code without theory |
| "V(s) is the reward at state s" | V is cumulative, r is immediate | "V(s) = E[r + γr' + ...], not just r. Value is long-term." | Confusing value and reward |
| "γ=0.9 is standard" | γ depends on task horizon | "What's your task horizon? γ=0.9 means ~10 steps. Need more?" | Arbitrary discount factor |
| "I don't need exploration, greedy is fine" | Gets stuck in local optimum | "Without exploration, you never try new actions. Use ε-greedy." | No exploration strategy |
| "Value iteration for Atari" | Atari doesn't have model (P, R unknown) | "Value iteration needs full model. Use model-free (DQN)." | DP on model-free problem |
| "Monte Carlo for continuing task" | MC requires episodes (termination) | "MC needs complete episodes. Use TD for continuing tasks." | MC on continuing task |
| "Q-learning and SARSA are the same" | Q-learning off-policy, SARSA on-policy | "Q-learning learns optimal, SARSA learns policy followed." | Confusing on-policy and off-policy |
| "I'll test with ε-greedy (ε=0.1)" | Test should be greedy (exploit only) | "Exploration is for learning. Test with ε=0 (greedy)." | Exploration at test time |
| "Bellman equation is just a formula" | It's the foundation of all algorithms | "Derive it. Understand why V(s) = r + γV(s'). Enables debugging." | Black-box understanding |
| "Deterministic transition, no need for P" | Correct, but must recognize when stochastic | "If stochastic, must weight by P(s' | s,a). Check environment." |
Watch for these signs of misunderstanding:
If any red flag triggered → Explain theory → Derive equation → Connect to algorithm
import numpy as np
# GridWorld: 4x4, goal at (3,3), walls at (1,1) and (2,2)
grid_size = 4
goal = (3, 3)
walls = {(1, 1), (2, 2)}
# MDP definition
gamma = 0.9
actions = ['UP', 'DOWN', 'LEFT', 'RIGHT']
def next_state(s, a):
"""Deterministic transition"""
x, y = s
if a == 'UP': x -= 1
elif a == 'DOWN': x += 1
elif a == 'LEFT': y -= 1
elif a == 'RIGHT': y += 1
# Boundary check
x = max(0, min(grid_size - 1, x))
y = max(0, min(grid_size - 1, y))
# Wall check
if (x, y) in walls:
return s # Bounce back
return (x, y)
def reward(s, a, s_next):
"""Reward function"""
if s_next == goal:
return 10
elif s_next in walls:
return -5
else:
return -1
# Value Iteration
V = np.zeros((grid_size, grid_size))
threshold = 0.01
max_iterations = 1000
for iteration in range(max_iterations):
V_new = np.zeros((grid_size, grid_size))
for x in range(grid_size):
for y in range(grid_size):
s = (x, y)
if s == goal:
V_new[x, y] = 0 # Terminal state
continue
# Bellman optimality backup
values = []
for a in actions:
s_next = next_state(s, a)
r = reward(s, a, s_next)
value = r + gamma * V[s_next[0], s_next[1]]
values.append(value)
V_new[x, y] = max(values)
# Check convergence
if np.max(np.abs(V_new - V)) < threshold:
print(f"Converged in {iteration} iterations")
break
V = V_new
# Extract policy
policy = {}
for x in range(grid_size):
for y in range(grid_size):
s = (x, y)
if s == goal:
policy[s] = None
continue
best_action = None
best_value = -float('inf')
for a in actions:
s_next = next_state(s, a)
r = reward(s, a, s_next)
value = r + gamma * V[s_next[0], s_next[1]]
if value > best_value:
best_value = value
best_action = a
policy[s] = best_action
print("Value Function:")
print(V)
print("\nOptimal Policy:")
for x in range(grid_size):
row = []
for y in range(grid_size):
action = policy.get((x, y), '')
if action == 'UP': symbol = '↑'
elif action == 'DOWN': symbol = '↓'
elif action == 'LEFT': symbol = '←'
elif action == 'RIGHT': symbol = '→'
else: symbol = 'G' # Goal
row.append(symbol)
print(' '.join(row))
Output:
Converged in 23 iterations
Value Function:
[[ 2.39 3.65 5.05 6.17]
[ 3.65 0. 6.17 7.59]
[ 5.05 0. 7.59 8.77]
[ 6.17 7.59 8.77 0. ]]
Optimal Policy:
→ → → ↓
↓ G → ↓
→ G → ↓
→ → → G
Key Observations:
import numpy as np
import random
# Same GridWorld setup
grid_size = 4
goal = (3, 3)
walls = {(1, 1), (2, 2)}
actions = ['UP', 'DOWN', 'LEFT', 'RIGHT']
gamma = 0.9
alpha = 0.1 # Learning rate
epsilon = 0.1 # Exploration
# Q-table
Q = {}
for x in range(grid_size):
for y in range(grid_size):
for a in actions:
Q[((x, y), a)] = 0.0
def epsilon_greedy(s, epsilon):
if random.random() < epsilon:
return random.choice(actions)
else:
# Greedy
best_action = actions[0]
best_value = Q[(s, best_action)]
for a in actions:
if Q[(s, a)] > best_value:
best_value = Q[(s, a)]
best_action = a
return best_action
# Training
num_episodes = 1000
for episode in range(num_episodes):
s = (0, 0) # Start state
while s != goal:
# Choose action
a = epsilon_greedy(s, epsilon)
# Take action
s_next = next_state(s, a)
r = reward(s, a, s_next)
# Q-learning update
if s_next == goal:
max_Q_next = 0 # Terminal
else:
max_Q_next = max(Q[(s_next, a_prime)] for a_prime in actions)
Q[(s, a)] += alpha * (r + gamma * max_Q_next - Q[(s, a)])
s = s_next
# Extract policy
print("Learned Policy:")
for x in range(grid_size):
row = []
for y in range(grid_size):
s = (x, y)
if s == goal:
row.append('G')
else:
best_action = max(actions, key=lambda a: Q[(s, a)])
if best_action == 'UP': symbol = '↑'
elif best_action == 'DOWN': symbol = '↓'
elif best_action == 'LEFT': symbol = '←'
elif best_action == 'RIGHT': symbol = '→'
row.append(symbol)
print(' '.join(row))
Output (similar to value iteration):
→ → → ↓
↓ G → ↓
→ G → ↓
→ → → G
Key Differences from Value Iteration:
import numpy as np
from collections import defaultdict
import random
# Simple chain MDP: s0 → s1 → s2 → goal
# Deterministic policy: always go right
# Reward: -1 per step, +10 at goal
# gamma = 0.9
gamma = 0.9
# Monte Carlo Policy Evaluation
def mc_policy_evaluation(num_episodes=1000):
V = defaultdict(float)
counts = defaultdict(int)
for _ in range(num_episodes):
# Generate episode
trajectory = [
(0, -1), # (state, reward)
(1, -1),
(2, -1),
(3, 10), # goal
]
# Compute returns
G = 0
visited = set()
for s, r in reversed(trajectory):
G = r + gamma * G
if s not in visited:
V[s] += G
counts[s] += 1
visited.add(s)
for s in V:
V[s] /= counts[s]
return V
# TD(0) Policy Evaluation
def td_policy_evaluation(num_episodes=1000, alpha=0.1):
V = defaultdict(float)
for _ in range(num_episodes):
s = 0
while s != 3: # Until goal
# Take action (deterministic policy)
s_next = s + 1
r = 10 if s_next == 3 else -1
# TD update
V[s] += alpha * (r + gamma * V[s_next] - V[s])
s = s_next
return V
# Compare
V_mc = mc_policy_evaluation()
V_td = td_policy_evaluation()
print("Monte Carlo V:")
print({s: round(V_mc[s], 2) for s in [0, 1, 2]})
print("\nTD(0) V:")
print({s: round(V_td[s], 2) for s in [0, 1, 2]})
# True values (analytical)
V_true = {
0: -1 + gamma * (-1 + gamma * (-1 + gamma * 10)),
1: -1 + gamma * (-1 + gamma * 10),
2: -1 + gamma * 10,
}
print("\nTrue V:")
print({s: round(V_true[s], 2) for s in [0, 1, 2]})
Output:
Monte Carlo V:
{0: 4.39, 1: 6.1, 2: 8.0}
TD(0) V:
{0: 4.41, 1: 6.12, 2: 8.01}
True V:
{0: 4.39, 1: 6.1, 2: 8.0}
Observations:
import numpy as np
# Simple MDP: chain of 10 states, +1 reward at end
# Compare different gamma values
def value_iteration_chain(gamma, num_states=10):
V = np.zeros(num_states + 1) # +1 for goal
# Value iteration
for _ in range(100):
V_new = np.zeros(num_states + 1)
for s in range(num_states):
# Deterministic: s → s+1, reward = +1 at goal
s_next = s + 1
r = 1 if s_next == num_states else 0
V_new[s] = r + gamma * V[s_next]
V = V_new
return V[:num_states] # Exclude goal
# Compare gamma values
for gamma in [0.5, 0.9, 0.99, 1.0]:
V = value_iteration_chain(gamma)
print(f"γ={gamma}:")
print(f" V(s_0) = {V[0]:.4f}")
print(f" V(s_5) = {V[5]:.4f}")
print(f" V(s_9) = {V[9]:.4f}")
print(f" Effective horizon = {1/(1-gamma) if gamma < 1 else 'inf':.1f}\n")
Output:
γ=0.5:
V(s_0) = 0.0010
V(s_5) = 0.0313
V(s_9) = 0.5000
Effective horizon = 2.0
γ=0.9:
V(s_0) = 0.3487
V(s_5) = 0.5905
V(s_9) = 0.9000
Effective horizon = 10.0
γ=0.99:
V(s_0) = 0.9044
V(s_5) = 0.9510
V(s_9) = 0.9900
Effective horizon = 100.0
γ=1.0:
V(s_0) = 1.0000
V(s_5) = 1.0000
V(s_9) = 1.0000
Effective horizon = inf
Key Insights:
Lesson: Choose γ based on how far ahead agent must plan.
import numpy as np
import random
# Simple bandit: 3 actions, true Q* = [1.0, 5.0, 3.0]
# Compare exploration strategies
true_Q = [1.0, 5.0, 3.0]
num_actions = 3
def sample_reward(action):
"""Stochastic reward"""
return true_Q[action] + np.random.randn() * 0.5
# Strategy 1: ε-greedy
def epsilon_greedy_experiment(epsilon=0.1, num_steps=1000):
Q = [0.0] * num_actions
counts = [0] * num_actions
total_reward = 0
for _ in range(num_steps):
# Choose action
if random.random() < epsilon:
action = random.randint(0, num_actions - 1)
else:
action = np.argmax(Q)
# Observe reward
reward = sample_reward(action)
total_reward += reward
# Update Q
counts[action] += 1
Q[action] += (reward - Q[action]) / counts[action]
return total_reward / num_steps
# Strategy 2: UCB
def ucb_experiment(c=2.0, num_steps=1000):
Q = [0.0] * num_actions
counts = [0] * num_actions
# Initialize: try each action once
for a in range(num_actions):
reward = sample_reward(a)
counts[a] = 1
Q[a] = reward
total_reward = 0
for t in range(num_actions, num_steps):
# UCB action selection
ucb_values = [Q[a] + c * np.sqrt(np.log(t) / counts[a])
for a in range(num_actions)]
action = np.argmax(ucb_values)
# Observe reward
reward = sample_reward(action)
total_reward += reward
# Update Q
counts[action] += 1
Q[action] += (reward - Q[action]) / counts[action]
return total_reward / num_steps
# Strategy 3: Greedy (no exploration)
def greedy_experiment(num_steps=1000):
Q = [0.0] * num_actions
counts = [0] * num_actions
total_reward = 0
for _ in range(num_steps):
action = np.argmax(Q)
reward = sample_reward(action)
total_reward += reward
counts[action] += 1
Q[action] += (reward - Q[action]) / counts[action]
return total_reward / num_steps
# Compare (average over 100 runs)
num_runs = 100
greedy_rewards = [greedy_experiment() for _ in range(num_runs)]
epsilon_rewards = [epsilon_greedy_experiment() for _ in range(num_runs)]
ucb_rewards = [ucb_experiment() for _ in range(num_runs)]
print(f"Greedy: {np.mean(greedy_rewards):.2f} ± {np.std(greedy_rewards):.2f}")
print(f"ε-greedy: {np.mean(epsilon_rewards):.2f} ± {np.std(epsilon_rewards):.2f}")
print(f"UCB: {np.mean(ucb_rewards):.2f} ± {np.std(ucb_rewards):.2f}")
print(f"\nOptimal: {max(true_Q):.2f}")
Output:
Greedy: 1.05 ± 0.52
ε-greedy: 4.62 ± 0.21
UCB: 4.83 ± 0.18
Optimal: 5.00
Insights:
Lesson: Exploration is critical. UCB > ε-greedy > greedy.
This skill covers theory and foundations. Route to other skills for:
Implementation:
Debugging:
Infrastructure:
Special Topics:
Evaluation:
You now understand:
Key Takeaways:
Next: Route to implementation skills (value-based, policy-gradient, actor-critic) to build real agents.
This foundation will enable you to implement, debug, and extend RL algorithms effectively.