强化学习调参技巧与实战经验

萌芽想法

初步构想、学习摘录或开放性猜想,结构精简,处于持续发酵中。

ℹ数字花园

🌿 什么是数字花园与思想演进?

遵循 Maggie Appleton 与 Andy Matuschak 的常青笔记理念:这里不是按时间沉底的流水账,而是持续修剪打磨的知识生态。文章按成熟度经历:

  • 🌱 萌芽 (Seedling): 发散灵感、读书摘录、开放猜想
  • 🌿 演进 (Budding): 结构渐明、初步代码推导、积极修正
  • 🌳 常青 (Evergreen): 逻辑闭环、持久经得起检验的基石
  • 🔬 实验待证 (Speculative): 结合沙盒与仿真探针的理论探索
浏览全站数字花园与笔记全景 →

引言#

强化学习(Reinforcement Learning, RL)算法的训练过程常常充满挑战:训练不稳定、收敛缓慢、性能波动大等问题层出不穷。与监督学习不同,RL的超参数调优更加困难,因为:

  1. 非平稳性:数据分布随策略更新不断变化
  2. 稀疏奖励:反馈信号延迟且稀少
  3. 高方差:梯度估计噪声大
  4. 超参数敏感:微小的参数变化可能导致完全不同的结果

本文将系统总结强化学习调参的实战经验,包括关键超参数的作用机制、调参策略、常见问题的解决方案,以及稳定性和样本效率的提升技巧。

1. 核心超参数详解#

1.1 学习率(Learning Rate)#

作用:控制参数更新的步长

影响:

  • 过大:训练不稳定,性能剧烈波动,甚至发散
  • 过小:收敛缓慢,可能陷入局部最优

推荐范围:

算法类型推荐学习率典型值
DQN系列1×10−41 \times 10^{-4} ~ 5×10−45 \times 10^{-4}2.5×10−42.5 \times 10^{-4}
A2C/A3C7×10−47 \times 10^{-4} ~ 1×10−31 \times 10^{-3}7×10−47 \times 10^{-4}
PPO1×10−41 \times 10^{-4} ~ 3×10−33 \times 10^{-3}3×10−43 \times 10^{-4}
SAC3×10−43 \times 10^{-4} ~ 1×10−31 \times 10^{-3}3×10−43 \times 10^{-4}
TD31×10−31 \times 10^{-3} ~ 3×10−33 \times 10^{-3}3×10−33 \times 10^{-3}

学习率衰减策略:

import torch.optim as optim
# 1. 线性衰减
def linear_schedule(initial_lr, final_lr, max_timesteps):
def lr_schedule(timestep):
progress = timestep / max_timesteps
return initial_lr + (final_lr - initial_lr) * progress
return lr_schedule
# 2. 指数衰减
scheduler = optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.99)
# 3. 余弦退火
scheduler = optim.lr_scheduler.CosineAnnealingLR(
optimizer, T_max=total_steps, eta_min=1e-6
)
# 4. 自适应学习率(推荐使用Adam)
optimizer = optim.Adam(model.parameters(), lr=3e-4)

调参技巧:

  1. 从默认值开始:先用典型值测试
  2. 观察训练曲线:
    • 如果损失震荡剧烈 → 降低学习率
    • 如果收敛太慢 → 提高学习率
  3. 网格搜索:在 [1e-5, 1e-2] 范围内对数搜索
  4. 使用学习率查找器:
def find_optimal_lr(model, env, lr_min=1e-6, lr_max=1e-2, num_steps=100):
"""学习率查找器"""
lrs = np.logspace(np.log10(lr_min), np.log10(lr_max), num_steps)
losses = []
for lr in lrs:
optimizer = optim.Adam(model.parameters(), lr=lr)
# 训练几步并记录损失
loss = train_step(model, optimizer, env)
losses.append(loss)
if loss > 2 * min(losses): # 损失爆炸,提前停止
break
# 绘制学习率-损失曲线
import matplotlib.pyplot as plt
plt.semilogx(lrs[:len(losses)], losses)
plt.xlabel('Learning Rate')
plt.ylabel('Loss')
plt.title('Learning Rate Finder')
plt.show()
# 选择梯度最大的点(最陡峭的下降)
optimal_idx = np.argmin(np.gradient(losses))
return lrs[optimal_idx]

1.2 折扣因子(Discount Factor, γ)#

作用:控制对未来奖励的重视程度

Gt=∑k=0∞γkRt+k+1G_t = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1}

影响:

  • 接近1(如0.99):重视长期回报,适合奖励延迟的任务
  • 较小(如0.9):重视即时奖励,适合短期决策任务

推荐范围:

任务特征推荐γ值
短期任务(步数<100)0.9 ~ 0.95
中期任务(步数100-1000)0.95 ~ 0.99
长期任务(步数>1000)0.99 ~ 0.999
无限时域任务0.99

实战示例:

# Atari游戏(长时域)
gamma = 0.99
# CartPole(短时域)
gamma = 0.95
# MuJoCo连续控制(中长时域)
gamma = 0.99

注意事项:

  • γ越大,值函数估计的方差越大
  • γ越小,算法越”短视”
  • 通常先固定γ=0.99,优先调整其他参数

1.3 批量大小(Batch Size)#

作用:每次更新使用的样本数量

影响:

  • 大批量:
    • ✅ 梯度估计更准确,训练稳定
    • ✅ GPU利用率高,计算效率高
    • ❌ 样本效率低,需要更多交互
  • 小批量:
    • ✅ 样本效率高
    • ❌ 梯度噪声大,训练不稳定

推荐值:

算法批量大小
DQN32 ~ 128
PPO64 ~ 2048
SAC256 ~ 1024
A2C/A3C128 ~ 256

动态批量大小:

def adaptive_batch_size(timestep, min_batch=32, max_batch=512):
"""根据训练进度调整批量大小"""
# 早期使用小批量探索,后期使用大批量稳定
progress = min(1.0, timestep / 1e6)
batch_size = int(min_batch + (max_batch - min_batch) * progress)
return batch_size

1.4 探索率(Exploration Rate, ε)#

作用:平衡探索(Exploration)与利用(Exploitation)

ε-greedy策略:

a={random actionwith probability ϵarg⁡max⁡aQ(s,a)with probability 1−ϵa = \begin{cases} \text{random action} & \text{with probability } \epsilon \\ \arg\max_a Q(s,a) & \text{with probability } 1-\epsilon \end{cases}

衰减策略:

# 1. 线性衰减(最常用)
def linear_epsilon_decay(timestep, epsilon_start=1.0, epsilon_end=0.01,
decay_steps=1000000):
epsilon = epsilon_start - (epsilon_start - epsilon_end) * min(1.0, timestep / decay_steps)
return epsilon
# 2. 指数衰减
def exponential_epsilon_decay(timestep, epsilon_start=1.0, epsilon_end=0.01,
decay_rate=0.99995):
epsilon = max(epsilon_end, epsilon_start * (decay_rate ** timestep))
return epsilon
# 3. 分段衰减
def piecewise_epsilon(timestep):
if timestep < 500000:
return 1.0
elif timestep < 1000000:
return 0.5
elif timestep < 2000000:
return 0.1
else:
return 0.01
# 使用示例
timestep = 0
for episode in range(num_episodes):
epsilon = linear_epsilon_decay(timestep)
state = env.reset()
done = False
while not done:
if np.random.rand() < epsilon:
action = env.action_space.sample() # 探索
else:
action = select_action(state) # 利用
state, reward, done, _ = env.step(action)
timestep += 1

推荐配置:

参数DQNPPO
初始ε1.0N/A(使用熵正则化)
最终ε0.01 ~ 0.05N/A
衰减步数1M ~ 10MN/A

替代方案:熵正则化(用于策略梯度算法)

# PPO中的熵奖励
entropy_coef = 0.01
loss = policy_loss + value_loss - entropy_coef * entropy

1.5 目标网络更新频率(Target Network Update)#

作用:稳定Q值估计,减少移动目标问题

DQN的目标网络:

class DQN:
def __init__(self):
self.q_network = QNetwork()
self.target_network = QNetwork()
self.target_network.load_state_dict(self.q_network.state_dict())
self.target_update_freq = 10000 # 每10000步更新一次
def update_target_network(self, timestep):
if timestep % self.target_update_freq == 0:
self.target_network.load_state_dict(self.q_network.state_dict())

软更新(Soft Update):

θtarget←τθ+(1−τ)θtarget\theta_{\text{target}} \leftarrow \tau \theta + (1-\tau) \theta_{\text{target}}
def soft_update(target_model, source_model, tau=0.005):
"""软更新目标网络"""
for target_param, param in zip(target_model.parameters(),
source_model.parameters()):
target_param.data.copy_(
tau * param.data + (1.0 - tau) * target_param.data
)
# 每次更新后调用
soft_update(target_network, q_network, tau=0.005)

推荐配置:

更新方式DQNDDPG/TD3/SAC
硬更新频率10000 ~ 50000 步不推荐
软更新τN/A0.001 ~ 0.01

1.6 经验回放缓冲区大小(Replay Buffer Size)#

作用:存储历史经验,打破数据相关性

影响:

  • 大缓冲区(1M+):
    • ✅ 数据多样性高,打破相关性
    • ❌ 内存消耗大,可能包含过时策略的数据
  • 小缓冲区(10K-100K):
    • ✅ 数据新鲜度高
    • ❌ 可能过拟合最近的经验

推荐大小:

算法缓冲区大小
DQN1M
DDPG1M
SAC1M
TD31M
PPO不使用回放缓冲区

优先级经验回放(PER):

class PrioritizedReplayBuffer:
def __init__(self, capacity, alpha=0.6, beta=0.4):
self.capacity = capacity
self.alpha = alpha # 优先级指数
self.beta = beta # 重要性采样指数
self.buffer = []
self.priorities = np.zeros(capacity, dtype=np.float32)
self.position = 0
def add(self, state, action, reward, next_state, done):
max_priority = self.priorities.max() if self.buffer else 1.0
if len(self.buffer) < self.capacity:
self.buffer.append((state, action, reward, next_state, done))
else:
self.buffer[self.position] = (state, action, reward, next_state, done)
self.priorities[self.position] = max_priority
self.position = (self.position + 1) % self.capacity
def sample(self, batch_size):
if len(self.buffer) == self.capacity:
priorities = self.priorities
else:
priorities = self.priorities[:len(self.buffer)]
# 计算采样概率
probabilities = priorities ** self.alpha
probabilities /= probabilities.sum()
# 采样
indices = np.random.choice(len(self.buffer), batch_size, p=probabilities)
samples = [self.buffer[idx] for idx in indices]
# 重要性采样权重
total = len(self.buffer)
weights = (total * probabilities[indices]) ** (-self.beta)
weights /= weights.max()
return samples, indices, weights
def update_priorities(self, indices, priorities):
for idx, priority in zip(indices, priorities):
self.priorities[idx] = priority

2. 算法特定的超参数#

2.1 PPO(Proximal Policy Optimization)#

关键超参数:

参数含义典型值作用
clip_range策略裁剪范围0.1 ~ 0.3限制策略更新幅度
n_epochs每批数据的训练轮数3 ~ 10提高样本效率
gae_lambdaGAE参数λ0.95 ~ 0.99平衡偏差和方差
value_coef值函数损失系数0.5 ~ 1.0值函数学习权重
entropy_coef熵正则化系数0.01 ~ 0.1鼓励探索

推荐配置(Stable Baselines3默认值):

from stable_baselines3 import PPO
model = PPO(
"MlpPolicy",
env,
learning_rate=3e-4,
n_steps=2048, # 每次更新采集的步数
batch_size=64, # 小批量大小
n_epochs=10, # 训练轮数
gamma=0.99,
gae_lambda=0.95,
clip_range=0.2, # 裁剪范围
clip_range_vf=None, # 值函数裁剪(可选)
ent_coef=0.0, # 熵系数
vf_coef=0.5, # 值函数系数
max_grad_norm=0.5, # 梯度裁剪
verbose=1
)

调参建议:

  1. clip_range:

    • 如果训练不稳定 → 减小到0.1
    • 如果收敛太慢 → 增大到0.3
  2. n_epochs:

    • 简单任务:3-5轮
    • 复杂任务:10-15轮
    • 注意过拟合:观察训练集和验证集表现
  3. entropy_coef:

    • 开始时较大(0.01-0.1)鼓励探索
    • 后期衰减到0,利用已学到的策略
def adaptive_entropy_coef(timestep, initial=0.1, final=0.001, decay_steps=1e6):
progress = min(1.0, timestep / decay_steps)
return initial + (final - initial) * progress

2.2 SAC(Soft Actor-Critic)#

关键超参数:

参数含义典型值
temperature (α)熵温度系数0.2(自动调整)
tau软更新系数0.005
target_entropy目标熵−dim⁡(A)-\dim(\mathcal{A})
learning_starts开始学习的步数10000

自动温度调整:

class SAC:
def __init__(self, action_dim):
# 自动调整熵温度
self.target_entropy = -action_dim # 启发式目标熵
self.log_alpha = torch.zeros(1, requires_grad=True)
self.alpha_optimizer = optim.Adam([self.log_alpha], lr=3e-4)
def update_alpha(self, entropy):
"""更新温度参数"""
alpha_loss = -(self.log_alpha * (entropy + self.target_entropy).detach()).mean()
self.alpha_optimizer.zero_grad()
alpha_loss.backward()
self.alpha_optimizer.step()
alpha = self.log_alpha.exp()
return alpha

2.3 DQN及其变体#

Rainbow DQN组件:

class RainbowDQN:
def __init__(self):
# 1. Double DQN:减少过估计
self.use_double_dqn = True
# 2. Dueling DQN:分离值函数和优势函数
self.use_dueling = True
# 3. Prioritized Experience Replay
self.use_per = True
self.per_alpha = 0.6
self.per_beta = 0.4
# 4. Multi-step Learning
self.n_step = 3
# 5. Distributional RL (C51)
self.use_distributional = True
self.v_min = -10
self.v_max = 10
self.n_atoms = 51
# 6. Noisy Networks:替代ε-greedy
self.use_noisy_net = True

推荐组合:

  • 简单任务:Double DQN + Dueling
  • 中等任务:+ PER
  • 复杂任务:Rainbow(全部组件)

3. 提升训练稳定性的技巧#

3.1 梯度裁剪(Gradient Clipping)#

问题:强化学习中梯度经常爆炸或消失

解决方案:

import torch.nn.utils as nn_utils
# 方法1:按范数裁剪(最常用)
nn_utils.clip_grad_norm_(model.parameters(), max_norm=0.5)
# 方法2:按值裁剪
nn_utils.clip_grad_value_(model.parameters(), clip_value=1.0)
# 完整训练循环
for epoch in range(num_epochs):
loss = compute_loss()
optimizer.zero_grad()
loss.backward()
# 梯度裁剪
nn_utils.clip_grad_norm_(model.parameters(), max_norm=0.5)
optimizer.step()

推荐值:

  • PPO: max_norm=0.5
  • DQN: max_norm=10
  • SAC: max_norm=1.0

3.2 奖励归一化与裁剪#

问题:奖励尺度差异大导致训练不稳定

解决方案:

class RewardNormalizer:
def __init__(self, gamma=0.99, epsilon=1e-8):
self.gamma = gamma
self.epsilon = epsilon
self.returns = []
self.mean = 0
self.var = 1
self.count = 0
def update(self, reward):
"""增量更新统计信息"""
self.returns.append(reward)
self.count += 1
if len(self.returns) > 1000:
self.returns.pop(0)
# 计算折扣回报的均值和方差
discounted_returns = []
R = 0
for r in reversed(self.returns):
R = r + self.gamma * R
discounted_returns.insert(0, R)
self.mean = np.mean(discounted_returns)
self.var = np.var(discounted_returns) + self.epsilon
def normalize(self, reward):
"""归一化奖励"""
return reward / np.sqrt(self.var)
# 奖励裁剪
def clip_reward(reward, min_value=-10, max_value=10):
return np.clip(reward, min_value, max_value)
# Atari游戏常用:符号裁剪
def sign_clip_reward(reward):
return np.sign(reward)

3.3 观测归一化#

VecNormalize包装器(Stable Baselines3):

from stable_baselines3.common.vec_env import DummyVecEnv, VecNormalize
# 创建向量化环境
env = DummyVecEnv([lambda: gym.make("LunarLander-v2")])
# 归一化观测和奖励
env = VecNormalize(
env,
norm_obs=True, # 归一化观测
norm_reward=True, # 归一化奖励
clip_obs=10.0, # 裁剪观测
clip_reward=10.0, # 裁剪奖励
gamma=0.99
)
# 训练
model = PPO("MlpPolicy", env)
model.learn(total_timesteps=100000)
# 保存归一化统计信息
env.save("vec_normalize.pkl")
# 测试时加载
env = VecNormalize.load("vec_normalize.pkl", env)
env.training = False # 不更新统计信息
env.norm_reward = False # 测试时不归一化奖励

3.4 网络初始化#

正交初始化(Orthogonal Initialization):

import torch.nn as nn
def orthogonal_init(module, gain=1.0):
"""正交初始化"""
if isinstance(module, (nn.Linear, nn.Conv2d)):
nn.init.orthogonal_(module.weight, gain=gain)
if module.bias is not None:
module.bias.data.fill_(0.0)
# 应用到网络
class ActorCritic(nn.Module):
def __init__(self):
super().__init__()
self.actor = nn.Linear(state_dim, action_dim)
self.critic = nn.Linear(state_dim, 1)
# 使用正交初始化
self.apply(lambda m: orthogonal_init(m, gain=np.sqrt(2)))
# 最后一层使用小增益
orthogonal_init(self.actor, gain=0.01)

推荐配置:

  • 隐藏层:gain=2\sqrt{2}(对应ReLU激活)
  • 策略输出层:gain=0.01(小初始值,避免策略更新过大)
  • 值函数输出层:gain=1.0

3.5 学习率预热(Learning Rate Warmup)#

class WarmupScheduler:
def __init__(self, optimizer, warmup_steps, initial_lr, target_lr):
self.optimizer = optimizer
self.warmup_steps = warmup_steps
self.initial_lr = initial_lr
self.target_lr = target_lr
self.current_step = 0
def step(self):
self.current_step += 1
if self.current_step <= self.warmup_steps:
lr = self.initial_lr + (self.target_lr - self.initial_lr) * \
(self.current_step / self.warmup_steps)
for param_group in self.optimizer.param_groups:
param_group['lr'] = lr
# 使用示例
optimizer = torch.optim.Adam(model.parameters(), lr=1e-6)
scheduler = WarmupScheduler(optimizer, warmup_steps=10000,
initial_lr=1e-6, target_lr=3e-4)
for step in range(total_steps):
# 训练步骤
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 更新学习率
scheduler.step()

4. 提升样本效率的技巧#

4.1 广义优势估计(GAE)#

问题:TD误差方差大,蒙特卡洛偏差大

解决方案:GAE结合两者优点

A^tGAE(γ,λ)=∑l=0∞(γλ)lδt+l\hat{A}_t^{\text{GAE}(\gamma,\lambda)} = \sum_{l=0}^{\infty} (\gamma\lambda)^l \delta_{t+l}

其中TD误差:δt=rt+γV(st+1)−V(st)\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)

def compute_gae(rewards, values, dones, gamma=0.99, gae_lambda=0.95):
"""
计算广义优势估计
参数:
rewards: 奖励序列 [T]
values: 值函数估计 [T+1]
dones: 终止标志 [T]
gamma: 折扣因子
gae_lambda: GAE参数
返回:
advantages: 优势估计 [T]
returns: 回报估计 [T]
"""
advantages = []
gae = 0
for t in reversed(range(len(rewards))):
if t == len(rewards) - 1:
next_value = values[-1]
else:
next_value = values[t + 1]
# TD误差
delta = rewards[t] + gamma * next_value * (1 - dones[t]) - values[t]
# GAE
gae = delta + gamma * gae_lambda * (1 - dones[t]) * gae
advantages.insert(0, gae)
advantages = np.array(advantages)
returns = advantages + values[:-1]
return advantages, returns
# 归一化优势(提高稳定性)
advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)

λ的选择:

  • λ=0:纯TD(低方差,高偏差)
  • λ=1:纯蒙特卡洛(高方差,无偏差)
  • λ=0.95:推荐折中值

4.2 N-step Returns#

思想:使用多步回报而非单步TD

Gt(n)=Rt+1+γRt+2+⋯+γn−1Rt+n+γnV(St+n)G_t^{(n)} = R_{t+1} + \gamma R_{t+2} + \cdots + \gamma^{n-1} R_{t+n} + \gamma^n V(S_{t+n})
class NStepReplayBuffer:
def __init__(self, capacity, n_step=3, gamma=0.99):
self.capacity = capacity
self.n_step = n_step
self.gamma = gamma
self.buffer = deque(maxlen=capacity)
self.n_step_buffer = deque(maxlen=n_step)
def add(self, state, action, reward, next_state, done):
# 添加到n步缓冲区
self.n_step_buffer.append((state, action, reward, next_state, done))
if len(self.n_step_buffer) < self.n_step:
return
# 计算n步回报
n_step_reward = 0
for i, (_, _, r, _, _) in enumerate(self.n_step_buffer):
n_step_reward += (self.gamma ** i) * r
# 获取n步后的状态
state_0 = self.n_step_buffer[0][0]
action_0 = self.n_step_buffer[0][1]
next_state_n = self.n_step_buffer[-1][3]
done_n = self.n_step_buffer[-1][4]
# 存储n步转移
self.buffer.append((state_0, action_0, n_step_reward,
next_state_n, done_n))
def sample(self, batch_size):
indices = np.random.choice(len(self.buffer), batch_size)
return [self.buffer[i] for i in indices]

推荐值:

  • 简单任务:n=1(单步)
  • 中等任务:n=3-5
  • 复杂任务:n=5-10(但注意方差增加)

4.3 并行环境(Vectorized Environments)#

加速采样:

from stable_baselines3.common.vec_env import SubprocVecEnv, DummyVecEnv
def make_env(env_id, rank, seed=0):
def _init():
env = gym.make(env_id)
env.reset(seed=seed + rank)
return env
return _init
if __name__ == '__main__':
num_envs = 8 # 并行环境数量
env_id = "CartPole-v1"
# 使用多进程(推荐)
env = SubprocVecEnv([make_env(env_id, i) for i in range(num_envs)])
# 或使用单进程(调试时)
# env = DummyVecEnv([make_env(env_id, i) for i in range(num_envs)])
# 训练
model = PPO("MlpPolicy", env, n_steps=128, batch_size=256)
model.learn(total_timesteps=100000)

优势:

  • 加速数据采集(线性加速)
  • 增加数据多样性
  • 更好地利用多核CPU

4.4 课程学习(Curriculum Learning)#

思想:从简单任务逐渐过渡到复杂任务

class CurriculumEnv:
def __init__(self, base_env, difficulty_schedule):
self.env = base_env
self.difficulty_schedule = difficulty_schedule
self.timestep = 0
def reset(self):
self.timestep += 1
# 根据进度调整难度
difficulty = self.difficulty_schedule(self.timestep)
self.env.set_difficulty(difficulty)
return self.env.reset()
def step(self, action):
return self.env.step(action)
# 难度调度函数
def linear_difficulty_schedule(timestep, max_timestep=1e6):
"""线性增加难度"""
return min(1.0, timestep / max_timestep)
def threshold_difficulty_schedule(timestep, thresholds):
"""基于成功率的阶梯式难度"""
for threshold, difficulty in thresholds:
if success_rate > threshold:
return difficulty
return thresholds[0][1]

5. 调参流程与最佳实践#

5.1 系统化调参流程#

Step 1: 建立基线

# 1. 使用默认超参数运行
from stable_baselines3 import PPO
model = PPO("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=100000)
# 2. 记录性能指标
baseline_reward = evaluate_policy(model, env, n_eval_episodes=100)
print(f"基线平均奖励: {baseline_reward}")

Step 2: 超参数重要性排序

优先级(从高到低):

  1. 学习率:影响最大,首先调整
  2. 网络架构:隐藏层大小、层数
  3. 批量大小:影响稳定性和效率
  4. 折扣因子γ:取决于任务特性
  5. 探索策略:ε或熵系数
  6. 目标网络更新频率
  7. 其他算法特定参数

Step 3: 网格搜索或随机搜索

from stable_baselines3.common.evaluation import evaluate_policy
import optuna
def objective(trial):
"""Optuna优化目标函数"""
# 定义超参数搜索空间
learning_rate = trial.suggest_loguniform('learning_rate', 1e-5, 1e-3)
gamma = trial.suggest_categorical('gamma', [0.9, 0.95, 0.99, 0.995])
gae_lambda = trial.suggest_categorical('gae_lambda', [0.8, 0.9, 0.95, 0.99])
clip_range = trial.suggest_uniform('clip_range', 0.1, 0.4)
ent_coef = trial.suggest_loguniform('ent_coef', 1e-4, 0.1)
# 创建模型
model = PPO(
"MlpPolicy",
env,
learning_rate=learning_rate,
gamma=gamma,
gae_lambda=gae_lambda,
clip_range=clip_range,
ent_coef=ent_coef,
verbose=0
)
# 训练
model.learn(total_timesteps=50000)
# 评估
mean_reward, _ = evaluate_policy(model, env, n_eval_episodes=20)
return mean_reward
# 运行优化
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100, timeout=3600)
print("最佳超参数:")
print(study.best_params)

Step 4: 精细调整

# 在最佳超参数附近进行精细搜索
best_lr = study.best_params['learning_rate']
fine_tune_lrs = [
best_lr * 0.5,
best_lr * 0.75,
best_lr,
best_lr * 1.25,
best_lr * 1.5
]
results = []
for lr in fine_tune_lrs:
model = PPO("MlpPolicy", env, learning_rate=lr, **other_best_params)
model.learn(total_timesteps=100000)
reward = evaluate_policy(model, env)
results.append((lr, reward))
best_lr_fine = max(results, key=lambda x: x[1])[0]

5.2 监控与可视化#

TensorBoard集成:

from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter(log_dir='runs/experiment1')
for episode in range(num_episodes):
# 训练过程
episode_reward = 0
state = env.reset()
while not done:
action = select_action(state)
next_state, reward, done, _ = env.step(action)
episode_reward += reward
# 记录损失
loss = update_model()
writer.add_scalar('Loss/policy_loss', loss, global_step)
state = next_state
global_step += 1
# 记录每回合指标
writer.add_scalar('Reward/episode', episode_reward, episode)
writer.add_scalar('Length/episode', episode_length, episode)
# 记录超参数
writer.add_scalar('Hyperparameters/learning_rate', current_lr, episode)
writer.add_scalar('Hyperparameters/epsilon', current_epsilon, episode)
writer.close()

Weights & Biases集成:

import wandb
# 初始化
wandb.init(
project="rl-tuning",
config={
"learning_rate": 3e-4,
"gamma": 0.99,
"architecture": "MLP",
"environment": "CartPole-v1"
}
)
# 训练过程中记录
for step in range(total_steps):
# 训练
loss = train_step()
reward = evaluate()
# 记录指标
wandb.log({
"loss": loss,
"reward": reward,
"epsilon": epsilon,
"learning_rate": current_lr
}, step=step)
# 保存最佳模型
wandb.save('best_model.pth')

5.3 常见问题诊断#

症状可能原因解决方案
训练不收敛学习率过大降低学习率
收敛很慢学习率过小提高学习率
性能剧烈波动批量大小太小增大批量
奖励曲线震荡探索率过高加快ε衰减
值函数发散目标网络更新太频繁降低更新频率
过拟合最近经验回放缓冲区太小增大缓冲区
陷入局部最优探索不足增加熵正则化
梯度爆炸网络初始化不当使用正交初始化

6. 实战案例#

6.1 CartPole调参示例#

import gym
from stable_baselines3 import PPO
from stable_baselines3.common.evaluation import evaluate_policy
# 创建环境
env = gym.make("CartPole-v1")
# 基线配置(默认参数)
baseline_model = PPO("MlpPolicy", env, verbose=0)
baseline_model.learn(total_timesteps=50000)
baseline_reward, _ = evaluate_policy(baseline_model, env, n_eval_episodes=100)
print(f"基线奖励: {baseline_reward:.2f}")
# 调优配置
tuned_model = PPO(
"MlpPolicy",
env,
learning_rate=5e-4, # 提高学习率加速收敛
n_steps=1024, # 增加采样步数
batch_size=64,
n_epochs=10,
gamma=0.99,
gae_lambda=0.98,
clip_range=0.2,
ent_coef=0.01, # 增加探索
vf_coef=0.5,
max_grad_norm=0.5,
verbose=0
)
tuned_model.learn(total_timesteps=50000)
tuned_reward, _ = evaluate_policy(tuned_model, env, n_eval_episodes=100)
print(f"调优后奖励: {tuned_reward:.2f}")
print(f"提升: {(tuned_reward - baseline_reward) / baseline_reward * 100:.1f}%")

6.2 MuJoCo调参示例#

import gym
from stable_baselines3 import SAC
from stable_baselines3.common.noise import NormalActionNoise
# 创建连续控制环境
env = gym.make("HalfCheetah-v3")
# 添加动作噪声
n_actions = env.action_space.shape[-1]
action_noise = NormalActionNoise(
mean=np.zeros(n_actions),
sigma=0.1 * np.ones(n_actions)
)
# SAC配置
model = SAC(
"MlpPolicy",
env,
learning_rate=3e-4,
buffer_size=1000000,
learning_starts=10000, # 预填充回放缓冲区
batch_size=256,
tau=0.005, # 软更新系数
gamma=0.99,
train_freq=1,
gradient_steps=1,
action_noise=action_noise,
ent_coef='auto', # 自动调整熵系数
target_entropy='auto',
verbose=1
)
# 训练
model.learn(total_timesteps=1000000)
# 评估
mean_reward, _ = evaluate_policy(model, env, n_eval_episodes=50)
print(f"平均奖励: {mean_reward:.2f}")

7. 总结与建议#

7.1 核心要点#

  1. 从默认值开始:使用经过验证的默认超参数
  2. 优先调整关键参数:学习率 > 网络架构 > 批量大小
  3. 系统化搜索:使用自动化工具(Optuna, Ray Tune)
  4. 监控训练过程:使用TensorBoard或W&B
  5. 稳定性优先:先让训练稳定,再追求性能

7.2 黄金法则#

  • ✅ 多次运行取平均:RL结果随机性大,至少3次
  • ✅ 保存最佳模型:定期评估并保存检查点
  • ✅ 记录所有实验:超参数、代码版本、结果
  • ✅ 渐进式调参:每次只改变一个参数
  • ✅ 复现性:固定随机种子,记录环境版本

7.3 常用默认配置速查表#

算法学习率批量大小γ其他关键参数
DQN1e-4320.99target_update=10000, buffer=1M
PPO3e-4640.99clip=0.2, epochs=10, GAE_λ=0.95
SAC3e-42560.99τ=0.005, auto_alpha=True
TD33e-41000.99τ=0.005, policy_delay=2
A2C7e-41280.99ent_coef=0.01, vf_coef=0.5

参考资源#

  1. 实用工具:

  2. 论文:

    • Henderson et al. (2018) “Deep Reinforcement Learning that Matters”
    • Engstrom et al. (2020) “Implementation Matters in Deep RL”
  3. 博客:


通过系统化的调参方法和工程技巧,你可以显著提升强化学习算法的性能和稳定性。记住,调参是一个迭代过程,需要耐心和经验积累。希望本文能帮助你在RL项目中少走弯路!

文章概念网络

从章节进入正文概念。实线表示所属层级,虚线连接跨章节重复出现的概念,不代表因果关系。

50 / 188 个节点 · 正文标记术语与关键词概念

强化学习调参技巧与实战经验同一概念 / Shared concept同一概念 / Shared concept强化学习调参技巧与实战经验强化学习调参技巧与实战经验引言引言非平稳性非平稳性稀疏奖励稀疏奖励高方差高方差超参数敏感超参数敏感1. 核心超参数详解1. 核心超参数详解1.1 学习率(Learning Rate)1.1 学习率(Learning Rate)1.2 折扣因子(Discount Factor, γ)1.2 折扣因子(Discount Factor, γ)1.3 批量大小(Batch Size)1.3 批量大小(Batch Size)1.4 探索率(Exploration Rate, ε)1.4 探索率(Exploration Rate, ε)1.5 目标网络更新频率(Target Network Update)1.5 目标网络更新频率(Target Network Update)1.6 经验回放缓冲区大小(Replay Buffer Size)1.6 经验回放缓冲区大小(Replay Buffer Size)2. 算法特定的超参数2. 算法特定的超参数2.1 PPO(Proximal Policy Optimization)2.1 PPO(Proximal Policy Optimization)2.2 SAC(Soft Actor-Critic)2.2 SAC(Soft Actor-Critic)2.3 DQN及其变体2.3 DQN及其变体3. 提升训练稳定性的技巧3. 提升训练稳定性的技巧3.1 梯度裁剪(Gradient Clipping)3.1 梯度裁剪(Gradient Clipping)3.2 奖励归一化与裁剪3.2 奖励归一化与裁剪3.3 观测归一化3.3 观测归一化3.4 网络初始化3.4 网络初始化3.5 学习率预热(Learning Rate Warmup)3.5 学习率预热(Learning Rate Warmup)4. 提升样本效率的技巧4. 提升样本效率的技巧4.1 广义优势估计(GAE)4.1 广义优势估计(GAE)4.2 N-step Returns4.2 N-step Returns4.3 并行环境(Vectorized Environments)4.3 并行环境(Vectorized Environments)4.4 课程学习(Curriculum Learning)4.4 课程学习(Curriculum Learning)5. 调参流程与最佳实践5. 调参流程与最佳实践5.1 系统化调参流程5.1 系统化调参流程5.2 监控与可视化5.2 监控与可视化5.3 常见问题诊断5.3 常见问题诊断6. 实战案例6. 实战案例6.1 CartPole调参示例6.1 CartPole调参示例6.2 MuJoCo调参示例6.2 MuJoCo调参示例7. 总结与建议7. 总结与建议7.1 核心要点7.1 核心要点7.2 黄金法则7.2 黄金法则7.3 常用默认配置速查表7.3 常用默认配置速查表参考资源参考资源实用工具实用工具博客博客强化学习强化学习策略策略reinforcement learningreinforcement learning监督学习监督学习奖励奖励反馈反馈reinforcement learningreinforcement learning强化学习强化学习
强化学习调参技巧与实战经验

强化学习调参技巧与实战经验

回到原文 ↗
BIBTEX

引用本文

@article{2024-08-05-强化学习调参技巧,
  author  = {枫语},
  title   = {强化学习调参技巧与实战经验},
  journal = {枫语},
  year    = {2024},
  month   = {aug},
  url     = {https://meiluosi.github.io/posts/2024-08-05-%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E8%B0%83%E5%8F%82%E6%8A%80%E5%B7%A7/}
}
🌐

本篇知识星丛与滑动侧栈

强化学习调参技巧与实战经验 当前主轴

搜索

正在载入索引…