ml-models / Specialized learning / Reinforcement learning core

Reinforcement learning

Learning from rewards instead of labels: Markov decision processes, returns and discounting, value functions and the Bellman equation, Q-learning and DQN, the policy gradient (REINFORCE, derived), actor–critic methods and PPO's clipped objective.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. MDPs, returns and discounting
  5. Value functions and the Bellman equation
  6. Q-learning and DQN
  7. Policy gradients: REINFORCE, derived
  8. Actor–critic
  9. PPO's clipped objective
  10. The PyTorch API
  11. Worked example
  12. Implementation
  13. Tradeoffs
  14. Connections
  15. Read first
  16. Read next
  17. References

Problem

Supervised learning needs the right answer for every input. Many problems only say how well things went, and only afterwards: a game is won or lost after hundreds of moves, a robot reaches the target or falls over, a chatbot's reply is rated after it is written. Nobody can label which move was right. The learner has to try actions, see what reward they lead to, and work out which earlier choices deserve the credit, all while the data it sees depends on the choices it makes. Reinforcement learning (RL) is the set of methods for that: learn a policy, a rule for picking actions, that maximizes the total reward collected over time.

Intuition

An agent sees a state, picks an action, and the environment answers with a reward and the next state. Two ideas carry almost every method. First, a state is worth the reward you can expect from it onwards, and that worth can be written recursively: the reward now plus the (discounted) worth of where you land. Learning those worths turns a delayed reward into an immediate signal for every step. Second, if a sampled action turned out better than expected, make it more likely; if worse, less likely. Value-based methods (Q-learning, DQN) follow the first idea; policy-gradient methods (REINFORCE, PPO) follow the second; actor–critic methods use the first to make the second less noisy.

The agent–environment loop The agent, holding a policy π(a | s), sends an action a at time t to the environment. The environment returns the next state s at time t + 1 and a reward r at time t + 1 back to the agent, and the loop repeats until the episode ends. agent policy π(a | s), maybe values V, Q environment p(s′, r | s, a), unknown action aₜ next state sₜ₊₁, reward rₜ₊₁

The data an RL agent learns from is produced by its own actions, so changing the policy changes the data.

Mechanics

MDPs, returns and discounting

The standard model is a Markov decision process (MDP): states s, actions a, a transition distribution p(s′, r | s, a) and a discount factor γ ∈ [0, 1). "Markov" means the next state and reward depend only on the current state and action, not on how the agent got there. A policy π(a | s) gives the probability of each action in each state. From time t, the return is the discounted sum of future rewards:

Gt = rt+1 + γ rt+2 + γ² rt+3 + … = rt+1 + γ Gt+1

Discounting keeps the sum finite in endless tasks and prefers sooner rewards to later ones; a reward k steps away counts γk as much, so γ = 0.9 looks roughly 1/(1 − γ) = 10 steps ahead. The goal is a policy that maximizes the expected return J(π) = E[G0].

Value functions and the Bellman equation

The state value Vπ(s) is the expected return from s when following π; the action value Qπ(s, a) is the expected return after taking a in s and following π from then on. Because Gt = r + γ Gt+1, both satisfy a one-step recursion, the Bellman equation:

Qπ(s, a) = E[ r + γ Vπ(s′) ]     Vπ(s) = Σa π(a | s) Qπ(s, a)

The best possible values satisfy the Bellman optimality equation, with a max in place of the average over the policy:

Q*(s, a) = E[ r + γ maxa′ Q*(s′, a′) ]     π*(s) = argmaxa Q*(s, a)

Knowing Q* is enough to act optimally: pick the action with the highest value. When the transition probabilities are known, value iteration finds V* by applying the right-hand side as an update, over and over, to every state; with γ < 1 each sweep shrinks the error by at least a factor γ, so it converges to the unique fixed point. The worked example runs it on the chain below.

A slippery four-state chain and its optimal values States 0, 1, 2 and a terminal goal state 3 sit in a row. Moving right succeeds with probability 0.8 and slips, leaving the agent where it is, with probability 0.2. Entering the goal pays reward 1; every other step pays 0. With discount 0.9, the optimal values are V*(0) = 0.752, V*(1) = 0.857 and V*(2) = 0.976. state 0 V* = 0.752 slip 0.2 0.8 state 1 V* = 0.857 slip 0.2 0.8 state 2 V* = 0.976 slip 0.2 0.8 goal terminal r = 1 on entering "right" moves one state with probability 0.8; "left" moves back, with the same slip (state 0 stays put) every step except entering the goal pays 0; discount γ = 0.9

The values fall by more than γ per state away from the goal because each move can slip and cost a step.

Q-learning and DQN

Without a model of the environment, the expectation in the Bellman equation cannot be computed, but it can be sampled. Q-learning keeps a table of estimates and, after each transition (s, a, r, s′), nudges Q(s, a) toward a one-sample Bellman target:

Q(s, a) ← Q(s, a) + α [ r + γ maxa′ Q(s′, a′) − Q(s, a) ]

The bracket is the temporal-difference (TD) error: how much better or worse the step went than the current estimate predicted. The target uses the max over next actions whatever action the agent will actually take next, so Q-learning is off-policy: it learns the greedy policy's values while behaving differently, typically ε-greedy (a random action with probability ε, otherwise the best one), which keeps it exploring. With every state–action pair visited often enough and a suitably decaying α, the table converges to Q*.

Tables do not scale to images or large state spaces. DQN replaces the table with a neural network Qθ(s, ·) that outputs one value per action, and trains it by regression on the TD target. Naively this is unstable, because consecutive samples are correlated and the target moves with every update. The DQN paper learned to play Atari games from pixels with two fixes: an experience replay buffer of past transitions sampled at random in minibatches, and targets computed from parameters held fixed while each update is made. The later Nature version made this a separate target network, copied from the online network every C steps, and clipped the error term, which amounts to a Huber loss (PyTorch's nn.HuberLoss).

Policy gradients: REINFORCE, derived

Value-based methods need a max over actions, which is awkward for continuous actions, and the policy they produce is deterministic. Policy-gradient methods instead parameterize the policy directly, πθ(a | s), for example a softmax over a network's logits, and do gradient ascent on J(θ). The difficulty is that θ changes which actions are sampled, and you cannot backpropagate through a random sample. The log-derivative trick gets around it. For one action drawn from πθ with reward R(a):

∇θ Ea∼π[R(a)] = Σa R(a) ∇θπθ(a) = Σa πθ(a) R(a) ∇θ log πθ(a) = Ea∼π[ R(a) ∇θ log πθ(a) ]

using ∇π = π ∇log π. The right-hand side is an expectation, so a sampled action gives an unbiased estimate of the gradient, and it needs only the reward's value, not its derivative. For a whole trajectory, the probability is a product of policy terms and environment terms; the environment terms do not depend on θ and drop out of ∇log, leaving

∇θ J(θ) = E[ Σt ∇θ log πθ(at | st) · Gt ]

where each action is credited only with the return that follows it (rewards before at cannot depend on it, and their terms average to zero). REINFORCE estimates this from sampled episodes and takes a gradient step. For a softmax policy, ∇logits log π(a) is one-hot(a) − π: raise the chosen action's logit, lower the rest in proportion to their probability. In code, the surrogate loss −log π(a | s) · G has exactly this gradient, which is how PyTorch's distributions documentation writes REINFORCE with Categorical.log_prob.

The estimate is unbiased but very noisy. Subtracting a baseline b(s) from Gt keeps it unbiased, because E[∇log π] = Σ ∇π = ∇ Σ π = ∇1 = 0, and can cut the variance a lot (worked example below).

Actor–critic

The best common baseline is the state's value. An actor–critic method learns a critic Vφ(s) alongside the actor πθ, and weights each log-probability by an advantage, how much better the action did than the state's average. The simplest estimate is the TD error:

A(s, a) ≈ δ = r + γ Vφ(s′) − Vφ(s)     actor: ∇θ ∝ δ ∇θ log πθ(a | s)     critic: minimize δ²

Using V(s′) instead of the full sampled return lowers variance further but adds bias wherever the critic is wrong; methods such as generalized advantage estimation blend one-step and multi-step estimates to trade the two off. The critic is trained by regression, like DQN's network, and the actor by the policy gradient. Under the true values the advantages average to zero in every state, which is what makes them a clean "better or worse than usual" signal.

PPO's clipped objective

Policy gradients are on-policy: each batch of experience is valid for the policy that collected it, and one gradient step uses it up. Taking several steps on the same batch, or one large step, can move the policy so far that performance collapses. PPO (proximal policy optimization) collects a batch with the current policy πold, then runs several epochs of minibatch updates on it, maximizing a surrogate that stops rewarding moves too far from πold. With the probability ratio ρ = πθ(a | s) / πold(a | s):

LCLIP(θ) = E[ min( ρ A, clip(ρ, 1 − ε, 1 + ε) A ) ]

with ε = 0.2 in the paper. For a good action (A > 0) the objective grows with ρ only up to 1 + ε, then goes flat, so there is no gradient pushing that action's probability further. For a bad action (A < 0) it stops at 1 − ε. The min makes the clip one-sided: if an update has already moved the policy the wrong way (made a bad action more likely, or a good one less likely), the unclipped term is the smaller one and its full gradient still applies. In practice the loss adds a value loss for the critic and an entropy bonus, which keeps the policy from collapsing onto one action too early. PPO is the RL algorithm behind RLHF (see preference optimization).

PPO's clipped objective as a function of the probability ratio Two plots of the objective against the ratio ρ from 0 to 2, with ε = 0.2. Left, advantage +1: the objective equals ρ up to ρ = 1.2, then stays flat at 1.2; the unclipped line ρ continues upward, dashed. Right, advantage −1: the objective is flat at −0.8 for ρ below 0.8, then follows −ρ downward; the unclipped line −ρ is dashed where the objective is flat. Flat parts have zero gradient. 0 1 2 ρ A = +1 (good action) 0 1 2 ρ A = −1 (bad action) flat at 1.2: no gradient flat at −0.8 −ρ, full gradient

Green: LCLIP for one sample; dashed grey: the unclipped ρA. Dotted verticals mark 1 − ε and 1 + ε.

The PyTorch API

PyTorch has no RL training loop of its own (libraries such as TorchRL build on it). What it provides are the pieces: distributions to sample actions and score them, autograd for the surrogate losses, and the regression losses for critics and Q-networks.

ComponentShape/ParamsMeaningSource
Categorical probs=None, logits=None (exactly one); batch shape = all but the last dim A discrete policy over K actions. Logits are normalized with logsumexp, probs by their sum. categorical.py 13-171
Categorical.sample sample_shape=() → integer actions Draws actions with torch.multinomial (with replacement); not differentiable. categorical.py 144-149
Categorical.log_prob value (actions) → log π(a) Gathers the normalized logit of each action; differentiable in the logits, so -log_prob(a) * G is the REINFORCE loss. categorical.py 151-157
Categorical.entropy → batch shape −Σ p log p, with logits clamped to the dtype's minimum; the entropy bonus in actor–critic and PPO losses. categorical.py 159-163
logits_to_probs, probs_to_logits (tensor, is_binary=False) Softmax over the last dim, and the clamped log back. utils.py 90-99, 128-138
Distribution.sample, rsample sample_shape The base class's sample is rsample under no_grad; distributions with a reparameterized rsample (such as Normal) allow the pathwise gradient instead. distribution.py 177-191
Score function in the docs Δθ = α r ∇θ log πθ(a | s) The package docstring's REINFORCE recipe with Categorical, and the pathwise alternative. distributions/__init__.py 25-71
nn.HuberLoss reduction="mean", delta=1.0 Squared error below delta, linear above it; the usual DQN regression loss. Equal to SmoothL1Loss when delta=1. loss.py 1081-1150

Worked example

Returns. With γ = 0.9, rewards [0, 0, 1] give returns G = [0.81, 0.9, 1.0] (computed backwards, Gt = rt+1 + γ Gt+1), and rewards [1, 1, 1, 1] give [3.439, 2.71, 1.9, 1.0]. A reward three steps away is worth γ² = 0.81 of one received now.

Value iteration on the chain. Start from V = 0 and apply V(s) ← maxa Σ p [r + γ V(s′)] to every state at once:

SweepV(0)V(1)V(2)
00.00000.00000.0000
10.00000.00000.8000
20.00000.57600.9440
30.41470.78340.9699
50.71930.85280.9754
100.75210.85660.9756
600.75220.85660.9756

Sweep 1 only rewards state 2, which reaches the goal with probability 0.8; the news spreads one state per sweep. The fixed point can be checked by hand: in state 2, V = 0.8 · 1 + 0.2 · γ · V, so V = 0.8 / (1 − 0.18) = 0.9756, and the same reasoning gives V(1) = 0.8566 and V(0) = 0.7522, matching the last row.

Q-learning without the model. One update by hand: Q(s, a) = 0.5, r = 0, maxa′ Q(s′, a′) = 0.8, α = 0.1: target = 0 + 0.9 × 0.8 = 0.72, TD error 0.22, new Q = 0.5 + 0.1 × 0.22 = 0.522.

Run on the chain for 500 episodes (α = 0.1, ε = 0.2, seeded), 2881 steps in all, the agent never sees the transition probabilities, yet:

StateQ*(s, left)Q*(s, right)Learned Q(s, left)Learned Q(s, right)
00.6770.7520.6800.757
10.6960.8570.7050.854
20.7920.9760.8000.996

The greedy policy is "right" in every state, and no learned value is more than 0.020 from Q*; with a constant α the estimates keep jittering around the fixed point instead of settling on it.

Advantages from the critic. In state 1 with the true values (V(1) = 0.8566), take "right":

  • With probability 0.8 it lands in state 2: δ = 0 + 0.9 × V(2) − V(1) = 0.0214, better than expected.
  • With probability 0.2 it lands in state 1: δ = 0 + 0.9 × V(1) − V(1) = −0.0857, worse than expected.

The probability-weighted average is 0.0000: under the true values, the TD error says only whether this outcome was luckier or unluckier than the state's average.

REINFORCE on a three-armed bandit. One state, three actions with fixed rewards 1, 2, 4, a softmax policy with all logits 0, so π = (1/3, 1/3, 1/3) and J = 2.3333. The exact gradient with respect to the logits is πk(Rk − J) = [−0.4444, −0.1111, 0.5556]. Each sampled action gives a different estimate R(a)(one-hot(a) − π):

Sampled actionEstimate, no baselineEstimate, baseline b = J
0 (reward 1)[0.667, −0.333, −0.333][−0.889, 0.444, 0.444]
1 (reward 2)[−0.667, 1.333, −0.667][0.111, −0.222, 0.111]
2 (reward 4)[−1.333, −1.333, 2.667][−0.556, −0.556, 1.111]

Both columns average (weighted by π) to the exact gradient, so both are unbiased. Without a baseline every estimate pushes up the sampled action, even the worst one, because all rewards are positive; the total variance is 4.148. With b = J the worst action is pushed down, and the variance falls to 0.519, 8 times smaller. Five steps of exact gradient ascent with learning rate 1:

StepπJ
0(0.333, 0.333, 0.333)2.333
1(0.196, 0.273, 0.532)2.868
2(0.103, 0.163, 0.734)3.366
3(0.058, 0.094, 0.847)3.636
4(0.039, 0.063, 0.898)3.757
5(0.029, 0.047, 0.924)3.820

PPO's clip, sample by sample (ε = 0.2):

AρρAclip(ρ)AObjectiveGradient in ρ
+10.50.50.80.51
+10.90.90.90.91
+11.11.11.11.11
+11.51.51.21.20 (clipped)
−10.5−0.5−0.8−0.80 (clipped)
−10.9−0.9−0.9−0.9−1
−11.1−1.1−1.1−1.1−1
−11.5−1.5−1.2−1.5−1

Only two of the eight rows lose their gradient: a good action that is already 1.2× or more as likely as before, and a bad action already at 0.8× or less. A good action that became less likely (ρ = 0.5, A = +1) or a bad one that became more likely (ρ = 1.5, A = −1) keeps its full gradient, which pulls the policy back.

Implementation

import torch
from torch.distributions import Categorical

def discounted(rewards, gamma=0.99):
    returns, g = [], 0.0
    for r in reversed(rewards):
        g = r + gamma * g
        returns.append(g)
    return torch.tensor(returns[::-1])

# REINFORCE with a mean baseline; env is any environment with reset() and step().
state, done, log_probs, rewards = env.reset(), False, [], []
while not done:
    dist = Categorical(logits=policy(state))
    action = dist.sample()
    log_probs.append(dist.log_prob(action))
    state, reward, done = env.step(action.item())
    rewards.append(reward)
G = discounted(rewards)
loss = -(torch.stack(log_probs) * (G - G.mean())).sum()
loss.backward(); optimizer.step(); optimizer.zero_grad()

# PPO: several epochs of minibatches from one batch collected with the old policy.
def ppo_loss(policy, critic, states, actions, old_log_probs, returns, advantages,
             clip_eps=0.2, value_coef=0.5, entropy_coef=0.01):
    dist = Categorical(logits=policy(states))
    ratio = torch.exp(dist.log_prob(actions) - old_log_probs)
    clipped = torch.clamp(ratio, 1 - clip_eps, 1 + clip_eps)
    policy_loss = -torch.min(ratio * advantages, clipped * advantages).mean()
    value_loss = (critic(states).squeeze(-1) - returns).pow(2).mean()
    return policy_loss + value_coef * value_loss - entropy_coef * dist.entropy().mean()

# DQN: regress Q(s, a) on a target computed by a frozen copy of the network.
q = q_net(s).gather(1, a.unsqueeze(1)).squeeze(1)
with torch.no_grad():
    target = r + gamma * (1 - done) * target_net(s_next).max(dim=1).values
loss = torch.nn.HuberLoss()(q, target)

The ratio is computed from log-probabilities, which is stabler than dividing probabilities. old_log_probs and advantages are computed once when the batch is collected and must carry no gradient; advantages are usually normalized to zero mean and unit variance per batch. The coefficients shown are common defaults rather than values from the pinned source.

Tradeoffs

  • Value-based versus policy gradient. Q-learning and DQN are off-policy and reuse old data through replay, so they are sample-efficient, but they need a max over actions (discrete actions) and can diverge with function approximation. Policy gradients handle continuous actions and stochastic policies and optimize the objective directly, but are on-policy and noisy.
  • Bias versus variance. Monte Carlo returns are unbiased but high-variance; bootstrapping from a learned value (TD targets, critics) cuts variance and adds bias wherever the value is wrong.
  • Exploration versus exploitation. ε-greedy and entropy bonuses keep trying new actions at the cost of reward now; too little exploration converges to a poor policy, too much never uses what it learned.
  • Discount. A larger γ values the long run but makes returns noisier and credit assignment harder; a smaller γ learns faster and is short-sighted.
  • PPO's ε and epochs. A smaller clip range or fewer epochs is safer but squeezes less learning from each batch.
  • Reward design. The agent optimizes exactly the reward given; a reward that is easy to game gets gamed, which is the reward-hacking problem in RLHF.
Why can REINFORCE use a reward that is not differentiable, such as a game score?

The log-derivative trick moves the gradient onto log πθ(a), which is differentiable in θ; the reward only multiplies it as a number. Nothing is backpropagated through the reward or the environment.

Why does subtracting a baseline not bias the policy gradient?

Its contribution is b · E[∇log π(a)] = b Σa ∇π(a) = b ∇ Σa π(a) = b ∇1 = 0, as long as b does not depend on the action.

Q-learning acts ε-greedily but its target uses maxa′ Q(s′, a′). Which policy's values does it learn?

The greedy policy's, which gives Q*: the target assumes the best action is taken next, whatever the behaviour policy actually does. That is what makes it off-policy, and why it can learn from a replay buffer of old experience.

Connections

References

  • Sutton and Barto. Reinforcement Learning: An Introduction, 2nd edition. MIT Press, 2018 (MDPs, Bellman equations, Q-learning, policy gradients).
  • Williams. Simple Statistical Gradient-Following Algorithms for Connectionist Reinforcement Learning. Machine Learning 8, 1992 (REINFORCE).
  • Mnih, Kavukcuoglu, Silver, Graves, Antonoglou, Wierstra and Riedmiller. Playing Atari with Deep Reinforcement Learning. 2013. arXiv:1312.5602 (DQN).
  • Schulman, Wolski, Dhariwal, Radford and Klimov. Proximal Policy Optimization Algorithms. 2017. arXiv:1707.06347.
  • PyTorch v2.15.0: torch/distributions/categorical.py, torch/distributions/__init__.py.