news 2026/9/28 16:41:51

强化学习代码实战:从Q-learning到PPO的算法迁移与调参指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
强化学习代码实战:从Q-learning到PPO的算法迁移与调参指南

简介:面向从入门到进阶的强化学习学习者,压缩包按理论章节与实战项目双线编排,系统覆盖马尔可夫决策过程、表格型方法、策略梯度、近端策略优化、深度Q网络基础与进阶技巧、演员评论家、稀疏奖励、模仿学习、深度确定性策略梯度等主流算法,并通过悬崖寻路、CartPole-v0、Pendulum-v0三个实战项目展示从建模到调参的完整流程,源码与讲解一一对应,理论推导与工程实现衔接紧密。包内共223个文件,以Python脚本、训练数据、示意图、说明文档和可运行笔记本为主体,另含多种模型权重文件,整体约173MB,目录层级清楚,便于按需查找。已有678人学习下载,适合高校学生、算法工程师系统自学、对照复现,并在此基础上扩展自己的强化学习实验,可作为课程设计或工程落地的直接参考资料。

1. 强化学习从基础到进阶:一包码源到底能帮你省多少试错

拿到解压完的码源包,很多人第一反应是兴奋,第二反应是迷茫。目录里躺着十几个算法名称,却不知道从哪行代码开始读。我见过太多人卡在 Q-learning 到 DQN 这一段,不是算法有多难,而是资料只有理论、没有能跑的代码。这份打包好的码源,价值在于把「从表格到神经网络」这条主线一次性铺开:基础算法的更新公式、进阶算法的网络结构、案例的环境适配、训练脚本的参数,全都摆在面前。它能解决的问题很具体——让强化学习不再停留在数学符号里,而是变成你能改、能跑、能迁移的工程代码。适合谁?想从理论跨到复现的初学者,以及要在新场景里快速验证算法、又不想从零写网络的工程师。省下的不只是写代码的时间,还有踩坑的时间。

2. 准备环境与第一个算法:解压、依赖和 Q-learning 最小复现

2.1 先归档再解压:zip 包的结构与伪加密坑

拿到这类打包好的码源,我从来不在下载目录里直接解压。先建一个工作目录,把压缩包按「项目名 + 日期」归档,再解压到独立文件夹。因为全系列算法包通常目录层数很深,文件也比较多,直接解压在桌面或下载文件夹里,后面找配置文件和模型权重都会很痛苦。

常见做法是用命令行解压,避免图形界面在中文文件名或深层路径下手滑:

mkdir -p rl-workspace unzip "强化学习从基础到进阶-案例与实践含码源-强化学习全系列超详细算法码源齐全.zip" -d rl-workspace find rl-workspace -maxdepth 2 -type d | head -50

这里把压缩包名用引号包起来,是为了防止文件名里的中文字符和空格被 shell 拆成多个参数。-d指定解压目标目录,find只列两层目录,先看清楚这个包的顶层结构,再决定从哪个算法入手。如果是在 Windows 上,可以用 PowerShell 的Expand-Archive,效果一样。

解压遇到「需要密码」但来源又没给密码时,先别急着找密码工具。Zip 有一种常见异常叫伪加密:文件的数据并没有真正加密,只是 ZIP 目录区里的加密标志位被置位了,解压工具看到标志位就弹出密码框。这种情况用zipinfo的详细模式看一眼就能确认:

zipinfo -v "强化学习从基础到进阶-案例与实践含码源-强化学习全系列超详细算法码源齐全.zip" | grep -i encrypt

zipinfo -v会逐个文件列出详细属性,grep -i encrypt让你快速定位哪些条目被标记为加密。如果大量条目显示 encrypted,而你又确定这个包应当公开无密码,那多半是伪加密。实际操作中,用 7-Zip 打开后不输入密码直接解压,很多伪加密包都能正常解出来。要强调一句:只处理你自己下载、确认无密码的包,真加密的文件没有密码就是访问不了,不要尝试绕过。

解压完成后,典型的全系列强化学习码源包一般会把算法和案例分开放。常见组织方式是:algorithms或src目录下每个算法一个子目录,里面是网络定义、训练脚本和配置;examples或demos目录下是 CartPole、Atari、机器人控制等案例;根目录放requirements.txt或environment.yml。先看根目录的说明文档,再挑一个你认识的算法目录打开,比从头翻代码高效得多。

2.2 环境依赖怎么配:Python、框架与 gym 版本别混搭

跑强化学习码源,环境问题比算法问题更容易劝退。常见血泪经验是:代码在作者机器上能跑,换到你机器上报错,多半是 Python 版本、深度学习框架和 gym 接口不匹配。先建虚拟环境是正经操作,别把依赖直接装进系统 Python。

python -m venv rl_env source rl_env/bin/activate pip install --upgrade pip pip install torch pip install -r requirements.txt

python -m venv创建独立环境,source rl_env/bin/activate激活。如果包里有requirements.txt,先看里面的版本约束再执行最后的安装,避免把不相容的版本一次性装进去。没有requirements.txt的包,我会手动装三样:PyTorch 或 TensorFlow 二选一、gym、numpy。版本选择上,Python 3.8 到 3.10 是强化学习代码最稳的范围,3.11 以上有些老包还没有对应的 wheel。

gym 版本是这里最大的坑。老代码常见gym==0.21或gym==0.25,新代码基本都迁到gymnasium。两者最明显的差异是:env.reset()返回一个值还是(obs, info),env.step()返回四个值还是五个值。码源里如果写的是obs = env.reset(),说明它按老 API 写的;如果写obs, info = env.reset(),就是新 API。后面跑不跑得起来,很大程度取决于你按哪个版本装了环境。

案例里如果涉及 Atari 或者机器人仿真,还需要额外装对应引擎。比如 Atari 需要ale-py,机械臂强化学习实战里常见 MuJoCo 或 PyBullet。这类依赖体积大、版本敏感,我的习惯是先读码源里的 README,看它明确写了哪几个引擎,再逐个装,不一次性全装。不然装了一堆用不上的包,版本冲突时自己都分不清是谁的问题。

2.3 Q-learning 最小案例:从表格里看懂强化学习的第一性原理

不管码源里有多少进阶算法,我都会先跑一个 Q-learning。不是因为显摆基础,而是因为它是理解整个强化学习的锚点:状态、动作、奖励、折扣因子、探索与利用,五个概念在几十行代码里就能看全。

import gym import numpy as np env = gym.make("FrozenLake-v1", is_slippery=False) n_states = env.observation_space.n n_actions = env.action_space.n q_table = np.zeros((n_states, n_actions)) alpha = 0.1 # 学习率:新信息覆盖旧信息的速度 gamma = 0.99 # 折扣因子:未来奖励打多少折扣 epsilon = 1.0 # 初始探索率 min_epsilon = 0.05 decay = 0.995 # 每个 episode 后探索率衰减 for episode in range(2000): state, _ = env.reset() done = False while not done: if np.random.rand() < epsilon: action = env.action_space.sample() else: action = np.argmax(q_table[state]) next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated best_next = np.max(q_table[next_state]) q_table[state, action] += alpha * (reward + gamma * best_next - q_table[state, action]) state = next_state epsilon = max(min_epsilon, epsilon * decay) print(q_table)

核心在更新那一行:alpha * (reward + gamma * best_next - q_table[state, action]),括号里的部分叫时间差分误差,表示「实际拿到的奖励加未来价值」和「当前估计」的差距。best_next用的是下一状态里最大的 Q 值,这是 Q-learning 属于 off-policy 的原因——它更新时假设下一步采取最优动作,而不是当前策略实际会采取的动作。

参数里最容易调坏的是epsilon。初始 1.0 表示完全随机探索,随着 episode 推进按decay衰减到min_epsilon。衰减太快,前期没探够,Q 表会过早收敛到局部最优;衰减太慢,后期一直在乱走,收敛速度肉眼可见地慢。FrozenLake 的is_slippery=False是让环境确定性更强,方便观察 Q 表收敛过程;改成True后同样的参数要多跑几倍 episode 才稳。

跑完打印出来的 Q 表,每一行对应一个状态,最大值就是当前状态下的最优动作价值。这是所有后续算法的雏形:Q-learning 用表格存 Q 值,DQN 用神经网络替代表格,PPO 干脆直接学策略。从这张表开始,标题里说的「从基础到进阶」才真正立得住。

3. 从表格到神经网络:DQN 与策略梯度算法的分水岭

3.1 DQN 为什么是基础到进阶的第一道分水岭

Q-learning 能解决的问题,状态空间是离散且有限的。一旦状态变成连续量,比如机械臂的关节角度和角速度,表格的行数会组合爆炸,存都存不下。DQN 做的事情很直接:用一个神经网络近似 Q 函数,输入状态,输出每个动作的 Q 值估计。这是强化学习从「查表」到「泛化」的关键一跃,也是标题里「从基础到进阶」最实质的一道坎。

码源里如果按算法排列,DQN 这一节几乎必然是承上启下的位置。它往前接 Q-learning 的更新思想,往后接 Double DQN、Dueling DQN、NoisyNet、Rainbow 这些变体。很多全系列包会把这些变体放在同一个目录下,因为它们骨架高度相似,差别集中在目标网络、动作选择和网络结构上。读通 DQN 一次,后面变体基本都能顺下来。

DQN 相对 Q-learning 有两个关键改动。第一是经验回放:把每一步的(state, action, reward, next_state, done)存进一个固定大小的缓冲区,训练时随机采样小批量。这样打断样本之间的时间相关性,避免网络被连续的相似样本带偏。第二是目标网络:单独维护一份参数冻结的 Q 网络,间隔一定步数才从主网络同步一次,让训练目标稳定下来,防止网络被自己的预测值追着跑导致发散。

class DQN(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.net = 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.net(x)

网络输出维度等于动作数,输入是状态向量。中间两个 64 维隐层是入门级配置,够跑 CartPole 和大多数低维控制任务。如果你在码源里看到 128 或 256 维的网络,通常是给更复杂环境准备的;入门案例用这个结构反而收敛更快。网络大小不是越大越好,至少在调试阶段,小网络能更快验证逻辑对不对。

3.2 DQN 关键参数:回放池、目标网络、探索衰减怎么调

DQN 能跑通不难,难在调参。同一个网络结构,不同超参可能一个收敛一个发散。按参数敏感度从高到低排,最值得盯的是四个:探索衰减速度、回放池大小、目标网络更新频率、batch size。

replay_buffer = deque(maxlen=50000) # 经验回放池上限 batch_size = 32 gamma = 0.99 target_update_freq = 1000 # 每 1000 步同步一次目标网络 # 从回放池随机采样一个小批量 batch = random.sample(replay_buffer, batch_size) state = torch.tensor([b[0] for b in batch], dtype=torch.float32) action = torch.tensor([b[1] for b in batch], dtype=torch.int64) reward = torch.tensor([b[2] for b in batch], dtype=torch.float32) next_state = torch.tensor([b[3] for b in batch], dtype=torch.float32) done = torch.tensor([b[4] for b in batch], dtype=torch.float32) q_value = q_net(state).gather(1, action.unsqueeze(1)).squeeze(1) with torch.no_grad(): max_next_q = target_net(next_state).max(1)[0] target = reward + gamma * max_next_q * (1 - done) loss = nn.MSELoss()(q_value, target) optimizer.zero_grad() loss.backward() optimizer.step() if total_step % target_update_freq == 0: target_net.load_state_dict(q_net.state_dict())

q_value通过gather取出当前动作对应的 Q 值,target用目标网络计算下一状态的最大 Q 值再打折。(1 - done)是关键细节:终局状态没有未来奖励,必须把max_next_q清零,否则终端状态会被错误地赋予高价值,导致训练不发散但策略很怪。

参数经验值:回放池一般 5 万到 100 万,太小容易遗忘早前经验,太大训练初期的随机经验会占太多比例。目标网络更新频率 500 到 2000 步比较常见,太频繁等于没有目标网络,太稀疏目标过时,早期容易震荡。探索率从 1.0 衰减到 0.01,衰减总步数占整个训练的三分之一到二分之一,具体看 reward 曲线什么时候进入平台期。

一个常见翻车现场是 loss 降了但 reward 不涨。这份 loss 只是 Q 值对目标的拟合误差,目标本身也在变,所以它不能直接代表策略质量。判断 DQN 好坏的唯一硬指标是评估时的平均 reward,不是 loss。

3.3 什么时候上 PPO:on-policy 与 off-policy 的选型边界

DQN 学的是 Q 函数,再从 Q 函数推动作。PPO 换了条路:直接优化策略网络,使当前策略下能获得更高回报的动作被选中的概率变大。它属于 on-policy 方法,训练数据必须来自当前策略的采样,不能用旧的离线数据反复训练。这让它样本效率比 DQN 低,但稳定性好,超参敏感度低,是进阶阶段适用范围最广的算法。

选型边界其实很清晰。状态动作空间连续、环境交互成本不高、希望稳定收敛,优先 PPO。环境交互成本高,比如真实机械臂一次 rollout 很贵,要尽量少采样的,选 off-policy 的 SAC 或 TD3。离散动作游戏,DQN 系依然能打;连续控制领域,PPO 和 SAC 是主流。全系列码源里看到这几个算法共存是很正常的,它们不是替代关系,是分工关系。

PPO 的核心是裁剪目标函数:把新旧策略的比率限制在1 - clip_eps到1 + clip_eps之间,避免更新步子太大。码源里的 PPO 通常比 DQN 长很多,因为还涉及 GAE 优势估计、旧策略的 log 概率记录、多轮 mini-batch 更新。看代码时别被长度吓到,主线只有三条:采样轨迹、算优势、裁剪更新。

关键技术参数我一般这样设:clip_eps=0.2,学习率3e-4,GAE 的lambda=0.95,gamma=0.99。如果训练波动大,可以降低学习率到1e-4并增加 rollout 长度。理解 PPO 之后,标题里剩下的进阶算法,比如 SAC 的熵正则、TD3 的双 Q 网络,都是同一座山的不同爬法,骨架你已经见过了。至于离线强化学习、多智能体强化学习、基于模型的强化学习,那是另外几条主线,等 PPO 跑通再开不迟。

4. 案例与实践迁移:把码源里的算法搬进自己的场景

4.1 复现案例的顺序:先跑通、再打断点、最后改代码

拿到码源之后,很多人喜欢从最炫酷的案例开始,比如 Atari 游戏或者机械臂控制。我的建议正相反:先挑一个单机、低维、训练时间短的案例跑通,比如 CartPole 或 Pendulum。它只需几十秒到几分钟就能看到收敛趋势,足够验证环境、算法、依赖三者的组合没有大问题。

复现顺序固定成三步。第一步,按 README 或配置文件跑起来,不要动任何参数,记录原始 reward 曲线;第二步,在训练循环里打断点,观察 state、action、reward 的形状和取值范围,确认数据流和你理解的一致;第三步,改一个小参数,比如学习率或探索衰减,看曲线变化是否符合预期。三步跑完,你才算真正「握住了」这一份码源。

python train.py --algo ppo --env CartPole-v1 --total-timesteps 100000

这行命令是通用入口风格,具体参数名以包里脚本为准。它表达的是一个习惯:把算法、环境、总步数作为命令行参数而不是写死在代码里。这样的脚本才能在不同环境间快速切换,迁移到自己场景时不用改代码,只改命令。

4.2 迁移到自己的场景:状态、动作与奖励函数的三个坑

码源的案例终究是别人的环境。迁移到自己的场景,翻车大多集中在三个地方:状态表示、动作空间、奖励函数。

第一个坑是状态量纲不统一。假设你的状态包含关节角度(弧度,范围可能正负 3)、角速度(每秒几弧度)、末端距离(几十厘米),直接拼成向量喂给网络,量纲大的维度会主导梯度。码源里的环境如果自带normalize_obs之类的处理,迁移时一定要保留;没有的话自己做一个标准化层,把每个维度缩放到相近范围。第二个坑是动作空间不匹配。离散动作用 DQN、PPO 都能处理;连续动作必须选 PPO、SAC、TD3 这类策略梯度算法,而且要注意动作是否需要在输出层做tanh缩放。第三个坑是奖励稀疏。真实场景很少像游戏一样每步都给分数,往往是「到达目标给 1,否则给 0」,这种稀疏奖励让探索变得极慢,常见做法是加距离惩罚或进度奖励,但奖励尺度太大会让策略变得激进,产生振荡。

以机械臂强化学习实战为例,状态通常是关节角度、角速度、末端位置,动作是关节力矩或位置增量,奖励常用末端到目标的距离负值加成功奖励。要接进现有码源,最方便的方式是把你自己的仿真封装成gym.Env接口:

import gym from gym import spaces import numpy as np class MyEnv(gym.Env): def __init__(self): super().__init__() self.observation_space = spaces.Box(low=-1, high=1, shape=(6,), dtype=np.float32) self.action_space = spaces.Box(low=-1, high=1, shape=(3,), dtype=np.float32) self.state = None def reset(self, seed=None, options=None): super().reset(seed=seed) self.state = np.zeros(6, dtype=np.float32) return self.state, {} def step(self, action): self.state = self.state + action reward = -np.linalg.norm(self.state) terminated = np.linalg.norm(self.state) < 0.1 return self.state, reward, terminated, False, {}

reset返回(obs, info),step返回(obs, reward, terminated, truncated, info),这是新版本 gym 的标准接口。注意terminated和truncated是分开的:前者表示任务确实完成或失败,后者表示超过步数上限被强制结束,DQN 的 target 计算里只有terminated需要把自举设为 0。码源如果是老版 API,你可能需要写一层适配,否则跑一个报一个。

动作类型推荐算法适用说明
离散DQN / Double DQN状态连续但动作有限,从 Q 函数选最大动作
连续PPO / SAC / TD3PPO 稳定,SAC 样本效率更高
图像输入CNN + DQN / PPO先做灰度化、帧堆叠,再进卷积网络

4.3 一个连续控制案例:用 PPO 训练 Pendulum 的最小脚本

Pendulum-v1 是连续控制入门案例,动作是力矩,状态是角度和角速度。用 PPO 跑它是验证「从基础到进阶」的合适落点。码源里的 PPO 大概率几百行,但核心骨架可以用一个训练循环概括:

def train_ppo(env, policy, optimizer, total_steps=100_000, rollout_steps=2048, gamma=0.99, gae_lambda=0.95, clip_eps=0.2, update_epochs=4): state, _ = env.reset() for global_step in range(0, total_steps, rollout_steps): states, actions, rewards, dones, log_probs = [], [], [], [], [] for _ in range(rollout_steps): action, log_prob = policy.sample(state) next_state, reward, terminated, truncated, _ = env.step(action) states.append(state) actions.append(action) rewards.append(reward) done = terminated or truncated dones.append(done) log_probs.append(log_prob) state = next_state if not done else env.reset()[0] advantages = compute_gae(states, rewards, dones, policy, gamma, gae_lambda) for _ in range(update_epochs): update_policy(policy, optimizer, states, actions, log_probs, advantages, clip_eps) return policy

这段不是完整实现,但点出了 PPO 和 DQN 最大的区别:它必须先用当前策略跑一整条轨迹,把log_probs和奖励存下来,然后才能在旧数据上做多轮更新。update_epochs=4表示每条轨迹被反复用四次,这是 on-policy 方法的「内部复用」,和 off-policy 经验回放的复用含义不同。

rollout_steps=2048是一条轨迹的长度,越长优势估计越稳,但单轮训练耗时也越长。gae_lambda=0.95控制偏差方差权衡:越接近 1 越偏向真实回报,方差更大;越接近 0 越偏向一步差分,偏差更大。拿到码源后,先找到这几个参数在配置文件里的位置,改一遍看效果,比通读全部代码更有效率。

5. 强化学习常见问题排查:复现翻车现场的 5 个血泪教训

5.1 训练 Reward 不涨反跌,是先看 loss 还是先看曲线

现象:DQN 训练日志里 loss 一路下降,但平均 reward 不涨甚至在下滑;PPO 的 loss 波动很大,你都不知道该信哪个。

原因:loss 是网络对某个拟合目标的误差,不代表策略回报。DQN 的目标本身会随网络更新而移动,loss 下降可能只是网络在拟合一个旧目标;PPO 的 loss 更是 proxy 目标,裁剪之后它和真实回报的关系更间接。

解决:以评估阶段的平均 reward 为准,训练曲线的原始值要先做滑动平均再判断趋势。如果 reward 确实停滞,优先检查探索衰减速度:DQN 的 epsilon 降得太快会导致策略固化,PPO 的 entropy bonus 太小会导致策略过早失去多样性。别把时间耗在读 loss 曲线上,它最多帮你判断实现有没有明显 bug。

5.2 同一套代码换个 gym 版本就不收敛,问题出在哪

现象:从码源包继承的环境代码,在作者环境里收敛,在你的机器上报错too many values to unpack,或者同样参数曲线差一大截。

原因:gym 的 API 改版是主因。老版env.reset()只返回 obs,新版返回(obs, info);老版env.step()返回四个值,新版返回五个值。环境内部还有max_episode_steps、随机种子等细节差异,都会影响最终收敛。

解决:严格按虚拟环境还原依赖版本,不要混装。遇到 unpack 错误,先统一改成新版接口再跑:

obs, info = env.reset() obs, reward, terminated, truncated, info = env.step(action) done = terminated or truncated

把done合并成terminated or truncated,因为对算法来说,超时截断和正常结束都意味着这一步没有未来奖励。如果你的算法需要区分两者,比如 DQN 的 target 计算里超时通常不该当终局,那就分开存;但第一步先保证代码能跑通。

5.3 复现结果对不上,多半是 seed 没管住

现象:同一个脚本连续跑两次,reward 曲线完全不一样,甚至一次收敛一次发散。你以为算法有随机性正常,但实验之间无法对比,调参就没有基础。

原因:随机源不止一个。numpy 的随机数、Python 的random模块、PyTorch 的权重初始化、gym 环境的内部随机,都在各自维护随机状态。只torch.manual_seed不够,环境不 seed 照样漂移。

解决:把所有随机源在训练开始时统一 seed:

import random, numpy as np, torch seed = 42 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) env.reset(seed=seed)

如果码源用了多进程并行环境,还需要给每个子进程传不同的 seed 偏移,否则所有子环境状态完全一样,等于只有一个环境在采样。追求完全确定还要设torch.backends.cudnn.deterministic = True,代价是变慢;一般项目里做到上面四步就够了。

5.4 zip 解压提示文件损坏或密码错误,真伪加密要分清

现象:压缩包解压到一半弹密码框,你没有密码;或者解压完提示「不可预料的文件末端」,某个算法目录里的代码缺失,训练一跑就ModuleNotFoundError。

原因:两种情况。一是 zip 伪加密,只是标志位问题,数据没加密;二是下载不完整或中文文件名在跨系统解压时编码错乱,导致文件内容截断。出现在码源包场景里,伪加密比真加密更常见。

解决:先用unzip -t测试压缩包完整性;确认文件完整后,用 7-Zip 打开,如果能看到文件列表且预览正常,基本可以判断是伪加密。对伪加密,多数情况下忽略密码直接提取即可。如果某个目录解出来文件数为零或大小异常,重新找来源下载一次,别在损坏的包上浪费时间。注意,这一步只适用于你确定来源、应当公开的码源包。真加密的文件没有密码就是访问不了,不要尝试绕过。

5.5 训练速度慢到没法调参,先查这 4 个瓶颈

现象:一个 PPO 案例跑起来要几小时,改一个参数再跑又是一晚,整个调参节奏被拖垮。你以为是算法问题,其实多数是工程问题。

原因:常见瓶颈有四。第一,环境没有并行化,单进程 rollout 很慢;第二,网络规模超出任务需求;第三,数据采样和网络更新用纯 Python 循环,没有批量向量化;第四,checkpoint 或日志写得太频繁,每 100 步就存一次模型。

解决:先看码源是否有并行环境接口,有就优先用它。比如用SubprocVecEnv开 8 个环境并行采样:

from stable_baselines3.common.vec_env import SubprocVecEnv def make_env(): return gym.make("CartPole-v1") env = SubprocVecEnv([make_env for _ in range(8)])

并行环境数不是越多越好的玄学,一般 8 到 16 个够用,太多反而增加进程通信开销。其次,调试阶段把网络隐层从 256 缩到 64,验证逻辑对再放大;评估频率放到 5000 步一次,checkpoint 每训完一个阶段再存。速度上来了,调参才有意义。

6. 让训练不再像黑匣子:日志、评估与调参三板斧

6.1 评估函数比训练曲线更值得信任

训练过程中模型还在探索,带随机性的 reward 并不能反映策略的真实水平。正确做法是每隔固定步数用确定性策略评估几次,取平均值作为进度指标:

def evaluate(env, policy, episodes=5): rewards = [] for _ in range(episodes): state, _ = env.reset() total = 0 done = False while not done: action = policy.select_action(state, deterministic=True) state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated total += reward rewards.append(total) return np.mean(rewards)

关键在deterministic=True:评估时不做探索,只按当前最优策略走,这样结果才稳定可比。episodes=5是起步值,环境随机性大的任务可以加到 10 或 20。

6.2 把超参和每次评估结果写进日志,调参才有后悔药

我吃过最大的亏是调参调出好结果,却忘了当时的参数组合。后来养成的习惯是每次实验建一个独立目录,把命令行参数、评估结果、模型权重一起存下来:

import json, time run_id = time.strftime("%Y%m%d_%H%M%S") with open(f"run_{run_id}/config.json", "w") as f: json.dump({"algo": "ppo", "lr": 3e-4, "seed": 42}, f, indent=2)

每次评估得到的平均 reward 追加到同一目录下的 CSV 里。回头对比实验时,只看目录名和第一行配置就能还原现场。调参不是玄学,是日志和曲线共同支撑的工程过程。

我现在开新实验的第一件事,永远是先写评估函数和日志目录,再碰算法代码。希望这个习惯也能帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 16:41:45

Model-Optimizer:面向真实GPU部署的量化剪枝蒸馏工程方法论

1. 项目概述&#xff1a;Model-Optimizer不是工具箱&#xff0c;而是一套可落地的模型瘦身方法论“Model-Optimizer”这个名字听起来像某个官方SDK或商业软件&#xff0c;但实际在工业界和一线AI工程实践中&#xff0c;它从来不是一个开箱即用的黑盒产品——而是工程师面对真实…

作者头像 李华
网站建设 2026/9/28 16:40:15

YOLOv5骨龄检测实战:从数据预处理到部署避坑全解析

简介&#xff1a;一套基于Python和YOLOv5实现的骨龄检测项目资料&#xff0c;面向毕业设计、课程设计及项目开发场景&#xff0c;适合有一定深度学习基础、希望快速掌握目标检测在医疗影像中如何落地的读者。压缩包共25个文件&#xff0c;其中20个为Python源码&#xff0c;涵盖…

作者头像 李华
网站建设 2026/9/28 16:40:06

STM32G474 ADC单端与差分输入对比:电压测量精度与抗干扰实践

做电机控制和数字电源的工程师&#xff0c;对STM32G474这颗芯片都不会陌生。相比早先的F1/F4系列&#xff0c;G474在模拟外设上做了大量增强&#xff0c;集成多个ADC、运放、比较器等&#xff0c;这让它在电流环、电压环这类应用里非常顺手。而ADC单端输入和差分输入的选择&…

作者头像 李华
网站建设 2026/9/28 16:39:15

Python人脸表情识别系统:从数据到部署的完整实现

简介&#xff1a;这份资源是面向高校计算机相关专业毕业设计场景的完整项目资料&#xff0c;围绕基于Python的人脸表情识别系统展开&#xff0c;适合正在准备毕设、需要可运行源码与配套文档参考的本科生及自学者。包内共376个文件&#xff0c;涵盖42个py源码文件、39个pyc编译…

作者头像 李华
网站建设 2026/9/28 16:38:23

强化学习从Q-learning到DDPG:完整代码拆解与避坑指南

简介&#xff1a;一套强化学习从基础到进阶的全套学习与实践资源&#xff0c;面向算法、人工智能及深度学习方向的学习者&#xff0c;适合从零起步系统掌握强化学习核心概念。内容覆盖马尔可夫决策过程、表格型方法、策略梯度、DQN基础与进阶、PPO、演员-评论家、DDPG、TD3、SA…

作者头像 李华
网站建设 2026/9/28 16:37:51

基于ADS的40W LDMOS射频功放设计全流程解析

做射频功放这活儿&#xff0c;最磨人的往往不是仿真跑不出来&#xff0c;而是板子回到手上那一刻——明明仿真结果挺漂亮&#xff0c;一上电却没有输出&#xff0c;或者频谱仪上冒出一堆不该有的杂散。我自己刚入行那会儿&#xff0c;在40W LDMOS功率放大器上栽过好几个跟头&am…

作者头像 李华