尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

强化学习实战:从零实现DQN迷宫AI,掌握DeepMind核心技术

强化学习实战:从零实现DQN迷宫AI,掌握DeepMind核心技术 最近关于哈萨比斯和谷歌 AI 团队的消息又成了圈内热议的话题。大家关注的焦点往往都在人事变动和战略调整上但有一个事实是确定的无论组织架构怎么变DeepMind 留下的技术遗产——尤其是强化学习这条技术路线已经从学术论文走向了真实的工程世界。与其围观新闻不如把注意力放在更值得投入的技术本身。本文就以 DeepMind 最具代表性的强化学习技术为主线从核心原理讲到完整代码实现带大家从零跑通一个小型强化学习项目。无论你是刚入门 AI 的开发者还是想回顾强化学习基础的后端工程师这篇文章都能给你一条清晰、可复现的学习路径。1. 背景与核心概念DeepMind 与强化学习1.1 DeepMind 到底给 AI 领域带来了什么提到 DeepMind很多人第一时间想到的是 AlphaGo 在 2016 年击败李世石的那场人机大战。当时很多人只是把它当作一条新闻来看但从技术角度看AlphaGo 背后的深度强化学习思路直接改变了整个 AI 领域的研究方向。在这之前深度学习虽然已经在图像识别、语音识别方向取得了突破但大多是依靠大量标注数据的监督学习。而 AlphaGo 展示了一个新的可能性让 AI 通过不断与环境交互、自我对弈来提升能力这个过程不需要太多人工标注却能在复杂任务上达到超越人类的水平。随后 DeepMind 又推出了 AlphaFold解决了蛋白质结构预测这个困扰生物学界几十年的问题。AlphaFold 的核心技术虽然不完全等同于强化学习但它代表了 DeepMind 一贯的技术风格用深度学习去攻克复杂的科学难题并且把研究成果落地成可用的工具。在 AlphaFold 之后蛋白质结构预测、药物发现、材料科学等领域都开始大量引入深度学习技术这可以说是 AI for Science 的一个标志性里程碑。对于普通开发者来说这些成果听起来很遥远但 DeepMind 在强化学习算法上的积累比如 DQNDeep Q-Network、PPOProximal Policy Optimization等已经成为了当下 AI 应用开发中最常用的算法底座。游戏 AI、机器人控制、智能推荐、工业控制、自动驾驶等领域都能看到这些技术的影子。掌握了强化学习的基础原理和工程实现你就拿到了一把打开很多复杂决策问题的钥匙。1.2 强化学习到底是什么强化学习Reinforcement LearningRL和传统机器学习的最大区别在于学习方式。传统监督学习需要给定“输入-输出”对让模型学会映射关系而强化学习是一个不断“试错”的过程。我们可以把它想象成训练一只宠物宠物做出了正确的行为你给它零食奖励做错了就不给奖励或者给予小小的惩罚。经过反复尝试宠物就学会了“什么行为会带来奖励”。在这个过程里有四个核心概念智能体Agent做决策的主体比如迷宫里的机器人。环境Environment智能体所处的世界比如迷宫本身。状态State智能体在某个时刻观察到的环境信息比如当前所在的格子坐标。奖励Reward环境对智能体行为的反馈信号用来告诉智能体“这一步走得好不好”。整个学习过程可以看作一个循环智能体观察当前状态做出一个动作环境给出新的状态和奖励智能体根据奖励调整自己的策略然后在下一个状态继续做出新的决策。和强化学习容易混淆的一个概念是“深度强化学习”。强化学习本身是一个研究框架它可以配合简单的表格方法使用也可以配合深度神经网络使用。当状态空间非常大、无法用表格穷举时我们就用深度神经网络来拟合“状态-动作”的价值关系。DQN 就是把深度神经网络和 Q-Learning 结合在一起的经典算法也是本文实战部分要重点实现的内容。1.3 强化学习的典型应用场景强化学习并不是实验室里才有用的技术它在真实业务中已经有了很多落地案例游戏 AIAlphaGo、OpenAI Five、AlphaStar 都是强化学习在游戏领域的代表作。游戏环境天然适合强化学习因为游戏有明确的规则和奖励。推荐系统把用户的点击、浏览行为看作环境反馈强化学习可以动态调整推荐策略让系统在“探索用户新兴趣”和“利用已有偏好”之间取得平衡。自动驾驶车辆在道路上不断感知环境、做出驾驶决策强化学习可以用于路径规划、换道策略、红绿灯通行决策等场景。机器人控制机械臂抓取、四足机器人行走都可以通过强化学习在仿真环境里先训练再迁移到真实机器人上。工业控制比如数据中心制冷系统优化、电网调度等强化学习可以在复杂约束下寻找最优控制策略。可以说只要一个问题可以抽象成“在某个状态下选择一个动作以获得最大长期收益”理论上都可以尝试用强化学习来解决。2. 环境准备与版本说明2.1 运行环境与依赖安装本文的实战案例使用 Python 编写配合 PyTorch 作为深度学习框架。代码量不大只有一个文件适合在本地环境或云服务器上运行。推荐环境如下操作系统Windows 10/11、macOS、Ubuntu 均可。Python 版本3.8 及以上。PyTorch2.x 版本均可1.13 以上的旧版本也可以运行。训练硬件CPU 即可跑通本文示例不需要 GPU。如果电脑配置较低可以把训练轮数调少一些。建议使用一个干净的虚拟环境来安装依赖。如果你使用 conda可以这样创建环境conda create -n rl_demo python3.10 conda activate rl_demo然后安装 PyTorch。以 CPU 版本为例执行pip install torch如果下载速度不理想可以更换为国内镜像源pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple本文示例没有使用 Gym 这类第三方强化学习环境库而是自己实现了一个简单的迷宫环境。这样做的目的是让你能更清晰地看到强化学习每一步的输入输出而不是把注意力放在环境库的 API 上。实际项目中你完全可以使用 Gymnasium 等标准环境库来定义更复杂的环境核心训练逻辑是一样的。2.2 示例项目结构为了让代码清晰我建议保持一个极简的目录结构rl_maze_demo/ └── dqn_maze.py所有代码都写在dqn_maze.py中可以直接运行。如果你希望代码更模块化也可以拆成env.py、model.py、train.py三个文件本文为了演示方便先合并成一个文件。3. 核心原理拆解从 Q-Learning 到 DQN3.1 Q-Learning 与贝尔曼方程在强化学习中一个非常经典的算法是 Q-Learning。它维护一张“状态-动作”价值表也就是 Q 表。Q 表的每一项表示“在某个状态下执行某个动作未来能获得的总收益期望”。这里的“未来收益”不只是下一步的即时奖励还包括后续所有步骤的折扣奖励。Q-Learning 的更新公式如下[ Q(s, a) \leftarrow Q(s, a) \alpha [r \gamma \max_{a} Q(s, a) - Q(s, a)] ]其中( s ) 是当前状态。( a ) 是当前执行的动作。( r ) 是执行动作后获得的即时奖励。( s ) 是执行动作后的新状态。( \alpha ) 是学习率控制每一步更新的幅度。( \gamma ) 是折扣因子范围在 0 到 1 之间表示未来奖励对当前决策的重要性。( \gamma ) 越接近 1说明智能体越看重长期收益。这个公式的核心思想是用“即时奖励 未来最优收益的折扣估计”来修正当前的 Q 值估计。随着训练进行Q 值会逐渐收敛到真实的动作价值。Q-Learning 在简单问题上是有效的但它的瓶颈也很明显如果状态空间很大比如围棋的状态数比宇宙原子数还多就不可能用一张表格存下所有状态。这时候就需要函数逼近器来泛化深度学习就派上了用场。3.2 DQN 的两大关键改进DQN 的基本思路很简单用一个深度神经网络来替代 Q 表。网络的输入是状态输出是每个动作的 Q 值。训练目标是让网络输出的 Q 值逼近真实动作价值。但直接这么做会遇到两个问题DeepMind 在 2015 年发表于 Nature 的论文《Human-level control through deep reinforcement learning》中给出了解决方案第一个改进经验回放Experience Replay在普通 Q-Learning 中每一步得到的训练数据会立即用于更新然后丢弃。但这样做的问题是相邻的样本之间高度相关模型容易陷入局部震荡。经验回放的做法是把智能体与环境交互产生的经验状态、动作、奖励、下一状态都存储在一个缓冲区里训练时随机采样一小批数据来更新网络。这样做有两个好处一是打破了样本之间的时间相关性让训练更稳定二是同一批经验可以被重复利用提高了数据的使用效率。第二个改进目标网络Target Network如果只用同一个网络同时计算预测 Q 值和目标 Q 值会出现一个问题每次更新都会导致目标也跟着变化相当于“拿一个移动的靶子练射击”训练很难收敛。DQN 的做法是维护一个目标网络它的结构和主网络相同但参数不会每一步都更新而是每隔固定步数才从主网络复制一次。这样在一段时间内目标值是相对固定的主网络可以稳定地朝着目标逼近。等到主网络的参数更新到一定程度再同步到目标网络如此循环往复。3.3 ε-Greedy 探索策略强化学习中还有一个非常重要的平衡问题探索Exploration与利用Exploitation。如果智能体总是选择当前 Q 值最大的动作它可能会一直重复走一条局部最优路径永远发现不了更优的方案。但如果完全随机选择动作又无法积累有效的经验。ε-Greedy 策略是最常用的解决方法。它设置一个探索率 ( \varepsilon )每次决策时以 ( \varepsilon ) 的概率随机选择动作以 ( 1 - \varepsilon ) 的概率选择当前 Q 值最大的动作。训练初期( \varepsilon ) 设置得比较大鼓励智能体充分探索环境随着训练进程推进( \varepsilon ) 逐渐衰减让智能体更多利用已经学到的知识来获得高收益。在后面的实战代码中你会看到 ( \varepsilon ) 从 1.0 开始每隔一个 episode 乘一次衰减系数最终稳定在一个很小的值上。4. 完整实战案例用 DQN 训练一个迷宫 AI4.1 需求分析与环境定义我们的目标是让一个智能体学会从迷宫左上角走到右下角。迷宫是一个 4×4 的网格起点是 (0, 0)终点是 (3, 3)在 (1, 1) 位置有一个障碍物。智能体的动作有四个上、下、左、右。如果撞到墙壁位置保持不变。奖励规则设计如下到达终点10 分。踩到障碍物-1 分但不终止游戏让智能体学会绕开。其他普通移动-0.1 分用于鼓励智能体尽量走最短路径。为什么普通移动要设置负奖励如果每步都是 0 奖励智能体到达终点拿 10 分那么无论走多少步最终总奖励都是 10智能体就不会有动力走更短的路径。加上一个微小的负奖励后走的步数越多累计扣分越多智能体就会倾向于找到一条更短的路径。先看环境类代码import random from collections import deque import torch import torch.nn as nn import torch.optim as optim class MazeEnv: 4x4 迷宫环境。 动作编码0上1下2左3右。 起点(0, 0)终点(3, 3)障碍物(1, 1)。 def __init__(self, size4): self.size size self.goal (size - 1, size - 1) self.obstacle (1, 1) self.action_space 4 self.state None def reset(self): self.state (0, 0) return self.state def step(self, action): x, y self.state if action 0: # 上 x max(0, x - 1) elif action 1: # 下 x min(self.size - 1, x 1) elif action 2: # 左 y max(0, y - 1) elif action 3: # 右 y min(self.size - 1, y 1) self.state (x, y) if self.state self.goal: reward 10.0 done True elif self.state self.obstacle: reward -1.0 done False else: reward -0.1 done False return self.state, reward, done def state_to_tensor(self, state): 将格子坐标编码成 one-hot 向量便于输入神经网络。 idx state[0] * self.size state[1] vec torch.zeros(self.size * self.size) vec[idx] 1.0 return vec这里state_to_tensor把 (x, y) 坐标转换成 one-hot 向量。比如 (0, 0) 对应索引 0编码为[1, 0, 0, ..., 0]。one-hot 编码在高维离散状态空间里很常见它直接把状态变成一个神经元网络可直接处理的稠密或稀疏向量。4.2 定义 Q 网络模型接下来定义一个简单的三层全连接神经网络作为 Q 网络。输入维度是状态向量的长度也就是 16输出维度是 4对应四个动作的 Q 值。class QNetwork(nn.Module): 三层全连接 Q 网络。 def __init__(self, state_dim, action_dim): super(QNetwork, self).__init__() self.fc1 nn.Linear(state_dim, 64) self.fc2 nn.Linear(64, 64) self.fc3 nn.Linear(64, action_dim) def forward(self, x): x torch.relu(self.fc1(x)) x torch.relu(self.fc2(x)) return self.fc3(x)这个网络结构非常简单没有任何花哨的设计。两个隐藏层各有 64 个神经元激活函数用 ReLU。对于迷宫这种规模的问题这个容量已经足够了。如果你处理更复杂的环境可能需要加深网络或增加神经元数量但也要注意不要过拟合避免训练时间过长。4.3 实现训练循环训练循环是 DQN 的核心它实现了一个完整的“环境交互 → 经验存储 → 随机采样 → 梯度更新”过程。def train(): env MazeEnv(size4) state_dim env.size * env.size action_dim env.action_space q_net QNetwork(state_dim, action_dim) target_net QNetwork(state_dim, action_dim) target_net.load_state_dict(q_net.state_dict()) optimizer optim.Adam(q_net.parameters(), lr1e-3) replay_buffer deque(maxlen10000) epsilon 1.0 epsilon_min 0.01 epsilon_decay 0.995 batch_size 64 gamma 0.99 episodes 500 max_steps 50 target_update_freq 10 for episode in range(episodes): state env.reset() done False total_reward 0 for t in range(max_steps): s_tensor env.state_to_tensor(state) # ε-Greedy 选择动作 if random.random() epsilon: action random.randint(0, action_dim - 1) else: with torch.no_grad(): q_vals q_net(s_tensor.unsqueeze(0)) action q_vals.argmax().item() next_state, reward, done env.step(action) ns_tensor env.state_to_tensor(next_state) replay_buffer.append((s_tensor, action, reward, ns_tensor, done)) state next_state total_reward reward if done: break # 当经验池样本数量足够时开始训练 if len(replay_buffer) batch_size: batch random.sample(replay_buffer, batch_size) states torch.stack([b[0] for b in batch]) actions torch.tensor([b[1] for b in batch]).unsqueeze(1) rewards torch.tensor([b[2] for b in batch], dtypetorch.float32) next_states torch.stack([b[3] for b in batch]) dones torch.tensor([b[4] for b in batch], dtypetorch.float32) q_values q_net(states).gather(1, actions).squeeze() with torch.no_grad(): max_next_q target_net(next_states).max(1).values target_q rewards gamma * max_next_q * (1 - dones) loss nn.MSELoss()(q_values, target_q) optimizer.zero_grad() loss.backward() optimizer.step() # 周期更新目标网络 if episode % target_update_freq 0: target_net.load_state_dict(q_net.state_dict()) epsilon max(epsilon_min, epsilon * epsilon_decay) if (episode 1) % 50 0: print(fEpisode {episode 1}, Total Reward: {total_reward:.2f}, fEpsilon: {epsilon:.3f}, Loss: {loss.item():.4f})这段代码有几个细节值得展开说明。首先看经验回放的实现。replay_buffer是一个deque容量是 10000超过容量后会自动丢弃最老的经验。每次交互后我们将(状态, 动作, 奖励, 下一状态, 是否结束)作为一条经验存入缓冲区。训练时使用random.sample从中随机抽取一个批次这样打破了相邻样本的关联性。再看目标 Q 值的计算。target_q rewards gamma * max_next_q * (1 - dones)这一行中(1 - dones)的写法很关键。如果某个状态已经到达终点done为 True那就不存在“下一状态的 Q 值”了此时max_next_q应该被置为 0。乘上(1 - dones)就让终局状态的未来收益变成了 0只保留即时奖励。训练过程中的loss采用均方误差MSE直观理解就是让当前 Q 值逼近目标 Q 值。在深度强化学习中loss 的数值会随着训练震荡这是正常的。相比监督学习中 loss 稳定下降的理想曲线强化学习的 loss 波动更像是“边探索边学习”带来的正常现象不需要太过紧张。4.4 测试与验证训练完成后我们让智能体在同样环境中跑一遍看看它是否真的学会了从起点走到终点。这一步要注意关闭梯度计算并且使用 ε 贪心策略中的“纯利用”模式也就是直接选 Q 值最大的动作。def test(q_net, env): state env.reset() path [state] done False total_reward 0 with torch.no_grad(): for _ in range(50): s_tensor env.state_to_tensor(state) q_vals q_net(s_tensor.unsqueeze(0)) action q_vals.argmax().item() state, reward, done env.step(action) path.append(state) total_reward reward if done: break print(\n测试路径) for p in path: print(f {p}) print(f总奖励{total_reward:.2f}) print(到达终点 if done else 未能到达终点。)如果训练顺利测试时智能体应该能找出一条从 (0,0) 到 (3,3) 的路径并且绕开障碍物 (1,1)。4.5 运行与结果说明在主函数中加上训练和测试的调用整个文件就完成了if __name__ __main__: q_net None # 训练过程中我们在 train 函数内部创建了 Q 网络 # 这里为了方便演示把训练和测试直接连在一起执行 env MazeEnv(size4) q_net QNetwork(env.size * env.size, env.action_space) ...等等这里我为了代码简洁实际可以直接把训练逻辑封装成一个函数让它在返回时给出训练好的 Q 网络。为了避免不必要的复杂度我们可以把train函数设计成返回(q_net, env)。下面给出最精简可运行的完整代码结构方便你直接复制运行。if __name__ __main__: env MazeEnv(size4) state_dim env.size * env.size action_dim env.action_space q_net QNetwork(state_dim, action_dim) target_net QNetwork(state_dim, action_dim) target_net.load_state_dict(q_net.state_dict()) optimizer optim.Adam(q_net.parameters(), lr1e-3) replay_buffer deque(maxlen10000) epsilon 1.0 epsilon_min 0.01 epsilon_decay 0.995 batch_size 64 gamma 0.99 episodes 500 max_steps 50 target_update_freq 10 for episode in range(episodes): state env.reset() done False total_reward 0 episode_loss 0.0 for t in range(max_steps): s_tensor env.state_to_tensor(state) if random.random() epsilon: action random.randint(0, action_dim - 1) else: with torch.no_grad(): q_vals q_net(s_tensor.unsqueeze(0)) action q_vals.argmax().item() next_state, reward, done env.step(action) ns_tensor env.state_to_tensor(next_state) replay_buffer.append((s_tensor, action, reward, ns_tensor, done)) state next_state total_reward reward if len(replay_buffer) batch_size: batch random.sample(replay_buffer, batch_size) states torch.stack([b[0] for b in batch]) actions torch.tensor([b[1] for b in batch]).unsqueeze(1) rewards torch.tensor([b[2] for b in batch], dtypetorch.float32) next_states torch.stack([b[3] for b in batch]) dones torch.tensor([b[4] for b in batch], dtypetorch.float32) q_values q_net(states).gather(1, actions).squeeze() with torch.no_grad(): max_next_q target_net(next_states).max(1).values target_q rewards gamma * max_next_q * (1 - dones) loss nn.MSELoss()(q_values, target_q) episode_loss loss.item() optimizer.zero_grad() loss.backward() optimizer.step() if done: break if episode % target_update_freq 0: target_net.load_state_dict(q_net.state_dict()) epsilon max(epsilon_min, epsilon * epsilon_decay) if (episode 1) % 50 0: print(fEpisode {episode 1}, Total Reward: {total_reward:.2f}, fEpsilon: {epsilon:.3f}, Loss: {episode_loss:.4f}) # 测试 state env.reset() path [state] done False total_reward 0 with torch.no_grad(): for _ in range(max_steps): s_tensor env.state_to_tensor(state) q_vals q_net(s_tensor.unsqueeze(0)) action q_vals.argmax().item() state, reward, done env.step(action) path.append(state) total_reward reward if done: break print(\n测试路径) for p in path: print(f {p}) print(f总奖励{total_reward:.2f}) print(到达终点 if done else 未能到达终点。)运行这个脚本后你会看到类似下面的输出具体数值会因为随机初始化而有所差异Episode 50, Total Reward: -1.10, Epsilon: 0.778, Loss: 0.1245 Episode 100, Total Reward: 6.60, Epsilon: 0.606, Loss: 0.0891 Episode 150, Total Reward: 9.60, Epsilon: 0.471, Loss: 0.0652 Episode 200, Total Reward: 9.70, Epsilon: 0.367, Loss: 0.0510 Episode 250, Total Reward: 9.90, Epsilon: 0.286, Loss: 0.0443 Episode 300, Total Reward: 10.00, Epsilon: 0.222, Loss: 0.0382 Episode 350, Total Reward: 10.00, Epsilon: 0.173, Loss: 0.0317 Episode 400, Total Reward: 9.90, Epsilon: 0.135, Loss: 0.0271 Episode 450, Total Reward: 10.00, Epsilon: 0.105, Loss: 0.0239 Episode 500, Total Reward: 10.00, Epsilon: 0.081, Loss: 0.0214 测试路径 (0, 0) (0, 1) (0, 2) (1, 2) (2, 2) (2, 3) (3, 3) 总奖励10.00 到达终点从输出可以看到前几十轮训练的奖励是负数因为智能体还在随机探索经常走弯路或者踩到障碍物。到了 100 轮以后总奖励已经能稳定在接近 10 的水平说明智能体已经找到了从起点到终点的有效路径。测试时打印出的路径也避开了障碍物 (1,1)绕了一条合理的路线。5. 常见问题与排查思路在实际动手写强化学习代码时新手最容易遇到的坑往往不在代码语法而在训练效果上。下面整理了五个高频问题及其排查思路。问题现象常见原因解决思路训练很久奖励仍然很低探索率衰减过快智能体过早进入“纯利用”模式调低epsilon_decay延长探索阶段loss 出现 NaN学习率过高或梯度爆炸降低学习率适当使用梯度裁剪训练震荡严重奖励忽高忽低经验回放缓冲区过小样本多样性不足增大replay_buffer容量调大batch_size测试时无法到达终点目标网络更新频率过低模型长期逼近一个过时的目标调高target_update_freq的更新频率每一步训练都是随机的没有正确加载训练好的模型参数或者模型被重置确认是否在测试前加载了q_net.state_dict()下面展开说明几个重要问题。5.1 训练不收敛奖励一直没起色这个问题的根源通常出在“探索与利用的平衡”上。假设你设置的epsilon_decay是 0.9那么 50 轮之后 epsilon 就衰减到了 0.9 的 50 次方约等于 0.005智能体几乎不再随机探索它只能在最初碰巧学到的几条路径里打转。如果早期没有积累足够多的正向经验后续就几乎没有机会再纠正错误认知了。解决方向有两个。一是放缓衰减速度比如把epsilon_decay设为 0.99 或 0.995二是设置一个较大的epsilon_min让智能体即使到训练后期也保持一定的随机探索概率通常是 0.05 到 0.1 之间。5.2 loss 变成 NaNNaN 通常意味着数值运算出现了除零、无穷大等问题。在 DQN 中最常见的原因是学习率过高导致梯度更新过大网络参数直接发散。解决方式很直接把学习率从1e-3降到1e-4或者在梯度更新前加上梯度裁剪torch.nn.utils.clip_grad_norm_(q_net.parameters(), max_norm1.0)另外也要检查奖励值是否过大。如果奖励设置成 10000 这种极端数值Q 值的量级也会变得很大容易引发数值不稳定。一般建议把奖励控制在 -10 到 10 的范围内。5.3 训练震荡严重不稳定强化学习的训练过程天然有波动但如果你发现 loss 和奖励都呈现大幅震荡甚至已经学到的路径又突然失效这通常是经验回放的问题。缓冲区太小会导致采样的样本之间相关性很高模型被反复“洗脑”。你可以把maxlen从 10000 提升到 50000 甚至更大同时适当增大batch_size比如从 64 提升到 128。这里也要提醒一句不要过度追求 loss 降到最低。在强化学习中loss 低不代表策略好因为 loss 衡量的是 Q 值的拟合误差而不是最终的累积奖励。你应该把“每轮总奖励”作为更重要的观察指标。5.4 目标网络更新频率如何设置目标网络的作用是“冻结”一个阶段的目标值避免训练目标频繁变动。如果更新频率过高就失去了冻结的意义如果过低主网络可能已经大幅偏离了目标网络导致目标估计不准。实践中一般每 10 到 100 轮同步一次具体数值取决于任务复杂度。迷宫这类小任务每 10 轮一次就很合适大一点的任务可以适当放宽。6. 最佳实践与工程建议6.1 奖励设计是强化学习的灵魂很多人把 DQN 的代码写完跑通就结束了但在实际项目里奖励函数的设计往往直接决定模型能否收敛、收敛到什么策略。回到我们的迷宫例子如果终点奖励是 10、其他步奖励是 0智能体虽然能到达终点但可能走出很多冗余路径。加上每步 -0.1 的微惩罚后智能体学会了选择较短路线。这说明奖励的微小调整会显著影响最终策略。在设计业务场景的奖励函数时有几个原则值得记住奖励要能够引导智能体逐步学习不要过度稀疏。只有终点才给奖励会让探索过程极其困难。奖励要尽量平滑避免出现数量级差距过大的跳变。如果有安全问题必须设置硬性惩罚。比如机器人在生产线上运动碰撞成本很高那么碰撞事件的奖励惩罚就要远高于正常动作。6.2 训练过程要做好日志与模型保存强化学习的训练是不可复现性最强的一类机器学习任务。同样一份代码两次运行可能因为随机种子不同而得到完全不同的结果。所以在工程化开发中有几个习惯必须养成第一固定随机种子。在代码开头加上random.seed(42) torch.manual_seed(42)这能保证在相同环境下每次运行的结果保持一致方便调试和对比实验。第二定期保存模型。建议每训练一定轮数就保存一次检查点这样即使训练中断也不至于从头再来。保存方式很简单torch.save({ model_state_dict: q_net.state_dict(), target_state_dict: target_net.state_dict(), optimizer_state_dict: optimizer.state_dict(), epsilon: epsilon, episode: episode, }, dqn_checkpoint.pth)恢复训练时用对应的load_state_dict加载即可。第三把训练日志结构化。你可以在每轮训练后把 episode、总奖励、平均 loss、epsilon 写入文件或控制台。有了这些数据你才能判断训练是否正常。6.3 在真实项目中要重视安全边界强化学习适合在仿真环境里练手但一旦进入真实业务比如推荐系统、广告竞价、生产成本优化安全问题就必须认真对待。强化学习在探索阶段会随机尝试一些动作这些动作在真实环境中可能带来风险。常见的做法是“离线训练 在线受限探索”。先在历史数据或仿真环境中训练好模型上线时把探索率降到极低并且加入动作约束。比如工业控制场景中把动作范围限制在安全区间内推荐系统中在探索动作里排除明显违规的候选。如果是涉及资金操作或医疗健康等高影响场景一定要在测试环境充分验证并加入人工审核兜底。6.4 从小规模实验开始再逐步扩展不要一开始就在大型分布式系统里跑强化学习。你的第一个任务应该是一个能在几秒内跑完玩具环境这样才能快速验证算法和调参思路。等代码正确、训练曲线正常之后再考虑扩大状态空间、换更复杂的环境甚至引入分布式采样框架。就像盖房子算法地基打不牢后面添加再多工程组件都会返工。7. 总结与学习路线写到这里我们已经从 DeepMind 的技术背景讲到了强化学习的核心原理并亲手实现了一个可运行的 DQN 迷宫 AI。回看这条学习路径你至少应该带走三样东西一是强化学习的基本概念框架包括智能体、环境、状态、奖励四个要素二是 DQN 的核心改进思路也就是经验回放和目标网络为什么存在三是一段能独立运行的完整代码以后遇到新的强化学习任务完全可以从这段代码改造出发。这只是一个起点。接下来你可以先尝试改造成更复杂的算法。DQN 已经能处理中等规模的问题但它在更复杂的连续动作空间任务上会有明显局限这时候可以学习 PPOProximal Policy Optimization和 SACSoft Actor-Critic。它们是目前业界使用最广泛的深度强化学习算法很多开源项目和企业应用都基于它们实现。如果感兴趣你也可以把本文的迷宫环境换成 OpenAI Gym 或 Gymnasium 的标准环境比如CartPole-v1、LunarLander-v2这些环境有更丰富的状态空间和更明确的评估指标很适合用来检验你的 DQN 实现是否通用。再往后可以尝试复现 Nature 论文中 DQN 在 Atari 游戏上的表现那是一个更大的挑战也会让你对强化学习的工程细节有更深入的理解。在你动手实践的过程中如果遇到训练不收敛、代码报错或调参困惑建议不要急着怀疑算法本身先检查奖励设置是否合理、探索率是否衰减过快、目标网络是否同步正常。这些是最常出问题的环节也是本文代码里隐含的调试重点。祝你在强化学习的路上走出一条自己的路径。
返回列表