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 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.
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).
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.
| Component | Shape/Params | Meaning | Source |
|---|---|---|---|
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:
| Sweep | V(0) | V(1) | V(2) |
|---|---|---|---|
| 0 | 0.0000 | 0.0000 | 0.0000 |
| 1 | 0.0000 | 0.0000 | 0.8000 |
| 2 | 0.0000 | 0.5760 | 0.9440 |
| 3 | 0.4147 | 0.7834 | 0.9699 |
| 5 | 0.7193 | 0.8528 | 0.9754 |
| 10 | 0.7521 | 0.8566 | 0.9756 |
| 60 | 0.7522 | 0.8566 | 0.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:
| State | Q*(s, left) | Q*(s, right) | Learned Q(s, left) | Learned Q(s, right) |
|---|---|---|---|---|
| 0 | 0.677 | 0.752 | 0.680 | 0.757 |
| 1 | 0.696 | 0.857 | 0.705 | 0.854 |
| 2 | 0.792 | 0.976 | 0.800 | 0.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 action | Estimate, no baseline | Estimate, 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 | ρ | ρA | clip(ρ)A | Objective | Gradient in ρ |
|---|---|---|---|---|---|
| +1 | 0.5 | 0.5 | 0.8 | 0.5 | 1 |
| +1 | 0.9 | 0.9 | 0.9 | 0.9 | 1 |
| +1 | 1.1 | 1.1 | 1.1 | 1.1 | 1 |
| +1 | 1.5 | 1.5 | 1.2 | 1.2 | 0 (clipped) |
| −1 | 0.5 | −0.5 | −0.8 | −0.8 | 0 (clipped) |
| −1 | 0.9 | −0.9 | −0.9 | −0.9 | −1 |
| −1 | 1.1 | −1.1 | −1.1 | −1.1 | −1 |
| −1 | 1.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
Read first
Read next
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.