Skip to content

Instantly share code, notes, and snippets.

@KuRRe8
Last active September 25, 2025 04:40
Show Gist options
  • Select an option

  • Save KuRRe8/55834ded8820e91b87fbae3a6b149297 to your computer and use it in GitHub Desktop.

Select an option

Save KuRRe8/55834ded8820e91b87fbae3a6b149297 to your computer and use it in GitHub Desktop.
强化学习个人笔记

强化学习

强化学习(Reinforcement Learning, RL)是一种机器学习方法。它通过智能体(agent)在环境(environment)中采取动作(action),接收环境反馈的奖励(reward),并根据长期累计回报(return)来学习最优策略(policy)。

核心要素:

  • 状态(state, s):环境当前的描述。
  • 动作(action, a):智能体可选择的行为。
  • 奖励(reward, r):环境对动作的即时反馈。
  • 策略(policy, π):从状态到动作的映射。
  • 价值函数(value function, V 或 Q):评估状态或状态-动作组合的长期回报。
  • 环境动态(transition, P):状态转移概率。

数学上通常建模为马尔可夫决策过程(MDP)。目标是学习最优策略 π*,最大化期望回报。

基于价值 (Value-based)

基于策略 (Policy-based)

基于模型 (Model-based)

  • Dyna-Q
  • 世界模型系
    • MuZero
    • Dreamer

A2C (Advantage Actor-Critic)

A2C 是 Actor-Critic 的同步批量版。 Actor 输出策略 πθ,Critic 估计状态值 V_w。 用 n-step 回报估计优势(advantage),同步在小批量轨迹上更新 Actor 与 Critic。 相比单步 Actor-Critic,A2C 更稳定,样本效率更高。


1. 通俗易懂解释

智能体并行(或在一段时间内累积)收集若干个状态-动作-回报序列。 Critic 用这些序列计算 n-step 回报作为目标。 Actor 根据优势(实际回报减去 Critic 估计)调整策略概率,使带来高于预期的动作更可能被选中。 所有样本一次性用于梯度更新,然后继续收集新数据。


2. 数学公式

n-step 回报(从时间 t 开始,长度 n)

G_t^{(n)} = R_{t+1} + γ R_{t+2} + ... + γ^{n-1} R_{t+n} + γ^n V(s_{t+n})

优势估计(Advantage)

A_t = G_t^{(n)} - V(s_t; w)

Actor 损失(含熵正则化)

L_{actor} = -E[ \log \pi_\theta(a_t|s_t) A_t ] - β E[ H(\pi_\theta(\cdot|s_t)) ]

Critic 损失

L_{critic} = E[ (G_t^{(n)} - V(s_t; w))^2 ]

总体损失常为加权和: L = L_{actor} + c_{v} L_{critic}


3. 手动数值例子(单状态、两动作,n=1 即一步 A2C)

设定:

  • 单状态 S,两个动作 A1/A2。
  • 若选 A1 获得奖励 r=+1 即终止;A2 获得 r=0 即终止。
  • 折扣 γ=1。
  • Actor 用两个 logits [θ1, θ2],初始 logits=[0,0] → softmax 概率 p1=p2=0.5。
  • Critic V(S)=0。
  • 学习率 actor α=0.1(作用于 logits),critic β=0.5。
  • 不使用熵项,n=1。

一次采样:选择 A1,得到 r=1。计算步骤:

  1. 计算 n-step 回报(n=1): $G_t^{(1)} = r + γ V(s') = 1 + 1*0 = 1$.

  2. 优势: $A_t = G_t^{(1)} - V(S) = 1 - 0 = 1$.

  3. Actor 的梯度(softmax logits 简化导数) 初始 probs p1=0.5, p2=0.5。对于选择动作 A1,梯度对 logits 为: ∂ log π(A1)/∂θ1 = 1 - p1 = 0.5 ∂ log π(A1)/∂θ2 = -p2 = -0.5

    参数更新(θ ← θ + α * grad * A): θ1 ← 0 + 0.1 * 0.5 * 1 = 0.05 θ2 ← 0 + 0.1 * (-0.5) * 1 = -0.05

    新 logits = [0.05, -0.05]。新概率: p1' = exp(0.05)/(exp(0.05)+exp(-0.05)) ≈ 0.525 p2' ≈ 0.475

    策略向选择 A1 的方向加强。

  4. Critic 更新(均方误差目标) 目标 G=1,当前 V=0,β=0.5: V ← V + β*(G - V) = 0 + 0.5*(1 - 0) = 0.5

结果显示一次同步更新后:策略对好动作的概率上升,价值估计也上升,二者协作加速学习。


4. Python 代码示例(简洁 A2C,n-step)

# 简化 A2C 示例(PyTorch),使用单个环境和 n-step 收集
import gym
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Categorical
from collections import deque

class ActorCritic(nn.Module):
    def __init__(self, s_dim, a_dim):
        super().__init__()
        self.shared = nn.Sequential(nn.Linear(s_dim, 128), nn.ReLU())
        self.actor = nn.Sequential(nn.Linear(128, a_dim), nn.Softmax(dim=-1))
        self.critic = nn.Linear(128, 1)
    def forward(self, x):
        f = self.shared(x)
        return self.actor(f), self.critic(f)

def compute_returns(rewards, last_value, gamma):
    R = last_value
    returns = []
    for r in reversed(rewards):
        R = r + gamma * R
        returns.insert(0, R)
    return returns

env = gym.make("CartPole-v1")
s_dim = env.observation_space.shape[0]
a_dim = env.action_space.n
model = ActorCritic(s_dim, a_dim)
optimizer = optim.Adam(model.parameters(), lr=1e-3)

n_steps = 5
gamma = 0.99
c_v = 0.5    # critic loss weight
beta = 0.01  # entropy weight

for episode in range(500):
    state = env.reset()[0]
    done = False
    ep_reward = 0
    while not done:
        states, actions, rewards = [], [], []
        for _ in range(n_steps):
            s_t = torch.FloatTensor(state).unsqueeze(0)
            probs, value = model(s_t)
            m = Categorical(probs)
            a = m.sample().item()

            next_state, r, done, _, _ = env.step(a)
            ep_reward += r

            states.append(s_t)
            actions.append(torch.tensor(a))
            rewards.append(r)

            state = next_state
            if done:
                break

        # bootstrap value for last state if not done
        if done:
            last_value = 0.0
        else:
            with torch.no_grad():
                _, last_value = model(torch.FloatTensor(state).unsqueeze(0))
                last_value = last_value.item()

        returns = compute_returns(rewards, last_value, gamma)
        returns = torch.FloatTensor(returns).unsqueeze(1)

        # compute losses over the collected n-step batch
        policy_losses = []
        value_losses = []
        entropies = []
        for s_t, a_t, R in zip(states, actions, returns):
            probs, value = model(s_t)
            m = Categorical(probs)
            logp = m.log_prob(a_t)
            entropy = m.entropy()
            advantage = R - value

            policy_losses.append(-logp * advantage.detach())
            value_losses.append((R - value).pow(2))
            entropies.append(entropy)

        loss = torch.stack(policy_losses).mean() + c_v * torch.stack(value_losses).mean() - beta * torch.stack(entropies).mean()

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    if episode % 50 == 0:
        print(f"Episode {episode}, Return {ep_reward}")

总结

  • A2C 用 n-step 回报批量更新 Actor 与 Critic。
  • Actor 用优势引导策略更新,Critic 用回归目标逼近价值。
  • 同步批量更新稳定且高效。
  • 可扩展为并行版本 A3C(多线程异步)。

Actor-Critic 算法

Actor-Critic 结合了 策略梯度 (Policy Gradient)价值函数 (Value-based) 方法。
它在 REINFORCE 基础上引入了基线 (baseline),并用价值函数来减少梯度估计的方差,从而更稳定更高效。


1. 通俗易懂解释

REINFORCE 只根据完整回报 G 更新策略,方差很大。
Actor-Critic 的想法是:

  • Actor:负责学策略 π(a|s;θ)。
  • Critic:负责学价值函数 V(s;w),评估当前策略好不好。
  • 更新 Actor 时,不再用原始回报 G,而是用 优势函数 A(s,a) = G - V(s) 或 TD 误差来指导。

这样,Actor 相当于“演员”,选择动作;Critic 相当于“评论员”,告诉演员该动作比预期好还是差。


2. 数学公式

目标:最大化期望回报

J(θ) = E_π[ G_t ]

策略梯度 (带基线 V):

∇θ J(θ) = E_π[ ∇θ log π(a_t|s_t;θ) (G_t - V(s_t;w)) ]

其中 (G_t - V(s_t;w)) 就是 优势 (Advantage)

价值函数更新 (Critic):
最小化 TD 误差:

δ_t = r_t + γ V(s_{t+1};w) - V(s_t;w)


3. 手动计算数值例子

小例子:

  • 状态 S,有动作 A1 和 A2。
  • 策略初始:π(A1)=0.5,π(A2)=0.5。
  • 奖励:A1 → +1,A2 → -1。
  • 价值函数初始:V(S)=0。

一次采样:

  • 选择 A1,得到奖励 +1。
  • TD 误差 δ = 1 + γ·V(S') - V(S) ≈ 1。

更新:

  • Actor 增加 A1 的概率,因为 δ > 0。
  • Critic 更新 V(S) ← V(S)+αδ。

几次迭代后:

  • 策略收敛到总是选 A1。
  • V(S) 收敛到 +1。

4. Python 代码示例

import gym
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Categorical

# Actor 网络
class Actor(nn.Module):
    def __init__(self, state_dim, action_dim):
        super().__init__()
        self.fc = nn.Sequential(
            nn.Linear(state_dim, 128),
            nn.ReLU(),
            nn.Linear(128, action_dim),
            nn.Softmax(dim=-1)
        )
    def forward(self, x):
        return self.fc(x)

# Critic 网络
class Critic(nn.Module):
    def __init__(self, state_dim):
        super().__init__()
        self.fc = nn.Sequential(
            nn.Linear(state_dim, 128),
            nn.ReLU(),
            nn.Linear(128, 1)
        )
    def forward(self, x):
        return self.fc(x)

def actor_critic(env, actor, critic, actor_opt, critic_opt, episodes=500, gamma=0.99):
    for ep in range(episodes):
        state = env.reset()[0]
        done = False
        ep_reward = 0
        while not done:
            state_t = torch.FloatTensor(state).unsqueeze(0)
            probs = actor(state_t)
            m = Categorical(probs)
            action = m.sample()

            next_state, reward, done, _, _ = env.step(action.item())
            ep_reward += reward

            next_state_t = torch.FloatTensor(next_state).unsqueeze(0)

            # Critic: TD 目标
            value = critic(state_t)
            next_value = critic(next_state_t)
            td_target = reward + gamma * next_value * (1 - int(done))
            td_error = td_target - value

            # 更新 Critic
            critic_loss = td_error.pow(2).mean()
            critic_opt.zero_grad()
            critic_loss.backward()
            critic_opt.step()

            # 更新 Actor
            actor_loss = -(m.log_prob(action) * td_error.detach())
            actor_opt.zero_grad()
            actor_loss.backward()
            actor_opt.step()

            state = next_state

        if ep % 50 == 0:
            print(f"Episode {ep}, Return: {ep_reward}")

env = gym.make("CartPole-v1")
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n

actor = Actor(state_dim, action_dim)
critic = Critic(state_dim)

actor_opt = optim.Adam(actor.parameters(), lr=1e-3)
critic_opt = optim.Adam(critic.parameters(), lr=1e-3)

actor_critic(env, actor, critic, actor_opt, critic_opt, episodes=500)

5. 总结

  • REINFORCE 的改进版,用 Critic 降低方差。
  • Actor:更新策略;Critic:估计状态价值。
  • 常作为 A2C、A3C、PPO、DDPG、SAC 等高级方法的基础框架。

深度确定性策略梯度 (DDPG)

DDPG 是一种 基于策略梯度的 actor-critic 算法,专门用于 连续动作空间。它结合了 DQN 的经验回放和目标网络思想,并采用 确定性策略 来输出动作。


1. 通俗易懂解释

在离散动作里,可以用 Q-learning 或 DQN 直接枚举动作,选最大值。但在连续动作里,动作集合是无限的,没法逐个比较。

DDPG 的思路:

  • 用一个 actor 网络 直接输出一个确定性动作 $a=\mu(s|\theta^\mu)$
  • 用一个 critic 网络 学习 Q 函数 $Q(s,a|\theta^Q)$,来评估 actor 的输出。
  • actor 的梯度通过 critic 反传更新。
  • 目标网络(软更新)和 经验回放 保持训练稳定。

2. 数学公式

  1. Critic 更新:最小化 TD 误差 L(\theta^Q) = \mathbb{E}[(Q(s_t,a_t|\theta^Q) - y_t)^2]

其中目标: y_t = r_t + \gamma Q'(s_{t+1}, \mu'(s_{t+1}|\theta^{\mu'})|\theta^{Q'})

  1. Actor 更新:通过链式法则对确定性策略梯度 \nabla_{\theta^\mu} J \approx \mathbb{E}[\nabla_a Q(s,a|\theta^Q)|{a=\mu(s)} \nabla{\theta^\mu} \mu(s|\theta^\mu)]

  2. 目标网络软更新\theta' \leftarrow \tau \theta + (1-\tau)\theta'


3. 手动数值例子

假设:

  • 状态 $s=[1.0]$
  • actor 输出动作 $a=\mu(s)=0.5$
  • critic 估计 $Q(s,a)=1.2$
  • 采样奖励 $r=1.0$,下一个状态 $s'=[0.8]$,折扣因子 $\gamma=0.9$
  • 目标网络输出 $\mu'(s')=0.4$,$Q'(s',a')=1.5$。

则目标:

$$ y = r + \gamma Q'(s',a') = 1.0 + 0.9 * 1.5 = 2.35 $$

critic 损失:

$$ L = (Q(s,a)-y)^2 = (1.2 - 2.35)^2 = 1.3225 $$

actor 更新:如果 $\nabla_a Q(s,a)=0.8$,而 $\nabla_{\theta^\mu}\mu(s)=0.5$, 则

$$ \nabla_{\theta^\mu}J = 0.8 * 0.5 = 0.4 $$

说明 actor 应该调整参数,使动作朝着 critic 评估更高的方向前进。


4. Python 代码示例(PyTorch 简化版)

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import copy

# Actor 网络: 状态 -> 动作
class Actor(nn.Module):
    def __init__(self, s_dim, a_dim):
        super().__init__()
        self.fc = nn.Sequential(
            nn.Linear(s_dim, 128), nn.ReLU(),
            nn.Linear(128, a_dim), nn.Tanh()  # 动作归一化到 [-1,1]
        )
    def forward(self, s):
        return self.fc(s)

# Critic 网络: (状态,动作) -> Q值
class Critic(nn.Module):
    def __init__(self, s_dim, a_dim):
        super().__init__()
        self.fc = nn.Sequential(
            nn.Linear(s_dim + a_dim, 128), nn.ReLU(),
            nn.Linear(128, 1)
        )
    def forward(self, s, a):
        return self.fc(torch.cat([s, a], dim=-1))

# 初始化
s_dim, a_dim = 3, 1
actor, critic = Actor(s_dim, a_dim), Critic(s_dim, a_dim)
target_actor, target_critic = copy.deepcopy(actor), copy.deepcopy(critic)

actor_opt = optim.Adam(actor.parameters(), lr=1e-3)
critic_opt = optim.Adam(critic.parameters(), lr=1e-3)

# 伪造一批数据
states = torch.randn(4, s_dim)
actions = torch.randn(4, a_dim)
rewards = torch.randn(4, 1)
next_states = torch.randn(4, s_dim)
gamma, tau = 0.99, 0.005

# Critic更新
with torch.no_grad():
    next_actions = target_actor(next_states)
    target_q = rewards + gamma * target_critic(next_states, next_actions)
q_vals = critic(states, actions)
critic_loss = F.mse_loss(q_vals, target_q)
critic_opt.zero_grad()
critic_loss.backward()
critic_opt.step()

# Actor更新 (最大化 Q)
actor_loss = -critic(states, actor(states)).mean()
actor_opt.zero_grad()
actor_loss.backward()
actor_opt.step()

# 软更新目标网络
for target_param, param in zip(target_actor.parameters(), actor.parameters()):
    target_param.data.copy_(tau * param.data + (1 - tau) * target_param.data)
for target_param, param in zip(target_critic.parameters(), critic.parameters()):
    target_param.data.copy_(tau * param.data + (1 - tau) * target_param.data)

print("Critic loss:", critic_loss.item(), "Actor loss:", actor_loss.item())

总结

  • DDPG 适用于 连续动作空间
  • 使用 actor 输出确定性动作,critic 评估动作质量。
  • 结合 经验回放软更新的目标网络 提高稳定性。
  • 是后续 TD3、SAC 等算法的基础。

Double DQN 与 Dueling DQN

DQN 在实践中存在一些不足:

  • DQN 的过估计偏差:使用 max_a Q(s',a) 时,既用于选择动作又用于评估动作,导致 Q 值系统性偏高。
  • 状态价值与优势混淆:在一些状态下,动作的选择并不重要(如已接近终止状态),但 DQN 仍然浪费计算在区分动作价值上。

Double DQN 和 Dueling DQN 分别解决了这两个问题。


1. Double DQN

通俗易懂解释

DQN 里 max Q(s',a) 同时“选择动作”和“评估动作”,就像学生自己出题再自己打分,容易乐观偏差。
Double DQN 把这两步分开:

  • 动作选择用 在线网络 θ。
  • 动作评估用 目标网络 θ⁻。

这样能降低过估计,提高稳定性。


数学公式

DQN 目标值:

y^{DQN} = r + γ max_a Q(s',a; θ⁻)

Double DQN 目标值:

y^{DDQN} = r + γ Q(s', argmax_a Q(s',a; θ); θ⁻)


2. Dueling DQN

通俗易懂解释

在一些状态下,所有动作价值差不多,比如“快结束了”。
传统 DQN 仍然要输出每个动作的 Q 值,学习效率低。
Dueling DQN 拆分 Q(s,a) 为:

  • 状态价值 V(s)
  • 动作优势 A(s,a)

这样网络能更好地区分哪些状态重要,哪些动作影响大。


数学公式

Dueling 结构定义为:

Q(s,a) = V(s) + (A(s,a) - 1/|A| Σ_{a'} A(s,a'))

其中:

  • V(s) 表示状态价值
  • A(s,a) 表示动作优势
  • 归一化项保证唯一性(否则 V 和 A 可相互偏移)

3. Python 代码示例

Double DQN

import torch
import torch.nn as nn
import torch.optim as optim
import random
import numpy as np
from collections import deque

class QNetwork(nn.Module):
    def __init__(self, state_dim, action_dim):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Linear(state_dim, 64),
            nn.ReLU(),
            nn.Linear(64, action_dim)
        )
    def forward(self, x):
        return self.layers(x)

class DoubleDQNAgent:
    def __init__(self, state_dim, action_dim):
        self.q_net = QNetwork(state_dim, action_dim)
        self.target_net = QNetwork(state_dim, action_dim)
        self.target_net.load_state_dict(self.q_net.state_dict())
        self.optimizer = optim.Adam(self.q_net.parameters(), lr=1e-3)
        self.memory = deque(maxlen=10000)
        self.gamma = 0.99
        self.batch_size = 64
        self.steps = 0
        self.update_target_freq = 100
        self.action_dim = action_dim

    def store(self, s,a,r,s_,d):
        self.memory.append((s,a,r,s_,d))

    def update(self):
        if len(self.memory) < self.batch_size: return
        batch = random.sample(self.memory, self.batch_size)
        s,a,r,s_,d = zip(*batch)
        s = torch.FloatTensor(s)
        a = torch.LongTensor(a).unsqueeze(1)
        r = torch.FloatTensor(r).unsqueeze(1)
        s_ = torch.FloatTensor(s_)
        d = torch.FloatTensor(d).unsqueeze(1)

        q_values = self.q_net(s).gather(1, a)

        # Double DQN: 动作选择用 θ, 评估用 θ⁻
        next_actions = self.q_net(s_).argmax(1, keepdim=True)
        with torch.no_grad():
            target_q = self.target_net(s_).gather(1, next_actions)
            y = r + self.gamma * (1-d) * target_q

        loss = nn.MSELoss()(q_values, y)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

        self.steps += 1
        if self.steps % self.update_target_freq == 0:
            self.target_net.load_state_dict(self.q_net.state_dict())

Dueling DQN

class DuelingQNetwork(nn.Module):
    def __init__(self, state_dim, action_dim):
        super().__init__()
        self.feature = nn.Sequential(
            nn.Linear(state_dim, 128),
            nn.ReLU()
        )
        self.value_stream = nn.Sequential(
            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Linear(64, 1)
        )
        self.advantage_stream = nn.Sequential(
            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Linear(64, action_dim)
        )

    def forward(self, x):
        x = self.feature(x)
        V = self.value_stream(x)
        A = self.advantage_stream(x)
        Q = V + (A - A.mean(dim=1, keepdim=True))
        return Q

4. 总结

  • Double DQN

    • 解决 DQN 过估计问题
    • 用 θ 选择动作,用 θ⁻ 评估动作
    • 更稳定,学习效果更好
  • Dueling DQN

    • 拆分 Q 为 V(s) + A(s,a)
    • 更好地学习状态价值,减少动作无关状态的学习负担
    • 特别适合大状态空间和无关动作较多的环境

两者常与 DQN 结合使用,形成更强大的强化学习算法。

深度 Q 网络 (Deep Q-Network, DQN)

DQN 是 Q-learning 的深度学习版本。传统 Q-learning 用表格存储 Q(s,a),但当状态空间很大(如图像输入)时,表格方法不可行。
DQN 使用神经网络近似 Q 函数,即:

Q(s,a; θ) ≈ Q*(s,a)


1. 通俗易懂解释

玩 Atari 游戏时,屏幕像素就是状态,无法用表格记录所有状态。
DQN 的思路:

  1. 用卷积神经网络从图像中提取特征。
  2. 输出每个动作的 Q 值。
  3. 用 Q-learning 的目标函数训练网络。

DQN 还引入了两个关键技巧,稳定训练:

  • 经验回放 (Experience Replay):将过往经验 (s,a,r,s′) 存入缓冲区,随机采样训练,避免样本相关性。
  • 目标网络 (Target Network):复制一份参数 θ⁻,固定一段时间不更新,用来计算目标值,减少震荡。

2. 数学公式

Q-learning 损失函数

在 DQN 中,参数 θ 表示 Q 网络,θ⁻ 表示目标网络。
更新目标:

y = r + γ max_a Q(s',a; θ⁻)

损失函数:

L(θ) = E[(y - Q(s,a; θ))^2]

更新规则

通过梯度下降更新 θ,使得 Q 网络预测逼近 Q-learning 的目标。


3. 手动计算数值例子

简化环境:

  • 两个状态 S1, S2;两个动作 A1, A2。
  • 奖励:S1-A1 → S2,奖励 +1;其余动作奖励 0。
  • γ=1。

假设当前网络估计:

  • Q(S1,A1)=0.5, Q(S1,A2)=0.2
  • Q(S2,A1)=0.3, Q(S2,A2)=0.4

一次交互:执行 (S1,A1),得到 r=+1, 下一状态 S2。

目标:
y = r + γ * max Q(S2, a; θ⁻) = 1 + 0.4 = 1.4

损失:
L = (1.4 - 0.5)^2 = 0.81

训练后,Q(S1,A1) 会被推向 1.4,更接近最优值。


4. Python 代码示例

以下是一个简化版 DQN 实现(以 Gym 的 CartPole 为例):

import random
import gym
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from collections import deque

# Q 网络
class QNetwork(nn.Module):
    def __init__(self, state_dim, action_dim):
        super(QNetwork, self).__init__()
        self.layers = nn.Sequential(
            nn.Linear(state_dim, 64),
            nn.ReLU(),
            nn.Linear(64, 64),
            nn.ReLU(),
            nn.Linear(64, action_dim)
        )
    def forward(self, x):
        return self.layers(x)

# DQN 训练器
class DQNAgent:
    def __init__(self, state_dim, action_dim):
        self.q_net = QNetwork(state_dim, action_dim)
        self.target_net = QNetwork(state_dim, action_dim)
        self.target_net.load_state_dict(self.q_net.state_dict())
        self.memory = deque(maxlen=10000)
        self.optimizer = optim.Adam(self.q_net.parameters(), lr=1e-3)
        self.gamma = 0.99
        self.batch_size = 64
        self.update_target_freq = 100
        self.steps = 0
        self.action_dim = action_dim

    def select_action(self, state, eps=0.1):
        if random.random() < eps:
            return random.randrange(self.action_dim)
        state = torch.FloatTensor(state).unsqueeze(0)
        return self.q_net(state).argmax().item()

    def store(self, s, a, r, s_, done):
        self.memory.append((s,a,r,s_,done))

    def update(self):
        if len(self.memory) < self.batch_size:
            return
        batch = random.sample(self.memory, self.batch_size)
        s, a, r, s_, d = zip(*batch)
        s = torch.FloatTensor(s)
        a = torch.LongTensor(a).unsqueeze(1)
        r = torch.FloatTensor(r).unsqueeze(1)
        s_ = torch.FloatTensor(s_)
        d = torch.FloatTensor(d).unsqueeze(1)

        q_values = self.q_net(s).gather(1, a)
        with torch.no_grad():
            target = r + self.gamma * (1-d) * self.target_net(s_).max(1, keepdim=True)[0]
        loss = nn.MSELoss()(q_values, target)

        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

        self.steps += 1
        if self.steps % self.update_target_freq == 0:
            self.target_net.load_state_dict(self.q_net.state_dict())

# 运行示例
env = gym.make("CartPole-v1")
agent = DQNAgent(env.observation_space.shape[0], env.action_space.n)

for episode in range(10):  # 简短演示
    state = env.reset()[0]
    done = False
    while not done:
        action = agent.select_action(state, eps=0.1)
        next_state, reward, done, _, _ = env.step(action)
        agent.store(state, action, reward, next_state, done)
        agent.update()
        state = next_state

总结

  • DQN 用神经网络近似 Q 函数,解决大规模状态空间问题。
  • 关键技巧:经验回放 + 目标网络。
  • 损失函数基于 Q-learning 的 TD 目标。
  • 为后续改进(Double DQN, Dueling DQN, Prioritized Replay 等)奠定了基础。

蒙特卡洛方法 (Monte Carlo, MC)

蒙特卡洛方法是强化学习中基于价值 (Value-based) 的一种方法。它不需要已知环境的转移概率模型,只依赖与环境的交互样本。核心思想是:
通过多次采样完整的经验轨迹,估计状态或状态-动作的价值。


1. 通俗易懂解释

想象你在玩一个游戏:

  • 从开始状态进入游戏,直到游戏结束,形成一条完整的“轨迹”。
  • 在这条轨迹中,每个状态会得到一个最终的回报(比如赢或输)。
  • 如果你多次重复这个游戏,把相同状态出现时得到的平均回报算出来,那么就得到了这个状态的“价值”。

这就是蒙特卡洛方法:

  • 依赖完整轨迹,不能在中途打断。
  • 无模型,只需要经验。
  • 通过采样 + 平均来近似真实期望。

2. 数学公式

回报定义

对于轨迹中的某个时刻 (t),回报定义为从该时刻到结束的折扣累计奖励:

G_t = R_{t+1} + γ R_{t+2} + γ^2 R_{t+3} + ... = Σ_{k=0}^∞ γ^k R_{t+k+1}

状态价值函数估计

给定策略 (\pi),状态价值函数定义为:

V^π(s) = E_π[G_t | S_t = s]

蒙特卡洛通过采样平均来近似:

V(s) ≈ 1/N Σ_{i=1}^N G_t^{(i)}

其中 (G_t^{(i)}) 是第 (i) 次采样得到的回报。


3. 手动计算数值例子

假设有一个小型 MDP:

  • 只有两个非终止状态:A 和 B。
  • 从 A 出发,等概率(0.5/0.5)进入 B 或终止。
  • 从 B 出发,必然进入终止。
  • 折扣因子 γ = 1。
  • 奖励规则:进入终止状态时奖励 = +1,否则奖励 = 0。

样本轨迹 1


A → B → 终止
奖励序列: 0, 1

  • G(A) = 0 + 1 = 1
  • G(B) = 1

样本轨迹 2


A → 终止
奖励序列: 1

  • G(A) = 1

样本轨迹 3


A → B → 终止
奖励序列: 0, 1

  • G(A) = 1
  • G(B) = 1

平均回报估计

  • V(A) ≈ (1 + 1 + 1) / 3 = 1.0
  • V(B) ≈ (1 + 1) / 2 = 1.0

这表明在该环境下,两个状态的期望回报均为 1。


4. Python 代码示例

import random
from collections import defaultdict

# 环境:A->B->终止,或 A->终止
def generate_episode():
    episode = []
    state = "A"
    while True:
        if state == "A":
            if random.random() < 0.5:
                next_state, reward = "B", 0
            else:
                next_state, reward = "T", 1
        elif state == "B":
            next_state, reward = "T", 1
        episode.append((state, reward))
        if next_state == "T":
            break
        state = next_state
    return episode

def mc_prediction(num_episodes=1000, gamma=1.0):
    returns = defaultdict(list)
    V = defaultdict(float)
    for _ in range(num_episodes):
        episode = generate_episode()
        G = 0
        for t in reversed(range(len(episode))):
            state, reward = episode[t]
            G = gamma * G + reward
            if state not in [x[0] for x in episode[:t]]:
                returns[state].append(G)
                V[state] = sum(returns[state]) / len(returns[state])
    return V

V = mc_prediction(10000)
print("蒙特卡洛估计的状态价值:")
for s, v in V.items():
    print(s, ":", round(v, 2))

运行结果显示:

  • V(A) 和 V(B) 都会逐渐收敛到接近 1.0。

总结

  • 蒙特卡洛方法基于采样完整轨迹进行估计。
  • 无需环境模型。
  • 收敛时可近似真实的价值函数。
  • 适用于情节任务(episodic task),常作为时序差分方法的基础。

协同策略优化 (PPO)

PPO 是当下常用的策略梯度算法。它在保证策略更新“不过大”的同时保持简单与高效。核心是裁剪的近端目标 (clipped surrogate objective),避免了复杂的二阶优化(如 TRPO)但获得类似的稳定性。


1. 通俗易懂解释

策略梯度更新时,如果一次更新步太大,会导致性能骤降。PPO 的做法是:

  • 计算 策略比率 $r=\frac{\pi_\theta(a|s)}{\pi_{\theta_{\text{old}}}(a|s)}$
  • 用该比率乘以优势 $A$ 得到原始目标。
  • 同时把比率裁剪到区间 $[1-\varepsilon,1+\varepsilon]$
  • 对两个值取最保守的(对正优势取较小,对负优势取较大),从而限制每次更新幅度。
  • 加上价值函数回归项和熵项作正则。

结果是:更新既能推动策略改进,又不会出现大的、危险的跳跃。


2. 数学公式

策略比率: r_t(θ) = \frac{\pi_θ(a_t|s_t)}{\pi_{θ_{old}}(a_t|s_t)}

裁剪型代理目标(期望形式): L^{CLIP}(θ)=E_t[ \min( r_t(θ) A_t,; \mathrm{clip}(r_t(θ),1-ε,1+ε) A_t ) ]

价值函数损失: L^{VF} = E_t[ (V_θ(s_t) - V^{target}_t)^2 ]

带熵项的总目标(用于最小化):

L(θ)= -L^{CLIP}(θ) + c_v L^{VF} - c_e E_t[H(\pi_θ(\cdot|s_t))]

其中 $ε$ 为裁剪阈值(常见取 0.1–0.3),$c_v$ 与 $c_e$ 为权重超参。


3. 手动数值例子(单步示例,便于理解)

设一次采样得到:旧策略概率 $\pi_{old}(a|s)=0.4$,新策略当前估计 $\pi_\theta(a|s)=0.6$。 优势 $A=2.0$。取 $ε=0.2$

计算:

  • 比率 $r = 0.6 / 0.4 = 1.5$
  • 未裁剪目标 $rA = 1.5 * 2 = 3.0$
  • 裁剪区间 $[1-ε,1+ε]=[0.8,1.2]$。裁剪后比率 = 1.2。
  • 裁剪目标 = $1.2 * 2 = 2.4$
  • PPO 代理取保守值:$\min(3.0, 2.4) = 2.4$。

所以这一步对该样本贡献的(期望)目标为 2.4,而不是 3.0。对带负优势的情形,PPO 会对负值取较大的(更保守)那一项,从而限制损害。

该机制在多样本批量上按平均应用,再与价值损失和熵正则组合最小化总体损失。


4. Python 代码示例(简洁可运行的 PPO 单步更新骨架,PyTorch)

import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Categorical

# 简单策略+价值网络(共享特征)
class ActorCritic(nn.Module):
    def __init__(self, s_dim, a_dim):
        super().__init__()
        self.shared = nn.Sequential(nn.Linear(s_dim, 128), nn.ReLU())
        self.policy = nn.Linear(128, a_dim)
        self.value = nn.Linear(128, 1)
    def forward(self, x):
        f = self.shared(x)
        return self.policy(f), self.value(f)

def ppo_update(model, optimizer, states, actions, old_log_probs, returns, advantages,
               clip_eps=0.2, c_v=0.5, c_e=0.01):
    # states: tensor [B, s_dim]
    # actions: tensor [B]
    # old_log_probs: tensor [B]
    # returns: tensor [B,1]
    # advantages: tensor [B,1]
    logits, values = model(states)
    dist = Categorical(logits=logits)
    log_probs = dist.log_prob(actions)           # [B]
    entropy = dist.entropy().mean()

    ratios = torch.exp(log_probs - old_log_probs)   # [B]
    # element-wise surrogate
    surr1 = ratios * advantages.squeeze(1)
    surr2 = torch.clamp(ratios, 1.0 - clip_eps, 1.0 + clip_eps) * advantages.squeeze(1)
    clipped_surrogate = torch.min(surr1, surr2).mean()   # maximize this

    value_loss = (returns.squeeze(1) - values.squeeze(1)).pow(2).mean()

    loss = -clipped_surrogate + c_v * value_loss - c_e * entropy

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    return loss.item()

# 使用示例(伪数据)
if __name__ == "__main__":
    s_dim = 4
    a_dim = 2
    model = ActorCritic(s_dim, a_dim)
    optim_p = optim.Adam(model.parameters(), lr=3e-4)

    B = 8
    states = torch.randn(B, s_dim)
    actions = torch.randint(0, a_dim, (B,))
    # 假设旧策略 log prob
    old_log_probs = torch.randn(B)
    returns = torch.randn(B, 1)
    advantages = torch.randn(B, 1)

    loss = ppo_update(model, optim_p, states, actions, old_log_probs, returns, advantages)
    print("PPO update loss:", loss)

要点总结

  • PPO 用裁剪的比率限制策略更新幅度,简单且稳定。
  • 主要超参:裁剪阈值 $ε$、价值损失权重 $c_v$、熵权重 $c_e$、以及批量/epoch 数。
  • 实践常用多轮小批量(epochs)在同一批经验上重复优化。
  • PPO 是实战中性能与实现复杂度的良好折中。

Q-learning

Q-learning 是强化学习中基于值 (Value-based) 的一种离策略 (off-policy) 时序差分方法
它直接学习最优状态-动作价值函数 (Q^*(s, a)),不依赖环境模型,也不需要执行当前策略即可学习最优策略。


1. 通俗易懂解释

Q-learning 的核心思想:

  • 记录每个状态选择每个动作的“价值” Q(s,a)。
  • 智能体在环境中探索,观察奖励和下一状态。
  • 根据“下一状态中最大 Q 值 + 当前奖励”来更新当前 Q 值。
  • 通过不断更新,最终 Q(s,a) 收敛到最优值 Q*(s,a),智能体就可以选择每个状态下最优动作。

可以理解为:

  • MC:用完整轨迹平均回报更新 V(s)。
  • TD:用一步预测更新 V(s)。
  • Q-learning:用一步预测更新 Q(s,a),且总是朝最优动作方向更新(off-policy)。

2. 数学公式

Q-learning 更新规则

Q(s_t,a_t) ← Q(s_t,a_t) + α [ R_{t+1} + γ max_a Q(s_{t+1},a) - Q(s_t,a_t) ]

其中:

  • (\alpha) 为学习率
  • (\gamma) 为折扣因子
  • max_a Q(s_{t+1},a) 表示下一状态可能动作的最大值

最优策略

当 Q(s,a) 收敛时,最优策略由下式确定:

π*(s) = argmax_a Q*(s,a)


3. 手动计算数值例子

假设一个 2x2 Gridworld,状态 S1~S4,S4 为终止状态,动作上下左右,折扣 γ=1。

  • 奖励:每步 -1,进入终止状态奖励 0。
  • 学习率 α=0.5。
  • 初始 Q(s,a)=0。

第一次更新示例

从 S1 向右走到 S2:

  • R = -1
  • max_a Q(S2, a) = 0
  • TD 误差 δ = R + γ*max Q(S2,a) - Q(S1,Right) = -1 + 0 - 0 = -1
  • 更新: Q(S1,Right) ← 0 + 0.5*(-1) = -0.5

继续从 S2 向右走到 S4(终止状态):

  • R = 0
  • max_a Q(S4,a) = 0
  • δ = 0 + 0 - 0 = 0
  • Q(S2,Right) 不变 = 0

多次迭代后,Q 值会收敛,最优策略选择每个状态的最大 Q 动作。


4. Python 代码示例

import random

# 2x2 Gridworld
states = [(0,0),(0,1),(1,0),(1,1)]
terminal = (1,1)
actions = [(-1,0),(1,0),(0,-1),(0,1)]
alpha = 0.5
gamma = 1.0

# 初始化 Q(s,a)
Q = {(s,a):0.0 for s in states for a in actions}

def step(state, action):
    if state == terminal:
        return state, 0
    r, c = state
    dr, dc = action
    nr, nc = r+dr, c+dc
    if nr<0 or nr>1 or nc<0 or nc>1:
        return state, -1
    reward = -1
    if (nr,nc) == terminal:
        reward = 0
    return (nr,nc), reward

# Q-learning 主循环
for episode in range(1000):
    state = (0,0)  # 每次从 S1 开始
    while state != terminal:
        action = random.choice(actions)  # 随机探索
        next_state, reward = step(state, action)
        max_q_next = max(Q[(next_state,a)] for a in actions)
        td_error = reward + gamma*max_q_next - Q[(state,action)]
        Q[(state,action)] += alpha * td_error
        state = next_state

# 输出 Q(s,a)
print("Q-learning 收敛后的 Q 值:")
for s in states:
    for a in actions:
        print(f"Q({s},{a}) = {Q[(s,a)]:.2f}")

总结

  • Q-learning 是离策略 TD 方法,直接学习 Q*(s,a)。
  • 不需要环境模型,可通过随机探索收敛到最优策略。
  • 更新基于 TD 误差,并选取下一状态最大 Q 值(off-policy)。
  • 最优策略:每个状态选择最大 Q 值动作。

REINFORCE 算法

REINFORCE 是最基本的策略梯度 (Policy Gradient) 方法之一。
它直接优化策略参数化函数 π(a|s;θ),通过采样轨迹估计梯度,更新策略,使得到的回报期望最大化。


1. 通俗易懂解释

在 Q-learning/DQN 中,我们学习的是 Q 值函数,再通过贪心选择动作。
在 REINFORCE 中,我们不再学习 Q 值,而是直接学习概率分布

  • 给定状态 s,策略 π(a|s;θ) 输出每个动作的概率。
  • 我们采样轨迹,观察奖励。
  • 如果某个动作带来了高回报,更新参数 θ 让该动作的概率更大;
  • 如果回报低,就减少该动作的概率。

换句话说,REINFORCE 就是“用试错的经验直接调整动作选择概率”。


2. 数学公式

目标:最大化期望回报

J(θ) = E_π[ G_t ]

策略梯度定理:

∇θ J(θ) = E_π[ ∇θ log π(a_t|s_t;θ) G_t ]

更新规则:

θ ← θ + α ∇θ log π(a_t|s_t;θ) G_t


3. 手动计算数值例子

简单 MDP:

  • 状态只有一个 S。
  • 动作 A1 和 A2。
  • 策略参数:π(A1|S)=0.5, π(A2|S)=0.5。
  • 如果选择 A1 → 奖励 +1;选择 A2 → 奖励 -1。

一次采样:

  • 选择 A1(概率 0.5),得到 G=+1。

更新:

  • ∇θ log π(A1|S;θ) ≈ “增加 A1 概率的方向”
  • θ ← θ + α * 1 * ∇θ log π(A1|S;θ)

结果:π(A1|S) 增加,π(A2|S) 减少。
随着多次更新,策略会收敛到总是选择 A1。


4. Python 代码示例

使用 PyTorch 实现 REINFORCE,环境为 Gym 的 CartPole:

import gym
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Categorical

# 策略网络
class PolicyNet(nn.Module):
    def __init__(self, state_dim, action_dim):
        super().__init__()
        self.fc = nn.Sequential(
            nn.Linear(state_dim, 128),
            nn.ReLU(),
            nn.Linear(128, action_dim),
            nn.Softmax(dim=-1)
        )
    def forward(self, x):
        return self.fc(x)

def reinforce(env, policy, optimizer, episodes=1000, gamma=0.99):
    for ep in range(episodes):
        states, actions, rewards = [], [], []
        state = env.reset()[0]
        done = False
        while not done:
            state_tensor = torch.FloatTensor(state).unsqueeze(0)
            probs = policy(state_tensor)
            m = Categorical(probs)
            action = m.sample()
            next_state, reward, done, _, _ = env.step(action.item())

            states.append(state_tensor)
            actions.append(action)
            rewards.append(reward)

            state = next_state

        # 计算回报
        G = 0
        returns = []
        for r in reversed(rewards):
            G = r + gamma * G
            returns.insert(0, G)
        returns = torch.FloatTensor(returns)

        # 归一化回报(帮助稳定训练)
        returns = (returns - returns.mean()) / (returns.std() + 1e-9)

        # 策略梯度更新
        loss = 0
        for logit_state, action, Gt in zip(states, actions, returns):
            probs = policy(logit_state)
            m = Categorical(probs)
            loss += -m.log_prob(action) * Gt

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if ep % 100 == 0:
            print(f"Episode {ep}, Return: {sum(rewards)}")

env = gym.make("CartPole-v1")
policy = PolicyNet(env.observation_space.shape[0], env.action_space.n)
optimizer = optim.Adam(policy.parameters(), lr=1e-2)

reinforce(env, policy, optimizer, episodes=500)

5. 总结

  • REINFORCE 是最早的策略梯度方法。
  • 直接优化策略,避免了 Q-learning 的动作选择问题。
  • 缺点:高方差,收敛慢。
  • 改进方向:基线函数 (baseline)、Actor-Critic 等。

TD3 (Twin Delayed DDPG)

TD3 是对 DDPG 的三项关键改进,目的是消除过估计并提高训练稳定性。三项改进为:

  1. 双重 Critic(twin critics),取最小值以抑制过估计;
  2. 延迟更新策略(delayed policy updates),Critic 更新多次后才更新 Actor 与目标网络;
  3. 目标策略平滑(target policy smoothing),在计算目标 Q 时对目标动作加小噪声并裁剪,减少估计误差峰值。

1. 通俗易懂解释

DDPG 在连续动作空间上工作良好但易过估计 Q 值。TD3 用两套 Critic 来相互制衡:选取两者的最小值作为目标 Q,从而降低乐观偏差。策略网络更新不那么频繁以保证 Critic 收敛更可靠。目标动作上加入小噪声并裁剪,避免目标 Q 被单个异常动作拉高。三者组合显著提升性能和稳定性。


2. 主要数学公式

目标动作加入噪声并裁剪(target policy smoothing)

\tilde{a}' = \mathrm{clip}(\mu_{\theta'}(s') + \epsilon,; -c, c),\quad \epsilon \sim \mathrm{clip}(\mathcal{N}(0,\sigma), -c, c)

双 Critic 的目标 Q(取最小)

y = r + \gamma \min_{i=1,2} Q_{\phi'_i}(s', \tilde{a}')

Critic 损失(两套 Critic)

L(\phi_i) = \mathbb{E}[(Q_{\phi_i}(s,a) - y)^2],\quad i=1,2

Actor 更新(延迟)

\nabla_{\theta} J \approx \mathbb{E}[\nabla_a Q_{\phi_1}(s,a)|{a=\mu\theta(s)} \nabla_\theta \mu_\theta(s)]

目标网络软更新

\theta' \leftarrow \tau \theta + (1-\tau)\theta',\quad \phi'_i \leftarrow \tau \phi_i + (1-\tau)\phi'_i


3. 手动数值例子(一步示意)

设当前采样 (s,a,r,s'):

  • r = 1.0,γ = 0.99。
  • 目标 actor 的输出 μ'(s') = 0.5。
  • 目标噪声 σ=0.2, 裁剪幅度 c=0.3。采样噪声 ε=0.25,裁剪后 ε_clipped=0.25(在[-0.3,0.3]内)。
  • 则目标动作 $\tilde a' = 0.5 + 0.25 = 0.75$
  • 两个目标 critic 给出 Q'_1(s',0.75)=2.0,Q'_2(s',0.75)=1.6。取最小值 1.6。
  • 目标 y = 1.0 + 0.99 * 1.6 = 1.0 + 1.584 = 2.584。

若当前 Critic 估计 Q(s,a)=1.8,则 Critic 的平方误差 = (1.8 - 2.584)^2 ≈ 0.6147。用梯度下降最小化该损失。Actor 更新使用 Q_{\phi_1} 的梯度,并且只每隔若干步才更新一次。


4. PyTorch 代码示例(简洁骨架)

# 简洁 TD3 实现骨架(非完整训练循环)
import copy
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F

# 简单 Actor 和 Critic
class Actor(nn.Module):
    def __init__(self, s_dim, a_dim, max_action=1.0):
        super().__init__()
        self.l1 = nn.Linear(s_dim, 256); self.l2 = nn.Linear(256, 256)
        self.l3 = nn.Linear(256, a_dim)
        self.max_action = max_action
    def forward(self, s):
        x = F.relu(self.l1(s)); x = F.relu(self.l2(x))
        return self.max_action * torch.tanh(self.l3(x))

class Critic(nn.Module):
    def __init__(self, s_dim, a_dim):
        super().__init__()
        # Q1
        self.l1 = nn.Linear(s_dim + a_dim, 256); self.l2 = nn.Linear(256, 256); self.l3 = nn.Linear(256,1)
        # Q2
        self.l4 = nn.Linear(s_dim + a_dim, 256); self.l5 = nn.Linear(256, 256); self.l6 = nn.Linear(256,1)
    def forward(self, s, a):
        sa = torch.cat([s,a], dim=1)
        x1 = F.relu(self.l1(sa)); x1 = F.relu(self.l2(x1)); q1 = self.l3(x1)
        x2 = F.relu(self.l4(sa)); x2 = F.relu(self.l5(x2)); q2 = self.l6(x2)
        return q1, q2

# 初始化网络与目标网络
s_dim, a_dim = 3, 1
actor = Actor(s_dim, a_dim); actor_target = copy.deepcopy(actor)
critic = Critic(s_dim, a_dim); critic_target = copy.deepcopy(critic)

actor_opt = optim.Adam(actor.parameters(), lr=1e-3)
critic_opt = optim.Adam(critic.parameters(), lr=1e-3)

# 超参
gamma = 0.99
tau = 0.005
policy_noise = 0.2
noise_clip = 0.5
policy_delay = 2  # 每2步更新一次actor/target

# 假设从 replay buffer 采样一个小批量
batch_size = 4
states = torch.randn(batch_size, s_dim)
actions = torch.randn(batch_size, a_dim).clamp(-1,1)
rewards = torch.randn(batch_size,1)
next_states = torch.randn(batch_size, s_dim)
dones = torch.randint(0,2,(batch_size,1)).float()

# 1) 计算目标动作并加噪声(并裁剪)
with torch.no_grad():
    next_actions = actor_target(next_states)
    noise = (torch.randn_like(next_actions) * policy_noise).clamp(-noise_clip, noise_clip)
    next_actions = (next_actions + noise).clamp(-1, 1)

    # 2) 用双 Critic 取最小 Q 作为目标
    target_q1, target_q2 = critic_target(next_states, next_actions)
    target_q = torch.min(target_q1, target_q2)
    y = rewards + (1 - dones) * gamma * target_q

# 3) 更新 critic(两组 Q)
current_q1, current_q2 = critic(states, actions)
critic_loss = F.mse_loss(current_q1, y) + F.mse_loss(current_q2, y)
critic_opt.zero_grad(); critic_loss.backward(); critic_opt.step()

# 4) 延迟更新 actor 与目标网络
# 假设全局 step % policy_delay == 0,则更新 actor
actor_loss = -critic.l3(F.relu(critic.l1(torch.cat([states, actor(states)],1)).detach())).mean()  # 仅示意,实际取 critic.Q1
actor_opt.zero_grad(); actor_loss.backward(); actor_opt.step()

# 5) 软更新目标网络
for param, target_param in zip(actor.parameters(), actor_target.parameters()):
    target_param.data.copy_(tau * param.data + (1 - tau) * target_param.data)
for param, target_param in zip(critic.parameters(), critic_target.parameters()):
    target_param.data.copy_(tau * param.data + (1 - tau) * target_param.data)

print("critic_loss:", critic_loss.item(), "actor_loss:", actor_loss.item())

注:上面 actor_loss 行为简化示意。实际实现中直接用 critic(states, actor(states)) 的第一输出作为目标 Q1,然后取负均值作为 actor loss。


5. 总结要点

  • TD3 = 双 Critic + 延迟策略更新 + 目标策略平滑。
  • 双 Critic 减少过估计,延迟更新让 Critic 更可靠,目标噪声平滑目标动作。
  • 适合连续动作控制任务。
  • TD3 常被视为 DDPG 的稳定改进,并且在多数基准环境上表现更好。

时序差分方法 (Temporal Difference, TD)

时序差分方法结合了动态规划 (DP)蒙特卡洛 (MC) 的优点:

  • 不需要已知环境模型(像 MC 一样)。
  • 可以在部分轨迹的基础上进行更新(不像 MC 一定要等到结尾)。

核心思想:
用当前估计的价值函数预测未来回报,并立即更新。


1. 通俗易懂解释

玩一个游戏时,你走到一个状态并获得奖励。你不必等到游戏结束再更新它的价值,而是可以立刻用“下一步的即时奖励 + 下一状态的价值估计”来更新。

这就像是:

  • 蒙特卡洛:考试后拿到最终成绩再改进学习策略。
  • 时序差分:每次练习题做完,就根据答案修正理解。

因此,TD 学习更高效,也能应用在连续、无终止的任务中。


2. 数学公式

TD(0) 更新规则

V(s_t) ← V(s_t) + α [ R_{t+1} + γ V(s_{t+1}) - V(s_t) ]

其中:

  • (\alpha):学习率
  • TD 误差(Temporal Difference Error):

δ_t = R_{t+1} + γ V(s_{t+1}) - V(s_t)

对比

  • MC:更新基于 完整回报 (G_t)。
  • TD:更新基于 一步预测(即时奖励 + 下一状态价值)。

3. 手动计算数值例子

环境设定:一条简单的 3 状态链:


S1 → S2 → 终止

  • 奖励规则:进入终止时奖励 = +1,其他转移奖励 = 0。
  • 折扣因子 γ = 1。
  • 学习率 α = 0.5。
  • 初始价值:V(S1) = V(S2) = 0。

样本轨迹 1


S1 --(0)--> S2 --(1)--> 终止

更新过程:

  1. 从 S1 到 S2,奖励 = 0:
    δ = 0 + 1*V(S2) - V(S1) = 0 + 0 - 0 = 0
    → V(S1) 不变 = 0

  2. 从 S2 到 终止,奖励 = 1:
    δ = 1 + 10 - 0 = 1
    → V(S2) ← 0 + 0.5
    1 = 0.5

结果:


V(S1) = 0
V(S2) = 0.5


样本轨迹 2

再次运行相同轨迹:

  1. S1 更新:
    δ = 0 + 10.5 - 0 = 0.5
    V(S1) ← 0 + 0.5
    0.5 = 0.25

  2. S2 更新:
    δ = 1 + 0 - 0.5 = 0.5
    V(S2) ← 0.5 + 0.5*0.5 = 0.75

结果:


V(S1) = 0.25
V(S2) = 0.75

可以看到,随着轨迹增多,V(S1), V(S2) 会逐渐接近真实值(V(S1)=1, V(S2)=1)。


4. Python 代码示例

import random

states = ["S1", "S2", "T"]  # T = 终止
gamma = 1.0
alpha = 0.5
V = {"S1": 0.0, "S2": 0.0, "T": 0.0}

def generate_episode():
    # 固定轨迹 S1->S2->T
    return [("S1", 0), ("S2", 1)]

def td_prediction(num_episodes=10):
    for _ in range(num_episodes):
        episode = generate_episode()
        for i, (s, r) in enumerate(episode):
            if s == "T": 
                continue
            next_state = episode[i+1][0]
            reward = episode[i][1]
            td_error = reward + gamma * V[next_state] - V[s]
            V[s] += alpha * td_error
    return V

V_est = td_prediction(10)
print("TD 预测的状态价值:")
for s, v in V_est.items():
    print(s, ":", round(v, 2))

运行结果显示:

  • V(S1), V(S2) 会逐渐向真实值 1 收敛。

总结

  • TD 方法结合了 MC 和 DP 的优点。
  • 不需要完整轨迹,可在线更新。
  • 更新依赖 TD 误差,能更快收敛。
  • 本文展示了 TD(0) 的基本形式,后续的 Q-learningSARSA 都基于此扩展。
@KuRRe8
Copy link
Author

KuRRe8 commented Sep 25, 2025

笔记是AI生成的,目前来看有一些错误,后面慢慢检查。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment