news 2026/9/14 21:39:24

Stable Baselines3 Monitor Wrapper 完全指南:训练数据采集、monitor.csv 日志格式与结果加载

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Stable Baselines3 Monitor Wrapper 完全指南:训练数据采集、monitor.csv 日志格式与结果加载

Stable Baselines3 Monitor Wrapper 完全指南:训练数据采集、monitor.csv 日志格式与结果加载

【免费下载链接】stable-baselines3PyTorch version of Stable Baselines, reliable implementations of reinforcement learning algorithms.项目地址: https://gitcode.com/GitHub_Trending/st/stable-baselines3

本指南以 Stable Baselines3(SB3)官方 API 文档docs/common/monitor.md为核心,围绕stable_baselines3.common.monitor模块,系统讲解Monitor环境包装器的完整使用方式:从单环境到向量化环境的接入、monitor.csv日志的精确格式与自定义字段扩展,到用load_results加载数据并绘制学习曲线的实战流程。读完本文,你将能够在自己训练的任何 Gymnasium 环境中无缝采集逐 episode 的回报(reward)、长度(length)与耗时(time),并直接复用 SB3 生态的分析与可视化能力。

Monitor 是什么:一个为记录训练过程而生的环境包装器

在强化学习训练中,我们关心的是"每个 episode 究竟取得了多少总回报、运行了多少步、耗时多久",而不是原始的 step 级 reward 流。Stable Baselines3 在 stable_baselines3/common/monitor.py 中实现了Monitor类,它是一个标准的 GymnasiumWrapper,职责是:

  • 在每个 episode 结束时,把累计回报(r)、episode 步数(l)、结束时刻的时间戳(t)写入日志;
  • 把上述信息注入step()返回的info["episode"]字典,供上层回调与算法读取;
  • 可选地把日志持久化到 CSV 文件(后缀固定为monitor.csv),供离线分析与绘图使用。

Monitor在 SB3 的整个训练链路中无处不在:BaseAlgorithm._wrap_env在检测到普通(非向量化)环境尚未被Monitor包裹且monitor_wrapper=True(默认开启)时,会自动执行env = Monitor(env)(见 stable_baselines3/common/base_class.py)。也就是说,即使你不显式包装,SB3 算法默认也会在内部套上Monitor,只是此时不写日志文件,只提供info["episode"]等信息。官方将Monitor作为训练数据采集的标准入口,这也是本文聚焦它的原因。

快速上手:三步接入 Monitor

Monitor的构造函数签名如下(见 monitor.py):

Monitor( env: gym.Env, filename: str | None = None, allow_early_resets: bool = True, reset_keywords: tuple[str, ...] = (), info_keywords: tuple[str, ...] = (), override_existing: bool = True, )

最简用法:在创建算法之前,把 Gymnasium 环境包一层即可:

import gymnasium as gym from stable_baselines3.common.monitor import Monitor env = gym.make("CartPole-v1") env = Monitor(env) # 不传 filename,仅收集数据,不落盘

训练并查看每个 episode 的数据——Monitor.step()会在 episode 结束时把{"r": ..., "l": ..., "t": ...}写入返回的info["episode"]

obs, info = env.reset() episode_rewards = [] for _ in range(2000): obs, reward, terminated, truncated, info = env.step(env.action_space.sample()) if terminated or truncated: if "episode" in info: print(f"Episode done. reward={info['episode']['r']:.2f}, " f"length={info['episode']['l']}, time={info['episode']['t']:.2f}s") episode_rewards.append(info["episode"]["r"]) obs, _ = env.reset() print(f"Mean episode reward: {sum(episode_rewards) / len(episode_rewards):.2f}")

如果希望把日志写入文件,只需传入filename。文件会以monitor.csv结尾(详见下文ResultsWriter的文件命名规则):

env = Monitor(gym.make("CartPole-v1"), filename="./logs/cartpole.monitor.csv")

官方测试 tests/test_monitor.py 展示了这一典型使用闭环:创建Monitor(env, monitor_file)、循环step采样直至 episode 结束、close()收尾,并断言get_total_steps()get_episode_rewards()get_episode_lengths()等统计与手工累计一致。这也说明Monitor的统计访问器是可信赖的。

核心 API 逐项解析

Monitor提供的统计访问器(定义于 monitor.py):

方法返回值含义
get_total_steps()int自包装以来累计的 step 总数
get_episode_rewards()list[float]每个已结束 episode 的累计回报
get_episode_lengths()list[int]每个已结束 episode 的步数
get_episode_times()list[float]每个 episode 结束时距t_start的墙钟秒数

内部实现上,step()terminated or truncated时累计sum(self.rewards)len(self.rewards),并写入ep_info(见 monitor.py)。t字段使用的是round(time.time() - self.t_start, 6),即相对t_start的秒数而非绝对时间戳——这是 monitor.csv 格式的一个重要细节,读取时需要还原(见下节)。

参数语义与边界行为

  • filename:为None时不落盘;否则交给ResultsWriter管理文件句柄。Monitor.__init__还会把env.spec.id(如"CartPole-v1")写入文件头作为元数据(见 monitor.py)。
  • allow_early_resets:默认为True,允许在 episode 尚未结束时调用reset()(训练回调中常见)。若设为False,在未结束时提前 reset 会抛出RuntimeError: Tried to reset an environment before done...(见 monitor.py),测试 test_monitor_error_cases 覆盖了该路径。
  • reset_keywords:声明哪些reset()的关键字参数会被记录进当前 episode 的信息(如options)。若声明了某个 key 而reset()未传入,会抛出ValueError: Expected you to pass keyword argument ... into reset(见 monitor.py)。
  • info_keywords:声明要从env.step()返回的info字典中额外抽取的字段,随r/l/t一起写入 CSV 与info["episode"]。典型用途是记录自定义的每个 episode 度量(例如成功率)。
  • override_existing:默认为True,覆盖已存在的同名文件;设为False则以追加模式(append)写入,便于多次训练结果并入同一文件——官方测试 test_monitor_load_results 验证了追加模式下load_results的行数会正确累加。

与 SB3 训练主流程的自动集成

通过make_vec_env创建向量化环境时,Monitor会被自动套在每个子环境上:monitor_dir指定日志目录(每个子环境的文件名是其在向量化环境中的 rank),monitor_kwargs可透传给Monitor构造函数(见 stable_baselines3/common/env_util.py):

from stable_baselines3.common.env_util import make_vec_env vec_env = make_vec_env( "LunarLander-v3", n_envs=4, monitor_dir="./logs", # None 则不落盘,但仍会包裹 Monitor monitor_kwargs={"info_keywords": ("success",)}, # 额外记录 info["success"] )

如果使用SubprocVecEnv等原生向量化环境(例如 Procgen 系列),可直接使用VecMonitor(stable_baselines3/common/vec_env/vec_monitor.py)在向量层面完成同样的r/l/t记录;若环境已被Monitor包裹又套上VecMonitor,会发出UserWarning提示统计会被后者覆盖(见 vec_monitor.py)。

monitor.csv 日志格式:逐字节看懂训练数据

当传入filename后,ResultsWriter(monitor.py)负责落盘。文件命名规则:若filename不以monitor.csv结尾,则——若是目录就拼成目录/monitor.csv,否则追加.monitor.csv后缀。写入前会自动os.makedirs创建缺失目录(monitor.py),官方测试也专门验证了这一点(tests/test_monitor.py)。

文件头两行固定为 JSON 元数据与 CSV 表头,之后每行对应一个已结束的 episode:

#{"t_start": 1771532779.9940808, "env_id": "Pendulum-v1"} r,l,t -1463.466035,200,1.622209

这是官方测试 tests/test_monitor.py 中定义的标准样例,字段含义:

  • r:该 episode 累计回报,四舍五入到 6 位小数(round(ep_rew, 6));
  • l:该 episode 的 step 数;
  • t:episode 结束时相对t_start的墙钟秒数,6 位小数;
  • 若指定了info_keywords/reset_keywords,其对应字段会作为额外的 CSV 列追加在r,l,t之后(ResultsWritercsv.DictWriter("r", "l", "t", *extra_keys)为表头,见 monitor.py)。

细节说明:

  • 打开文件时显式指定newline="\n",避免 Windows 平台上出现\r\r\n换行问题(见 monitor.py,相关历史见 docs/misc/changelog.md);
  • 每写完一行都会立即flush(),保证进程崩溃时已完成的 episode 数据不丢失(monitor.py);
  • override_existing=False时只写数据行、不再重复写 JSON 头与 CSV 表头(monitor.py)。

加载与分析:load_results 与配套工具

load_results:把 monitor.csv 变成 DataFrame

load_results(path)会扫描目录下所有匹配*monitor.csv的文件,逐个读取 JSON 头与 CSV 数据,把所有 DataFrame 拼接后按时间排序,并把t字段从"相对各自文件t_start的秒数"还原为相对最早t_start的秒数(见 monitor.py):

from stable_baselines3.common.monitor import load_results df = load_results("./logs") print(df.head()) # 列:index, r, l, t(以及自定义的 extra 列)

注意事项(均有源码/测试佐证):

  • 依赖pandas,未安装时会抛出带安装指引的ImportError,提示pip install 'stable-baselines3[extra]'(monitor.py);
  • 目录下没有任何monitor.csv时抛出LoadMonitorResultsError(monitor.py),测试 test_monitor_load_results 覆盖;
  • 空文件(只有表头无数据行)会返回空 DataFrame,且与有数据的文件混合加载时不会产生警告——这是针对 GH#2213 的专门回归修复(见 tests/test_monitor.py 与 docs/misc/changelog.md)。

配套工具:get_monitor_files 与绘图

get_monitor_files(path)用 glob 匹配*monitor.csv,返回日志文件路径列表(monitor.py)。在此基础上,SB3 的results_plotter模块(stable_baselines3/common/results_plotter.py)提供了一键绘图能力:plot_results(dirs, num_timesteps, x_axis, task_name)对多个日志目录调用load_results,并通过ts2xy将 DataFrame 拆成 x/y 坐标。支持的三种 x 轴模式定义于 results_plotter.py:

常量x 轴含义
X_TIMESTEPS"timesteps"累计步数(np.cumsum(df.l)),反映样本效率
X_EPISODES"episodes"episode 序号
X_WALLTIME"walltime_hrs"墙钟时间(秒数 ÷ 3600,单位小时)

官方绘图指南 docs/guide/plotting.md 给出了完整闭环示例——训练后直接出图:

import os import gymnasium as gym import matplotlib.pyplot as plt from stable_baselines3 import PPO from stable_baselines3.common.monitor import Monitor from stable_baselines3.common.results_plotter import plot_results from stable_baselines3.common import results_plotter log_dir = "tmp/" os.makedirs(log_dir, exist_ok=True) env = gym.make("CartPole-v1") env = Monitor(env, log_dir) # 训练数据写入 tmp/monitor.csv model = PPO("MlpPolicy", env, verbose=1) model.learn(total_timesteps=20_000) plot_results([log_dir], 20_000, results_plotter.X_TIMESTEPS, "PPO CartPole") plt.show()

更精细的手工处理可借助ts2xywindow_func(滑动窗口平滑),示例同样见 docs/guide/plotting.md:

from stable_baselines3.common.monitor import load_results from stable_baselines3.common.results_plotter import ts2xy, window_func import numpy as np df = load_results(log_dir) x, y = ts2xy(df, "timesteps") # x=累计步数, y=episode 回报 x_smooth, y_smooth = window_func(x, y, 50, np.mean) # 50 个 episode 的滑动均值

实战案例:用 Monitor + 回调实现"按最优训练回报自动保存模型"

官方示例 docs/guide/examples.md 展示了Monitor与自定义BaseCallback的经典组合:回调在每隔check_freq步时调用load_results(self.log_dir)读取 Monitor 数据,用ts2xy(..., "timesteps")提取回报序列,当最近 100 个 episode 的平均回报创下新高时保存模型:

import os import numpy as np from stable_baselines3 import TD3 from stable_baselines3.common.monitor import Monitor from stable_baselines3.common.results_plotter import ts2xy, load_results from stable_baselines3.common.callbacks import BaseCallback class SaveOnBestTrainingRewardCallback(BaseCallback): def __init__(self, check_freq: int, log_dir: str, verbose: int = 1): super().__init__(verbose) self.check_freq = check_freq self.log_dir = log_dir self.save_path = os.path.join(log_dir, "best_model") self.best_mean_reward = -np.inf def _on_step(self) -> bool: if self.n_calls % self.check_freq == 0: x, y = ts2xy(load_results(self.log_dir), "timesteps") if len(x) > 0: mean_reward = np.mean(y[-100:]) # 最近 100 个 episode 平均回报 if mean_reward > self.best_mean_reward: self.best_mean_reward = mean_reward self.model.save(self.save_path) return True log_dir = "tmp/" os.makedirs(log_dir, exist_ok=True) env = gym.make("LunarLanderContinuous-v3") env = Monitor(env, log_dir) # 关键:日志目录必须包含 Monitor 产生的文件 model = TD3("MlpPolicy", env, verbose=0) model.learn(total_timesteps=100_000, callback=SaveOnBestTrainingRewardCallback(1000, log_dir))

该案例再次印证了 Monitor 的定位:它是所有训练数据分析(绘图、回调、TensorBoard 之外的自定义监控)的事实数据源

常见问题与排查清单

  • RuntimeError: Tried to reset an environment before done:在allow_early_resets=False下提前 reset 触发。要么显式传allow_early_resets=True,要么确保在terminated/truncated后再 reset。
  • ValueError: Expected you to pass keyword argument ... into resetreset_keywords中声明的参数未在reset()传入。
  • LoadMonitorResultsError: No monitor files ... foundload_results的目标目录下没有*monitor.csv,确认Monitor(filename=...)确实写入了文件。
  • ImportError: pandas is required ...load_results/绘图依赖pandas,用pip install 'stable-baselines3[extra]'安装额外依赖。
  • CSV 中t字段为何不是绝对时间戳:monitor.csv 记录的是相对t_start的秒数,load_results会在加载时按全局最早t_start统一归零,直接手写解析器时需注意。
  • 环境已用Monitor包裹却又被VecMonitor包裹:会触发警告,VecMonitor的统计会覆盖Monitor的统计,避免双重包裹。

延伸阅读

  • 模块源码:stable_baselines3/common/monitor.py(Monitor / ResultsWriter / get_monitor_files / load_results / LoadMonitorResultsError)
  • 官方测试:tests/test_monitor.py(数据一致性、追加模式、空文件回归、错误路径全覆盖)
  • 向量化版包装器:stable_baselines3/common/vec_env/vec_monitor.py(VecMonitor)
  • 训练绘图指南:docs/guide/plotting.md;回调 + 监控示例:docs/guide/examples.md
  • 从 SB2 迁移说明(bench/monitor.pycommon/monitor.py):docs/guide/migration.md

【免费下载链接】stable-baselines3PyTorch version of Stable Baselines, reliable implementations of reinforcement learning algorithms.项目地址: https://gitcode.com/GitHub_Trending/st/stable-baselines3

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

小学数学几何入门:平面图形概念、周长面积与实操路线全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 21:36:49

纳米催化载体|介孔二氧化锰纳米酶 MnO₂-NZs|中空介孔MnO₂纳米酶

介孔二氧化锰纳米酶是一类兼具介孔多孔结构与多重类酶催化活性的人工纳米酶,依托独特的中空空腔与介孔孔道结构,成为当前生物医学、环境催化领域的核心功能纳米材料。核心基础属性‌结构特征‌:具备1~2nm的有序介孔孔道与内部中空空腔&#x…

作者头像 李华
网站建设 2026/9/14 21:36:25

React路由初始

一、SPA单页面1、单页Web应用(单个html)2、整个应用只有一个完整的页面3、点击页面中的链接不会刷新页面,只会做页面的局部刷新4、数据都需要通过ajax请求获取,并在前端异步展现二、路由的理解1、什么是路由一个路由就是一个映射关…

作者头像 李华
网站建设 2026/9/14 21:36:00

LC振荡电路实操指南:从衰减振荡到高频稳定设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 21:34:59

骨传导与空气传导混合录音技术解析

1. 项目背景与概念解析"听觉世界中的骨头与眼泪"这个充满诗意的标题,实际上指向了一个专业音频制作领域的技术实践——通过骨传导与空气传导的混合录音技术,捕捉人类情感表达的生理声学特征。这种技术最初应用于心理学研究,后来被音…

作者头像 李华
网站建设 2026/9/14 21:34:23

Python函数设计核心:从基础到高阶实践

1. Python函数的核心价值与设计哲学在Python编程实践中,函数从来不只是简单的代码封装工具。我见过太多初学者把函数当作"不得已而为之"的语法结构,直到他们在实际项目中遭遇重复代码维护的噩梦。真正高效的Python开发者会把函数视为程序设计的…

作者头像 李华