Глава 4: Консервативное Q-обучение (CQL)
«Не доверяй значениям, которых не видел. А если не видел — занижай их.»
Проблема, переформулированная
В главе 2 мы увидели, что стандартное Q-обучение на офлайн данных порождает катастрофически оптимистичные Q-значения для действий вне распределения (OOD). Жадная политика эксплуатирует эти завышенные значения, выбирая действия, которых в датасете никогда не было — и терпит неудачу при развёртывании.
Корень проблемы: обновление Беллмана использует $\max_{a'} Q(s', a')$, перебирая все действия, включая ненаблюдавшиеся. Для таких действий Q-функция обобщается оптимистично.
Conservative Q-Learning (CQL) — Kumar et al., NeurIPS 2020 — исправляет это одной элегантной идеей: добавить регуляризационный член, явно штрафующий Q-значения для действий вне датасета.
Результат: Q-функция, которая по построению пессимистична для OOD-действий. Жадная политика, видя более низкие значения вне датасета, естественным образом остаётся близко к поведенческой политике — без явного ограничения.
Идея
Стандартное TD-обучение минимизирует:
CQL добавляет к этой цели два члена:
где: - $\mu$ — некоторое распределение над действиями (как правило, равномерное или текущая политика) - $\alpha > 0$ — гиперпараметр, управляющий силой консерватизма - Первое математическое ожидание занижает Q-значения для действий из $\mu$ - Второе математическое ожидание завышает Q-значения для действий из датасета
Иными словами: минимизировать Q-значения везде, но максимизировать их в точках датасета. Разрыв между двумя членами — то, что минимизирует CQL: действия из датасета выглядят лучше, чем OOD-действия.
Формализация
Целевая функция CQL
Точнее, CQL минимизирует следующую регуляризованную ошибку Беллмана:
Первый член — log-sum-exp по всем действиям — гладкая аппроксимация $\max_a Q(s,a)$. Он опускает всю Q-поверхность вниз. Второй член поднимает Q-значения именно в точках датасета.
Определим:
Тогда: $\mathcal{L}_{CQL}(\theta) = \mathcal{L}_{TD}(\theta) + \alpha \cdot \mathcal{R}_{CQL}(\theta)$
Замечание о множителе $\frac{1}{2}$. В оригинальной статье CQL написано $\frac{1}{2}\mathcal{L}_{TD}$ — это соглашение о нормировке в рамках их вывода. На практике множитель поглощается скоростью обучения и $\alpha$, поэтому реализации (включая нашу) его опускают.
Почему Log-Sum-Exp?
Член logsumexp — это аппроксимация максимума через softmax:
При температуре → 0 это сходится к $\max_a Q(s,a)$. При конечной температуре выражение дифференцируемо и штрафует всё распределение Q-значений, а не только максимум.
Для непрерывных пространств действий перебрать все $a$ невозможно. CQL аппроксимирует logsumexp через importance sampling:
где $\mu$ — предложенное распределение. На практике $\mu$ — либо равномерное над пространством действий, либо текущая политика $\pi_\theta$.
Зачем вычитать $\log \mu(a|s)$ в коде? При $\mu = \pi_\theta$ importance-sampling оценка принимает вид:
Именно поэтому в коде вычисляется q_policy - policy_log_probs перед logsumexp: вычитание $\log \pi_\theta(a|s)$ реализует коррекцию importance weights. Без неё мы бы аппроксимировали $\mathbb{E}_{\pi_\theta}[Q]$ (простое среднее Монте-Карло), а не $\log \sum_a \exp Q$ (мягкий максимум, который требует CQL). Для случайных равномерных действий эта коррекция не нужна: их лог-плотность константна и сокращается внутри logsumexp.
Теоретическая гарантия
Теорема (Kumar et al., 2020). Пусть $\hat{Q}^\pi$ — Q-функция, обученная CQL, а $Q^\pi$ — истинная Q-функция политики $\pi$. Тогда:
при подходящих условиях на $\alpha$.
Иными словами: при предположениях анализа CQL и достаточной оптимизации метод строит консервативную оценку, математическое ожидание которой даёт нижнюю границу истинной ценности политики в точках датасета. Это снижает стимул эксплуатировать OOD-переоценки, но не является абсолютной гарантией безопасности или отсутствия переоценки в произвольной нейросетевой реализации.
Более практично (при тех же предположениях) ожидаемая производительность политики удовлетворяет:
Граница (при предположениях теоремы) связывает производительность CQL с BC плюс штраф, пропорциональный отклонению политики от поведенческой — это не безусловная гарантия в каждой практической настройке.
Реализация
📄 Полный код:
cql.py
Сети
CQL обычно использует архитектуру в стиле SAC: два Q-networks (для снижения переоценки через double-Q), стохастический актор и регуляризацию энтропией.
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from copy import deepcopy
class QNetwork(nn.Module):
def __init__(self, state_dim, action_dim, hidden_dim=256):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim + action_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, 1),
)
def forward(self, state, action):
return self.net(torch.cat([state, action], dim=-1)).squeeze(-1)
class GaussianPolicy(nn.Module):
def __init__(self, state_dim, action_dim, hidden_dim=256):
super().__init__()
self.trunk = nn.Sequential(
nn.Linear(state_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(),
)
self.mean_head = nn.Linear(hidden_dim, action_dim)
self.log_std_head = nn.Linear(hidden_dim, action_dim)
def sample(self, state):
h = self.trunk(state)
mean = self.mean_head(h)
log_std = self.log_std_head(h).clamp(-4, 2)
std = log_std.exp()
eps = torch.randn_like(mean)
raw = mean + std * eps
action = torch.tanh(raw)
log_prob = (
torch.distributions.Normal(mean, std).log_prob(raw)
- torch.log(1 - action.pow(2) + 1e-6)
).sum(-1)
return action, log_prob
Потеря CQL
def cql_loss(Q1, Q2, Q1_target, Q2_target, policy,
states, actions, rewards, next_states, dones,
alpha_cql=1.0, gamma=0.99, n_action_samples=10):
"""
CQL loss = стандартная TD-потеря + консервативный штраф.
alpha_cql: сила консерватизма. Больше = более пессимистично.
"""
batch_size = states.shape[0]
# ── 1. Стандартная TD-цель ────────────────────────────────────────────
with torch.no_grad():
next_actions, next_log_probs = policy.sample(next_states)
q_next = torch.min(Q1_target(next_states, next_actions),
Q2_target(next_states, next_actions))
q_next -= 0.1 * next_log_probs # SAC: энтропийный бонус
# ↑ 0.1 — фиксированный коэффициент энтропии. Автонастройку см. ниже.
td_target = rewards + gamma * (1 - dones) * q_next
td_loss = F.mse_loss(Q1(states, actions), td_target)
# ── 2. CQL штраф ──────────────────────────────────────────────────────
# Случайные OOD-действия из равномерного распределения
random_actions = torch.FloatTensor(
batch_size * n_action_samples, actions.shape[-1]
).uniform_(-1, 1).to(states.device)
# Действия текущей политики
states_rep = states.unsqueeze(1).repeat(1, n_action_samples, 1).view(
batch_size * n_action_samples, -1)
policy_actions, policy_log_probs = policy.sample(states_rep)
# Q-значения для случайных OOD-действий
q_rand = Q1(states.unsqueeze(1).repeat(1, n_action_samples, 1)
.view(batch_size * n_action_samples, -1),
random_actions).view(batch_size, n_action_samples)
# Q-значения для policy-действий — с коррекцией importance weights.
# Вычитаем log π(a|s): сэмплируем a ~ π, но нужен E_{uniform}[exp Q].
q_policy_raw = Q1(states_rep, policy_actions).view(batch_size, n_action_samples)
lp = policy_log_probs.detach().view(batch_size, n_action_samples)
q_policy = q_policy_raw - lp # importance weight: exp(Q)/π → exp(Q - log π)
# Q-значения для действий из датасета
q_data = Q1(states, actions)
# Усредняем два предложения отдельно, затем объединяем (не смешивать в одном logsumexp)
# log E[exp Q] ≈ log( (1/2) E_rand[exp Q] + (1/2) E_π[exp Q] )
term_rand = torch.logsumexp(q_rand, dim=1) - math.log(n_action_samples)
term_pi = torch.logsumexp(q_policy, dim=1) - math.log(n_action_samples)
logsumexp = torch.logsumexp(torch.stack([term_rand, term_pi], dim=1), dim=1) - math.log(2)
cql_penalty = (logsumexp - q_data).mean()
return td_loss + alpha_cql * cql_penalty, td_loss.item(), cql_penalty.item()
Автоматическая настройка alpha
# Настройка alpha через двойственный градиентный спуск
log_alpha_cql = torch.zeros(1, requires_grad=True, device=device)
alpha_opt = optim.Adam([log_alpha_cql], lr=1e-4)
target_gap = 10.0 # τ: целевой CQL-штраф (разрыв logsumexp, ≥ 0)
# В шаге обновления:
alpha_cql = log_alpha_cql.exp().item()
alpha_loss = -log_alpha_cql * (cql_penalty - target_gap)
alpha_opt.zero_grad()
alpha_loss.backward()
alpha_opt.step()
Замечание об энтропийном коэффициенте. В коде используется фиксированное 0.1 для SAC-члена энтропии. В полноценном CQL этот коэффициент тоже настраивается автоматически — с целью поддерживать энтропию политики на уровне $\mathcal{H}^* = -\dim(\mathcal{A})$ (одна нат на измерение действия):
# Автонастройка коэффициента энтропии (SAC-стиль)
log_alpha_ent = torch.zeros(1, requires_grad=True, device=device)
ent_opt = optim.Adam([log_alpha_ent], lr=3e-4)
target_entropy = -action_dim # H* = -dim(A)
# В шаге обновления политики:
alpha_ent = log_alpha_ent.exp().item()
loss_pi = (alpha_ent * log_probs - q_pi).mean()
# ...
ent_loss = -(log_alpha_ent * (log_probs + target_entropy).detach()).mean()
ent_opt.zero_grad()
ent_loss.backward()
ent_opt.step()
Фиксированное 0.1 работает для задачи управления тепловым процессом, но для других сред может потребоваться корректировка. При неопределённости рекомендуется автонастройка.
$\alpha$ — важнейший гиперпараметр CQL. Он управляет компромиссом между консерватизмом и производительностью:
| $\alpha$ | Поведение |
|---|---|
| $\alpha = 0$ | Стандартный SAC — нет консерватизма, эксплуатация OOD |
| $\alpha$ мало (0.1–1.0) | Мягкий консерватизм — допускает некоторое улучшение политики |
| $\alpha$ велико (5–10) | Сильный консерватизм — политика близка к $\pi_\beta$ |
| $\alpha \to \infty$ | Эквивалентно поведенческому клонированию |
Интуиция: ландшафт Q-функции
Представьте Q-функцию как ландшафт над пространством состояние-действие. Стандартное Q-обучение формирует этот ландшафт только через точки данных — между ними ландшафт не ограничен и стремится расти из-за оптимистичного обобщения.
CQL добавляет гравитацию: тянет весь ландшафт вниз, пока TD-потеря удерживает его в точках данных. Результат — ландшафт, высокий в точках датасета и низкий везде остальном.
Политика, действующая жадно по этому ландшафту, предпочитает действия из датасета — не потому что её явно ограничили, а потому что ландшафт естественно направляет её туда.
Стандартное Q-обучение: CQL:
Q ↑ * * Q ↑ * *
| / \ / \ | * \ / *
| / \ / \ | / \ / \
|/ * \ |/ * \
+----------→ a +----------→ a
↑ точки датасета ↑ точки датасета (закреплены высоко)
↑↑ OOD оптимизм OOD занижены
(В HTML-версии эта диаграмма отображается как SVG-рисунок.)
Как выбрать α
$\alpha$ — важнейший гиперпараметр CQL. Правильное значение зависит от качества датасета, масштаба наград и доверия к поведенческой политике.
Шаг 1 — Проверьте знак CQL-штрафа
Перед настройкой убедитесь, что штраф положителен: $\mathbb{E}_\mu[Q] > \mathbb{E}_D[Q]$ должно выполняться после нескольких сотен обновлений. Если штраф отрицателен с самого начала — сэмплирование OOD-действий сломано.
Шаг 2 — Грид-поиск по логарифмической шкале
for alpha in [0.1, 0.5, 1.0, 5.0, 10.0]:
agent = CQLAgent(..., alpha_cql=alpha)
train_agent(agent, loader, n_epochs=80)
res = evaluate(agent, env, ...)
print(f"alpha={alpha:.1f} reward={res['reward_mean']:.2f}")
Пять прогонов дают грубую карту компромисса награда–консерватизм.
Шаг 3 — Читайте диагностику
| Симптом | Диагноз | Действие |
|---|---|---|
| Политика ≈ BC, нет улучшения | $\alpha$ слишком велик — Q подавлен везде | Уменьшить в 5–10× |
| Q-значения расходятся | $\alpha$ слишком мал — OOD не контролируется | Увеличить в 2–5× |
| CQL-штраф уходит в минус | $\alpha$ слишком мал | Увеличить $\alpha$ |
| CQL-штраф >> TD-потеря | $\alpha$ слишком велик или проблема масштаба наград | Нормализовать награды; уменьшить $\alpha$ |
Типичные значения по типу данных
| Тип датасета | Стартовый $\alpha$ | Обоснование |
|---|---|---|
| Только экспертные (почти оптимальные) | 0.1 – 0.5 | Поведенческая политика хороша; сильный консерватизм вредит |
| Смешанное качество (эксперт + случайный) | 1.0 – 2.0 | Безопасный дефолт |
| Случайные / шумные операторские данные | 2.0 – 5.0 | Высокий шум → больше OOD-риска → больше консерватизма |
| Промышленные логи с несколькими режимами | 1.0 + авто-$\alpha$ | Лагранжиан адаптируется к разным режимам |
Когда использовать автонастройку
Используйте auto_alpha=True если: (а) политика будет развёрнута в реальной системе и безопасность важнее производительности, или (б) датасет охватывает несколько режимов работы с разными масштабами наград. Задайте target_gap положительным значением в шкале logsumexp-разрыва (например, 10.0).
Используйте фиксированный $\alpha$ если: проводите воспроизводимые офлайн-эксперименты, или уже нашли хорошее значение грид-поиском.
CQL vs TD3+BC
TD3+BC (Fujimoto & Gu, 2021) — более простая альтернатива, добавляющая BC-член прямо в потерю политики:
| CQL | TD3+BC | |
|---|---|---|
| Где живёт пессимизм | Q-функция | Целевая функция политики |
| OOD Q-значения | Явно занижаются | Не изменяются |
| Теоретическая гарантия | Да (нижняя оценка) | Нет |
| Гиперпараметры | $\alpha$ | $\lambda$ |
| Сложность реализации | Средняя | Низкая |
TD3+BC — хорошая отправная точка для детерминированных политик. CQL более строго обоснован и обычно сильнее на сложных задачах.
Практические советы
Нормализуйте наблюдения. CQL чувствителен к масштабу. Всегда нормализуйте состояния по статистикам датасета.
Начинайте с $\alpha = 1.0$. Безопасное значение по умолчанию. Если политика слишком консервативна — уменьшайте. Если Q-значения расходятся — увеличивайте.
Используйте Double-Q. Всегда используйте две Q-сети и берите минимум. Это снижает переоценку независимо от CQL — два механизма дополняют друг друга.
Мониторьте CQL-штраф. Логируйте E_μ[Q] - E_D[Q] во время обучения. Он должен быть положительным и стабильным. Если уходит в минус — $\alpha$ слишком мало.
Совет для промышленных данных. Если датасет содержит несколько режимов работы (остановленный/медленный/быстрый), рассмотрите обучение отдельных агентов CQL для каждого режима или добавление режима как признака состояния.
Ограничения
Требует разметки наград. В отличие от BC, CQL нужны кортежи $(s, a, r, s')$. Если датасет содержит только $(s, a, s')$ — необходимо определить функцию вознаграждения.
Чувствителен к масштабу наград. Баланс между TD-потерей и CQL-штрафом зависит от величины наград. Если награды большие (например, в тысячах), TD-потеря доминирует и CQL теряет эффект. Нормализуйте награды в $[-1, 1]$ или $[0, 1]$.
Консервативен по построению. CQL не превзойдёт поведенческую политику значительно в регионах с редкими данными. Он создан для безопасности, а не для агрессивной экстраполяции. Для задач, требующих существенного выхода за пределы датасета, более уместны модельные методы (Глава 8).
Непрерывные пространства действий требуют сэмплирования. Аппроксимация logsumexp требует сэмплирования — как правило, 10 случайных + 10 политиковых действий на состояние. В коде два предложения усредняются отдельно (log E_rand[exp Q] и log E_π[exp Q] с importance weights для π), затем объединяются как log((1/2)(E_rand + E_π)), а не один logsumexp по всем 20 сэмплам, чтобы не искажать веса важности. Это увеличивает вычислительные затраты по сравнению с BC или TD3+BC.
Итоги
| Свойство | CQL |
|---|---|
| Необходимые данные | Переходы $(s, a, r, s')$ с наградами |
| Целевая функция | TD-потеря + CQL штраф (logsumexp − Q датасета) |
| Обработка OOD | Явная: Q-значения занижаются для OOD-действий |
| Теоретическая гарантия | Нижняя оценка истинной Q-функции в датасете |
| Ключевой гиперпараметр | $\alpha$ (сила консерватизма) |
CQL закрывает разрыв между поведенческим клонированием и полным офлайн RL. Он использует информацию о награде для улучшения над поведенческой политикой, а пессимизм относительно OOD-действий предотвращает катастрофические отказы стандартного Q-обучения.
Остающееся ограничение: CQL является безмодельным. Глава 5 (IQL) уточняет идею пессимизма по значениям. Глава 8 (MOPO) показывает, как обучение модели мира позволяет генерировать синтетические данные — расширяя эффективный датасет за пределы собранного.
Литература
- Kumar, A., Zhou, A., Tucker, G., & Levine, S. (2020). Conservative Q-Learning for Offline Reinforcement Learning. NeurIPS. arXiv:2006.04779.
- Fujimoto, S., & Gu, S. (2021). A Minimalist Approach to Offline RL (TD3+BC). NeurIPS. arXiv:2106.06860.
- Haarnoja, T. et al. (2018). Soft Actor-Critic. ICML. arXiv:1801.01290.
- Levine, S. et al. (2020). Offline Reinforcement Learning: Tutorial, Review, and Perspectives. arXiv:2005.01643.