在AI技术快速发展的浪潮中,强化学习作为实现智能决策的核心方法,正从游戏、机器人控制走向更广阔的工业与科研领域。然而,其背后复杂的数学原理和算法变体,常常让初学者望而却步,感觉无从下手。本文旨在为你提供一份结构清晰、代码完备的强化学习实战入门指南。我们将从最基础的概念讲起,逐步深入到PPO、A3C、Q-learning、DQN等核心算法的原理与实现,确保你不仅能理解其思想,更能亲手搭建并运行代码。无论你是AI领域的学生,还是希望将强化学习应用于具身智能或业务优化的开发者,这份教程都将为你提供一个坚实的起点。
1. 强化学习核心概念:从零理解智能体如何学习
在开始敲代码之前,我们必须建立正确的认知框架。强化学习不同于有明确标签的监督学习,也不同于寻找数据内在结构的无监督学习,它解决的是一种“试错学习”问题。
1.1 什么是强化学习?
想象一下教小狗学习握手:你发出“握手”指令(状态),小狗抬起爪子(动作),你立刻给予零食奖励(奖励)。经过多次重复,小狗就学会了在听到指令时做出抬爪动作以获取奖励。强化学习就是模拟这个过程,让一个智能体(Agent)通过与环境(Environment)的持续交互来学习最优策略(Policy)。
其核心要素可以概括为以下几个部分:
- 智能体 (Agent):做出决策的学习者。
- 环境 (Environment):智能体交互的外部世界,它接收动作,返回新的状态和奖励。
- 状态 (State, s):对环境当前情况的描述。
- 动作 (Action, a):智能体可以做出的选择。
- 奖励 (Reward, r):环境对智能体动作的即时反馈信号。
- 策略 (Policy, π):智能体的行为函数,定义了在给定状态下选择每个动作的概率。
- 价值函数 (Value Function, V(s) 或 Q(s,a)):评估状态或“状态-动作对”的长期价值,是智能体学习的核心目标。
1.2 核心目标:最大化累积奖励
智能体的终极目标不是追求单步的高奖励,而是最大化从当前时刻开始,未来所能获得的所有奖励的总和,即累积奖励(Return)。由于未来的不确定性,我们通常会引入一个折扣因子 γ (Gamma, 0≤γ≤1),来权衡即时奖励和未来奖励的重要性。
折扣累积奖励公式:G_t = R_{t+1} + γ * R_{t+2} + γ² * R_{t+3} + ...
其中,γ 越接近 0,智能体越“短视”,只关心眼前利益;γ 越接近 1,智能体越“有远见”。这个公式是理解后续所有算法价值计算的基础。
1.3 探索与利用的权衡
这是强化学习中最经典的困境。
- 探索 (Exploration):尝试新的、未曾选择过的动作,以收集更多环境信息,可能发现更高回报的策略。
- 利用 (Exploitation):根据当前已知的最佳策略选择动作,以获取稳定的奖励。
一个只利用不探索的智能体可能会陷入局部最优(比如一直选择第一个找到的零食点,而错过了不远处更大的蛋糕)。一个只探索不利用的智能体则永远无法稳定获得高收益。如何平衡二者,是算法设计的关键。
2. 环境准备与工具说明
工欲善其事,必先利其器。为了高效学习和实验,我们选择 Python 作为编程语言,并依赖几个强大的开源库。
2.1 基础环境配置
建议使用 Python 3.8 或 3.9 版本,这是目前主流深度学习框架兼容性最好的版本。使用 Anaconda 或 Miniconda 来管理环境是一个好习惯,可以避免包冲突。
# 创建一个新的conda环境(可选) conda create -n rl_tutorial python=3.8 conda activate rl_tutorial # 安装核心依赖 pip install numpy matplotlib pandas2.2 强化学习与深度学习库
我们将使用gym库提供标准化的测试环境,使用torch作为深度学习框架来实现神经网络。
# 安装OpenAI Gym,提供经典强化学习环境(如CartPole, MountainCar) pip install gym # 安装PyTorch,请根据你的CUDA版本前往官网获取对应安装命令 # 例如,对于无GPU或CUDA 11.3的环境: pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu # 有GPU的用户请查阅PyTorch官网选择对应命令2.3 验证安装
创建一个简单的Python脚本验证环境是否就绪。
# verify_env.py import gym import torch import numpy as np print(f"PyTorch version: {torch.__version__}") print(f"Gym version: {gym.__version__}") # 创建一个经典环境:CartPole(小车立杆) env = gym.make('CartPole-v1', render_mode='human') # 使用‘human’模式可以看到图形界面 initial_state = env.reset() print(f"Initial state: {initial_state}") print(f"Action space: {env.action_space}") # 离散动作空间:0或1 print(f"Observation space: {env.observation_space}") # 状态空间:4个浮点数 env.close() print("环境验证成功!")运行此脚本,如果能看到一个图形窗口(或至少不报错),并且打印出版本和空间信息,说明基础环境配置成功。
3. 算法基石:从表格法到价值函数
理解强化学习,最好从最简单的场景开始:状态和动作数量有限,且环境模型已知或可通过采样估计。我们介绍两种奠基性算法。
3.1 Q-learning:离线学习的经典
Q-learning 是一种无模型(Model-Free)、离线策略(Off-Policy)算法。它直接学习一个名为 Q-Table 的表格,其中Q(s, a)表示在状态s下采取动作a所能获得的预期累积奖励。
核心更新公式(时间差分更新):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) ]
α:学习率,控制新信息覆盖旧信息的程度。γ:折扣因子。r_{t+1} + γ * max_{a} Q(s_{t+1}, a):被称为目标值,是基于下一步最优动作的估计。Q(s_t, a_t):当前估计值。- 两者的差就是时序差分误差,驱动Q值更新。
Q-learning 特点:在更新Q(s_t, a_t)时,它使用的是下一步状态s_{t+1}下所有动作中最大的Q值(即max_{a} Q(s_{t+1}, a)),而实际上智能体在s_t时执行的动作a_t可能并非由当前最优策略产生(比如是随机探索得到的)。这种“学习的目标策略(贪婪策略)与行为策略(如ε-贪婪策略)不同”的特性,就是“离线策略”。
下面是一个在简单网格世界(Grid World)中实现 Q-learning 的示例。
# q_learning_gridworld.py import numpy as np import random # 定义一个简单的4x4网格世界 # 状态0是起点,状态15是终点(宝藏),状态5、7、11、12是陷阱(立即结束,负奖励) WORLD_SIZE = 4 START = 0 GOAL = 15 TRAPS = [5, 7, 11, 12] ACTIONS = ['上', '右', '下', '左'] # 对应动作索引 0, 1, 2, 3 def step(state, action): """执行动作,返回下一个状态和奖励""" i, j = divmod(state, WORLD_SIZE) # 将状态编号转换为网格坐标 (行,列) if action == 0: # 上 i = max(i - 1, 0) elif action == 1: # 右 j = min(j + 1, WORLD_SIZE - 1) elif action == 2: # 下 i = min(i + 1, WORLD_SIZE - 1) elif action == 3: # 左 j = max(j - 1, 0) next_state = i * WORLD_SIZE + j # 定义奖励 if next_state == GOAL: reward = 10.0 done = True elif next_state in TRAPS: reward = -10.0 done = True else: reward = -0.1 # 每走一步有小惩罚,鼓励智能体尽快找到目标 done = False return next_state, reward, done def choose_action(state, q_table, epsilon): """ε-贪婪策略选择动作""" if random.uniform(0, 1) < epsilon: return random.randint(0, 3) # 探索:随机选择 else: return np.argmax(q_table[state]) # 利用:选择Q值最大的动作 # 初始化Q表 q_table = np.zeros((WORLD_SIZE * WORLD_SIZE, len(ACTIONS))) # 超参数 alpha = 0.1 # 学习率 gamma = 0.9 # 折扣因子 epsilon = 0.1 # 探索概率 episodes = 500 # 训练回合数 # 训练过程 for episode in range(episodes): state = START done = False total_reward = 0 while not done: action = choose_action(state, q_table, epsilon) next_state, reward, done = step(state, action) total_reward += reward # Q-learning 更新公式 old_value = q_table[state, action] next_max = np.max(q_table[next_state]) # 离线策略关键:使用max target_value = reward + gamma * next_max * (not done) # 终止状态无未来奖励 q_table[state, action] = old_value + alpha * (target_value - old_value) state = next_state if (episode + 1) % 50 == 0: print(f"Episode {episode+1}, Total Reward: {total_reward:.2f}") # 测试学到的策略 print("\n训练后的Q表(部分):") print(q_table) print("\n最优策略(每个状态下的最佳动作):") for s in range(WORLD_SIZE * WORLD_SIZE): best_a = np.argmax(q_table[s]) print(f"状态{s:2d}: {ACTIONS[best_a]}", end=' | ') if (s + 1) % WORLD_SIZE == 0: print()运行此代码,你将看到智能体通过约500回合的学习,成功找到了避开陷阱、抵达终点的策略,并输出了最终的Q表和每个状态下的最优动作。
3.2 SARSA:在线策略的对比
与 Q-learning 同为时序差分算法,SARSA 是一种在线策略(On-Policy)算法。其更新公式为:Q(s_t, a_t) = Q(s_t, a_t) + α * [ r_{t+1} + γ * Q(s_{t+1}, a_{t+1}) - Q(s_t, a_t) ]
关键区别在于目标值部分:SARSA 使用的是在下一步状态s_{t+1}下实际将要执行的动作a_{t+1}的 Q 值。这个a_{t+1}是根据当前策略(如ε-贪婪策略)选出来的,因此 SARSA 是在优化它正在执行的策略本身。
如何选择?
- Q-learning:更激进,直接学习最优策略,即使当前行为不是最优的。通常收敛更快,但可能在随机环境中不够稳定。
- SARSA:更保守,学习的是包含探索的策略(如ε-贪婪策略),因此学到的策略会考虑到探索带来的风险(比如靠近悬崖边时会更加小心)。在需要安全性的场景中可能更合适。
4. 当世界变得复杂:从表格到函数逼近与DQN
现实问题中,状态空间往往是连续或高维的(如图像、传感器数据),不可能用表格存储所有Q值。解决方案是使用一个函数来近似Q 函数,即Q(s, a; θ) ≈ Q*(s, a),其中θ是函数参数(如神经网络的权重)。这就是深度Q网络(DQN)的核心思想。
4.1 DQN的核心创新与挑战
DQN 并非简单地将神经网络作为Q函数的拟合器,它引入了两个关键技巧来解决训练不稳定的问题:
- 经验回放 (Experience Replay):智能体将每一步交互的经验
(s_t, a_t, r_t, s_{t+1}, done)存储到一个固定大小的回放缓冲区中。训练时,随机从缓冲区中采样一小批(mini-batch)经验,打破数据间的时序相关性,使数据分布更平稳,提高样本效率。 - 目标网络 (Target Network):使用一个独立的、参数更新较慢的网络(目标网络)来计算 Q-learning 更新公式中的
max_a Q(s_{t+1}, a; θ-)。而用于选择动作的主网络参数θ则定期(或软更新)同步到目标网络θ-。这避免了“追逐移动目标”的问题,大大提高了训练的稳定性。
4.2 DQN实战:玩转CartPole
让我们用 PyTorch 实现一个标准的 DQN 来解决 Gym 中的 CartPole(小车立杆)问题。
# dqn_cartpole.py import gym import random import numpy as np import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F from collections import deque import matplotlib.pyplot as plt class DQN(nn.Module): """定义Q网络结构""" def __init__(self, state_size, action_size): super(DQN, self).__init__() self.fc1 = nn.Linear(state_size, 64) self.fc2 = nn.Linear(64, 64) self.fc3 = nn.Linear(64, action_size) def forward(self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) return self.fc3(x) class ReplayBuffer: """经验回放缓冲区""" def __init__(self, capacity): self.buffer = deque(maxlen=capacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): batch = random.sample(self.buffer, batch_size) state, action, reward, next_state, done = zip(*batch) return (np.array(state), np.array(action), np.array(reward, dtype=np.float32), np.array(next_state), np.array(done, dtype=np.uint8)) def __len__(self): return len(self.buffer) class DQNAgent: def __init__(self, state_size, action_size): self.state_size = state_size self.action_size = action_size self.memory = ReplayBuffer(capacity=10000) self.gamma = 0.99 # 折扣因子 self.epsilon = 1.0 # 初始探索率 self.epsilon_min = 0.01 self.epsilon_decay = 0.995 self.learning_rate = 0.001 self.batch_size = 64 self.update_target_every = 10 # 每10步更新一次目标网络 self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"Using device: {self.device}") # 主网络和目标网络 self.policy_net = DQN(state_size, action_size).to(self.device) self.target_net = DQN(state_size, action_size).to(self.device) self.target_net.load_state_dict(self.policy_net.state_dict()) # 初始参数相同 self.target_net.eval() # 目标网络设置为评估模式 self.optimizer = optim.Adam(self.policy_net.parameters(), lr=self.learning_rate) self.loss_fn = nn.MSELoss() self.steps_done = 0 def select_action(self, state): """根据ε-贪婪策略选择动作""" self.steps_done += 1 if random.random() < self.epsilon: return random.randrange(self.action_size) # 探索 else: with torch.no_grad(): state_t = torch.FloatTensor(state).unsqueeze(0).to(self.device) q_values = self.policy_net(state_t) return q_values.argmax().item() # 利用 def train_step(self): """从回放缓冲区采样并训练网络""" if len(self.memory) < self.batch_size: return # 1. 采样 states, actions, rewards, next_states, dones = self.memory.sample(self.batch_size) states = torch.FloatTensor(states).to(self.device) actions = torch.LongTensor(actions).unsqueeze(1).to(self.device) # 形状[batch, 1] rewards = torch.FloatTensor(rewards).unsqueeze(1).to(self.device) next_states = torch.FloatTensor(next_states).to(self.device) dones = torch.FloatTensor(dones).unsqueeze(1).to(self.device) # 2. 计算当前Q值 (Q(s, a)) current_q_values = self.policy_net(states).gather(1, actions) # 取出对应动作的Q值 # 3. 计算目标Q值 (r + γ * max_a‘ Q_target(s', a’)) with torch.no_grad(): next_q_values = self.target_net(next_states).max(1)[0].unsqueeze(1) # 目标网络计算 target_q_values = rewards + (self.gamma * next_q_values * (1 - dones)) # 4. 计算损失并更新 loss = self.loss_fn(current_q_values, target_q_values) self.optimizer.zero_grad() loss.backward() # 梯度裁剪,防止梯度爆炸 torch.nn.utils.clip_grad_norm_(self.policy_net.parameters(), max_norm=1.0) self.optimizer.step() # 5. 衰减探索率 self.epsilon = max(self.epsilon_min, self.epsilon * self.epsilon_decay) # 6. 定期更新目标网络(硬更新) if self.steps_done % self.update_target_every == 0: self.target_net.load_state_dict(self.policy_net.state_dict()) return loss.item() def train_agent(env, agent, episodes=500, render_every=100): """训练循环""" scores = [] for episode in range(episodes): state, _ = env.reset() state = np.array(state, dtype=np.float32) total_reward = 0 done = False while not done: # 每隔一定回合渲染一次,方便观察 if episode % render_every == 0: env.render() action = agent.select_action(state) next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated next_state = np.array(next_state, dtype=np.float32) # 存储经验 agent.memory.push(state, action, reward, next_state, done) state = next_state total_reward += reward # 执行一步训练 agent.train_step() scores.append(total_reward) # 打印训练进度 if (episode + 1) % 20 == 0: avg_score = np.mean(scores[-20:]) print(f"Episode {episode+1:4d}, Score: {total_reward:6.1f}, Avg Score (last 20): {avg_score:6.1f}, Epsilon: {agent.epsilon:.3f}") env.close() return scores if __name__ == "__main__": env = gym.make('CartPole-v1') state_size = env.observation_space.shape[0] action_size = env.action_space.n agent = DQNAgent(state_size, action_size) scores = train_agent(env, agent, episodes=300) # 绘制得分曲线 plt.figure(figsize=(10,5)) plt.plot(scores, alpha=0.6, label='Episode Score') plt.plot([np.mean(scores[max(0,i-19):i+1]) for i in range(len(scores))], 'r-', label='Moving Avg (20)') plt.xlabel('Episode') plt.ylabel('Score') plt.title('DQN Training Progress on CartPole-v1') plt.legend() plt.grid(True) plt.show()运行这个脚本,你会看到智能体在 CartPole 环境中的得分随着训练逐渐上升并稳定在高分(接近或达到500,这是该环境的最高分),同时探索率epsilon逐渐降低。图形窗口会每隔一定回合展示一次智能体的表现。
5. 策略梯度与高级算法:PPO与A3C
DQN 属于价值基(Value-Based)方法,它先学习价值函数,再间接推导出策略。另一大类方法是策略基(Policy-Based)方法,它直接参数化策略π(a|s; θ),并通过优化参数θ来最大化期望回报。演员-评论家(Actor-Critic)架构结合了二者优点,其中“演员”(Actor)负责根据状态输出动作概率,“评论家”(Critic)负责评估状态的价值,指导演员的更新。
5.1 近端策略优化(PPO)
PPO 是当前最流行、最稳定的策略梯度算法之一。它通过一个“裁剪”的替代目标函数,解决了传统策略梯度方法中步长难以选择、训练不稳定的问题。
PPO的核心思想(PPO-Clip): 在每次更新时,我们不想让新策略π_θ离旧策略π_θ_old太远,以避免性能崩溃。PPO 通过限制策略更新的幅度来实现这一点。
其目标函数为:L^{CLIP}(θ) = E_t [ min( r_t(θ) * A_t, clip(r_t(θ), 1-ε, 1+ε) * A_t ) ]
r_t(θ) = π_θ(a_t|s_t) / π_θ_old(a_t|s_t):新旧策略的概率比。A_t:优势函数,表示动作a_t相对于平均水平的优势,通常由评论家网络估计。ε:一个超参数(如0.2),用于定义裁剪范围。min和clip操作确保了更新是保守的。
5.2 异步优势演员-评论家(A3C)
A3C 是一个并行化训练框架。它创建多个 worker(线程或进程),每个 worker 都有自己的一份环境副本和网络参数。这些 worker 异步地与各自的环境交互,收集经验,并独立计算梯度。然后,这些梯度被异步地推送到一个全局共享的网络参数中。这种方法不仅大大加快了数据收集速度,而且由于不同 worker 探索的不同轨迹,相当于为优化过程引入了噪声,有助于逃离局部最优。
A3C 的关键点:
- 异步:多个 worker 不同步,谁先算完梯度谁就先更新全局参数。
- 优势函数:使用
A(s, a) = Q(s, a) - V(s)来评估动作的好坏,减少了方差。 - 熵正则化:在策略的损失函数中加入熵项,鼓励探索,防止策略过早收敛到次优解。
5.3 PPO实战(简化版)
由于完整的PPO实现涉及价值网络、广义优势估计(GAE)等,代码较长。这里提供一个高度简化的PPO核心更新步骤的概念性代码,帮助你理解其流程。
# ppo_conceptual.py (核心更新步骤示意) import torch import torch.nn as nn import torch.optim as optim from torch.distributions import Categorical class ActorCritic(nn.Module): """共享部分特征的演员-评论家网络""" def __init__(self, state_dim, action_dim): super().__init__() self.shared = nn.Sequential( nn.Linear(state_dim, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), ) self.actor = nn.Linear(64, action_dim) # 输出动作概率 self.critic = nn.Linear(64, 1) # 输出状态价值 def forward(self, x): shared_out = self.shared(x) return self.actor(shared_out), self.critic(shared_out) def compute_ppo_loss(states, actions, old_log_probs, returns, advantages, model, clip_epsilon=0.2): """计算PPO-Clip损失""" logits, state_values = model(states) dist = Categorical(logits=logits) new_log_probs = dist.log_prob(actions) entropy = dist.entropy().mean() # 概率比 ratio = (new_log_probs - old_log_probs).exp() # 裁剪的替代目标 surr1 = ratio * advantages surr2 = torch.clamp(ratio, 1.0 - clip_epsilon, 1.0 + clip_epsilon) * advantages actor_loss = -torch.min(surr1, surr2).mean() # 评论家损失(价值函数拟合) critic_loss = (returns - state_values.squeeze()).pow(2).mean() # 总损失(包含熵正则项) total_loss = actor_loss + 0.5 * critic_loss - 0.01 * entropy return total_loss, actor_loss.item(), critic_loss.item(), entropy.item() # 训练循环中的伪代码步骤: # 1. 用旧策略模型收集一批轨迹数据 (states, actions, rewards, ...) # 2. 使用广义优势估计(GAE)计算优势(advantages)和回报(returns) # 3. 将数据转换为Tensor # 4. 对当前批次数据进行多次(如K=4)优化迭代: # a. 计算损失 compute_ppo_loss(...) # b. optimizer.zero_grad() # c. loss.backward() # d. optimizer.step() # 5. 用新模型参数覆盖旧模型参数,准备下一轮数据收集。这个示意代码省略了数据收集、GAE计算、优化循环等复杂部分,但清晰地展示了PPO损失函数的核心计算。在实际项目中,强烈建议使用 Stable-Baselines3 这样的成熟库来应用PPO。
6. 常见问题与调试指南
在实现和训练强化学习模型时,你几乎一定会遇到以下问题。
6.1 智能体完全不学习(得分不增长)
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 奖励始终为负或很低 | 奖励函数设计不合理 | 检查奖励是否提供了有意义的信号。尝试稀疏奖励改密集奖励,或调整奖励尺度。 |
| 损失不下降,Q值或价值估计异常 | 学习率过高或过低 | 尝试调整学习率(如1e-4, 1e-3, 1e-2)。使用Adam优化器通常更稳定。 |
| 网络结构太深或太浅 | 对于简单环境(如CartPole),1-3层全连接网络足够。复杂环境(如Atari)需要CNN。 | |
| 没有使用目标网络或经验回放(针对DQN) | 确保实现了这两个稳定训练的关键组件。检查目标网络的更新频率。 | |
| 梯度爆炸或消失 | 使用梯度裁剪(clip_grad_norm_)。检查激活函数,ReLU是常见选择。 | |
| 探索率ε始终很高或衰减太快 | ε衰减策略有问题 | 确保ε在训练过程中有足够的衰减。可以记录ε值观察其变化。初期需要充分探索。 |
6.2 训练不稳定(得分波动大)
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 得分曲线剧烈震荡 | 批次大小太小 | 增大经验回放的采样批次大小(batch_size),如从32增至64、128。 |
| 环境随机性大 | 这是正常的。可以多跑几次取平均学习曲线,或增加环境帧的堆叠以提供历史信息。 | |
| 策略更新步长太大(针对策略梯度) | 对于PPO,减小clip_epsilon(如从0.2到0.1)。对于普通策略梯度,减小学习率。 | |
| 智能体“遗忘”,学会后又变差 | 经验回放缓冲区过小或更新太快 | 增大缓冲区容量。对于在线策略算法(如A3C),这是异常现象,检查代码逻辑。 |
| 环境或任务发生了非平稳变化 | 检查环境设置是否在训练中意外改变。 |
6.3 代码实现相关错误
- 维度不匹配:这是PyTorch/TensorFlow中最常见的错误。仔细检查网络输入输出维度、损失函数输入维度、以及从环境获取的
state、action、reward、done的维度和数据类型。 done信号处理错误:在计算目标Q值时,必须判断是否为终止状态(done=True)。如果是终止状态,未来奖励部分应为0。代码中常用reward + gamma * next_value * (1 - done)来处理。- 在计算图中使用了
torch.no_grad():确保在需要计算梯度的部分(如policy_net的前向传播用于选择动作时)不要误用no_grad;而在不需要梯度的地方(如target_net计算目标值)要使用no_grad。
7. 工程最佳实践与进阶方向
掌握基础算法后,要将其应用于实际项目,还需要遵循一些工程实践。
7.1 训练流程标准化
- 日志与可视化:使用
TensorBoard或Weights & Biases记录关键指标(每回合得分、平均奖励、损失值、探索率、策略熵等)。可视化是调试和理解训练过程的生命线。 - 模型检查点:定期保存模型参数。这样可以在训练中断后恢复,也可以保留训练过程中不同阶段的模型用于评估。
- 超参数调优:使用网格搜索、随机搜索或更高级的贝叶斯优化工具(如Optuna)来系统化地调整学习率、折扣因子、网络大小等超参数。
- 环境封装:对原始环境进行预处理是常态,例如图像缩放到84x84、灰度化、帧堆叠(Atari)、奖励裁剪、观察标准化等。将这些步骤封装成类,使代码更清晰。
7.2 针对复杂环境的策略
- 图像输入:使用卷积神经网络(CNN)作为特征提取器,接全连接层输出价值或策略。
- 连续动作空间:对于像机器人控制这类输出连续值(如力矩)的任务,不能使用DQN。应使用输出动作分布参数(如高斯分布的均值和方差)的策略梯度方法,如PPO、DDPG、SAC。
- 多智能体:环境中有多个智能体相互协作或竞争,问题复杂度指数上升。可以参考MADDPG、QMIX等算法。
- 稀疏奖励:在大多数步骤奖励为0,只有达成目标时才有正奖励。这需要更高级的探索技术,如内在好奇心、基于模型的规划、分层强化学习等。
7.3 从仿真到现实(Sim2Real)
这是具身智能等领域的关键挑战。在仿真器中训练的策略,直接部署到物理机器人上往往失效。
- 领域随机化:在仿真训练时,随机化环境的物理参数(如摩擦系数、物体质量、颜色、光照)。这迫使策略学习更鲁棒的特征,从而更好地迁移到现实世界。
- 系统辨识:先让机器人在真实环境中执行一些动作,根据观测数据校准仿真器的参数,使其更接近现实,然后再训练。
- 在线自适应:在真实环境中进行少量、安全的在线微调。
强化学习是一个实践性极强的领域。本文为你搭建了从基础概念到经典算法(Q-learning, DQN)再到前沿方法(PPO, A3C)的完整知识框架,并提供了可运行的代码和实用的调试指南。真正的掌握始于动手实践:尝试修改超参数观察效果,将DQN应用到另一个Gym环境(如MountainCar-v0),或者使用Stable-Baselines3库快速验证PPO在复杂环境下的性能。当你亲手解决掉第一个CartPole问题,看到智能体从茫然无措到游刃有余时,你对强化学习的理解将不再停留在纸面。