Distributional Soft Actor-Critic (DSAC)¶
DSAC is a state-of-the-art off-policy reinforcement learning algorithm that combines the benefits of Soft Actor-Critic (SAC) with distributional RL using Implicit Quantile Networks (IQN). It was specifically designed for robust control in aerospace applications where uncertainty estimation and smooth control signals are critical.
Architecture Overview¶
DSAC extends the standard SAC framework with several key innovations:
- Distributional Critics (IQN): Instead of predicting a single Q-value, DSAC learns the full return distribution using twin Implicit Quantile Networks
- CAPS Regularization: Conditional Action Policy Smoothness ensures smooth, flight-safe control commands
- Risk-Sensitive Control: Supports risk distortion functions (CVaR, CPW, Wang) for conservative or aggressive policies
Key Components¶
Twin IQN Critics¶
The core innovation of DSAC is the use of Implicit Quantile Networks (IQN) as critics. Each critic predicts a distribution of possible Q-values rather than a single point estimate:
Key features of the IQN critic:
- Cosine Embedding: Quantile levels τ ∈ (0,1) are embedded using cosine basis functions: φ(τ)ᵢ = cos(π · i · τ)
- Hadamard Product: State-action features are combined with quantile embeddings via element-wise multiplication
- Quantile Huber Loss: Robust loss function that handles the asymmetric nature of quantile regression
Training Loop¶
The DSAC training loop follows the SAC structure with modifications for distributional learning:
Training Steps:
- Sample Mini-Batch: Draw (s, a, r, s', done) from replay buffer
- Compute Target: Calculate distributional Bellman target with entropy bonus
- Update Critics: Minimize quantile Huber loss for both Z₁ and Z₂
- Freeze Critics: Temporarily disable critic gradients
- Update Actor: Maximize expected Q-value with CAPS regularization
- Unfreeze Critics: Re-enable critic gradients
- Update Temperature: Adjust entropy coefficient α (if automatic)
- Soft Update Targets: Polyak averaging for target networks
CAPS Regularization¶
CAPS (Conditional Action Policy Smoothness) is critical for aerospace applications:
- Spatial Smoothness: Penalizes policy sensitivity to small state perturbations
\(L_{spatial} = \lambda_s \cdot \frac{1}{B} \|\mu(s) - \mu(s + \epsilon)\|^2\)
- Temporal Smoothness: Encourages consistent actions over time
\(L_{temporal} = \lambda_t \cdot \frac{1}{B} \|a_t - a_{t+1}\|^2\)
Risk Distortion Functions¶
DSAC supports risk-sensitive control through distortion of quantile levels:
| Function | Formula | Use Case |
|---|---|---|
| Neutral | τ | Standard expected value |
| CVaR | clamp(τ · ξ, 0, 1) | Conservative (worst-case) |
| CPW | τ^ξ / (τ^ξ + (1-τ)^ξ)^{1/ξ} | Probability weighting |
| Wang | Φ(Φ⁻¹(τ) + ξ) | Normal transform |
Key Differences vs SAC¶
| Feature | SAC | DSAC |
|---|---|---|
| Critic Output | Single Q-value | N quantile values |
| Loss Function | MSE | Quantile Huber |
| Uncertainty | None | Full distribution |
| Smoothness | None | CAPS regularization |
| Risk Sensitivity | None | Distortion functions |
Quick Start¶
import numpy as np
import torch
from tensoraerospace.agent import DSAC
from tensoraerospace.envs.b747 import ImprovedB747Env
def step_reference(steps: int, deg: float = 5.0) -> np.ndarray:
ref = np.zeros((1, steps), dtype=np.float32)
ref[:, steps // 5 :] = np.deg2rad(deg)
return ref
device = "cuda" if torch.cuda.is_available() else "cpu"
num_steps = 800
env = ImprovedB747Env(
initial_state=np.array([0.0, 0.0, 0.0, 0.0], dtype=float),
reference_signal=step_reference(num_steps, deg=5.0),
number_time_steps=num_steps,
dt=0.02,
reward_mode="step_response",
)
agent = DSAC(
env,
batch_size=256,
memory_capacity=500_000,
learning_starts=10_000,
updates_per_step=1,
num_quantiles=32,
embedding_dim=64,
hidden_layers=[64, 64],
huber_threshold=1.0,
lr=4.4e-4,
policy_lr=4.4e-4,
gamma=0.99,
tau=0.005,
caps_lambda_smoothness=400.0,
caps_lambda_temporal=400.0,
caps_noise_std=0.05,
device=device,
log_every_updates=50,
automatic_entropy_tuning=True,
)
# Training
agent.train(num_episodes=100, save_best=True, save_path="./runs")
agent.close()
Hyperparameters¶
Critical Parameters¶
| Parameter | Default | Description |
|---|---|---|
num_quantiles |
8 | Number of quantile samples (16-64 recommended) |
embedding_dim |
64 | Cosine embedding dimension |
huber_threshold |
1.0 | Huber loss threshold κ |
batch_size |
256 | Mini-batch size |
learning_starts |
10,000 | Steps before training starts |
CAPS Parameters¶
| Parameter | Default | Description |
|---|---|---|
caps_lambda_smoothness |
400.0 | Spatial smoothness weight |
caps_lambda_temporal |
400.0 | Temporal smoothness weight |
caps_noise_std |
0.05 | Noise for spatial perturbation |
Optimization Parameters¶
| Parameter | Default | Description |
|---|---|---|
lr |
4.4e-4 | Critic learning rate |
policy_lr |
4.4e-4 | Actor learning rate (defaults to lr) |
gamma |
0.99 | Discount factor |
tau |
0.005 | Soft update coefficient |
target_update_interval |
1 | Target network update frequency |
Risk Control¶
| Parameter | Default | Description |
|---|---|---|
risk_distortion |
"neutral" | Distortion function name |
risk_measure |
1.0 | Distortion parameter ξ |
Training Tips
- Keep
num_quantilesbetween 16-64 for stable training - Higher
caps_lambdavalues produce smoother but potentially slower-converging policies - Use
risk_distortion="cvar"withrisk_measure < 1.0for conservative flight control - Reduce
updates_per_stepif actions become overly smooth or training unstable
Vectorized Training¶
For environments with parallel simulation (e.g., GPU-accelerated):
agent.train_vector(
total_steps=500_000,
warmup_steps=10_000,
log_every=2_000,
reward_window=200,
save_best=True,
save_path="./runs",
)
Unified training interface¶
DSAC implements the shared unified train() API from BaseRLModel:
def train(
self,
num_episodes: int = 1,
*,
max_steps: Optional[int] = None,
save_best: bool = False,
save_path: Optional[str] = None,
verbose: bool = True,
**kwargs,
) -> dict
Algorithm-specific options accepted via **kwargs:
save_best_with_gradients(bool): include optimizer gradients when saving the best model checkpoint.
Returns a metrics dictionary with episode_rewards, best_reward,
updates and total_steps.
API Reference¶
DSAC(env, *, updates_per_step=1, batch_size=256, memory_capacity=500000, lr=0.00044, policy_lr=None, gamma=0.99, tau=0.005, alpha=0.2, policy_type='Gaussian', target_update_interval=1, automatic_entropy_tuning=True, target_entropy_scale=1.0, min_alpha=0.0, exploration_noise_std=0.0, max_grad_norm=None, reward_clip=None, hidden_size=64, num_quantiles=8, num_quantiles_exp=None, embedding_dim=64, hidden_layers=None, layer_norm=True, huber_threshold=1.0, learning_starts=10000, warmup_action_scale=1.0, caps_lambda_smoothness=400.0, caps_lambda_temporal=400.0, caps_noise_std=0.05, risk_distortion='neutral', risk_measure=1.0, device='cpu', verbose_histogram=False, seed=42, log_dir=None, log_every_updates=1, wandb_project=None, wandb_entity=None, wandb_run_name=None, wandb_tags=None, wandb_config=None)
¶
Bases: BaseRLModel
Distributional SAC (dsac-flight port).
train(num_episodes=1, *, max_steps=None, save_best=False, save_path=None, verbose=True, **kwargs)
¶
Train DSAC for num_episodes (unified interface).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_episodes
|
int
|
Number of training episodes. |
1
|
max_steps
|
Optional[int]
|
Optional per-episode step cap. |
None
|
save_best
|
bool
|
If True, checkpoint the best-reward model. |
False
|
save_path
|
Optional[str]
|
Destination for best-reward checkpoints. |
None
|
verbose
|
bool
|
If True, display a tqdm progress bar. |
True
|
**kwargs
|
Any
|
Algorithm-specific options:
|
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
Training metrics ( |
dict
|
|
to_device(device)
¶
Move all DSAC components (nets, optim states) to the target device.
eval()
¶
Switch all DSAC networks to eval mode.
from_pretrained(repo_name, access_token=None, version=None, load_gradients=False)
classmethod
¶
Load DSAC checkpoint from local dir or Hugging Face Hub.
push_to_hub(repo_name, access_token=None, save_path=None, include_gradients=False)
¶
Save model checkpoint and upload it to Hugging Face Hub.
ZNet(*, n_states, n_actions, n_hidden_layers, n_hidden_units, n_cos, device)
¶
Bases: Module
Wrapper around IQN that outputs Z(s, a; taus) with shape (B, N).
generate_taus(*, batch_size, n_taus, device)
staticmethod
¶
Uniform taus in (0,1), shape (B, N).
IQN(*, n_inputs, n_outputs, embedding_size, n_hidden_layers, n_hidden_units, device)
¶
Bases: Module
Implicit Quantile Network (dsac-flight style).
NormalPolicyNet(*, obs_dim, action_dim, n_hidden_layers, n_hidden_units)
¶
Bases: Module
Outputs an Independent Normal distribution for continuous actions.
risk_distortions
¶
Risk distortion functions copied from dsac-flight.
normal_cdf(tau, mean=0.0, std=1.0)
¶
CDF of the normal distribution.
normal_inverse_cdf(tau, mean=0.0, std=1.0)
¶
Inverse CDF of the normal distribution.
neutral(tau, _xi)
¶
Neutral distortion returns the original quantiles.
cvar(tau, xi)
¶
Conditional value at risk distortion (clamped to [0,1]).
cpw(tau, xi)
¶
Cumulative probability weighting.
wang(tau, xi)
¶
Wang transform.
Acknowledgements¶
The DSAC implementation in TensorAeroSpace was inspired by and partially based on the excellent work by Peter Seres on risk-sensitive distributional reinforcement learning for flight control:
- Repository: peter-seres/dsac-flight
- Description: Risk-sensitive Distributional Reinforcement Learning for Flight Control (MSc thesis project)
We gratefully acknowledge Peter Seres's contribution to the field of distributional RL for aerospace applications. His implementation of DSAC with IQN critics for the PH-LAB (Cessna Citation II) research aircraft provided valuable insights and architectural decisions that informed our implementation.
Citation
If you use the DSAC agent in your research, please consider citing both TensorAeroSpace and the original dsac-flight repository.