StableBaselines3中两次model.learn(50000)为何不等同于单次learn(100000)
MaskablePPO 分段调用
learn()与单次调用结果不一致的原因 两种写法训练出的模型参数存在差异,核心是忽略了Stable-Baselines3(含sb3-contrib中的MaskablePPO)learn()方法的默认行为逻辑,和是否设置固定随机种子没有关系,具体差异点如下:
- 默认重置训练计数器,打断所有调度逻辑
learn()方法有个非显性的入参reset_num_timesteps,默认值为True。连续两次不带额外参数调用learn(50000)时,第二次调用会把模型内部记录的总训练步数、参数更新次数计数器全部重置为0。
这直接导致所有依赖训练进度的调度逻辑完全错位:哪怕没有自定义调度规则,PPO默认的学习率、clip_range线性衰减都是基于当前训练进度/总训练步数计算的,单次调用learn(100000)时学习率会从0.0003开始按照10w总步长平滑衰减到0;拆成两次默认调用时,第一个5w步学习率从0.0003衰减到一半,第二个5w步又会从0.0003重新开始衰减,每一步的实际更新步长和连续训练完全不同,参数更新方向自然出现偏差。 - 默认重新播种,打断连续随机数流
初始化时设置的seed=10仅在模型实例创建时给所有随机数生成器(torch、numpy、环境本身)设置初始状态,连续训练时随机数流是无中断推进的,轨迹采样、mini-batch拆分、网络前向的随机过程是完全固定的。
但二次调用learn()时如果保持reset_num_timesteps=True,框架会基于初始seed重新给所有随机数生成器播种,直接打断原本连续的随机序列,从5w步分界点开始,采样到的轨迹、拆分的训练批次都会和连续训练时的结果完全不一样,累积下来参数差异会非常明显。 - Rollout缓冲区的截断丢弃
PPO是同策略算法,训练时需要先收集满固定长度的轨迹存入Rollout缓冲区,再批量做参数更新。单次连续训练10w步时,缓冲区的填充、更新、清空流程是连贯的,不会在5w步位置做额外中断;但第一次learn(50000)调用结束时,框架会自动丢弃缓冲区中还没凑够整批、未参与更新的残余轨迹数据,第二次调用时会从空缓冲区开始重新收集轨迹,分界点附近的训练数据分布、GAE优势计算结果都会和连续训练存在差异,进一步放大参数偏差。
正确的分段续训写法
如果要实现和单次调用learn(100000)完全一致的训练结果,分段调用时从第二次开始必须传入reset_num_timesteps=False,示例代码如下:
model = MaskablePPO(MaskableActorCriticPolicy, env, verbose=1, learning_rate=0.0003, gamma=0.975, seed=10, batch_size=256, clip_range=0.2) model.learn(50000) # 续训时关闭计数器重置和随机数重播种 model.learn(50000, reset_num_timesteps=False)
在固定随机种子的前提下,上述写法得到的模型参数和单次调用10w步训练的结果完全一致,测试性能不会出现明显差异。
内容的提问来源于stack exchange,提问作者Vladimir Belik
相关产品推荐
相关产品推荐

