DQN, DDPG, SAC, and Modern RL Applications

9/9/2026 tech

From DQN to continuous action spaces: the deterministic policy gradient, TD3's 3 fixes, SAC's maximum-entropy formulation, the squashed Gaussian policy, and where these ideas stand in modern robot learning.

Notation. Critic params θ\theta, actor params ϕ\phi, target networks θˉ,ϕˉ\bar\theta, \bar\phi. Note this is the opposite of the lecture notes convention, where θ\theta is the actor and ϕ\phi the critic — the DQN/DDPG/TD3/SAC papers all use the convention below, so it's easier to read the code against it.

# Background: Q-learning & DQN

What does QπQ^\pi mean? For a fixed policy π\pi, Qπ(s,a)Q^\pi(\mathbf{s}, \mathbf{a}) is the expected discounted return if we start in state s\mathbf{s}, take action a\mathbf{a} first, and follow π\pi from the next step onward:

Qπ(s,a)=Eπ,p[∑t=0∞γtrt∣s0=s,a0=a] Q^\pi(\mathbf{s}, \mathbf{a}) = \mathbb{E}_{\pi,p} \left[ \left. \sum_{t=0}^{\infty} \gamma^t r_t \,\right|\, \mathbf{s}_0 = \mathbf{s},\; \mathbf{a}_0 = \mathbf{a} \right]

Here rtr_t is the reward after taking at\mathbf{a}_t, and at∼π(⋅∣st)\mathbf{a}_t \sim \pi(\cdot \mid \mathbf{s}_t) for t≥1t \geq 1; the expectation includes both policy randomness and environment transitions pp. The superscript π\pi labels which policy we follow afterward, not a power. The first action a\mathbf{a} need not be the one π\pi would choose.

QπQ^\pi vs. QQ. Plain QQ is a generic action-value function; in the update rules below it denotes the current learned estimate. Evaluating a fixed π\pi aims for Q→QπQ \to Q^\pi, while Q-learning aims for Q→Q∗Q \to Q^*, the optimal action-value function. The current estimate need not equal either one yet.

Goal: learn the optimal action-value function Q∗(s,a)Q^*(\mathbf{s}, \mathbf{a}), the expected discounted return starting from s\mathbf{s}, taking a\mathbf{a}, then acting optimally.

Bellman optimality equation:

Q∗(s,a)=r(s,a)+γEs′∼p(⋅∣s,a)[max⁡a′Q∗(s′,a′)] Q^*(\mathbf{s}, \mathbf{a}) = r(\mathbf{s}, \mathbf{a}) + \gamma \mathbb{E}_{\mathbf{s}^\prime \sim p(\cdot | \mathbf{s}, \mathbf{a})} \left[ \max_{\mathbf{a}^\prime} Q^*(\mathbf{s}^\prime, \mathbf{a}^\prime) \right]

# Tabular Q-learning update: evaluation & improvement

Given a transition (s,a,s′)(\mathbf{s}, \mathbf{a}, \mathbf{s}^\prime) and reward rr, separate the update into 2 ideas (assume s′\mathbf{s}^\prime is nonterminal for now).

1. Policy evaluation — how good is the current policy? Hold a policy π\pi fixed and use its action probabilities at the next state to form a TD target:

yπ=r+γ∑a′π(a′∣s′)Q(s′,a′)Q(s,a)←Q(s,a)+α[yπ−Q(s,a)] \begin{aligned} y^\pi &= r + \gamma \sum_{\mathbf{a}^\prime} \pi(\mathbf{a}^\prime \mid \mathbf{s}^\prime) Q(\mathbf{s}^\prime, \mathbf{a}^\prime) \\ Q(\mathbf{s}, \mathbf{a}) &\leftarrow Q(\mathbf{s}, \mathbf{a}) + \alpha \left[ y^\pi - Q(\mathbf{s}, \mathbf{a}) \right] \end{aligned}

This changes the value estimate, not the policy. Repeated evaluation of a fixed π\pi aims to recover QπQ^\pi; one TD update is only a partial evaluation step.

2. Policy improvement — which action should we choose? Hold the current Q-table fixed and define a greedy policy (break ties arbitrarily):

πnew(s)∈arg⁡max⁡aQ(s,a) \pi_{\text{new}}(\mathbf{s}) \in \arg\max_{\mathbf{a}} Q(\mathbf{s}, \mathbf{a})

Why ∈\in instead of ==? Here arg⁡max⁡\arg\max means the set of all maximizing actions, while πnew(s)\pi_{\text{new}}(\mathbf{s}) is one chosen action from that set. For example, if left and right both have Q-value 55 and stay has Q-value 22, then arg⁡max⁡aQ(s,a)={left,right}\arg\max_{\mathbf{a}} Q(\mathbf{s}, \mathbf{a}) = \{\text{left}, \text{right}\}; choosing πnew(s)=left\pi_{\text{new}}(\mathbf{s}) = \text{left} satisfies the ∈\in relation. Writing πnew(s)=arg⁡max⁡aQ(s,a)\pi_{\text{new}}(\mathbf{s}) = \arg\max_{\mathbf{a}} Q(\mathbf{s}, \mathbf{a}) is common shorthand when arg⁡max⁡\arg\max is understood to return a single action using a tie-breaking rule.

We write this greedy policy as a deterministic action map πnew(s)\pi_{\text{new}}(\mathbf{s}). In the evaluation formula above, π(a∣s)\pi(\mathbf{a} \mid \mathbf{s}) instead denotes an action probability; a deterministic policy assigns probability 11 to its chosen action and 00 to the others.

This changes the policy, not the value estimate. With an exact QπQ^\pi, the policy improvement theorem guarantees that this greedy policy is at least as good as π\pi.

Putting them together gives Q-learning. At each update, use the greedy policy πQ\pi_Q implied by the current table as the target policy. Its evaluation target becomes

yπQ=r+γQ(s′,πQ(s′))=r+γmax⁡a′Q(s′,a′) y^{\pi_Q} = r + \gamma Q(\mathbf{s}^\prime, \pi_Q(\mathbf{s}^\prime)) = r + \gamma \max_{\mathbf{a}^\prime} Q(\mathbf{s}^\prime, \mathbf{a}^\prime)

so the combined update is

Q(s,a)←Q(s,a)+α[r+γmax⁡a′Q(s′,a′)⏟target−Q(s,a)] Q(\mathbf{s}, \mathbf{a}) \leftarrow Q(\mathbf{s}, \mathbf{a}) + \alpha \left[ \underbrace{r + \gamma \max_{\mathbf{a}^\prime} Q(\mathbf{s}^\prime, \mathbf{a}^\prime)}_{\text{target}} - Q(\mathbf{s}, \mathbf{a}) \right]

The max⁡\max supplies the greedy improvement, and moving QQ toward the resulting target supplies a TD evaluation step. Q-learning interleaves these through Bellman optimality backups; it does not evaluate each fixed policy to convergence before improving it. It is also off-policy: the behavior policy that collected the transition can be exploratory (e.g., ϵ\epsilon-greedy), while the target policy is greedy.

DDPG makes these roles explicit in separate networks: the critic evaluates the actor's policy, and the actor improves its actions using the critic.

# From a Q-table to a Q-network

When state spaces are large (e.g., images), we can't store a table, so approximate QQ w/ a NN Qθ(s,a)Q_\theta(\mathbf{s}, \mathbf{a}) and regress on the squared TD error:

L(θ)=E(s,a,s′)∼D[(Qθ(s,a)−(r+γmax⁡a′Qθ(s′,a′)))2] \mathcal{L}(\theta) = \mathbb{E}_{(\mathbf{s}, \mathbf{a}, \mathbf{s}^\prime) \sim \mathcal{D}} \left[ \left( Q_\theta(\mathbf{s}, \mathbf{a}) - \left( r + \gamma \max_{\mathbf{a}^\prime} Q_\theta(\mathbf{s}^\prime, \mathbf{a}^\prime) \right) \right)^2 \right]

Naive deep Q-learning is unstable, from 2 sources:

  1. correlated samples: sequential transitions (st,st+1,st+2,…)(\mathbf{s}_t, \mathbf{s}_{t+1}, \mathbf{s}_{t+2}, \dots) are highly correlated
  2. moving target: the target r+γmax⁡a′Qθ(s′,a′)r + \gamma \max_{\mathbf{a}^\prime} Q_\theta(\mathbf{s}^\prime, \mathbf{a}^\prime) shifts as we update θ\theta

DQN (Mnih et al., 2015) fixes these w/ 2 tricks.

# Trick 1: experience replay

Store transitions in a replay buffer D\mathcal{D} (fixed size, FIFO) and sample random mini-batches for each gradient step.

+: breaks correlation btw consecutive samples
+: reuses data efficiently (each transition used multiple times)
+: reduces non-stationarity of the training data

# Trick 2: target networks

Use a separate target network QθˉQ_{\bar\theta} that updates slowly, so the regression label stops moving under us:

L(θ)=E(s,a,s′)∼D[(Qθ(s,a)−(r+γmax⁡a′Qθˉ(s′,a′))⏟computed w/ frozen θˉ)2] \mathcal{L}(\theta) = \mathbb{E}_{(\mathbf{s}, \mathbf{a}, \mathbf{s}^\prime) \sim \mathcal{D}} \left[ \left( Q_\theta(\mathbf{s}, \mathbf{a}) - \underbrace{\left( r + \gamma \max_{\mathbf{a}^\prime} Q_{\bar\theta}(\mathbf{s}^\prime, \mathbf{a}^\prime) \right)}_{\text{computed w/ frozen } \bar\theta} \right)^2 \right]

2 ways to update the target network:

method update rule typical value
hard update θˉ←θ\bar\theta \leftarrow \theta every NN steps N=10000N = 10000
soft update (Polyak) θˉ←τθ+(1−τ)θˉ\bar\theta \leftarrow \tau \theta + (1 - \tau) \bar\theta each step τ=0.005\tau = 0.005

The soft update is what DDPG/TD3/SAC all use: instead of a discontinuous jump every NN steps, the target drifts smoothly toward the online net. This matters more in the continuous-action setting, where the actor is chasing the critic's gradient and a sudden target jump can knock the policy off a good region.

# Overestimation bias & double Q-learning

The max⁡\max operator makes Q-learning overestimate Q-values. Formally, max⁡\max and E\mathbb{E} don't commute:

E[max⁡a′Q(s′,a′)]≥max⁡a′E[Q(s′,a′)] \mathbb{E} \left[ \max_{\mathbf{a}^\prime} Q(\mathbf{s}^\prime, \mathbf{a}^\prime) \right] \geq \max_{\mathbf{a}^\prime} \mathbb{E} \left[ Q(\mathbf{s}^\prime, \mathbf{a}^\prime) \right]

Using the same network to both select and evaluate the best action amplifies noise: whichever action happens to have a positive error gets picked, so the target inherits that error. Worse, the inflated target becomes the next regression label, so the bias compounds through bootstrapping.

Double DQN (van Hasselt et al., 2016) decouples the two: the online network selects, the target network evaluates.

y=r+γQθˉ(s′,arg⁡max⁡a′Qθ(s′,a′)⏟selected by online network) y = r + \gamma \, Q_{\bar\theta} \Big( \mathbf{s}^\prime, \underbrace{\arg\max_{\mathbf{a}^\prime} Q_\theta(\mathbf{s}^\prime, \mathbf{a}^\prime)}_{\text{selected by online network}} \Big)

# Exploration: ϵ\epsilon-greedy

a={random actionw/ prob ϵarg⁡max⁡aQθ(s,a)w/ prob 1−ϵ \mathbf{a} = \begin{cases} \text{random action} & \text{w/ prob } \epsilon \\ \arg\max_\mathbf{a} Q_\theta(\mathbf{s}, \mathbf{a}) & \text{w/ prob } 1 - \epsilon \end{cases}

Common schedule: decay ϵ\epsilon from 1.01.0 to 0.010.01 over the first NN env steps.

# Full DQN algorithm

  1. init replay buffer D\mathcal{D}, Q-network QθQ_\theta, target network Qθˉ←QθQ_{\bar\theta} \leftarrow Q_\theta
  2. for each step:
    1. select action a\mathbf{a} using ϵ\epsilon-greedy w.r.t. QθQ_\theta
    2. execute a\mathbf{a}, observe r,s′r, \mathbf{s}^\prime; store (s,a,s′,r)(\mathbf{s}, \mathbf{a}, \mathbf{s}^\prime, r) in D\mathcal{D}
    3. sample mini-batch from D\mathcal{D}
    4. compute target y=r+γmax⁡a′Qθˉ(s′,a′)y = r + \gamma \max_{\mathbf{a}^\prime} Q_{\bar\theta}(\mathbf{s}^\prime, \mathbf{a}^\prime)
    5. update θ\theta by minimizing (Qθ(s,a)−y)2(Q_\theta(\mathbf{s}, \mathbf{a}) - y)^2
    6. update target network (hard or soft)

# Implementation tip: terminal states

Store a done flag dd w/ each transition, d=1d = 1 if s′\mathbf{s}^\prime is terminal, else d=0d = 0:

y=r+γ(1−d)max⁡a′Qθˉ(s′,a′) y = r + \gamma (1 - d) \max_{\mathbf{a}^\prime} Q_{\bar\theta}(\mathbf{s}^\prime, \mathbf{a}^\prime)

The (1−d)(1 - d) term makes sure we don't bootstrap from terminal states, where there is no future return. Every target below carries this factor.


# DDPG (Deep Deterministic Policy Gradient)

DDPG adapts DQN's success (experience replay, target networks) into continuous action spaces using deterministic policies.

actor-critic gradient flow: grad_a Q passing back through the actor

# Core theory: the DPG theorem

In discrete action spaces max⁡aQ(s,a)\max_\mathbf{a} Q(\mathbf{s}, \mathbf{a}) is an enumeration; in continuous spaces it is its own optimization problem at every step. Instead, train a deterministic actor a=μϕ(s)\mathbf{a} = \mu_\phi(\mathbf{s}) to output a high-value action, and let the critic tell it which way to move. The actor learns an approximate maximizer; it does not compute an exact arg⁡max⁡\arg\max.

For a fixed state, the differentiable path is ϕ→μϕ(s)→Qθ(s,μϕ(s))\phi \to \mu_\phi(\mathbf{s}) \to Q_\theta(\mathbf{s}, \mu_\phi(\mathbf{s})). Holding the critic parameters θ\theta fixed, the chain rule gives

∇ϕQθ(s,μϕ(s))=(∂μϕ(s)∂ϕ)⊤∇aQθ(s,a)∣a=μϕ(s) \nabla_\phi Q_\theta(\mathbf{s}, \mu_\phi(\mathbf{s})) = \left( \frac{\partial \mu_\phi(\mathbf{s})}{\partial \phi} \right)^\top \left. \nabla_\mathbf{a} Q_\theta(\mathbf{s}, \mathbf{a}) \right|_{\mathbf{a} = \mu_\phi(\mathbf{s})}
  • ∇aQθ(s,a)\nabla_\mathbf{a} Q_\theta(\mathbf{s}, \mathbf{a}) — the critic's gradient w.r.t. the action. At the actor's current action, it points in the direction of the steepest local increase in the estimated Q-value: the coach says how to adjust the wrist (手腕怎么压).
  • ∂μϕ(s)/∂ϕ\partial \mu_\phi(\mathbf{s}) / \partial \phi — the actor's Jacobian w.r.t. its parameters. It describes how changing each network parameter changes the output action: how the muscles control the wrist (怎么调整肌肉). For vector actions this is a matrix, so the transpose maps the critic's action-space gradient back into parameter space.

Multiplying the 2 gives the actor a parameter update direction. In the scalar case this is simply ∂Q∂a∂μ∂ϕ\frac{\partial Q}{\partial a}\frac{\partial \mu}{\partial \phi}: “教练指出动作往哪改,Actor 再把这个方向传回自己的参数。” Gradient ascent moves along this direction; minimizing the negative Q-value below implements the same update.

What the DPG theorem adds. The chain rule above only differentiates a fixed-state critic output. The deterministic policy gradient theorem (Silver et al., 2014) connects it to the expected long-term return: with the true critic QμϕQ^{\mu_\phi}, the appropriate discounted state-visitation weights under μϕ\mu_\phi, and standard differentiability assumptions, integrating this local gradient gives the policy's return gradient. We do not need to differentiate the environment dynamics or explicitly differentiate the state-visitation distribution. Determinism gives one action per state and a direct gradient path; differentiability is also required.

DDPG's practical approximation. Replace the true critic by QθQ_\theta and sample states from the replay buffer. With θ\theta and the replay sampling distribution fixed during the actor step, optimize the surrogate JD(ϕ)=Es∼D[Qθ(s,μϕ(s))]J_{\mathcal{D}}(\phi) = \mathbb{E}_{\mathbf{s} \sim \mathcal{D}}[Q_\theta(\mathbf{s}, \mu_\phi(\mathbf{s}))]:

∇ϕJD=Es∼D[(∂μϕ(s)∂ϕ)⊤∇aQθ(s,a)∣a=μϕ(s)] \nabla_\phi J_{\mathcal{D}} = \mathbb{E}_{\mathbf{s} \sim \mathcal{D}} \left[ \left( \frac{\partial \mu_\phi(\mathbf{s})}{\partial \phi} \right)^\top \left. \nabla_\mathbf{a} Q_\theta(\mathbf{s}, \mathbf{a}) \right|_{\mathbf{a} = \mu_\phi(\mathbf{s})} \right]

This is the exact gradient of the replay surrogate, but generally an approximation to the true return gradient because the critic is learned and replay states come from past behavior policies. In code, freeze the critic's parameters during the actor update, while keeping the gradient through its action input; detaching the action or critic output would break the chain.

# Critic loss (evaluation)

MSE against the target network:

y=r+γ(1−d)Qθˉ(s′,μϕˉ(s′))LQ(θ)=E[(Qθ(s,a)−y)2] \begin{aligned} y &= r + \gamma (1 - d) Q_{\bar\theta}(\mathbf{s}^\prime, \mu_{\bar\phi}(\mathbf{s}^\prime)) \\ \mathcal{L}_Q(\theta) &= \mathbb{E} \left[ (Q_\theta(\mathbf{s}, \mathbf{a}) - y)^2 \right] \end{aligned}

# Actor loss (improvement)

Maximize the critic's Q-value estimation, minimized via the negative sign:

Lμ(ϕ)=−E[Qθ(s,μϕ(s))] \mathcal{L}_\mu(\phi) = - \mathbb{E} \left[ Q_\theta(\mathbf{s}, \mu_\phi(\mathbf{s})) \right]

# Target networks: soft update

Both the critic and the actor keep a target copy, updated by Polyak averaging every step rather than hard-copied every NN:

θˉ←τθ+(1−τ)θˉϕˉ←τϕ+(1−τ)ϕˉ \begin{aligned} \bar\theta &\leftarrow \tau \theta + (1 - \tau) \bar\theta \\ \bar\phi &\leftarrow \tau \phi + (1 - \tau) \bar\phi \end{aligned}

w/ τ≈0.005\tau \approx 0.005. This is the single change that makes deep Q-learning workable in continuous spaces — without it the actor and critic chase each other and diverge.

# Flaws

-: suffers from severe overestimation bias in Q-values
-: exploration is rigid, relying on hard-coded additive noise (e.g., Gaussian or Ornstein-Uhlenbeck) whose scale is a hyperparameter you must tune per env


# TD3: 3 fixes for DDPG

TD3 (Fujimoto et al., 2018) keeps DDPG's structure and patches its failure modes. SAC borrows the first fix; the other 2 are worth knowing b/c they explain why the first one is needed.

1. Clipped double Q-learning. Keep 2 independent critics Qθ1,Qθ2Q_{\theta_1}, Q_{\theta_2} and take the min for the target:

y=r+γ(1−d)min⁡i=1,2Qθˉi(s′,a′) y = r + \gamma (1 - d) \min_{i=1,2} Q_{\bar\theta_i}(\mathbf{s}^\prime, \mathbf{a}^\prime)

Double DQN's select/evaluate split is not enough here: in actor-critic the actor is trained through the critic, so the online and target nets stay highly correlated and the decoupling breaks down. Taking the min of 2 critics instead induces a deliberate underestimate. See why underestimation is the safer error below.

2. Target policy smoothing. Add clipped noise to the target action, so the critic can't exploit a sharp spurious peak:

a′=μϕˉ(s′)+clip(ϵ,−c,c),ϵ∼N(0,σ2) \mathbf{a}^\prime = \mu_{\bar\phi}(\mathbf{s}^\prime) + \text{clip}(\epsilon, -c, c), \quad \epsilon \sim \mathcal{N}(0, \sigma^2)

This is a regularizer that enforces the prior that similar actions should have similar values.

3. Delayed policy updates. Update the actor (and the target nets) once every kk critic updates, typically k=2k = 2. A policy trained against a high-variance critic amplifies the critic's error; letting the critic settle first reduces that.


# SAC (Soft Actor-Critic)

SAC is built on maximum-entropy RL, optimizing for both maximum reward and maximum policy randomness to ensure robust exploration and prevent local optima.

# From Q-learning to actor-critic

Problem w/ continuous actions: can't compute max⁡a′Q(s′,a′)\max_{\mathbf{a}^\prime} Q(\mathbf{s}^\prime, \mathbf{a}^\prime) exactly.
Solution: learn a policy (actor) πϕ(a∣s)\pi_\phi(\mathbf{a} | \mathbf{s}) alongside the Q-function (critic).

  • critic Qθ(s,a)Q_\theta(\mathbf{s}, \mathbf{a}): estimates expected return
  • actor πϕ(a∣s)\pi_\phi(\mathbf{a} | \mathbf{s}): outputs actions, replacing the arg⁡max⁡\arg\max

# The maximum-entropy objective

SAC adds an entropy bonus to the usual RL objective:

J(π)=∑tE[r(st,at)+αH(π(⋅∣st))] J(\pi) = \sum_t \mathbb{E} \left[ r(\mathbf{s}_t, \mathbf{a}_t) + \alpha \mathcal{H}(\pi(\cdot | \mathbf{s}_t)) \right]

where H(π(⋅∣s))=−Ea∼π(⋅∣s)[log⁡π(a∣s)]\mathcal{H}(\pi(\cdot | \mathbf{s})) = -\mathbb{E}_{\mathbf{a} \sim \pi(\cdot | \mathbf{s})} [\log \pi(\mathbf{a} | \mathbf{s})] is the conditional entropy.

Why entropy regularization?

+: encourages exploration — the policy doesn't collapse to deterministic too early
+: more robust, b/c it captures multiple good solutions instead of committing to one
+: better convergence in practice

The temperature α\alpha controls the exploration vs. exploitation tradeoff. Note the contrast w/ DDPG: exploration is now part of the objective rather than noise bolted on at action-selection time.

reward vs entropy tradeoff as the temperature alpha varies

# Soft policy iteration: the 2 theoretical pillars

The whole algorithm is a practical approximation of soft policy iteration, which alternates 2 steps, each w/ its own guarantee.

Lemma 1 (soft policy evaluation). First fold the entropy bonus into the state value:

V(s)=Ea∼π[Q(s,a)−αlog⁡π(a∣s)] V(\mathbf{s}) = \mathbb{E}_{\mathbf{a} \sim \pi} \left[ Q(\mathbf{s}, \mathbf{a}) - \alpha \log \pi(\mathbf{a} | \mathbf{s}) \right]

Then the soft Bellman backup operator is

TπQ(s,a)≜r(s,a)+γEs′∼p(⋅∣s,a)[V(s′)] \mathcal{T}^\pi Q(\mathbf{s}, \mathbf{a}) \triangleq r(\mathbf{s}, \mathbf{a}) + \gamma \, \mathbb{E}_{\mathbf{s}^\prime \sim p(\cdot | \mathbf{s}, \mathbf{a})} \left[ V(\mathbf{s}^\prime) \right]

Substituting VV gives the form the critic actually regresses on:

TπQ(s,a)=r(s,a)+γEs′∼p,a′∼π[Q(s′,a′)−αlog⁡π(a′∣s′)] \mathcal{T}^\pi Q(\mathbf{s}, \mathbf{a}) = r(\mathbf{s}, \mathbf{a}) + \gamma \, \mathbb{E}_{\mathbf{s}^\prime \sim p, \, \mathbf{a}^\prime \sim \pi} \left[ Q(\mathbf{s}^\prime, \mathbf{a}^\prime) - \alpha \log \pi(\mathbf{a}^\prime | \mathbf{s}^\prime) \right]

The lemma: repeatedly applying Tπ\mathcal{T}^\pi to any bounded initial QQ converges to the true soft Q-value QπQ^\pi. The proof is the standard one — Tπ\mathcal{T}^\pi is still a γ\gamma-contraction, w/ the entropy term riding along as part of an augmented reward rπ(s,a)=r+γαEs′[H(π(⋅∣s′))]r_\pi(\mathbf{s}, \mathbf{a}) = r + \gamma\alpha\mathbb{E}_{\mathbf{s}^\prime}[\mathcal{H}(\pi(\cdot|\mathbf{s}^\prime))]. Compare this against the SAC critic target below: they are the same expression, w/ θˉ\bar\theta and the min⁡\min as the practical approximations.

Lemma 2 (soft policy improvement). Improve the policy by maximizing soft value — expected Q plus entropy — at each state:

πnew=arg⁡max⁡π′∈ΠEa∼π′[Qπold(s,a)]+αH ⁣(π′(⋅∣s)) \pi_{\text{new}} = \arg\max_{\pi^\prime \in \Pi} \ \mathbb{E}_{\mathbf{a} \sim \pi^\prime} \left[ Q^{\pi_{\text{old}}}(\mathbf{s}, \mathbf{a}) \right] + \alpha \mathcal{H}\!\left( \pi^\prime(\cdot | \mathbf{s}) \right)

This is the natural improvement step, and negating it is exactly the actor loss you implement below. Two facts make it concrete:

  • Unconstrained, the maximizer is the Boltzmann distr exp⁡(Qπold/α)/Z\exp(Q^{\pi_{\text{old}}}/\alpha) / Z — an action's prob grows exponentially in its Q-value, w/ α\alpha as temperature (α→0\alpha \to 0 recovers the greedy arg⁡max⁡\arg\max, α→∞\alpha \to \infty gives uniform). Derived in the appendix.
  • The paper writes this as a KL projection onto that same Boltzmann distr:
πnew=arg⁡min⁡π′∈ΠDKL(π′(⋅∣s)∥exp⁡(Qπold(s,⋅)/α)Zπold(s)) \pi_{\text{new}} = \arg\min_{\pi^\prime \in \Pi} D_{KL} \left( \pi^\prime(\cdot | \mathbf{s}) \,\middle\|\, \frac{\exp\left( Q^{\pi_{\text{old}}}(\mathbf{s}, \cdot) / \alpha \right)}{Z^{\pi_{\text{old}}}(\mathbf{s})} \right)

The two forms are the same optimization (derived just below). The restriction π′∈Π\pi^\prime \in \Pi is what turns "solve exactly" into "project": unconstrained we'd land on the Boltzmann distr, but confined to a tractable family we settle for its closest member.

The lemma: Qπnew(s,a)≥Qπold(s,a)Q^{\pi_{\text{new}}}(\mathbf{s}, \mathbf{a}) \geq Q^{\pi_{\text{old}}}(\mathbf{s}, \mathbf{a}) for all (s,a)(\mathbf{s}, \mathbf{a}) — monotone improvement.

Theorem (soft policy iteration). Alternating the 2 converges to the optimal maximum-entropy policy in Π\Pi. SAC is the function-approximation version: rather than running each step to convergence, it takes a single gradient step on each per env step.

# The mathematical bridge: the KL form is the same optimization

Lemma 2's two forms — the argmax of E[Q]+αH\mathbb{E}[Q] + \alpha\mathcal{H} and the KL projection — are one problem written twice. Rewrite the entropy as H(π)=Ea∼π[−log⁡π]\mathcal{H}(\pi) = \mathbb{E}_{\mathbf{a}\sim\pi}[-\log\pi], flip the sign to a minimization, then divide by α\alpha (neither move shifts the argmin):

π∗=arg⁡min⁡πEa∼π[αlog⁡π(a∣s)−Q(s,a)⏟this is the actor loss]=arg⁡min⁡πEa∼π[log⁡π(a∣s)−Q(s,a)/α] \pi^* = \arg\min_\pi \mathbb{E}_{\mathbf{a} \sim \pi} \left[ \underbrace{\alpha \log \pi(\mathbf{a} | \mathbf{s}) - Q(\mathbf{s}, \mathbf{a})}_{\text{this is the actor loss}} \right] = \arg\min_\pi \mathbb{E}_{\mathbf{a} \sim \pi} \left[ \log \pi(\mathbf{a} | \mathbf{s}) - Q(\mathbf{s}, \mathbf{a}) / \alpha \right]

The Boltzmann target q(a∣s)=exp⁡(Q/α)/Z(s)q(\mathbf{a}|\mathbf{s}) = \exp(Q/\alpha)/Z(\mathbf{s}) isn't pulled from nowhere — it's the exact unconstrained optimum from the appendix. By its definition Q/α=log⁡q+log⁡ZQ/\alpha = \log q + \log Z, so substituting:

π∗=arg⁡min⁡πEa∼π[log⁡π−log⁡q−log⁡Z(s)]=arg⁡min⁡π[DKL(π(⋅∣s)∥q(⋅∣s))−log⁡Z(s)] \pi^* = \arg\min_\pi \mathbb{E}_{\mathbf{a} \sim \pi} \left[ \log \pi - \log q - \log Z(\mathbf{s}) \right] = \arg\min_\pi \Big[ D_{KL}\big(\pi(\cdot|\mathbf{s}) \,\|\, q(\cdot|\mathbf{s})\big) - \log Z(\mathbf{s}) \Big]

log⁡Z(s)\log Z(\mathbf{s}) is constant in π\pi, so it drops — leaving Lemma 2's projection exactly.

The useful direction is the reverse one. What you actually implement is the plain E[αlog⁡π−Q]\mathbb{E}[\alpha\log\pi - Q] (the braced term above); the KL reading is what certifies it's a principled projection onto the Boltzmann distr rather than an arbitrary penalty.

# What SAC changes relative to DDPG

If you have to name it in one breath: SAC changes the actor and the critic, one thing each.

  1. The actor becomes stochastic — a reparameterized squashed Gaussian πϕ(a∣s)\pi_\phi(\mathbf{a}|\mathbf{s}) instead of DDPG's deterministic μϕ(s)\mu_\phi(\mathbf{s}), w/ an entropy term −αlog⁡πϕ-\alpha\log\pi_\phi carried into both the target and the actor loss. Exploration moves into the objective; DDPG's hand-tuned additive noise is gone.
  2. The critic becomes 2 critics w/ a min⁡\min — clipped double-Q borrowed from TD3, to cancel DDPG's overestimation bias.

Everything else DDPG already had: replay buffer, Polyak-averaged target networks, and an actor trained by backprop through the critic.

Those 2 changes map exactly onto DDPG's 2 flaws — rigid exploration and overestimation. The comparison table breaks all of this down line by line.

The rest of this section works through the machinery each change needs: the reparameterization trick and the squashed Gaussian for change 1, clipped double-Q for change 2, and α\alpha auto-tuning as a convenience on top of change 1 rather than a change of its own.

# Stochastic policy w/ reparameterization

The reparameterization trick bypasses the inability to backprop through random sampling. The network outputs deterministic μϕ\mu_\phi and σϕ\sigma_\phi, and the randomness comes from external standard noise:

a=tanh⁡(μϕ(s)+σϕ(s)⊙ϵ⏟u),ϵ∼N(0,I) \mathbf{a} = \tanh \left( \underbrace{\mu_\phi(\mathbf{s}) + \sigma_\phi(\mathbf{s}) \odot \epsilon}_{u} \right), \quad \epsilon \sim \mathcal{N}(0, I)

The tanh⁡\tanh squashes actions to (−1,1)(-1, 1); the reparameterization allows backprop through sampling, since ϵ\epsilon carries no parameters and the path ϕ→μ,σ→a\phi \to \mu, \sigma \to \mathbf{a} is fully differentiable.

In code the whole trick is one method call — rsample() instead of sample(), which keeps the path ϕ→a\phi \to \mathbf{a} in the autograd graph so ∇aQ\nabla_\mathbf{a} Q can flow back to ϕ\phi:

dist = actor(s)              # a squashed-Gaussian distribution over actions
a = dist.rsample()           # sample()  → detaches a from φ (score-function estimator only)
                             # rsample() → keeps ∂a/∂φ (the low-variance pathwise gradient)
logp = dist.log_prob(a)      # closed-form, including the tanh Jacobian correction below
1
2
3
4

# Is the Gaussian specific to SAC, or just a convenient choice?

Just a convenient choice. Nothing in Lemma 1 or Lemma 2 assumes a Gaussian — the ideal target is always the Boltzmann distr exp⁡(Q/α)/Z\exp(Q/\alpha)/Z, which is generally not Gaussian. Lemma 2 says the policy improves when we project onto the target within our chosen policy class Π\Pi, and the squashed Gaussian is simply a tractable Π\Pi.

What SAC actually requires of the policy class is 3 things:

  1. reparameterizable, so the actor loss has a low-variance pathwise gradient
  2. a closed-form log⁡π(a∣s)\log \pi(\mathbf{a} | \mathbf{s}), b/c log⁡π\log \pi appears in all three losses
  3. bounded support matching the action space

The Gaussian happens to satisfy all 3 cheaply. Other valid choices: a Categorical distr for discrete SAC (there you can drop reparameterization entirely and sum the entropy over actions exactly), or a mixture of Gaussians / normalizing flow when the action distr is genuinely multi-modal. What breaks the recipe is a policy w/ no tractable density — which is exactly the problem diffusion policies run into.

# Clipped double-Q (from TD3)

To reduce overestimation, SAC uses 2 Q-networks and takes the minimum. Combined w/ the entropy term, the target is:

y=r+γ(1−d)[min⁡i=1,2Qθˉi(s′,a′)−αlog⁡πϕ(a′∣s′)],a′∼πϕ(⋅∣s′) y = r + \gamma (1 - d) \left[ \min_{i=1,2} Q_{\bar\theta_i}(\mathbf{s}^\prime, \mathbf{a}^\prime) - \alpha \log \pi_\phi(\mathbf{a}^\prime | \mathbf{s}^\prime) \right], \quad \mathbf{a}^\prime \sim \pi_\phi(\cdot | \mathbf{s}^\prime)

Note a′\mathbf{a}^\prime is a fresh sample from the current policy, not the action stored in the replay buffer — this is the standard off-policy correction.

# Why does taking the min suppress overestimation?

Because the 2 error directions are not symmetric in their consequences.

Write each critic as truth plus noise, Qθi=Q∗+εiQ_{\theta_i} = Q^* + \varepsilon_i. The max⁡\max in the target systematically selects whichever action drew a positive ε\varepsilon, so E[max⁡]≥max⁡E\mathbb{E}[\max] \geq \max \mathbb{E} and the target is biased upward. Taking min⁡i=1,2\min_{i=1,2} over 2 critics pulls in the opposite direction — E[min⁡(Q1,Q2)]≤min⁡(E[Q1],E[Q2])\mathbb{E}[\min(Q_1, Q_2)] \leq \min(\mathbb{E}[Q_1], \mathbb{E}[Q_2]) — so the 2 biases partly cancel.

The deeper reason to prefer erring low is that the 2 kinds of error have very different dynamics under bootstrapping:

  • an overestimate is self-reinforcing: an action that looks spuriously good gets selected by the max⁡\max/actor, its inflated value becomes the next regression target, and the error propagates backward through the whole value function
  • an underestimate is self-correcting: an action that looks spuriously bad simply stops being taken, so the wrong value never gets amplified into other targets

So a deliberate pessimistic bias is cheap insurance. The cost is real but bounded — the agent can be slow to discover genuinely good actions it currently underrates.

# Automatic temperature tuning

Instead of manually setting α\alpha, learn it by minimizing

Lα=Es∼D,a∼πϕ(⋅∣s)[−α(log⁡πϕ(a∣s)+Hˉ)] \mathcal{L}_\alpha = \mathbb{E}_{\mathbf{s} \sim \mathcal{D}, \, \mathbf{a} \sim \pi_\phi(\cdot | \mathbf{s})} \left[ -\alpha \left( \log \pi_\phi(\mathbf{a} | \mathbf{s}) + \bar{\mathcal{H}} \right) \right]

where Hˉ\bar{\mathcal{H}} is the target entropy, typically −dim⁡(A)-\dim(\mathcal{A}).

The gradient is ∂Lα/∂α=H−Hˉ\partial \mathcal{L}_\alpha / \partial \alpha = \mathcal{H} - \bar{\mathcal{H}}, so if the current entropy H<Hˉ\mathcal{H} < \bar{\mathcal{H}}, α\alpha increases to force exploration, and vice versa. It's a feedback controller holding entropy at a setpoint.

Implementation tip: optimize log⁡α\log \alpha instead of α\alpha directly to guarantee α>0\alpha > 0.

# SAC loss functions

  1. Critic loss, for each Qθi,i∈{1,2}Q_{\theta_i}, i \in \{1, 2\} (the squared TD error):

    LQ(θi)=E(s,a,s′)∼D[(Qθi(s,a)−y)2] \mathcal{L}_Q(\theta_i) = \mathbb{E}_{(\mathbf{s}, \mathbf{a}, \mathbf{s}^\prime) \sim \mathcal{D}} \left[ (Q_{\theta_i}(\mathbf{s}, \mathbf{a}) - y)^2 \right]
  2. Actor loss (the braced term from the bridge — Lemma 2's improvement step, negated):

    Lπ(ϕ)=Es∼D,a∼πϕ(⋅∣s)[αlog⁡πϕ(a∣s)−min⁡i=1,2Qθi(s,a)] \mathcal{L}_\pi(\phi) = \mathbb{E}_{\mathbf{s} \sim \mathcal{D}, \, \mathbf{a} \sim \pi_\phi(\cdot | \mathbf{s})} \left[ \alpha \log \pi_\phi(\mathbf{a} | \mathbf{s}) - \min_{i=1,2} Q_{\theta_i}(\mathbf{s}, \mathbf{a}) \right]
  3. Temperature loss: exactly Lα\mathcal{L}_\alpha from automatic temperature tuning above.

Note the two roles of a\mathbf{a}: the critic loss regresses Qθi(s,a)Q_{\theta_i}(\mathbf{s}, \mathbf{a}) on the buffer action, while the actor and temperature losses use a fresh reparameterized sample a∼πϕ(⋅∣s)\mathbf{a} \sim \pi_\phi(\cdot|\mathbf{s}). The critic's TD target yy carries a third, separate sample a′∼πϕ(⋅∣s′)\mathbf{a}^\prime \sim \pi_\phi(\cdot|\mathbf{s}^\prime) (the clipped double-Q target).

In PyTorch the three losses map almost line-for-line onto the equations above:

# --- critic: regress both Q_i onto the shared soft TD target ---
with torch.no_grad():                                   # no gradient through the target
    next_dist = actor(next_s)
    next_a = next_dist.sample()                         # sample ok here: we're in no_grad
    next_logp = next_dist.log_prob(next_a)
    next_q = torch.min(target_q1(next_s, next_a),
                       target_q2(next_s, next_a))        # clipped double-Q
    y = r + gamma * (1 - d) * (next_q - alpha * next_logp)   # soft TD target: − α log π
loss_q = F.mse_loss(q1(s, a), y) + F.mse_loss(q2(s, a), y)

# --- actor: minimize  E[α log π − Q]  on a fresh reparameterized sample ---
dist = actor(s)
a_new = dist.rsample()                                  # rsample: keep ∂a/∂φ for the pathwise grad
logp = dist.log_prob(a_new)
q_new = torch.min(q1(s, a_new), q2(s, a_new))
loss_pi = (alpha * logp - q_new).mean()

# --- temperature: drive entropy toward the setpoint H̄ ---
loss_alpha = -(log_alpha.exp() * (logp + target_entropy).detach()).mean()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Three details carry all the math: rsample() (not sample()) makes the actor loss a low-variance pathwise gradient; the buffer action a feeds the critic while the fresh a_new feeds the actor; and the target uses frozen target_q* under no_grad, but the actor keeps the gradient through q1/q2's action input (detaching it would break the DPG-style chain).


# DDPG vs. TD3 vs. SAC

DDPG TD3 SAC
policy deterministic μϕ(s)\mu_\phi(\mathbf{s}) deterministic μϕ(s)\mu_\phi(\mathbf{s}) stochastic πϕ(a∣s)\pi_\phi(\mathbf{a} \mid \mathbf{s})
# critics 1 2, take min⁡\min 2, take min⁡\min
exploration additive noise at act time additive noise at act time entropy term in the objective
target action μϕˉ(s′)\mu_{\bar\phi}(\mathbf{s}^\prime) μϕˉ(s′)+clipped noise\mu_{\bar\phi}(\mathbf{s}^\prime) + \text{clipped noise} a′∼πϕ(⋅∣s′)\mathbf{a}^\prime \sim \pi_\phi(\cdot \mid \mathbf{s}^\prime)
entropy term none none −αlog⁡πϕ-\alpha \log \pi_\phi in target & actor loss
actor update every step every kk critic steps every step
target update Polyak, τ≈0.005\tau \approx 0.005 Polyak, τ≈0.005\tau \approx 0.005 Polyak, τ≈0.005\tau \approx 0.005
key hyperparameter noise scale noise scale, kk Hˉ\bar{\mathcal{H}} (and α\alpha auto-tuned)

# The squashed Gaussian policy & math corrections

The problem: standard Gaussian distrs have infinite bounds [−∞,+∞][-\infty, +\infty]. If an environment clips an out-of-bound action (e.g., 2.52.5 clipped to 1.01.0), the critic evaluates an action that never actually occurred, ruining the Q-value.

The fix: SAC passes the sampled action u∼N(μ,σ2)u \sim \mathcal{N}(\mu, \sigma^2) through a tanh⁡\tanh to strictly bound it to (−1,1)(-1, 1):

a=tanh⁡(u) \mathbf{a} = \tanh(u)
how tanh squashes the Gaussian density toward the action boundary

The derivation of the Jacobian correction: b/c tanh⁡\tanh squashes the prob density, we must use the change of variables formula to correct the entropy calculation:

log⁡p(a)=log⁡p(u)−log⁡∣dadu∣ \log p(\mathbf{a}) = \log p(u) - \log \left| \frac{d\mathbf{a}}{du} \right|

The derivative of a=tanh⁡(u)\mathbf{a} = \tanh(u) w.r.t. uu is exactly 1−tanh⁡2(u)1 - \tanh^2(u). Plugging this in yields the SAC correction term:

log⁡π(a∣s)=log⁡p(u∣s)−∑ilog⁡(1−tanh⁡2(ui)) \log \pi(\mathbf{a} | \mathbf{s}) = \log p(u | \mathbf{s}) - \sum_i \log(1 - \tanh^2(u_i))

# Implementation pitfalls

NaN / gradient explosion. If uu is large, tanh⁡(u)≈1\tanh(u) \approx 1, making 1−tanh⁡2(u)1 - \tanh^2(u) effectively 00. Computing log⁡(0)\log(0) gives -inf or NaN.

A common patch is to add a tiny epsilon, log⁡(1−tanh⁡2(u)+ϵ)\log(1 - \tanh^2(u) + \epsilon) w/ ϵ=10−6\epsilon = 10^{-6}. But this is only a band-aid, and it biases the log-prob. There is an exact numerically stable form — from 1−tanh⁡2u=4/(eu+e−u)21 - \tanh^2 u = 4 / (e^u + e^{-u})^2:

log⁡(1−tanh⁡2(u))=2(log⁡2−u−softplus(−2u)) \log(1 - \tanh^2(u)) = 2 \left( \log 2 - u - \text{softplus}(-2u) \right)

softplus is stable for both signs of its argument, so this needs no epsilon and introduces no bias. Prefer it.

Clamp log⁡σ\log \sigma. The actor outputs log⁡σ\log \sigma, not σ\sigma; clamp it to roughly [−20,2][-20, 2]. Unbounded log⁡σ\log \sigma is the other main upstream source of NaN: too large blows up the sample uu (feeding straight into the pitfall above), too small makes log⁡π\log \pi diverge.

Optimize log⁡α\log \alpha. Same reasoning as σ\sigma: parameterize the temperature in log space so α>0\alpha > 0 holds by construction.

Rescale to the real action bounds. tanh⁡\tanh gives (−1,1)(-1, 1); for an env w/ bounds [low,high][\text{low}, \text{high}], apply a=c⋅tanh⁡(u)+b\mathbf{a} = c \cdot \tanh(u) + b, where c=(high−low)/2c = (\text{high} - \text{low}) / 2 (action_scale) and b=(high+low)/2b = (\text{high} + \text{low}) / 2 (action_bias). The Jacobian term then picks up a constant log⁡c\log c per dim, which doesn't affect gradients but does shift the reported entropy — so make sure Hˉ\bar{\mathcal{H}} is set in the same units.

Deterministic evaluation. At test time use a=tanh⁡(μϕ(s))\mathbf{a} = \tanh(\mu_\phi(\mathbf{s})) rather than sampling. The entropy bonus is a training-time exploration device, not part of the behavior you want to deploy.


# Modern applications: is squashed Gaussian still used?

Standard continuous control: yes. For traditional physics simulations (e.g., Mujoco, robotic arms, quadrupeds), the squashed Gaussian policy remains the standard default baseline, and SAC is still the reference off-policy algorithm.

VLA (Vision-Language-Action) models: mostly no — but the reason is not simply "VLAs are discrete". What actually happens is that the unimodal Gaussian head gets replaced, and there are 2 distinct families of replacement:

  • action tokenization (discrete): VLAs built directly on an LLM backbone, e.g. RT-2 and OpenVLA, discretize each continuous action dim into bins (typically 256 tokens per dim, covering end-effector delta pose ++ gripper) and emit them as ordinary tokens w/ a categorical distr. This inherits the LLM's next-token machinery unchanged.
  • continuous generative heads: the trend in recent robot policies goes the other way — back to continuous actions, but w/ an expressive generative model instead of a Gaussian. Diffusion policies frame action generation as a denoising process, and the π0\pi_0-style flow matching action experts attach a continuous action head to a VLM backbone. Neither one tokenizes.

The common thread is multi-modality: a squashed Gaussian can only express one mode per state, so it averages over distinct valid behaviors (go left vs. go right around an obstacle) into a bad middle action. Discretization and diffusion/flow are 2 different ways to buy multi-modality back.

The tradeoff is the one flagged in is the Gaussian specific to SAC: both replacements give up a cheap closed-form log⁡π(a∣s)\log \pi(\mathbf{a} | \mathbf{s}) — categorical keeps it but only over a discretized space, while diffusion/flow have no tractable density at all. That is exactly why RL post-training on top of these policies is harder than plugging in SAC, and why methods in this space lean on value-weighted or advantage-conditioned supervised objectives rather than an entropy-regularized actor loss.


# Appendix: why the unconstrained optimum is the Boltzmann distribution

Lemma 2 and the KL bridge both lean on one fact: over all distributions, the soft-improvement objective is maximized by exp⁡(Q/α)/Z\exp(Q/\alpha)/Z. The body asserts it; here is the derivation. It also removes the "rabbit out of a hat" feel of the bridge, where that target is otherwise introduced by definition.

Fix a state s\mathbf{s} and optimize over the density p(a)p(\mathbf{a}) directly. The soft-improvement objective is Ep[Q]+αH(p)\mathbb{E}_p[Q] + \alpha\mathcal{H}(p), i.e.

max⁡p∫p(a)[Q(s,a)−αlog⁡p(a)]das.t.∫p(a)da=1 \max_{p}\ \int p(\mathbf{a})\big[\,Q(\mathbf{s},\mathbf{a}) - \alpha\log p(\mathbf{a})\,\big]\,d\mathbf{a} \qquad \text{s.t.}\quad \int p(\mathbf{a})\,d\mathbf{a} = 1

Form the Lagrangian with a multiplier λ\lambda for normalization, and set the functional derivative w.r.t. p(a)p(\mathbf{a}) to zero (treat each p(a)p(\mathbf{a}) as its own variable):

δδp(a) ⁣[∫p(Q−αlog⁡p)da−λ ⁣(∫pda−1)]=Q(s,a)−αlog⁡p(a)−α−λ=0 \frac{\delta}{\delta p(\mathbf{a})}\!\left[\int p(Q-\alpha\log p)\,d\mathbf{a} - \lambda\!\left(\int p\,d\mathbf{a}-1\right)\right] = Q(\mathbf{s},\mathbf{a}) - \alpha\log p(\mathbf{a}) - \alpha - \lambda = 0

The −α-\alpha comes from ∂∂p[−αplog⁡p]=−α(log⁡p+1)\frac{\partial}{\partial p}[-\alpha p\log p] = -\alpha(\log p + 1). Solving for pp:

log⁡p(a)=Q(s,a)/α−1−λα⟹p(a)∝exp⁡ ⁣(Q(s,a)/α) \log p(\mathbf{a}) = Q(\mathbf{s},\mathbf{a})/\alpha - 1 - \frac{\lambda}{\alpha} \quad\Longrightarrow\quad p(\mathbf{a}) \propto \exp\!\left(Q(\mathbf{s},\mathbf{a})/\alpha\right)

Normalization fixes the leftover constant, giving

p∗(a)=exp⁡ ⁣(Q(s,a)/α)Z(s),Z(s)=∫exp⁡ ⁣(Q(s,a′)/α)da′ p^*(\mathbf{a}) = \frac{\exp\!\left(Q(\mathbf{s},\mathbf{a})/\alpha\right)}{Z(\mathbf{s})}, \qquad Z(\mathbf{s}) = \int \exp\!\left(Q(\mathbf{s},\mathbf{a}^\prime)/\alpha\right) d\mathbf{a}^\prime

This is a maximum, not a saddle: −αplog⁡p-\alpha p \log p is concave in pp, so the stationary point is the global max. Positivity holds automatically since exp⁡>0\exp > 0, so normalization is the only binding constraint.

One-line corollary of the bridge. Once you have the bridge's identity — objective =DKL(π∥q)−log⁡Z(s)= D_{KL}(\pi \| q) - \log Z(\mathbf{s}) with qq the Boltzmann distr — the same result is immediate: DKL≥0D_{KL} \geq 0 with equality iff π=q\pi = q, so the unconstrained minimizer is π=q\pi = q. The Lagrangian above is the "from scratch" version that produces the Boltzmann form instead of guessing it; the KL corollary verifies it once the form is known.

Restricting to a tractable family Π\Pi (squashed Gaussians) is what turns this exact solution into Lemma 2's projection: we can no longer land on p∗p^* exactly, so we settle for its closest member in Π\Pi.