lin267 commited on
Commit
c3d6fc9
·
verified ·
1 Parent(s): f5d8c0b

value-based 实践:Q-Learning 智能体(FrozenLake + Taxi) + 回放视频 + 评估分数

Browse files
.gitattributes CHANGED
@@ -33,3 +33,12 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ logs/frozenlake4x4/train_curve.png filter=lfs diff=lfs merge=lfs -text
37
+ logs/frozenlake4x4_slippery/train_curve.png filter=lfs diff=lfs merge=lfs -text
38
+ logs/frozenlake8x8/train_curve.png filter=lfs diff=lfs merge=lfs -text
39
+ logs/taxi/train_curve.png filter=lfs diff=lfs merge=lfs -text
40
+ videos/replay_frozenlake4x4.mp4 filter=lfs diff=lfs merge=lfs -text
41
+ videos/replay_frozenlake4x4_slippery.mp4 filter=lfs diff=lfs merge=lfs -text
42
+ videos/replay_frozenlake8x8.mp4 filter=lfs diff=lfs merge=lfs -text
43
+ videos/replay_taxi.mp4 filter=lfs diff=lfs merge=lfs -text
44
+ videos/replay_value_based_all.mp4 filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Value-based 实践 · Q-Learning(FrozenLake-v1 + Taxi-v3)
2
+
3
+ > 《第3周-深度强化学习原理实践》PPT 第 27–40 页 value-based 实践产物:**从零手写 Q-Learning**(Q 表 + epsilon-greedy + Bellman 更新),训练好的智能体 + 训练代码 + 回放视频 + 评估分数。
4
+
5
+ ## 1. 评估分数
6
+
7
+ | 智能体 | 环境 | 平均回报(100 局) | 成功率 | 平均步数 | 训练回合 |
8
+ |---|---|---|---|---|---|
9
+ | FrozenLake-v1 · 4x4 不滑 | `FrozenLake-v1` | **1.00 ± 0.00** | 100% | 6.0 | 10000 |
10
+ | FrozenLake-v1 · 4x4 滑冰版 | `FrozenLake-v1` | **0.74 ± 0.44** | 74% | 39.4 | 20000 |
11
+ | FrozenLake-v1 · 8x8 | `FrozenLake-v1` | **1.00 ± 0.00** | 100% | 14.0 | 60000 |
12
+ | Taxi-v3 | `Taxi-v3` | **7.56 ± 2.71** | 100% | 13.4 | 25000 |
13
+
14
+ > 评估方式:独立环境、贪心策略 `argmax_a Q(s,a)`、每局最多 99 步,跑 100 局;FrozenLake 的回报是「是否到达目标」(0/1),Taxi 的回报是「每步 −1,成功送达 +20」。
15
+
16
+ ## 2. 回放视频
17
+
18
+ **总回放(三个智能体合集)**
19
+
20
+ <video controls width="100%" src="videos/replay_value_based_all.mp4"></video>
21
+
22
+ **FrozenLake-v1 · 4x4 不滑**
23
+
24
+ <video controls width="100%" src="videos/replay_frozenlake4x4.mp4"></video>
25
+
26
+ **FrozenLake-v1 · 4x4 滑冰版**
27
+
28
+ <video controls width="100%" src="videos/replay_frozenlake4x4_slippery.mp4"></video>
29
+
30
+ **FrozenLake-v1 · 8x8**
31
+
32
+ <video controls width="100%" src="videos/replay_frozenlake8x8.mp4"></video>
33
+
34
+ **Taxi-v3**
35
+
36
+ <video controls width="100%" src="videos/replay_taxi.mp4"></video>
37
+
38
+ ## 3. 学到的策略(Q 表可视化)
39
+
40
+ ## 4. 训练曲线
41
+
42
+ ![FrozenLake-v1 · 4x4 不滑 训练曲线](logs/frozenlake4x4/train_curve.png)
43
+
44
+ ![FrozenLake-v1 · 4x4 滑冰版 训练曲线](logs/frozenlake4x4_slippery/train_curve.png)
45
+
46
+ ![FrozenLake-v1 · 8x8 训练曲线](logs/frozenlake8x8/train_curve.png)
47
+
48
+ ![Taxi-v3 训练曲线](logs/taxi/train_curve.png)
49
+
50
+ ## 5. 怎么复现
51
+
52
+ ```powershell
53
+ # 环境(本机已有):conda activate DRL
54
+ conda activate DRL
55
+
56
+ # 训练(每个实验 1~30 秒)
57
+ python train_qlearning.py --env frozenlake4x4
58
+ python train_qlearning.py --env frozenlake4x4_slippery
59
+ python train_qlearning.py --env frozenlake8x8
60
+ python train_qlearning.py --env taxi
61
+
62
+ # 评估 + 录视频(生成 eval_results/*.json 和 videos/*.mp4)
63
+ python evaluate_and_record.py --model models/qtable_frozenlake4x4.pkl
64
+ python evaluate_and_record.py --model models/qtable_frozenlake4x4_slippery.pkl --video-episodes 5 --slowmo 4
65
+ python evaluate_and_record.py --model models/qtable_frozenlake8x8.pkl
66
+ python evaluate_and_record.py --model models/qtable_taxi.pkl
67
+
68
+ # Q 表可视化 + 总回放 + 打包
69
+ python visualize_qtable.py --all
70
+ python make_combined_video.py
71
+ python build_hub_package.py
72
+ ```
73
+
74
+ ## 6. 文件说明
75
+
76
+ - `models/qtable_*.pkl`:模型文件。pickle 格式,内含 Q 表、超参数、训练种子和评估指标;
77
+ `evaluate_and_record.py` 可以直接加载(不需要重新训练)。
78
+ - `videos/*.mp4`:回放,每帧标注步数/动作/奖励/Q 值条形图,片头片尾是标题卡和分数卡。
79
+ - `eval_results/*.json`:评估分数 + 逐局明细(起点种子、回报、步数、是否成功)。
80
+ - `logs/<run>/train_console.log`:完整训练日志(环境自检、超参数、每 N 回合进度、每次评估)。
81
+ - `code/`:全部代码(训练 / 评估录像 / 可视化 / 打包 / 上传 / 一键脚本)。
82
+
83
+ ## 7. 对应 PPT 页码
84
+
85
+ | PPT 页 | 内容 | 对应实现 |
86
+ |---|---|---|
87
+ | 29 | Q 表 / Q 函数 / 最优策略 | `train_qlearning.py` 的 `initialize_q_table` / `greedy_policy` |
88
+ | 31–32 | FrozenLake 观测/动作/奖励 | 训练日志里的「环境自检」 |
89
+ | 33 | Q 表初始化 + 贪心策略 | `initialize_q_table` / `greedy_policy` |
90
+ | 34 | epsilon-greedy + 超参数 | `epsilon_greedy_policy` + `EXPERIMENTS` 配置 |
91
+ | 35 | 训练循环(Bellman 更新) | `train_qlearning.py::train` 里的 `Q[s][a] += lr*(...)` |
92
+ | 36–37 | 训练、Q 表可视化、评估、保存模型 | 训练日志 / `visualize_qtable.py` / `evaluate_and_record.py` / `models/*.pkl` |
93
+ | 38–40 | Taxi-v3(500 状态 × 6 动作) | `--env taxi`(含 PPT 的 100 个官方评估种子) |
94
+ | 28 | 上传 Hub + 视频 + 分数 | `build_hub_package.py` + `upload_to_hub.py` + README |
95
+
96
+ - 生成时间:2026-10-30 23:05
code/build_hub_package.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 把训练好的智能体 + 代码 + 视频 + 评估分数打包成"可以直接上传 Hub"的文件夹。
4
+ ================================================================
5
+ 对应 PPT 第 28 页学习目标第 4 条:
6
+ "能够将训练好的智能体和代码上传到 Hub,并附上一个漂亮的视频回放和评估分数"
7
+
8
+ 产物 hub_package/ 结构:
9
+ README.md 自动填好分数 + 内嵌视频的模型卡
10
+ metrics.json 三/四个智能体的评估分数汇总
11
+ requirements.txt
12
+ models/qtable_*.pkl 训练出的模型文件(Q 表 + 超参 + 指标)
13
+ videos/replay_*.mp4 回放视频(含总回放 replay_value_based_all.mp4)
14
+ eval_results/eval_*.json 评估分数(逐局明细)
15
+ logs/<run>/train_console.log 训练日志
16
+ logs/<run>/train_curve.png 训练曲线
17
+ assets/qtable_policy_*.png 学到的策略可视化
18
+ code/ 训练 + 评估 + 可视化 + 上传脚本(可复现)
19
+ run_all.ps1 一键复现脚本
20
+
21
+ 用法:
22
+ D:\\study\\robot\\pyenv\\DRL\\python.exe build_hub_package.py
23
+ D:\\study\\robot\\pyenv\\DRL\\python.exe build_hub_package.py --repo-id 你的用户名/value-based-qlearning
24
+ """
25
+
26
+ import argparse
27
+ import json
28
+ import os
29
+ import shutil
30
+ from datetime import datetime
31
+
32
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
33
+
34
+ # 要打包的智能体(按 PPT 顺序)
35
+ AGENTS = [
36
+ dict(run="frozenlake4x4", title="FrozenLake-v1 · 4x4 不滑",
37
+ desc="PPT 第一部分主实验:确定性冰面,从零手写 Q 表,10,000 回合"),
38
+ dict(run="frozenlake4x4_slippery", title="FrozenLake-v1 · 4x4 滑冰版",
39
+ desc="对照实验:只改 is_slippery=True,冰面会打滑,20,000 回合"),
40
+ dict(run="frozenlake8x8", title="FrozenLake-v1 · 8x8",
41
+ desc="进阶实验:地图 8x8 + 拉长探索期,60,000 回合"),
42
+ dict(run="taxi", title="Taxi-v3",
43
+ desc="PPT 第二部分主实验:500 状态 × 6 动作,25,000 回合,官方 100 个评估种子"),
44
+ ]
45
+
46
+ CODE_FILES = [
47
+ "train_qlearning.py",
48
+ "evaluate_and_record.py",
49
+ "visualize_qtable.py",
50
+ "make_combined_video.py",
51
+ "build_hub_package.py",
52
+ "upload_to_hub.py",
53
+ "run_all.ps1",
54
+ "01_实践3说明.md",
55
+ ]
56
+
57
+ REQUIREMENTS = """gymnasium>=1.0
58
+ numpy>=1.24
59
+ imageio>=2.31
60
+ imageio-ffmpeg>=0.4
61
+ Pillow>=9.0
62
+ matplotlib>=3.7
63
+ tqdm>=4.65
64
+ """
65
+
66
+
67
+ def read_json(path):
68
+ if not os.path.exists(path):
69
+ return None
70
+ with open(path, encoding="utf-8") as f:
71
+ return json.load(f)
72
+
73
+
74
+ def build_readme(repo_id=None, scores=None, now=None):
75
+ raw = f"https://huggingface.co/{repo_id}/resolve/main/" if repo_id else ""
76
+ link = (lambda p: raw + p) if repo_id else (lambda p: p)
77
+
78
+ lines = []
79
+ lines.append("# Value-based 实践 · Q-Learning(FrozenLake-v1 + Taxi-v3)")
80
+ lines.append("")
81
+ lines.append("> 《第3周-深度强化学习原理实践》PPT 第 27–40 页 value-based 实践产物:"
82
+ "**从零手写 Q-Learning**(Q 表 + epsilon-greedy + Bellman 更新),"
83
+ "训练好的智能体 + 训练代码 + 回放视频 + 评估分数。")
84
+ lines.append("")
85
+ lines.append("## 1. 评估分数")
86
+ lines.append("")
87
+ lines.append("| 智能体 | 环境 | 平均回报(100 局) | 成功率 | 平均步数 | 训练回合 |")
88
+ lines.append("|---|---|---|---|---|---|")
89
+ for agent in AGENTS:
90
+ s = (scores or {}).get(agent["run"])
91
+ if s:
92
+ lines.append(
93
+ f"| {agent['title']} | `{s['env_id']}` | **{s['mean_reward']:.2f} ± "
94
+ f"{s['std_reward']:.2f}** | {s['success_rate'] * 100:.0f}% | "
95
+ f"{s['mean_steps']:.1f} | {s.get('n_training_episodes', '—')} |"
96
+ )
97
+ else:
98
+ lines.append(f"| {agent['title']} | — | 待运行 | — | — | — |")
99
+ lines.append("")
100
+ lines.append("> 评估方式:独立环境、贪心策略 `argmax_a Q(s,a)`、每局最多 99 步,跑 100 局;"
101
+ "FrozenLake 的回报是「是否到达目标」(0/1),Taxi 的回报是"
102
+ "「每步 −1,成功送达 +20」。")
103
+ lines.append("")
104
+
105
+ lines.append("## 2. 回放视频")
106
+ lines.append("")
107
+ if os.path.exists(os.path.join(BASE_DIR, "videos", "replay_value_based_all.mp4")):
108
+ lines.append("**总回放(三个智能体合集)**")
109
+ lines.append("")
110
+ lines.append(f'<video controls width="100%" src="{link("videos/replay_value_based_all.mp4")}"></video>')
111
+ lines.append("")
112
+ for agent in AGENTS:
113
+ video = f"videos/replay_{agent['run']}.mp4"
114
+ if os.path.exists(os.path.join(BASE_DIR, video)):
115
+ lines.append(f"**{agent['title']}**")
116
+ lines.append("")
117
+ lines.append(f'<video controls width="100%" src="{link(video)}"></video>')
118
+ lines.append("")
119
+
120
+ lines.append("## 3. 学到的策略���Q 表可视化)")
121
+ lines.append("")
122
+ for agent in AGENTS:
123
+ fig = f"assets/qtable_policy_{agent['run']}.png"
124
+ if os.path.exists(os.path.join(BASE_DIR, fig)):
125
+ lines.append(f"![{agent['title']}]({link(fig)})")
126
+ lines.append("")
127
+
128
+ lines.append("## 4. 训练曲线")
129
+ lines.append("")
130
+ for agent in AGENTS:
131
+ fig = f"logs/{agent['run']}/train_curve.png"
132
+ if os.path.exists(os.path.join(BASE_DIR, fig)):
133
+ lines.append(f"![{agent['title']} 训练曲线]({link(fig)})")
134
+ lines.append("")
135
+
136
+ lines.append("## 5. 怎么复现")
137
+ lines.append("")
138
+ lines.append("```powershell")
139
+ lines.append("# 环境(本机已有):conda activate DRL")
140
+ lines.append("conda activate DRL")
141
+ lines.append("")
142
+ lines.append("# 训练(每个实验 1~30 秒)")
143
+ lines.append("python train_qlearning.py --env frozenlake4x4")
144
+ lines.append("python train_qlearning.py --env frozenlake4x4_slippery")
145
+ lines.append("python train_qlearning.py --env frozenlake8x8")
146
+ lines.append("python train_qlearning.py --env taxi")
147
+ lines.append("")
148
+ lines.append("# 评估 + 录视频(生成 eval_results/*.json 和 videos/*.mp4)")
149
+ lines.append("python evaluate_and_record.py --model models/qtable_frozenlake4x4.pkl")
150
+ lines.append("python evaluate_and_record.py --model models/qtable_frozenlake4x4_slippery.pkl --video-episodes 5 --slowmo 4")
151
+ lines.append("python evaluate_and_record.py --model models/qtable_frozenlake8x8.pkl")
152
+ lines.append("python evaluate_and_record.py --model models/qtable_taxi.pkl")
153
+ lines.append("")
154
+ lines.append("# Q 表可视化 + 总回放 + 打包")
155
+ lines.append("python visualize_qtable.py --all")
156
+ lines.append("python make_combined_video.py")
157
+ lines.append("python build_hub_package.py")
158
+ lines.append("```")
159
+ lines.append("")
160
+ lines.append("## 6. 文件说明")
161
+ lines.append("")
162
+ lines.append("- `models/qtable_*.pkl`:模型文件。pickle 格式,内含 Q 表、超参数、训练种子和评估指标;")
163
+ lines.append(" `evaluate_and_record.py` 可以直接加载(不需要重新训练)。")
164
+ lines.append("- `videos/*.mp4`:回放,每帧标注步数/动作/奖励/Q 值条形图,片头片尾是标题卡和分数卡。")
165
+ lines.append("- `eval_results/*.json`:评估分数 + 逐局明细(起点种子、回报、步数、是否成功)。")
166
+ lines.append("- `logs/<run>/train_console.log`:完整训练日志(环境自检、超参数、每 N 回合进度、每次评估)。")
167
+ lines.append("- `code/`:全部代码(训练 / 评估录像 / 可视化 / 打包 / 上传 / 一键脚本)。")
168
+ lines.append("")
169
+ lines.append("## 7. 对应 PPT 页码")
170
+ lines.append("")
171
+ lines.append("| PPT 页 | 内容 | 对应实现 |")
172
+ lines.append("|---|---|---|")
173
+ lines.append("| 29 | Q 表 / Q 函数 / 最优策略 | `train_qlearning.py` 的 `initialize_q_table` / `greedy_policy` |")
174
+ lines.append("| 31–32 | FrozenLake 观测/动作/奖励 | 训练日志里的「环境自检」 |")
175
+ lines.append("| 33 | Q 表初始化 + 贪心策略 | `initialize_q_table` / `greedy_policy` |")
176
+ lines.append("| 34 | epsilon-greedy + 超参数 | `epsilon_greedy_policy` + `EXPERIMENTS` 配置 |")
177
+ lines.append("| 35 | 训练循环(Bellman 更新) | `train_qlearning.py::train` 里的 `Q[s][a] += lr*(...)` |")
178
+ lines.append("| 36–37 | 训练、Q 表可视化、评估、保存模型 | 训练日志 / `visualize_qtable.py` / `evaluate_and_record.py` / `models/*.pkl` |")
179
+ lines.append("| 38–40 | Taxi-v3(500 状态 × 6 动作) | `--env taxi`(含 PPT 的 100 个官方评估种子) |")
180
+ lines.append("| 28 | 上传 Hub + 视频 + 分数 | `build_hub_package.py` + `upload_to_hub.py` + README |")
181
+ lines.append("")
182
+ lines.append(f"- 生成时间:{now:%Y-%m-%d %H:%M}")
183
+ lines.append("")
184
+ return "\n".join(lines)
185
+
186
+
187
+ def copy_file(src, dst):
188
+ os.makedirs(os.path.dirname(dst), exist_ok=True)
189
+ shutil.copy2(src, dst)
190
+
191
+
192
+ def main():
193
+ parser = argparse.ArgumentParser(description="打包成可上传 Hub 的文件夹")
194
+ parser.add_argument("--repo-id", type=str, default=None,
195
+ help="你的 HF 用户名/仓库名,例如 zhangsan/value-based-qlearning")
196
+ parser.add_argument("--out", type=str, default=os.path.join(BASE_DIR, "hub_package"))
197
+ args = parser.parse_args()
198
+
199
+ out = args.out
200
+ if os.path.exists(out):
201
+ shutil.rmtree(out)
202
+ os.makedirs(out)
203
+ now = datetime.now()
204
+
205
+ # 1) 读取所有评估分数
206
+ scores = {}
207
+ for agent in AGENTS:
208
+ run = agent["run"]
209
+ s = read_json(os.path.join(BASE_DIR, "eval_results", f"eval_{run}.json"))
210
+ if s is None:
211
+ print(f"[警告] 缺少 eval_results/eval_{run}.json,"
212
+ f"请先跑 evaluate_and_record.py --model models/qtable_{run}.pkl")
213
+ continue
214
+ scores[run] = s
215
+ metrics = {}
216
+ for run, s in scores.items():
217
+ metrics[run] = {k: s[k] for k in
218
+ ("env_id", "n_episodes", "mean_reward", "std_reward",
219
+ "success_rate", "mean_steps", "qtable_shape",
220
+ "qtable_nonzero", "evaluated_at")}
221
+
222
+ # 2) 拷模型
223
+ for agent in AGENTS:
224
+ src = os.path.join(BASE_DIR, "models", f"qtable_{agent['run']}.pkl")
225
+ if os.path.exists(src):
226
+ copy_file(src, os.path.join(out, "models", os.path.basename(src)))
227
+ else:
228
+ print(f"[警告] 缺少模型 {src}")
229
+
230
+ # 3) 拷视频(含总回放)
231
+ video_dir = os.path.join(BASE_DIR, "videos")
232
+ if os.path.isdir(video_dir):
233
+ for name in sorted(os.listdir(video_dir)):
234
+ if name.endswith(".mp4"):
235
+ copy_file(os.path.join(video_dir, name),
236
+ os.path.join(out, "videos", name))
237
+
238
+ # 4) 拷评估 json
239
+ for agent in AGENTS:
240
+ src = os.path.join(BASE_DIR, "eval_results", f"eval_{agent['run']}.json")
241
+ if os.path.exists(src):
242
+ copy_file(src, os.path.join(out, "eval_results", os.path.basename(src)))
243
+
244
+ # 5) 拷日志 + 训练曲线
245
+ for agent in AGENTS:
246
+ run_dir = os.path.join(BASE_DIR, "logs", agent["run"])
247
+ for name in ("train_console.log", "train_curve.png", "progress.csv"):
248
+ src = os.path.join(run_dir, name)
249
+ if os.path.exists(src):
250
+ copy_file(src, os.path.join(out, "logs", agent["run"], name))
251
+
252
+ # 6) 拷 Q 表可视化
253
+ for agent in AGENTS:
254
+ src = os.path.join(BASE_DIR, "assets", f"qtable_policy_{agent['run']}.png")
255
+ if os.path.exists(src):
256
+ copy_file(src, os.path.join(out, "assets", os.path.basename(src)))
257
+
258
+ # 7) 拷代码
259
+ for name in CODE_FILES:
260
+ src = os.path.join(BASE_DIR, name)
261
+ if os.path.exists(src):
262
+ copy_file(src, os.path.join(out, "code", name))
263
+
264
+ # 8) 写 README / metrics / requirements
265
+ with open(os.path.join(out, "README.md"), "w", encoding="utf-8") as f:
266
+ f.write(build_readme(repo_id=args.repo_id, scores=scores, now=now))
267
+ with open(os.path.join(out, "metrics.json"), "w", encoding="utf-8") as f:
268
+ json.dump(metrics, f, ensure_ascii=False, indent=2)
269
+ with open(os.path.join(out, "requirements.txt"), "w", encoding="utf-8") as f:
270
+ f.write(REQUIREMENTS)
271
+
272
+ n_files = sum(len(files) for _, _, files in os.walk(out))
273
+ print(f"[完成] hub_package/ 共 {n_files} 个文件")
274
+ for root, dirs, files in os.walk(out):
275
+ rel = os.path.relpath(root, out)
276
+ for name in sorted(files):
277
+ path = os.path.join(root, name)
278
+ print(f" {os.path.join(rel, name) if rel != '.' else name}"
279
+ f" ({os.path.getsize(path) / 1024:.0f} KB)")
280
+ print("\n下一步上传:")
281
+ print(" hf auth login # 粘贴你的 write token")
282
+ print(" python upload_to_hub.py --repo-id 你的用户名/value-based-qlearning")
283
+
284
+
285
+ if __name__ == "__main__":
286
+ main()
code/evaluate_and_record.py ADDED
@@ -0,0 +1,542 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 第3周 · 实战3(value-based / Q-Learning)—— 评估 + 录视频脚本
4
+ =============================================================
5
+ 对应 PPT 第 36–37 页(评估方法、Q 表可视化、模型文件)与第 28 页学习目标第 4 条:
6
+ "能够将训练好的智能体和代码上传到 Hub,并附上一个漂亮的视频回放和评估分数"
7
+
8
+ 做三件事:
9
+ 1) 加载训练好的模型文件(models/qtable_*.pkl,里面同时存着 Q 表和全套配置)
10
+ 2) 用贪婪策略独立评估 N 局,输出评估分数(平均回报 ± 标准差、成功率、平均步数)
11
+ -> eval_results/eval_<run>.json
12
+ 3) 回放录像:每一帧都标注"第几局/第几步/动作/奖励/Q 值条形图/累计回报",
13
+ 片头有标题卡,片尾有分数卡
14
+ -> videos/replay_<run>.mp4
15
+
16
+ 用法:
17
+ D:\\study\\robot\\pyenv\\DRL\\python.exe evaluate_and_record.py --model models/qtable_frozenlake4x4.pkl
18
+ D:\\study\\robot\\pyenv\\DRL\\python.exe evaluate_and_record.py --model models/qtable_taxi.pkl --episodes 100
19
+ # 想录得更慢/更多局:
20
+ python evaluate_and_record.py --model models/qtable_taxi.pkl --video-episodes 10 --slowmo 8
21
+ """
22
+
23
+ import argparse
24
+ import json
25
+ import os
26
+ import pickle
27
+ import time
28
+ from datetime import datetime
29
+
30
+ import gymnasium as gym
31
+ import imageio
32
+ import numpy as np
33
+ from PIL import Image, ImageDraw, ImageFont
34
+
35
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
36
+ EVAL_DIR = os.path.join(BASE_DIR, "eval_results")
37
+ VIDEO_DIR = os.path.join(BASE_DIR, "videos")
38
+
39
+ # ----------------------------------------------------------------------
40
+ # 录像画面参数(1280x720 / 30fps,B 站、Hub、PPT 里都合适)
41
+ # ----------------------------------------------------------------------
42
+ WIDTH, HEIGHT = 1280, 720
43
+ HEADER_H = 78
44
+ FOOTER_H = 52
45
+ FPS = 30
46
+ SLOWMO = 6 # 每个动作帧重复次数(让 Q-Learning 这种短回合看得清)
47
+ HOLD_LAST = 1.2 # 每局结束后的定格秒数
48
+ TITLE_SECONDS = 2.5
49
+ END_SECONDS = 4.0
50
+
51
+ # 配色
52
+ BG = (15, 23, 42)
53
+ PANEL = (30, 41, 59)
54
+ PANEL_EDGE = (51, 65, 85)
55
+ TEXT = (226, 232, 240)
56
+ MUTED = (148, 163, 184)
57
+ ACCENT = (56, 189, 248)
58
+ GREEN = (34, 197, 94)
59
+ RED = (239, 68, 68)
60
+ AMBER = (245, 158, 11)
61
+
62
+ FONT_DIR = r"C:\Windows\Fonts"
63
+
64
+
65
+ def load_font(size, bold=False):
66
+ """优先微软雅黑,找不到再退回黑体/默认字体。"""
67
+ names = ["msyhbd.ttc", "msyh.ttc"] if bold else ["msyh.ttc", "simhei.ttf"]
68
+ for name in names:
69
+ path = os.path.join(FONT_DIR, name)
70
+ if os.path.exists(path):
71
+ try:
72
+ return ImageFont.truetype(path, size)
73
+ except Exception:
74
+ pass
75
+ return ImageFont.load_default()
76
+
77
+
78
+ FONT_TITLE = load_font(40, bold=True)
79
+ FONT_H1 = load_font(28, bold=True)
80
+ FONT_H2 = load_font(22, bold=True)
81
+ FONT_BODY = load_font(20)
82
+ FONT_SMALL = load_font(16)
83
+ FONT_TINY = load_font(14)
84
+
85
+ # 动作名称(PPT 第 32 页 / 第 38 页)
86
+ ACTION_NAMES = {
87
+ "FrozenLake-v1": ["← 向左 (0)", "↓ 向下 (1)", "→ 向右 (2)", "↑ 向上 (3)"],
88
+ "Taxi-v3": ["↓ 向南 (0)", "↑ 向北 (1)", "→ 向东 (2)", "← 向西 (3)",
89
+ "P 搭载乘客 (4)", "D 送达乘客 (5)"],
90
+ }
91
+ ACTION_SHORT = {
92
+ "FrozenLake-v1": ["←", "↓", "→", "↑"],
93
+ "Taxi-v3": ["南", "北", "东", "西", "载", "送"],
94
+ }
95
+
96
+ # Taxi-v3 地图上的四个上下客点(R 红 / G 绿 / Y 黄 / B 蓝)
97
+ TAXI_LOCS = [(0, 0), (0, 4), (4, 0), (4, 3)]
98
+ TAXI_LOC_NAMES = ["R 红", "G 绿", "Y 黄", "B 蓝"]
99
+
100
+
101
+ # ======================================================================
102
+ # 模型 / 环境
103
+ # ======================================================================
104
+ def load_model(path):
105
+ with open(path, "rb") as f:
106
+ model = pickle.load(f)
107
+ if "qtable" not in model:
108
+ raise ValueError(f"{path} 里没有 qtable 字段,不是本脚本保存的模型")
109
+ return model
110
+
111
+
112
+ def make_env(model, render_mode="rgb_array"):
113
+ kwargs = dict(model.get("env_kwargs") or {})
114
+ kwargs.pop("render_mode", None)
115
+ return gym.make(model["env_id"], render_mode=render_mode, **kwargs)
116
+
117
+
118
+ def greedy_action(q_table, state):
119
+ return int(np.argmax(q_table[state]))
120
+
121
+
122
+ # ======================================================================
123
+ # 评估:贪婪策略跑 N 局(PPT 第 36 页 evaluate_agent 的同一件事)
124
+ # ======================================================================
125
+ def evaluate(env, model, n_episodes, seeds):
126
+ q = model["qtable"]
127
+ max_steps = model["max_steps"]
128
+ rows = []
129
+ for i in range(n_episodes):
130
+ if seeds is not None and i < len(seeds):
131
+ state, _ = env.reset(seed=int(seeds[i]))
132
+ else:
133
+ state, _ = env.reset()
134
+ total, steps = 0.0, 0
135
+ for step in range(max_steps):
136
+ action = greedy_action(q, state)
137
+ state, reward, terminated, truncated, _ = env.step(action)
138
+ total += float(reward)
139
+ steps = step + 1
140
+ if terminated or truncated:
141
+ break
142
+ rows.append(dict(episode=i + 1, seed=None if seeds is None else int(seeds[i]),
143
+ reward=round(total, 4), steps=steps, success=int(total > 0)))
144
+ rewards = np.array([r["reward"] for r in rows], dtype=float)
145
+ success = np.array([r["success"] for r in rows], dtype=float)
146
+ steps = np.array([r["steps"] for r in rows], dtype=float)
147
+ result = dict(
148
+ run=model.get("run_name", "unknown"),
149
+ env_id=model["env_id"],
150
+ env_kwargs=model.get("env_kwargs", {}),
151
+ n_training_episodes=model.get("n_training_episodes"),
152
+ n_episodes=n_episodes,
153
+ mean_reward=float(rewards.mean()),
154
+ std_reward=float(rewards.std()),
155
+ min_reward=float(rewards.min()),
156
+ max_reward=float(rewards.max()),
157
+ success_rate=float(success.mean()),
158
+ mean_steps=float(steps.mean()),
159
+ policy="greedy (argmax Q)",
160
+ model_file=os.path.relpath(model["_path"], BASE_DIR) if "_path" in model else None,
161
+ qtable_shape=list(q.shape),
162
+ qtable_nonzero=int(np.count_nonzero(q)),
163
+ evaluated_at=datetime.now().isoformat(timespec="seconds"),
164
+ episodes=rows,
165
+ )
166
+ return result
167
+
168
+
169
+ # ======================================================================
170
+ # 画面工具
171
+ # ======================================================================
172
+ def rounded_rect(draw, box, radius=16, fill=PANEL, outline=PANEL_EDGE, width=2):
173
+ draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width)
174
+
175
+
176
+ def draw_text(draw, xy, text, font=FONT_BODY, fill=TEXT, anchor="la"):
177
+ draw.text(xy, text, font=font, fill=fill, anchor=anchor)
178
+
179
+
180
+ def fit_image(frame, max_w, max_h, nearest=True):
181
+ """把 env.render() 的一帧缩放后居中贴到画布上(像素风用最近邻更清晰)。"""
182
+ img = Image.fromarray(np.asarray(frame, dtype=np.uint8))
183
+ scale = min(max_w / img.width, max_h / img.height)
184
+ new_size = (max(1, int(img.width * scale)), max(1, int(img.height * scale)))
185
+ resample = Image.NEAREST if nearest else Image.LANCZOS
186
+ return img.resize(new_size, resample)
187
+
188
+
189
+ def base_canvas():
190
+ canvas = Image.new("RGB", (WIDTH, HEIGHT), BG)
191
+ draw = ImageDraw.Draw(canvas)
192
+ # 顶栏
193
+ draw.rectangle([0, 0, WIDTH, HEADER_H], fill=(15, 23, 42))
194
+ draw.line([0, HEADER_H, WIDTH, HEADER_H], fill=PANEL_EDGE, width=2)
195
+ # 底栏
196
+ draw.rectangle([0, HEIGHT - FOOTER_H, WIDTH, HEIGHT], fill=(15, 23, 42))
197
+ draw.line([0, HEIGHT - FOOTER_H, WIDTH, HEIGHT - FOOTER_H], fill=PANEL_EDGE, width=2)
198
+ return canvas, draw
199
+
200
+
201
+ def title_card(model, result):
202
+ """片头标题卡。"""
203
+ canvas, draw = base_canvas()
204
+ draw_text(draw, (WIDTH // 2, 150), "Q-Learning 智能体 · 训练后回放", FONT_TITLE,
205
+ fill=ACCENT, anchor="mm")
206
+ draw_text(draw, (WIDTH // 2, 215), model.get("title", model["env_id"]), FONT_H1,
207
+ fill=TEXT, anchor="mm")
208
+ draw.line([WIDTH // 2 - 220, 260, WIDTH // 2 + 220, 260], fill=PANEL_EDGE, width=2)
209
+
210
+ # 中间:三个信息块
211
+ blocks = [
212
+ ("算法", "Q-Learning(从零手写 Q 表)"),
213
+ ("训练规模", f"{model['n_training_episodes']:,} 回合"),
214
+ ("Q 表", f"{model['qtable'].shape[0]} 状态 × {model['qtable'].shape[1]} 动作"),
215
+ ("超参数", f"lr={model['learning_rate']} γ={model['gamma']} "
216
+ f"ε {model['max_epsilon']}→{model['min_epsilon']}"),
217
+ ]
218
+ y = 310
219
+ for k, v in blocks:
220
+ draw_text(draw, (WIDTH // 2 - 300, y), f"{k}", FONT_SMALL, fill=MUTED)
221
+ draw_text(draw, (WIDTH // 2 - 160, y), f"{v}", FONT_BODY, fill=TEXT)
222
+ y += 46
223
+
224
+ draw_text(draw, (WIDTH // 2, 570),
225
+ f"独立评估 {result['n_episodes']} 局:"
226
+ f"平均回报 {result['mean_reward']:.2f} ± {result['std_reward']:.2f} "
227
+ f"成功率 {result['success_rate'] * 100:.0f}%",
228
+ FONT_H2, fill=GREEN, anchor="mm")
229
+ draw_text(draw, (WIDTH // 2, 615),
230
+ "对应《第3周-深度强化学习原理实践》PPT 第 27–40 页 · value-based 实践",
231
+ FONT_SMALL, fill=MUTED, anchor="mm")
232
+ draw_text(draw, (WIDTH // 2, 640), "模型文件:models/" + os.path.basename(model["_path"]),
233
+ FONT_SMALL, fill=MUTED, anchor="mm")
234
+ return canvas
235
+
236
+
237
+ def score_card(model, result):
238
+ """片尾分数卡。"""
239
+ canvas, draw = base_canvas()
240
+ draw_text(draw, (WIDTH // 2, 120), "评估结果", FONT_TITLE, fill=ACCENT, anchor="mm")
241
+ draw_text(draw, (WIDTH // 2, 185), model.get("title", model["env_id"]), FONT_H1,
242
+ fill=TEXT, anchor="mm")
243
+
244
+ # 大号分数
245
+ mean, std = result["mean_reward"], result["std_reward"]
246
+ draw_text(draw, (WIDTH // 2 - 200, 300), "平均回报", FONT_BODY, fill=MUTED, anchor="mm")
247
+ draw_text(draw, (WIDTH // 2 - 200, 360), f"{mean:.2f}", load_font(64, bold=True),
248
+ fill=GREEN if mean > 0 else RED, anchor="mm")
249
+ draw_text(draw, (WIDTH // 2 - 200, 410), f"± {std:.2f}", FONT_H2, fill=MUTED, anchor="mm")
250
+
251
+ draw_text(draw, (WIDTH // 2 + 60, 300), "成功率", FONT_BODY, fill=MUTED, anchor="mm")
252
+ draw_text(draw, (WIDTH // 2 + 60, 360), f"{result['success_rate'] * 100:.0f}%",
253
+ load_font(64, bold=True), fill=ACCENT, anchor="mm")
254
+
255
+ draw_text(draw, (WIDTH // 2 + 300, 300), "平均步数", FONT_BODY, fill=MUTED, anchor="mm")
256
+ draw_text(draw, (WIDTH // 2 + 300, 360), f"{result['mean_steps']:.1f}",
257
+ load_font(64, bold=True), fill=AMBER, anchor="mm")
258
+
259
+ draw_text(draw, (WIDTH // 2, 505),
260
+ f"{result['n_episodes']} 局独立评估 · 贪婪策略 argmax_a Q(s,a)",
261
+ FONT_BODY, fill=TEXT, anchor="mm")
262
+ # 迷你分数分布
263
+ rewards = [e["reward"] for e in result["episodes"]]
264
+ values, counts = np.unique(np.round(rewards, 2), return_counts=True)
265
+ draw_text(draw, (WIDTH // 2, 555),
266
+ "逐局回报分布:" + " ".join(f"{v:g}×{c}" for v, c in zip(values[:8], counts[:8]))
267
+ + (" …" if len(values) > 8 else ""),
268
+ FONT_SMALL, fill=MUTED, anchor="mm")
269
+ draw_text(draw, (WIDTH // 2, 620),
270
+ "模型与代码已打包上传 Hub:附视频回放 + 评估分数",
271
+ FONT_SMALL, fill=MUTED, anchor="mm")
272
+ return canvas
273
+
274
+
275
+ def draw_q_bars(draw, q_row, action_names, chosen_action, x, y, width):
276
+ """把当前状态各动作的 Q 值画成水平条形图(直观看出贪心策略选了什么)。"""
277
+ q = np.asarray(q_row, dtype=float)
278
+ lo, hi = float(q.min()), float(q.max())
279
+ span = hi - lo if hi - lo > 1e-9 else 1.0
280
+ bar_h = 26
281
+ gap = 10
282
+ for a in range(len(q)):
283
+ yy = y + a * (bar_h + gap)
284
+ chosen = a == chosen_action
285
+ fill = ACCENT if chosen else (71, 85, 105)
286
+ # 动作标签
287
+ draw_text(draw, (x, yy + bar_h // 2), action_names[a], FONT_SMALL,
288
+ fill=TEXT if chosen else MUTED, anchor="lm")
289
+ # 条形
290
+ left = x + 150
291
+ draw.rectangle([left, yy, left + width, yy + bar_h], fill=(51, 65, 85))
292
+ frac = (q[a] - lo) / span
293
+ draw.rectangle([left, yy, left + max(3, int(width * frac)), yy + bar_h], fill=fill)
294
+ # 数值
295
+ draw_text(draw, (left + width + 12, yy + bar_h // 2), f"{q[a]:.3f}", FONT_SMALL,
296
+ fill=ACCENT if chosen else MUTED, anchor="lm")
297
+ if chosen:
298
+ draw_text(draw, (left + width + 78, yy + bar_h // 2), "← 最优", FONT_SMALL,
299
+ fill=ACCENT, anchor="lm")
300
+
301
+
302
+ def game_frame(model, env, state, step, episode_idx, n_video_eps, total_reward,
303
+ last_reward, action, ended, success, eval_result):
304
+ """合成一帧:左边是环境画面,右边是 Q 值/动作/奖励信息。"""
305
+ canvas, draw = base_canvas()
306
+
307
+ title = model.get("title", model["env_id"])
308
+ draw_text(draw, (32, HEADER_H // 2), f"Q-Learning 智能体回放 · {title}",
309
+ FONT_H2, fill=TEXT, anchor="lm")
310
+ draw_text(draw, (WIDTH - 32, HEADER_H // 2),
311
+ "PPT 第 27–40 页 · value-based", FONT_SMALL, fill=MUTED, anchor="rm")
312
+
313
+ # ---------- 左边:环境画面 ----------
314
+ box = [32, HEADER_H + 22, 672, HEIGHT - FOOTER_H - 22]
315
+ rounded_rect(draw, box)
316
+ raw = env.render()
317
+ if raw is not None:
318
+ inner_w = box[2] - box[0] - 24
319
+ inner_h = box[3] - box[1] - 24
320
+ img = fit_image(raw, inner_w, inner_h)
321
+ px = box[0] + (box[2] - box[0] - img.width) // 2
322
+ py = box[1] + (box[3] - box[1] - img.height) // 2
323
+ canvas.paste(img, (px, py))
324
+
325
+ # ---------- 右边:信息面板 ----------
326
+ px0, px1 = 700, WIDTH - 32
327
+ rounded_rect(draw, [px0, HEADER_H + 22, px1, HEIGHT - FOOTER_H - 22])
328
+
329
+ x = px0 + 24
330
+ y = HEADER_H + 50
331
+ draw_text(draw, (x, y), f"第 {episode_idx + 1} / {n_video_eps} 局", FONT_H2, fill=TEXT)
332
+ y += 34
333
+
334
+ # Taxi 额外显示乘客/目的地
335
+ if model["env_id"] == "Taxi-v3":
336
+ taxi_row, taxi_col, pass_idx, dest_idx = env.unwrapped.decode(int(state))
337
+ pass_text = "在车上" if pass_idx == 4 else TAXI_LOC_NAMES[pass_idx] + " 处"
338
+ draw_text(draw, (x, y), f"出租车 ({taxi_row},{taxi_col}) 乘客 {pass_text} "
339
+ f"目的地 {TAXI_LOC_NAMES[dest_idx]}", FONT_SMALL, fill=MUTED)
340
+ y += 30
341
+ else:
342
+ draw_text(draw, (x, y), f"状态编号 s = {int(state)}", FONT_SMALL, fill=MUTED)
343
+ y += 30
344
+
345
+ # 步数 / 动作 / 奖励
346
+ draw_text(draw, (x, y), "步数", FONT_SMALL, fill=MUTED)
347
+ draw_text(draw, (x + 70, y), f"{step}", FONT_H2, fill=TEXT)
348
+ draw_text(draw, (x + 180, y), "动作", FONT_SMALL, fill=MUTED)
349
+ draw_text(draw, (x + 250, y),
350
+ ACTION_NAMES[model["env_id"]][action] if action is not None else "—",
351
+ FONT_H2, fill=ACCENT)
352
+ y += 38
353
+
354
+ draw_text(draw, (x, y), "单步奖励", FONT_SMALL, fill=MUTED)
355
+ draw_text(draw, (x + 120, y), f"{last_reward:+.2f}" if last_reward is not None else "—",
356
+ FONT_H2, fill=GREEN if (last_reward or 0) > 0 else TEXT)
357
+ draw_text(draw, (x + 260, y), "累计回报", FONT_SMALL, fill=MUTED)
358
+ draw_text(draw, (x + 360, y), f"{total_reward:+.2f}", FONT_H2,
359
+ fill=GREEN if total_reward > 0 else TEXT)
360
+ y += 44
361
+
362
+ # Q 值条形图(终局状态不再更新 Q,改成提示文字)
363
+ if ended:
364
+ draw_text(draw, (x, y), "终局状态:回合结束,Q 表在这一帧不再更新", FONT_SMALL, fill=MUTED)
365
+ else:
366
+ draw_text(draw, (x, y), f"Q(s={int(state)}, ·) ← 贪心策略从这里选动作", FONT_SMALL, fill=MUTED)
367
+ y += 30
368
+ q = model["qtable"][int(state)]
369
+ chosen = int(np.argmax(q))
370
+ bars_width = 180
371
+ draw_q_bars(draw, q, ACTION_SHORT[model["env_id"]], chosen, x, y, bars_width)
372
+
373
+ # 回合结束横幅
374
+ if ended:
375
+ banner_y = HEIGHT - FOOTER_H - 105
376
+ if model["env_id"] == "Taxi-v3":
377
+ text, color = ("√ 成功送达乘客(+20 奖励)", GREEN) if success else \
378
+ ("× 超时 / 未送达(回合结束)", RED)
379
+ else:
380
+ text, color = ("√ 成功到达目标(+1 奖励)", GREEN) if success else \
381
+ ("× 掉进冰洞(奖励 0,回合结束)", RED)
382
+ draw.rounded_rectangle([px0 + 20, banner_y, px1 - 20, banner_y + 62],
383
+ radius=12, fill=(color[0] // 4, color[1] // 4, color[2] // 4),
384
+ outline=color, width=2)
385
+ draw_text(draw, ((px0 + px1) // 2, banner_y + 31), text, FONT_H2, fill=color, anchor="mm")
386
+
387
+ # ---------- 底栏:总体评估分数 ----------
388
+ draw_text(draw, (32, HEIGHT - FOOTER_H // 2),
389
+ f"评估 {eval_result['n_episodes']} 局:平均回报 "
390
+ f"{eval_result['mean_reward']:.2f} ± {eval_result['std_reward']:.2f} "
391
+ f"成功率 {eval_result['success_rate'] * 100:.0f}% "
392
+ f"平均步数 {eval_result['mean_steps']:.1f}",
393
+ FONT_SMALL, fill=MUTED, anchor="lm")
394
+ draw_text(draw, (WIDTH - 32, HEIGHT - FOOTER_H // 2), "贪心策略 argmax_a Q(s,a)",
395
+ FONT_SMALL, fill=MUTED, anchor="rm")
396
+ return canvas
397
+
398
+
399
+ # ======================================================================
400
+ # 录像
401
+ # ======================================================================
402
+ def record_video(env, model, result, out_path, n_video_episodes, slowmo, fps):
403
+ q = model["qtable"]
404
+ max_steps = model["max_steps"]
405
+ seeds = model.get("eval_seed")
406
+ writer = imageio.get_writer(out_path, fps=fps, codec="libx264", quality=8,
407
+ macro_block_size=2)
408
+ n_frames = 0
409
+
410
+ def push(img, repeat=1):
411
+ nonlocal n_frames
412
+ frame = np.asarray(img, dtype=np.uint8)
413
+ for _ in range(repeat):
414
+ writer.append_data(frame)
415
+ n_frames += 1
416
+
417
+ # 片头
418
+ push(title_card(model, result), int(TITLE_SECONDS * fps))
419
+ hold_end = int(HOLD_LAST * fps)
420
+
421
+ for ep in range(n_video_episodes):
422
+ if seeds is not None and ep < len(seeds):
423
+ state, _ = env.reset(seed=int(seeds[ep]))
424
+ else:
425
+ state, _ = env.reset()
426
+
427
+ total_reward = 0.0
428
+ last_reward = None
429
+ prev_reward = None
430
+ last_action = None
431
+ step = 0
432
+ for step in range(max_steps):
433
+ action = greedy_action(q, state)
434
+ last_action = action
435
+ new_state, reward, terminated, truncated, _ = env.step(action)
436
+ done = terminated or truncated
437
+ total_reward += float(reward)
438
+ success = total_reward > 0
439
+ last_reward = float(reward)
440
+ # 这一步的"决策画面":画面里的状态是"还没执行这个动作之前"的状态,
441
+ # 所以累计回报用执行前的值,单步奖励显示上一步拿到的奖励
442
+ push(game_frame(model, env, state, step, ep, n_video_episodes,
443
+ total_reward - last_reward, prev_reward, action, False, success, result),
444
+ slowmo)
445
+ prev_reward = last_reward
446
+ state = new_state
447
+ if done:
448
+ break
449
+ # 回合结束:定格 + 结束横幅
450
+ ended_frame = game_frame(model, env, state, step + 1, ep, n_video_episodes,
451
+ total_reward, last_reward, last_action, True,
452
+ total_reward > 0, result)
453
+ push(ended_frame, hold_end)
454
+ print(f" [录像] 第 {ep + 1}/{n_video_episodes} 局:回报={total_reward:+.2f},"
455
+ f"步数={step + 1},{'√ 成功' if total_reward > 0 else '× 失败'}")
456
+
457
+ # 片尾分数卡
458
+ push(score_card(model, result), int(END_SECONDS * fps))
459
+ writer.close()
460
+ return n_frames
461
+
462
+
463
+ # ======================================================================
464
+ # 主流程
465
+ # ======================================================================
466
+ def main():
467
+ parser = argparse.ArgumentParser(description="评估 Q-Learning 智能体并录制回放视频")
468
+ parser.add_argument("--model", type=str, required=True, help="models/qtable_*.pkl")
469
+ parser.add_argument("--episodes", type=int, default=None,
470
+ help="评估局数(默认用模型里记录的 n_eval_episodes)")
471
+ parser.add_argument("--video-episodes", type=int, default=8, help="录几局回放(默认 8)")
472
+ parser.add_argument("--slowmo", type=int, default=SLOWMO, help="每个动作帧重复几次(默认 6)")
473
+ parser.add_argument("--fps", type=int, default=FPS, help="视频帧率(默认 30)")
474
+ parser.add_argument("--out-video", type=str, default=None, help="视频输出路径(.mp4)")
475
+ parser.add_argument("--out-json", type=str, default=None, help="评估结果输出路径(.json)")
476
+ parser.add_argument("--skip-video", action="store_true",
477
+ help="只评估、不录视频(改完分数想快速重算时用)")
478
+ args = parser.parse_args()
479
+
480
+ model = load_model(args.model)
481
+ model["_path"] = os.path.abspath(args.model)
482
+ model["run_name"] = os.path.splitext(os.path.basename(args.model))[0].replace("qtable_", "")
483
+ if not model.get("title"):
484
+ model["title"] = model["env_id"]
485
+
486
+ os.makedirs(EVAL_DIR, exist_ok=True)
487
+ os.makedirs(VIDEO_DIR, exist_ok=True)
488
+
489
+ run = model["run_name"]
490
+ out_json = args.out_json or os.path.join(EVAL_DIR, f"eval_{run}.json")
491
+ out_video = args.out_video or os.path.join(VIDEO_DIR, f"replay_{run}.mp4")
492
+
493
+ n_eval = args.episodes or model.get("n_eval_episodes", 100)
494
+ seeds = model.get("eval_seed")
495
+ if seeds is not None and len(seeds) < n_eval:
496
+ seeds = None # 种子不够就不指定,退回随机起点
497
+
498
+ print("=" * 78)
499
+ print(f"评估 + 录像 · {model['title']}")
500
+ print(f"模型文件:{os.path.relpath(model['_path'], BASE_DIR)}")
501
+ print(f"贪婪策略:argmax_a Q(s,a) Q 表形状:{model['qtable'].shape}")
502
+ print("=" * 78)
503
+
504
+ env = make_env(model)
505
+ print(f"\n[评估] 用贪婪策略跑 {n_eval} 局(独立环境,起点种子:"
506
+ f"{'模型自带的 ' + str(len(seeds)) + ' 个种子' if seeds is not None else '随机'})")
507
+ started = time.time()
508
+ result = evaluate(env, model, n_eval, seeds)
509
+ result["evaluation_seconds"] = round(time.time() - started, 2)
510
+ print(f" Mean_reward = {result['mean_reward']:.2f} +/- {result['std_reward']:.2f}")
511
+ print(f" 成功率 = {result['success_rate'] * 100:.0f}%")
512
+ print(f" 平均步数 = {result['mean_steps']:.1f}")
513
+ print(f" 回报范围 = [{result['min_reward']:.2f}, {result['max_reward']:.2f}]")
514
+ with open(out_json, "w", encoding="utf-8") as f:
515
+ json.dump(result, f, ensure_ascii=False, indent=2)
516
+ print(f"[输出] 评估分数 = {os.path.relpath(out_json, BASE_DIR)}")
517
+
518
+ if args.skip_video:
519
+ print("\n[跳过录像](--skip-video)")
520
+ env.close()
521
+ print("\n" + "=" * 78)
522
+ print(f"完成。评估分数 {result['mean_reward']:.2f} ± {result['std_reward']:.2f},"
523
+ f"成功率 {result['success_rate'] * 100:.0f}%")
524
+ print("=" * 78)
525
+ return
526
+
527
+ print(f"\n[录像] 录 {args.video_episodes} 局回放,每帧慢放 ×{args.slowmo},{args.fps}fps")
528
+ n_frames = record_video(env, model, result, out_video, args.video_episodes,
529
+ args.slowmo, args.fps)
530
+ secs = n_frames / args.fps
531
+ print(f"[输出] 回放视频 = {os.path.relpath(out_video, BASE_DIR)} "
532
+ f"({n_frames} 帧 / {secs:.1f} 秒 / {os.path.getsize(out_video) / 1024:.0f} KB)")
533
+ env.close()
534
+
535
+ print("\n" + "=" * 78)
536
+ print(f"完成。评估分数 {result['mean_reward']:.2f} ± {result['std_reward']:.2f},"
537
+ f"成功率 {result['success_rate'] * 100:.0f}%")
538
+ print("=" * 78)
539
+
540
+
541
+ if __name__ == "__main__":
542
+ main()
code/make_combined_video.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 把三个智能体的回放视频拼成一支"总回放"视频(方便上传 Hub / 交作业)。
4
+ ==============================================================
5
+ 输入:videos/replay_frozenlake4x4.mp4、replay_frozenlake8x8.mp4、replay_taxi.mp4
6
+ 输出:videos/replay_value_based_all.mp4(1280x720 / 30fps,开头有总览卡,结尾有分数汇总卡)
7
+
8
+ 用法:
9
+ D:\\study\\robot\\pyenv\\DRL\\python.exe make_combined_video.py
10
+ """
11
+
12
+ import json
13
+ import os
14
+
15
+ import imageio.v2 as imageio
16
+ import numpy as np
17
+
18
+ import evaluate_and_record as ev # 复用录像脚本里的画布/字体/配色
19
+
20
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
21
+ VIDEO_DIR = os.path.join(BASE_DIR, "videos")
22
+ EVAL_DIR = os.path.join(BASE_DIR, "eval_results")
23
+
24
+ FPS = ev.FPS
25
+ RUNS = [
26
+ ("frozenlake4x4", "FrozenLake-v1 · 4x4 不滑", "PPT 第一部分(10,000 回合)"),
27
+ ("frozenlake8x8", "FrozenLake-v1 · 8x8", "进阶:地图变大 + 探索调度"),
28
+ ("taxi", "Taxi-v3", "PPT 第二部分(25,000 回合)"),
29
+ ]
30
+
31
+
32
+ def load_scores():
33
+ scores = {}
34
+ for run, _, _ in RUNS:
35
+ path = os.path.join(EVAL_DIR, f"eval_{run}.json")
36
+ if os.path.exists(path):
37
+ with open(path, encoding="utf-8") as f:
38
+ scores[run] = json.load(f)
39
+ return scores
40
+
41
+
42
+ def intro_card(scores):
43
+ canvas, draw = ev.base_canvas()
44
+ ev.draw_text(draw, (ev.WIDTH // 2, 110), "value-based 实践 · Q-Learning 智能体回放合集",
45
+ ev.FONT_TITLE, fill=ev.ACCENT, anchor="mm")
46
+ ev.draw_text(draw, (ev.WIDTH // 2, 165),
47
+ "从零手写 Q 表 + epsilon-greedy + Bellman 更新(PPT 第 27–40 页)",
48
+ ev.FONT_BODY, fill=ev.TEXT, anchor="mm")
49
+ draw.line([120, 205, ev.WIDTH - 120, 205], fill=ev.PANEL_EDGE, width=2)
50
+
51
+ y = 265
52
+ for i, (run, name, note) in enumerate(RUNS):
53
+ s = scores.get(run)
54
+ ev.draw_text(draw, (110, y), f"{i + 1}. {name}", ev.FONT_H2, fill=ev.TEXT)
55
+ ev.draw_text(draw, (110, y + 34), note, ev.FONT_SMALL, fill=ev.MUTED)
56
+ if s:
57
+ score_text = f"{s['mean_reward']:.2f} ± {s['std_reward']:.2f}"
58
+ ev.draw_text(draw, (ev.WIDTH - 110, y + 16), score_text, ev.FONT_H1,
59
+ fill=ev.GREEN if s["mean_reward"] > 0 else ev.RED, anchor="rm")
60
+ ev.draw_text(draw, (ev.WIDTH - 110, y + 46),
61
+ f"成功率 {s['success_rate'] * 100:.0f}% "
62
+ f"平均步数 {s['mean_steps']:.1f}",
63
+ ev.FONT_SMALL, fill=ev.MUTED, anchor="rm")
64
+ y += 95
65
+
66
+ ev.draw_text(draw, (ev.WIDTH // 2, 620),
67
+ "每段回放都标注了步数 / 动作 / Q 值条形图,片尾有该智能体的评估分数",
68
+ ev.FONT_SMALL, fill=ev.MUTED, anchor="mm")
69
+ return canvas
70
+
71
+
72
+ def end_card(scores):
73
+ canvas, draw = ev.base_canvas()
74
+ ev.draw_text(draw, (ev.WIDTH // 2, 105), "三个智能体评估分数汇总", ev.FONT_TITLE,
75
+ fill=ev.ACCENT, anchor="mm")
76
+ ev.draw_text(draw, (ev.WIDTH // 2, 155),
77
+ "独立环境 · 贪婪策略 argmax_a Q(s,a) · 100 局", ev.FONT_BODY,
78
+ fill=ev.MUTED, anchor="mm")
79
+ draw.line([120, 195, ev.WIDTH - 120, 195], fill=ev.PANEL_EDGE, width=2)
80
+
81
+ y = 265
82
+ for run, name, _ in RUNS:
83
+ s = scores.get(run)
84
+ if not s:
85
+ continue
86
+ ev.draw_text(draw, (120, y), name, ev.FONT_H2, fill=ev.TEXT)
87
+ ev.draw_text(draw, (560, y), f"回报 {s['mean_reward']:.2f} ± {s['std_reward']:.2f}",
88
+ ev.FONT_H2, fill=ev.GREEN if s["mean_reward"] > 0 else ev.RED)
89
+ ev.draw_text(draw, (880, y), f"成功率 {s['success_rate'] * 100:.0f}%",
90
+ ev.FONT_H2, fill=ev.ACCENT)
91
+ y += 90
92
+
93
+ ev.draw_text(draw, (ev.WIDTH // 2, 610),
94
+ "模型文件(Q 表 pkl)+ 训练代码 + 日志 已打包上传 Hub",
95
+ ev.FONT_BODY, fill=ev.TEXT, anchor="mm")
96
+ return canvas
97
+
98
+
99
+ def main():
100
+ scores = load_scores()
101
+ clips = []
102
+ for run, _, _ in RUNS:
103
+ path = os.path.join(VIDEO_DIR, f"replay_{run}.mp4")
104
+ if not os.path.exists(path):
105
+ print(f"[跳过] 找不到 {path},请先跑 evaluate_and_record.py --model models/qtable_{run}.pkl")
106
+ continue
107
+ clips.append(path)
108
+ if not clips:
109
+ raise SystemExit("没有可拼接的视频。")
110
+
111
+ out_path = os.path.join(VIDEO_DIR, "replay_value_based_all.mp4")
112
+ writer = imageio.get_writer(out_path, fps=FPS, codec="libx264", quality=8,
113
+ macro_block_size=2)
114
+ frames = 0
115
+
116
+ def push(img, repeat=1):
117
+ nonlocal frames
118
+ arr = np.asarray(img, dtype=np.uint8)
119
+ for _ in range(repeat):
120
+ writer.append_data(arr)
121
+ frames += 1
122
+
123
+ push(intro_card(scores), int(3.0 * FPS))
124
+ for path in clips:
125
+ reader = imageio.get_reader(path)
126
+ n = 0
127
+ for frame in reader:
128
+ writer.append_data(frame)
129
+ frames += 1
130
+ n += 1
131
+ reader.close()
132
+ print(f" [拼接] {os.path.basename(path)}:{n} 帧")
133
+ push(end_card(scores), int(5.0 * FPS))
134
+
135
+ writer.close()
136
+ size_mb = os.path.getsize(out_path) / (1024 * 1024)
137
+ print(f"[输出] 总回放视频 = videos/{os.path.basename(out_path)} "
138
+ f"({frames} 帧 / {frames / FPS:.1f} 秒 / {size_mb:.1f} MB)")
139
+
140
+
141
+ if __name__ == "__main__":
142
+ main()
code/train_qlearning.py ADDED
@@ -0,0 +1,604 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 第3周 · 实战3(value-based / Q-Learning)—— 训练脚本
4
+ =====================================================
5
+ 对应 PPT《第3周-深度强化学习原理实践》第 27–40 页:
6
+ · 第 29 页:Q 表 / Q 函数 / 最优策略 pi*(s) = argmax_a Q*(s,a)
7
+ · 第 31–32 页:FrozenLake-v1(4x4 不滑版)观测/动作/奖励
8
+ · 第 33 页:Q 表初始化 + 贪心策略
9
+ · 第 34 页:epsilon-greedy 行为策略 + 超参数
10
+ · 第 35 页:Q-Learning 训练循环(Bellman 更新)
11
+ · 第 36–37 页:训练、Q 表可视化、评估
12
+ · 第 38–40 页:Taxi-v3
13
+
14
+ 本脚本从零手写 Q-Learning(不依赖 stable-baselines3),一次跑通三个环境:
15
+ frozenlake4x4 FrozenLake-v1 4x4,is_slippery=False(PPT 第一部分,确定性)
16
+ frozenlake8x8 FrozenLake-v1 8x8,is_slippery=True (PPT 第 31 页提到的进阶版本,随机性)
17
+ taxi Taxi-v3(PPT 第二部分,500 个状态 × 6 个动作)
18
+
19
+ 输出(全部落在本目录下):
20
+ logs/<run>/train_console.log 完整控制台日志(含环境自检/超参/每 N 回合进度/每次评估)
21
+ logs/<run>/progress.csv 结构化进度数据(画曲线 / 写报告用)
22
+ logs/<run>/train_curve.png 训练曲线(回报、epsilon、成功率、评估)
23
+ models/qtable_<run>.pkl 训练出的模型文件(Q 表 + 全套配置,可被 evaluate_and_record.py 直接加载)
24
+
25
+ 用法(本机已装好 DRL 环境,无需再装包):
26
+ D:\\study\\robot\\pyenv\\DRL\\python.exe train_qlearning.py --env frozenlake4x4
27
+ D:\\study\\robot\\pyenv\\DRL\\python.exe train_qlearning.py --env frozenlake8x8
28
+ D:\\study\\robot\\pyenv\\DRL\\python.exe train_qlearning.py --env taxi
29
+
30
+ 只改一个变量做对照实验(例如演示"epsilon 衰减太慢"):
31
+ python train_qlearning.py --env frozenlake4x4 --decay-rate 0.0001 --run-name fl4x4_slow_decay
32
+ """
33
+
34
+ import argparse
35
+ import csv
36
+ import os
37
+ import pickle
38
+ import random
39
+ import sys
40
+ import time
41
+ from datetime import datetime
42
+
43
+ import gymnasium as gym
44
+ import matplotlib
45
+ matplotlib.use("Agg") # 不弹窗,直接存 png
46
+ import matplotlib.pyplot as plt
47
+ import numpy as np
48
+
49
+ # ======================================================================
50
+ # 全局路径
51
+ # ======================================================================
52
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
53
+ LOG_ROOT = os.path.join(BASE_DIR, "logs")
54
+ MODEL_ROOT = os.path.join(BASE_DIR, "models")
55
+
56
+ # 中文字体:Windows 自带的微软雅黑 / 黑体
57
+ plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "DejaVu Sans"]
58
+ plt.rcParams["axes.unicode_minus"] = False
59
+
60
+ # ======================================================================
61
+ # 第 38–39 页:Taxi-v3 官方评估种子(PPT 原文:请勿修改 EVAL_SEED)
62
+ # 作用:让所有同学都在相同的出租车起始位置上评估,班级分数才可比。
63
+ # ======================================================================
64
+ EVAL_SEED_TAXI = [
65
+ 16, 54, 165, 177, 191, 191, 120, 80, 149, 178, 48, 38, 6, 125, 174, 73, 50,
66
+ 172, 100, 148, 146, 6, 25, 40, 68, 148, 49, 167, 9, 97, 164, 176, 61, 7, 54,
67
+ 55, 161, 131, 184, 51, 170, 12, 120, 113, 95, 126, 51, 98, 36, 135, 54, 82,
68
+ 45, 95, 89, 59, 95, 124, 9, 113, 58, 85, 51, 134, 121, 169, 105, 21, 30, 11,
69
+ 50, 65, 12, 43, 82, 145, 152, 97, 106, 55, 31, 85, 38, 112, 102, 168, 123, 97,
70
+ 21, 83, 158, 26, 80, 63, 5, 81, 32, 11, 28, 148,
71
+ ]
72
+
73
+ # ======================================================================
74
+ # 环境 + 超参数配置(严格对应 PPT 第 34 页与第 39 页)
75
+ # ======================================================================
76
+ EXPERIMENTS = {
77
+ # ---- 第一部分:冰冻湖 4x4,不滑(确定性) -------------------------
78
+ "frozenlake4x4": dict(
79
+ title="FrozenLake-v1(4x4,is_slippery=False)",
80
+ env_id="FrozenLake-v1",
81
+ env_kwargs=dict(map_name="4x4", is_slippery=False),
82
+ n_training_episodes=10000, # 第 34 页:总训练回合数
83
+ learning_rate=0.7, # 第 34 页:学习率
84
+ gamma=0.95, # 第 34 页:折扣率
85
+ max_steps=99, # 第 34 页:每回合最大步数
86
+ max_epsilon=1.0, # 第 34 页:开始时探索概率
87
+ min_epsilon=0.05, # 第 34 页:最小探索概率
88
+ decay_rate=0.0005, # 第 34 页:探索概率指数衰减率
89
+ n_eval_episodes=100, # 第 34 页:评估回合数
90
+ eval_seed=None, # 第 34 页 eval_seed = []
91
+ eval_every=1000,
92
+ log_every=500,
93
+ ),
94
+ # ---- 第 31 页提到的另一种模式:4x4 + 滑冰(is_slippery=True) ------
95
+ # 与第一部分只差一个变量 is_slippery,但冰面会以 1/3 概率把智能体推偏,
96
+ # 所以同样的超参数明显更难学:这里把探索期拉长(min_epsilon 0.1 / decay 0.0003)
97
+ # 并多训一倍回合,成功率才能从 ~20% 提到 ~75%。
98
+ "frozenlake4x4_slippery": dict(
99
+ title="FrozenLake-v1(4x4,is_slippery=True)",
100
+ env_id="FrozenLake-v1",
101
+ env_kwargs=dict(map_name="4x4", is_slippery=True),
102
+ n_training_episodes=20000,
103
+ learning_rate=0.7,
104
+ gamma=0.95,
105
+ max_steps=99,
106
+ max_epsilon=1.0,
107
+ min_epsilon=0.1,
108
+ decay_rate=0.0003,
109
+ n_eval_episodes=100,
110
+ eval_seed=list(range(100)),
111
+ eval_every=2000,
112
+ log_every=1000,
113
+ ),
114
+ # ---- 第 31 页提到的进阶版:冰冻湖 8x8(地图变大) -----------------
115
+ # 8x8 最优路径约 14 步,比 4x4 长一倍多;Q 表从 16x4 变成 64x4。
116
+ # 注意:8x8 的稀疏奖励让"随机探索"很难碰到终点,如果沿用 4x4 的快速 epsilon 衰减,
117
+ # Q 表会一直是全 0(这一点在实验报告里作为对照实验记录)。
118
+ # 所以这里把探索期拉长(min_epsilon 0.3 / decay 0.00003),让智能体先"逛遍"地图。
119
+ "frozenlake8x8": dict(
120
+ title="FrozenLake-v1(8x8,is_slippery=False)",
121
+ env_id="FrozenLake-v1",
122
+ env_kwargs=dict(map_name="8x8", is_slippery=False),
123
+ n_training_episodes=60000,
124
+ learning_rate=0.8,
125
+ gamma=0.95,
126
+ max_steps=200,
127
+ max_epsilon=1.0,
128
+ min_epsilon=0.3,
129
+ decay_rate=0.00003,
130
+ n_eval_episodes=100,
131
+ eval_seed=list(range(100)),
132
+ eval_every=6000,
133
+ log_every=3000,
134
+ ),
135
+ # ---- 第二部分:Taxi-v3 -------------------------------------------
136
+ "taxi": dict(
137
+ title="Taxi-v3",
138
+ env_id="Taxi-v3",
139
+ env_kwargs=dict(),
140
+ n_training_episodes=25000, # 第 39 页:总训练回合数
141
+ learning_rate=0.7,
142
+ gamma=0.95,
143
+ max_steps=99,
144
+ max_epsilon=1.0,
145
+ min_epsilon=0.05,
146
+ decay_rate=0.005, # 第 39 页:0.005(比冰冻湖衰减快,因为回合更长)
147
+ n_eval_episodes=100,
148
+ eval_seed=EVAL_SEED_TAXI, # 第 39 页:官方评估种子
149
+ eval_every=2500,
150
+ log_every=1000,
151
+ ),
152
+ }
153
+
154
+
155
+ # ======================================================================
156
+ # 工具:同时写控制台 + 日志文件(这就是"有日志输出的训练代码")
157
+ # ======================================================================
158
+ class Tee:
159
+ """把 print 的内容同时写进 stdout 和日志文件。"""
160
+
161
+ def __init__(self, log_path):
162
+ self.terminal = sys.stdout
163
+ self.log_file = open(log_path, "w", encoding="utf-8")
164
+
165
+ def write(self, message):
166
+ self.terminal.write(message)
167
+ self.log_file.write(message)
168
+ self.log_file.flush()
169
+
170
+ def flush(self):
171
+ self.terminal.flush()
172
+ self.log_file.flush()
173
+
174
+
175
+ def timestamp():
176
+ return datetime.now().strftime("%H:%M:%S")
177
+
178
+
179
+ # ======================================================================
180
+ # 第 33 页:创建并初始化 Q 表
181
+ # Q 表大小 = (状态数, 动作数),初始全 0("在开始时 Q 表是无用的")
182
+ # ======================================================================
183
+ def initialize_q_table(state_space, action_space):
184
+ return np.zeros((state_space, action_space))
185
+
186
+
187
+ # ======================================================================
188
+ # 第 33 页:贪婪策略(更新策略 / 最终策略)
189
+ # pi*(s) = argmax_a Q(s,a)
190
+ # ======================================================================
191
+ def greedy_policy(q_table, state):
192
+ return int(np.argmax(q_table[state]))
193
+
194
+
195
+ # ======================================================================
196
+ # 第 34 页:epsilon-greedy 策略(行为策略)
197
+ # 以 epsilon 的概率探索(随机动作),否则利用(贪心动作)
198
+ # —— Q-Learning 是离线策略(off-policy):行动用 epsilon-greedy,
199
+ # 更新时却用 max_a Q(s', a),两套策略。
200
+ # ======================================================================
201
+ def epsilon_greedy_policy(q_table, state, epsilon, action_space):
202
+ if random.random() > epsilon:
203
+ return greedy_policy(q_table, state) # 利用
204
+ return int(action_space.sample()) # 探索
205
+
206
+
207
+ # ======================================================================
208
+ # 第 35 页:训练循环
209
+ # Q(s,a) <- Q(s,a) + lr * [ r + gamma * max_a' Q(s',a') - Q(s,a) ]
210
+ # ======================================================================
211
+ def train(
212
+ env,
213
+ q_table,
214
+ n_training_episodes,
215
+ max_steps,
216
+ learning_rate,
217
+ gamma,
218
+ max_epsilon,
219
+ min_epsilon,
220
+ decay_rate,
221
+ train_seed=0,
222
+ log_every=500,
223
+ eval_every=None,
224
+ eval_kwargs=None,
225
+ ):
226
+ """
227
+ 返回:q_table、每回合数据、评估历史
228
+ """
229
+ episode_rewards = []
230
+ episode_steps = []
231
+ episode_success = [] # 是否到达目标(FrozenLake 的 reward==1 / Taxi 的 reward==20)
232
+ epsilons = []
233
+ eval_history = [] # [(episode, mean, std, success_rate), ...]
234
+
235
+ started = time.time()
236
+ eval_kwargs = eval_kwargs or {}
237
+
238
+ for episode in range(n_training_episodes):
239
+ # ---- epsilon 指数衰减:越往后越少探索 ----
240
+ epsilon = min_epsilon + (max_epsilon - min_epsilon) * np.exp(-decay_rate * episode)
241
+ epsilons.append(epsilon)
242
+
243
+ # ---- 重置环境 ----
244
+ # 每回合都用固定种子 reset(seed=train_seed+episode):
245
+ # 同一个 --seed 重跑,起点序列、滑冰随机性和探索随机性完全一致 -> 分数可复现。
246
+ # 注意:gymnasium 的 env.reset(seed=...) 只给 env 播种,不给 action_space 播种,
247
+ # 而 epsilon-greedy 的"探索"要用 env.action_space.sample(),
248
+ # 所以这里再单独给 action_space 播种一次(这一行是可复现的关键)。
249
+ if train_seed is not None:
250
+ state, _ = env.reset(seed=train_seed + episode)
251
+ env.action_space.seed(train_seed + episode)
252
+ else:
253
+ state, _ = env.reset()
254
+
255
+ total_reward = 0.0
256
+ reached_goal = False
257
+
258
+ for step in range(max_steps):
259
+ # 1) 用 epsilon-greedy 选择动作 At
260
+ action = epsilon_greedy_policy(q_table, state, epsilon, env.action_space)
261
+
262
+ # 2) 执行动作,观察 Rt+1 和 St+1
263
+ new_state, reward, terminated, truncated, _ = env.step(action)
264
+ done = terminated or truncated
265
+
266
+ # 3) Bellman 更新(Q-Learning 的核心这一行)
267
+ # 注意:真实终止状态没有未来价值,next_q 取 0;
268
+ # PPT 截图里没做这个掩码,这里修正后结果更稳(FrozenLake 两者等价)。
269
+ next_q = 0.0 if terminated else float(np.max(q_table[new_state]))
270
+ q_table[state][action] += learning_rate * (
271
+ reward + gamma * next_q - q_table[state][action]
272
+ )
273
+
274
+ total_reward += reward
275
+ state = new_state
276
+ if done:
277
+ reached_goal = reward > 0
278
+ break
279
+
280
+ episode_rewards.append(total_reward)
281
+ episode_steps.append(step + 1)
282
+ episode_success.append(1 if reached_goal else 0)
283
+
284
+ # ---- 每 log_every 回合打印一行进度(可直接粘进实验报告)----
285
+ if (episode + 1) % log_every == 0 or episode == 0:
286
+ window = episode_rewards[-min(100, len(episode_rewards)):]
287
+ window_success = episode_success[-min(100, len(episode_success)):]
288
+ window_steps = episode_steps[-min(100, len(episode_steps)):]
289
+ elapsed_min = (time.time() - started) / 60.0
290
+ print(
291
+ f"[train] ep={episode + 1:>6}/{n_training_episodes} | "
292
+ f"eps={epsilon:.3f} | "
293
+ f"最近100回合平均回报={np.mean(window):6.2f} | "
294
+ f"成功率={np.mean(window_success) * 100:5.1f}% | "
295
+ f"平均步数={np.mean(window_steps):5.1f} | "
296
+ f"Q非零项={np.count_nonzero(q_table):5d} | "
297
+ f"用时={elapsed_min:5.2f}min"
298
+ )
299
+
300
+ # ---- 周期评估(用贪婪策略跑独立回合)----
301
+ if eval_every and (episode + 1) % eval_every == 0:
302
+ mean_r, std_r, succ = evaluate_agent(
303
+ env, q_table, max_steps, eval_kwargs.get("n_eval", 20),
304
+ eval_kwargs.get("eval_seed"),
305
+ )
306
+ eval_history.append((episode + 1, mean_r, std_r, succ))
307
+ print(
308
+ f"[eval ] ep={episode + 1:>6} | 贪婪策略 {eval_kwargs.get('n_eval', 20)} 局: "
309
+ f"回报={mean_r:6.2f} ± {std_r:5.2f} | 成功率={succ * 100:5.1f}%"
310
+ )
311
+
312
+ return q_table, episode_rewards, episode_steps, episode_success, epsilons, eval_history
313
+
314
+
315
+ # ======================================================================
316
+ # 第 36 页:评估智能体(贪婪策略,返回平均回报 ± 标准差)
317
+ # ======================================================================
318
+ def evaluate_agent(env, q_table, max_steps, n_episodes, seeds=None):
319
+ rewards = []
320
+ successes = []
321
+ lengths = []
322
+ for i in range(n_episodes):
323
+ if seeds is not None and i < len(seeds):
324
+ state, _ = env.reset(seed=int(seeds[i]))
325
+ else:
326
+ state, _ = env.reset()
327
+ total_reward = 0.0
328
+ for step in range(max_steps):
329
+ action = greedy_policy(q_table, state)
330
+ state, reward, terminated, truncated, _ = env.step(action)
331
+ total_reward += reward
332
+ if terminated or truncated:
333
+ break
334
+ rewards.append(total_reward)
335
+ lengths.append(step + 1)
336
+ successes.append(1 if total_reward > 0 else 0)
337
+ return float(np.mean(rewards)), float(np.std(rewards)), float(np.mean(successes))
338
+
339
+
340
+ # ======================================================================
341
+ # 画训练曲线
342
+ # ======================================================================
343
+ def moving_average(values, window=100):
344
+ values = np.asarray(values, dtype=float)
345
+ if len(values) < window:
346
+ return values
347
+ kernel = np.ones(window) / window
348
+ return np.convolve(values, kernel, mode="valid")
349
+
350
+
351
+ def plot_curve(run_dir, title, episode_rewards, epsilons, episode_success,
352
+ eval_history, n_training_episodes):
353
+ fig, axes = plt.subplots(2, 1, figsize=(12, 8), sharex=False)
354
+
355
+ # 上图:每回合回报 + 100 回合滑动平均 + 周期评估
356
+ ax = axes[0]
357
+ ax.plot(np.arange(1, len(episode_rewards) + 1), episode_rewards,
358
+ color="#93c5fd", linewidth=0.5, alpha=0.6, label="每回合回报")
359
+ ma = moving_average(episode_rewards, 100)
360
+ ax.plot(np.arange(100, 100 + len(ma)), ma,
361
+ color="#1d4ed8", linewidth=2, label="100 回合滑动平均")
362
+ if eval_history:
363
+ xs = [h[0] for h in eval_history]
364
+ ys = [h[1] for h in eval_history]
365
+ errs = [h[2] for h in eval_history]
366
+ ax.errorbar(xs, ys, yerr=errs, fmt="o--", color="#dc2626", markersize=4,
367
+ linewidth=1.2, capsize=3, label="周期评估(贪婪策略)")
368
+ ax.set_xlabel("训练回合 episode")
369
+ ax.set_ylabel("回报 reward")
370
+ ax.set_title(f"{title} · 训练曲线(共 {n_training_episodes} 回合)")
371
+ # 早期有几局极端负分(例如 Taxi 乱按上下客被 -10 罚到 -400),会把曲线压扁,
372
+ # 这里按 0.5%~99.5% 分位数裁一下 y 轴,极端点仍在图外但趋势看得清。
373
+ rewards_arr = np.asarray(episode_rewards, dtype=float)
374
+ low = min(float(np.percentile(rewards_arr, 0.5)), 0.0) - 2
375
+ high = max(float(np.percentile(rewards_arr, 99.5)), 1.0) + 1
376
+ if high - low > 5:
377
+ ax.set_ylim(low, high)
378
+ ax.legend(loc="lower right", fontsize=9)
379
+ ax.grid(alpha=0.3)
380
+
381
+ # 下图:epsilon 衰减 + 探索/利用的阶段
382
+ ax2 = axes[1]
383
+ ax2.plot(np.arange(1, len(epsilons) + 1), epsilons, color="#f59e0b", linewidth=2,
384
+ label="epsilon(探索概率)")
385
+ ax2.fill_between(np.arange(1, len(epsilons) + 1), 0, epsilons, color="#f59e0b", alpha=0.15)
386
+ ax2.set_xlabel("训练回合 episode")
387
+ ax2.set_ylabel("epsilon")
388
+ ax2.set_title("epsilon 指数衰减:从\"大胆探索\"到\"相信 Q 表\"")
389
+ ax2.grid(alpha=0.3)
390
+ ax2.legend(loc="upper right", fontsize=9)
391
+
392
+ fig.tight_layout()
393
+ path = os.path.join(run_dir, "train_curve.png")
394
+ fig.savefig(path, dpi=130)
395
+ plt.close(fig)
396
+ return path
397
+
398
+
399
+ def write_progress_csv(run_dir, rewards, steps, success, epsilons, eval_history):
400
+ path = os.path.join(run_dir, "progress.csv")
401
+ with open(path, "w", newline="", encoding="utf-8-sig") as f:
402
+ writer = csv.writer(f)
403
+ writer.writerow(["episode", "epsilon", "episode_reward", "episode_steps", "success"])
404
+ for i, (r, s, ok, eps) in enumerate(zip(rewards, steps, success, epsilons), start=1):
405
+ writer.writerow([i, f"{eps:.6f}", f"{r:.6f}", s, ok])
406
+ writer.writerow([])
407
+ writer.writerow(["eval_episode", "eval_mean_reward", "eval_std_reward", "eval_success_rate"])
408
+ for ep, mean_r, std_r, succ in eval_history:
409
+ writer.writerow([ep, f"{mean_r:.6f}", f"{std_r:.6f}", f"{succ:.4f}"])
410
+ return path
411
+
412
+
413
+ # ======================================================================
414
+ # 主流程
415
+ # ======================================================================
416
+ def main():
417
+ parser = argparse.ArgumentParser(description="Q-Learning 训练(FrozenLake / Taxi)")
418
+ parser.add_argument("--env", choices=list(EXPERIMENTS.keys()), default="frozenlake4x4",
419
+ help="用哪个环境训练")
420
+ parser.add_argument("--episodes", type=int, default=None, help="覆盖总训练回合数")
421
+ parser.add_argument("--learning-rate", type=float, default=None, help="覆盖学习率 lr")
422
+ parser.add_argument("--gamma", type=float, default=None, help="覆盖折扣率 gamma")
423
+ parser.add_argument("--decay-rate", type=float, default=None, help="覆盖 epsilon 衰减率")
424
+ parser.add_argument("--min-epsilon", type=float, default=None, help="覆盖最小探索概率")
425
+ parser.add_argument("--max-steps", type=int, default=None, help="覆盖每回合最大步数")
426
+ parser.add_argument("--eval-episodes", type=int, default=None, help="覆盖最终评估回合数")
427
+ parser.add_argument("--eval-every", type=int, default=None, help="每多少回合评估一次")
428
+ parser.add_argument("--log-every", type=int, default=None, help="每多少回合打印一行日志")
429
+ parser.add_argument("--seed", type=int, default=0, help="训练随机种子(默认 0)")
430
+ parser.add_argument("--run-name", type=str, default=None, help="日志/模型文件名前缀")
431
+ parser.add_argument("--slippery", action="store_true",
432
+ help="把 FrozenLake 的冰面切换成滑冰版(is_slippery=True,随机转移)")
433
+ args = parser.parse_args()
434
+
435
+ cfg = dict(EXPERIMENTS[args.env])
436
+ if args.slippery:
437
+ cfg["env_kwargs"] = dict(cfg["env_kwargs"], is_slippery=True)
438
+ cfg["title"] += " · 滑冰版"
439
+ # 滑冰版起点随机性大,固定 100 个起点种子才能公平比较
440
+ cfg["eval_seed"] = list(range(100))
441
+ # 命令行覆盖(方便做"一次只改一个变量"的对照实验)
442
+ if args.episodes is not None:
443
+ cfg["n_training_episodes"] = args.episodes
444
+ if args.learning_rate is not None:
445
+ cfg["learning_rate"] = args.learning_rate
446
+ if args.gamma is not None:
447
+ cfg["gamma"] = args.gamma
448
+ if args.decay_rate is not None:
449
+ cfg["decay_rate"] = args.decay_rate
450
+ if args.min_epsilon is not None:
451
+ cfg["min_epsilon"] = args.min_epsilon
452
+ if args.max_steps is not None:
453
+ cfg["max_steps"] = args.max_steps
454
+ if args.eval_episodes is not None:
455
+ cfg["n_eval_episodes"] = args.eval_episodes
456
+ if args.eval_every is not None:
457
+ cfg["eval_every"] = args.eval_every
458
+ if args.log_every is not None:
459
+ cfg["log_every"] = args.log_every
460
+ cfg["train_seed"] = args.seed
461
+
462
+ run_name = args.run_name or args.env
463
+ run_dir = os.path.join(LOG_ROOT, run_name)
464
+ os.makedirs(run_dir, exist_ok=True)
465
+ os.makedirs(MODEL_ROOT, exist_ok=True)
466
+
467
+ # 每次运行先设置随机种子:Python random 管探索、np 管 Q 表初始化
468
+ random.seed(args.seed)
469
+ np.random.seed(args.seed)
470
+
471
+ tee = Tee(os.path.join(run_dir, "train_console.log"))
472
+ sys.stdout = tee
473
+ try:
474
+ print("=" * 78)
475
+ print(f"Q-Learning 训练 · {cfg['title']}")
476
+ print(f"运行名称: {run_name} 开始时间: {datetime.now():%Y-%m-%d %H:%M:%S}")
477
+ print("=" * 78)
478
+
479
+ # ---------------- 第 31–32 页:创建并理解环境 ----------------
480
+ env = gym.make(cfg["env_id"], render_mode="rgb_array", **cfg["env_kwargs"])
481
+ state_space = env.observation_space.n
482
+ action_space = env.action_space.n
483
+ sample_state, _ = env.reset(seed=args.seed)
484
+
485
+ print("\n[环境自检]")
486
+ print(f" env_id = {cfg['env_id']}")
487
+ print(f" env_kwargs = {cfg['env_kwargs']}")
488
+ print(f" 观测空间 = {env.observation_space} -> {state_space} 个状态")
489
+ print(f" 动作空间 = {env.action_space} -> {action_space} 个动作")
490
+ if cfg["env_id"] == "FrozenLake-v1":
491
+ ncol = env.unwrapped.ncol
492
+ print(f" 一次 reset 的观测 = {sample_state}"
493
+ f"(整数 = 状态编号,{env.unwrapped.nrow}x{ncol} 地图按 row*{ncol}+col 编码)")
494
+ else:
495
+ print(f" 一次 reset 的观测 = {sample_state}(整数 = 状态编号,共 500 个)")
496
+ print(f" 每回合最大步数 = {cfg['max_steps']}")
497
+
498
+ # ---------------- 第 33 页:Q 表 ----------------
499
+ q_table = initialize_q_table(state_space, action_space)
500
+ print("\n[Q 表初始化]")
501
+ print(f" Q 表形状 = {q_table.shape}(= (状态数, 动作数)),初始值全为 0")
502
+ print(f" 需要学习的参数个数 = {state_space * action_space}")
503
+
504
+ # ---------------- 第 34 页:超参数 ----------------
505
+ print("\n[超参数]")
506
+ print(f" n_training_episodes = {cfg['n_training_episodes']}")
507
+ print(f" learning_rate = {cfg['learning_rate']}")
508
+ print(f" gamma = {cfg['gamma']}")
509
+ print(f" max_steps = {cfg['max_steps']}")
510
+ print(f" max_epsilon = {cfg['max_epsilon']}")
511
+ print(f" min_epsilon = {cfg['min_epsilon']}")
512
+ print(f" decay_rate = {cfg['decay_rate']}")
513
+ print(f" n_eval_episodes = {cfg['n_eval_episodes']}")
514
+ print(f" eval_seed = {'官方 100 个种子' if cfg['eval_seed'] else '[](不指定)'}")
515
+ print(f" train_seed = {args.seed}")
516
+
517
+ # ---------------- 第 35 页:训练 ----------------
518
+ print(f"\n[开始训练] {timestamp()} 每 {cfg['log_every']} 回合打印一行,"
519
+ f"每 {cfg['eval_every']} 回合评估一次")
520
+ print(f" 可复现性:每回合 env.reset(seed={args.seed}+episode),同 --seed 重跑分数一致")
521
+ started = time.time()
522
+ (q_table, rewards, steps, success, epsilons, eval_history) = train(
523
+ env=env,
524
+ q_table=q_table,
525
+ n_training_episodes=cfg["n_training_episodes"],
526
+ max_steps=cfg["max_steps"],
527
+ learning_rate=cfg["learning_rate"],
528
+ gamma=cfg["gamma"],
529
+ max_epsilon=cfg["max_epsilon"],
530
+ min_epsilon=cfg["min_epsilon"],
531
+ decay_rate=cfg["decay_rate"],
532
+ train_seed=args.seed,
533
+ log_every=cfg["log_every"],
534
+ eval_every=cfg["eval_every"],
535
+ eval_kwargs=dict(
536
+ n_eval=20,
537
+ eval_seed=cfg["eval_seed"],
538
+ ),
539
+ )
540
+ elapsed = (time.time() - started) / 60.0
541
+ print(f"\n[训练完成] {timestamp()} 总用时 = {elapsed:.2f} 分钟")
542
+
543
+ # ---------------- 第 36 页:最终评估 ----------------
544
+ print(f"\n[最终评估] 用贪婪策略跑 {cfg['n_eval_episodes']} 局(独立于训练)")
545
+ mean_r, std_r, succ = evaluate_agent(
546
+ env, q_table, cfg["max_steps"], cfg["n_eval_episodes"], cfg["eval_seed"]
547
+ )
548
+ print(f" Mean_reward = {mean_r:.2f} +/- {std_r:.2f}")
549
+ print(f" 成功率 = {succ * 100:.0f}%")
550
+ if eval_history:
551
+ best = max(eval_history, key=lambda h: h[1])
552
+ print(f" 训练途中最好的周期评估:{best[1]:.2f} ± {best[2]:.2f} @ 第 {best[0]} 回合")
553
+ print(f" Q 表非零项 = {np.count_nonzero(q_table)} / {q_table.size}")
554
+ print(f" Q 表最大价值 = {q_table.max():.4f}")
555
+
556
+ # ---------------- 第 36–37 页:Q 表可视化 + 训练曲线 ----------------
557
+ curve_path = plot_curve(run_dir, cfg["title"], rewards, epsilons, success,
558
+ eval_history, cfg["n_training_episodes"])
559
+ csv_path = write_progress_csv(run_dir, rewards, steps, success, epsilons, eval_history)
560
+ print(f"\n[输出] 训练曲线 = {os.path.relpath(curve_path, BASE_DIR)}")
561
+ print(f"[输出] 进度 CSV = {os.path.relpath(csv_path, BASE_DIR)}")
562
+
563
+ # ---------------- 第 37 页:保存模型(= "训练出的模型文件") ----------------
564
+ model = {
565
+ "run_name": run_name,
566
+ "title": cfg["title"],
567
+ "env_id": cfg["env_id"],
568
+ "env_kwargs": cfg["env_kwargs"],
569
+ "max_steps": cfg["max_steps"],
570
+ "n_training_episodes": cfg["n_training_episodes"],
571
+ "n_eval_episodes": cfg["n_eval_episodes"],
572
+ "eval_seed": cfg["eval_seed"],
573
+ "learning_rate": cfg["learning_rate"],
574
+ "gamma": cfg["gamma"],
575
+ "max_epsilon": cfg["max_epsilon"],
576
+ "min_epsilon": cfg["min_epsilon"],
577
+ "decay_rate": cfg["decay_rate"],
578
+ "train_seed": args.seed,
579
+ "qtable": q_table,
580
+ "metrics": {
581
+ "mean_reward": mean_r,
582
+ "std_reward": std_r,
583
+ "success_rate": succ,
584
+ "training_minutes": elapsed,
585
+ },
586
+ "trained_at": datetime.now().isoformat(timespec="seconds"),
587
+ }
588
+ model_path = os.path.join(MODEL_ROOT, f"qtable_{run_name}.pkl")
589
+ with open(model_path, "wb") as f:
590
+ pickle.dump(model, f)
591
+ print(f"[输出] 模型文件 = {os.path.relpath(model_path, BASE_DIR)} "
592
+ f"({os.path.getsize(model_path) / 1024:.1f} KB)")
593
+
594
+ print("\n" + "=" * 78)
595
+ print(f"完成。下一步:python evaluate_and_record.py --model models/qtable_{run_name}.pkl")
596
+ print("=" * 78)
597
+ env.close()
598
+ finally:
599
+ sys.stdout = tee.terminal
600
+ tee.log_file.close()
601
+
602
+
603
+ if __name__ == "__main__":
604
+ main()
code/upload_to_hub.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 把 hub_package/ 上传到 Hugging Face Hub(对应 PPT 第 28 页第 4 条学习目标)。
4
+ ================================================================
5
+ 等价于 PPT 里的 package_to_hub(...),只是拆成"先打包、再上传"两步,更透明。
6
+
7
+ 准备:
8
+ 1) 注册 https://huggingface.co ,在 Settings -> Access Tokens 建一个 write token
9
+ 2) 登录(二选一):
10
+ hf auth login # 交互式粘贴 token
11
+ 或者直接设环境变量:$env:HF_TOKEN = "hf_xxx"
12
+ 3) 打包:python build_hub_package.py --repo-id 你的用户名/value-based-qlearning
13
+
14
+ 上传:
15
+ python upload_to_hub.py --repo-id 你的用户名/value-based-qlearning
16
+ python upload_to_hub.py --repo-id 你的用户名/value-based-qlearning --private
17
+
18
+ 如果本机连不上 huggingface.co(本机实测会超时),有三条路:
19
+ A) 开代理后重试本脚本;
20
+ B) 换镜像(只能下载不能上传,不适用于上传);
21
+ C) 浏览器手动上传:https://huggingface.co/new 建仓库,把 hub_package/ 里的文件拖进去。
22
+ """
23
+
24
+ import argparse
25
+ import os
26
+ import sys
27
+
28
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
29
+
30
+
31
+ def list_files(folder):
32
+ rows = []
33
+ for root, _, files in os.walk(folder):
34
+ for name in sorted(files):
35
+ path = os.path.join(root, name)
36
+ rows.append((os.path.relpath(path, folder), os.path.getsize(path)))
37
+ return rows
38
+
39
+
40
+ def main():
41
+ parser = argparse.ArgumentParser(description="上传 hub_package 到 Hugging Face Hub")
42
+ parser.add_argument("--repo-id", required=True, help="用户名/仓库名,例如 zhangsan/value-based-qlearning")
43
+ parser.add_argument("--folder", default=os.path.join(BASE_DIR, "hub_package"))
44
+ parser.add_argument("--token", default=os.environ.get("HF_TOKEN"), help="默认读环境变量 HF_TOKEN")
45
+ parser.add_argument("--private", action="store_true", help="创建私有仓库")
46
+ parser.add_argument("--endpoint", default=os.environ.get("HF_ENDPOINT"),
47
+ help="默认 huggingface.co,可用环境变量 HF_ENDPOINT 覆盖")
48
+ parser.add_argument("--dry-run", action="store_true", help="只列出要上传的文件,不真的上传")
49
+ args = parser.parse_args()
50
+
51
+ if not os.path.isdir(args.folder):
52
+ raise SystemExit(f"找不到 {args.folder},请先运行 python build_hub_package.py")
53
+
54
+ files = list_files(args.folder)
55
+ total_mb = sum(size for _, size in files) / (1024 * 1024)
56
+ print(f"准备上传 {len(files)} 个文件(共 {total_mb:.1f} MB)到仓库 {args.repo_id}")
57
+ for rel, size in files:
58
+ print(f" {rel} ({size / 1024:.0f} KB)")
59
+
60
+ if args.dry_run:
61
+ print("\n[--dry-run] 不上传。")
62
+ return
63
+
64
+ if not args.token:
65
+ print("\n[错误] 没有 token。请先执行 hf auth login,或设置 $env:HF_TOKEN。")
66
+ print(" 也可以去掉 --token 参数让 huggingface_hub 读本机缓存的登录信息。")
67
+ return
68
+
69
+ try:
70
+ from huggingface_hub import HfApi
71
+ except ImportError:
72
+ print("\n[错误] 缺 huggingface_hub,请先:pip install huggingface_hub")
73
+ return
74
+
75
+ api = HfApi(endpoint=args.endpoint, token=args.token) if args.endpoint else HfApi(token=args.token)
76
+
77
+ try:
78
+ print(f"\n[1/2] 创建/确认仓库 {args.repo_id} ...")
79
+ api.create_repo(repo_id=args.repo_id, repo_type="model",
80
+ private=args.private, exist_ok=True)
81
+ print("[2/2] 上传文件 ...")
82
+ api.upload_folder(
83
+ repo_id=args.repo_id,
84
+ repo_type="model",
85
+ folder_path=args.folder,
86
+ commit_message="value-based 实践:Q-Learning 智能体(FrozenLake + Taxi)"
87
+ " + 回放视频 + 评估分数",
88
+ )
89
+ except Exception as exc: # noqa: BLE001
90
+ print(f"\n[上传失败] {type(exc).__name__}: {exc}")
91
+ print("\n常见原因与对策:")
92
+ print(" 1) 网络连不上 huggingface.co(本机常见)-> 开代理后重试,或浏览器手动上传;")
93
+ print(" 2) token 没有 write 权限 -> 重新建一个 write token;")
94
+ print(" 3) 仓库名格式不对 -> 必须是 用户名/仓库名。")
95
+ print(f"\n手动上传入口:https://huggingface.co/new (仓库名建议 value-based-qlearning)")
96
+ return
97
+
98
+ url = f"https://huggingface.co/{args.repo_id}"
99
+ print(f"\n[完成] 仓库地址:{url}")
100
+ print(f" 视频和评估分数已经写进 README.md,打开仓库就能看到。")
101
+
102
+
103
+ if __name__ == "__main__":
104
+ sys.exit(main())
eval_results/eval_frozenlake4x4.json ADDED
@@ -0,0 +1,727 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "run": "frozenlake4x4",
3
+ "env_id": "FrozenLake-v1",
4
+ "env_kwargs": {
5
+ "map_name": "4x4",
6
+ "is_slippery": false
7
+ },
8
+ "n_training_episodes": 10000,
9
+ "n_episodes": 100,
10
+ "mean_reward": 1.0,
11
+ "std_reward": 0.0,
12
+ "min_reward": 1.0,
13
+ "max_reward": 1.0,
14
+ "success_rate": 1.0,
15
+ "mean_steps": 6.0,
16
+ "policy": "greedy (argmax Q)",
17
+ "model_file": "models\\qtable_frozenlake4x4.pkl",
18
+ "qtable_shape": [
19
+ 16,
20
+ 4
21
+ ],
22
+ "qtable_nonzero": 35,
23
+ "evaluated_at": "2026-10-30T22:46:48",
24
+ "episodes": [
25
+ {
26
+ "episode": 1,
27
+ "seed": null,
28
+ "reward": 1.0,
29
+ "steps": 6,
30
+ "success": 1
31
+ },
32
+ {
33
+ "episode": 2,
34
+ "seed": null,
35
+ "reward": 1.0,
36
+ "steps": 6,
37
+ "success": 1
38
+ },
39
+ {
40
+ "episode": 3,
41
+ "seed": null,
42
+ "reward": 1.0,
43
+ "steps": 6,
44
+ "success": 1
45
+ },
46
+ {
47
+ "episode": 4,
48
+ "seed": null,
49
+ "reward": 1.0,
50
+ "steps": 6,
51
+ "success": 1
52
+ },
53
+ {
54
+ "episode": 5,
55
+ "seed": null,
56
+ "reward": 1.0,
57
+ "steps": 6,
58
+ "success": 1
59
+ },
60
+ {
61
+ "episode": 6,
62
+ "seed": null,
63
+ "reward": 1.0,
64
+ "steps": 6,
65
+ "success": 1
66
+ },
67
+ {
68
+ "episode": 7,
69
+ "seed": null,
70
+ "reward": 1.0,
71
+ "steps": 6,
72
+ "success": 1
73
+ },
74
+ {
75
+ "episode": 8,
76
+ "seed": null,
77
+ "reward": 1.0,
78
+ "steps": 6,
79
+ "success": 1
80
+ },
81
+ {
82
+ "episode": 9,
83
+ "seed": null,
84
+ "reward": 1.0,
85
+ "steps": 6,
86
+ "success": 1
87
+ },
88
+ {
89
+ "episode": 10,
90
+ "seed": null,
91
+ "reward": 1.0,
92
+ "steps": 6,
93
+ "success": 1
94
+ },
95
+ {
96
+ "episode": 11,
97
+ "seed": null,
98
+ "reward": 1.0,
99
+ "steps": 6,
100
+ "success": 1
101
+ },
102
+ {
103
+ "episode": 12,
104
+ "seed": null,
105
+ "reward": 1.0,
106
+ "steps": 6,
107
+ "success": 1
108
+ },
109
+ {
110
+ "episode": 13,
111
+ "seed": null,
112
+ "reward": 1.0,
113
+ "steps": 6,
114
+ "success": 1
115
+ },
116
+ {
117
+ "episode": 14,
118
+ "seed": null,
119
+ "reward": 1.0,
120
+ "steps": 6,
121
+ "success": 1
122
+ },
123
+ {
124
+ "episode": 15,
125
+ "seed": null,
126
+ "reward": 1.0,
127
+ "steps": 6,
128
+ "success": 1
129
+ },
130
+ {
131
+ "episode": 16,
132
+ "seed": null,
133
+ "reward": 1.0,
134
+ "steps": 6,
135
+ "success": 1
136
+ },
137
+ {
138
+ "episode": 17,
139
+ "seed": null,
140
+ "reward": 1.0,
141
+ "steps": 6,
142
+ "success": 1
143
+ },
144
+ {
145
+ "episode": 18,
146
+ "seed": null,
147
+ "reward": 1.0,
148
+ "steps": 6,
149
+ "success": 1
150
+ },
151
+ {
152
+ "episode": 19,
153
+ "seed": null,
154
+ "reward": 1.0,
155
+ "steps": 6,
156
+ "success": 1
157
+ },
158
+ {
159
+ "episode": 20,
160
+ "seed": null,
161
+ "reward": 1.0,
162
+ "steps": 6,
163
+ "success": 1
164
+ },
165
+ {
166
+ "episode": 21,
167
+ "seed": null,
168
+ "reward": 1.0,
169
+ "steps": 6,
170
+ "success": 1
171
+ },
172
+ {
173
+ "episode": 22,
174
+ "seed": null,
175
+ "reward": 1.0,
176
+ "steps": 6,
177
+ "success": 1
178
+ },
179
+ {
180
+ "episode": 23,
181
+ "seed": null,
182
+ "reward": 1.0,
183
+ "steps": 6,
184
+ "success": 1
185
+ },
186
+ {
187
+ "episode": 24,
188
+ "seed": null,
189
+ "reward": 1.0,
190
+ "steps": 6,
191
+ "success": 1
192
+ },
193
+ {
194
+ "episode": 25,
195
+ "seed": null,
196
+ "reward": 1.0,
197
+ "steps": 6,
198
+ "success": 1
199
+ },
200
+ {
201
+ "episode": 26,
202
+ "seed": null,
203
+ "reward": 1.0,
204
+ "steps": 6,
205
+ "success": 1
206
+ },
207
+ {
208
+ "episode": 27,
209
+ "seed": null,
210
+ "reward": 1.0,
211
+ "steps": 6,
212
+ "success": 1
213
+ },
214
+ {
215
+ "episode": 28,
216
+ "seed": null,
217
+ "reward": 1.0,
218
+ "steps": 6,
219
+ "success": 1
220
+ },
221
+ {
222
+ "episode": 29,
223
+ "seed": null,
224
+ "reward": 1.0,
225
+ "steps": 6,
226
+ "success": 1
227
+ },
228
+ {
229
+ "episode": 30,
230
+ "seed": null,
231
+ "reward": 1.0,
232
+ "steps": 6,
233
+ "success": 1
234
+ },
235
+ {
236
+ "episode": 31,
237
+ "seed": null,
238
+ "reward": 1.0,
239
+ "steps": 6,
240
+ "success": 1
241
+ },
242
+ {
243
+ "episode": 32,
244
+ "seed": null,
245
+ "reward": 1.0,
246
+ "steps": 6,
247
+ "success": 1
248
+ },
249
+ {
250
+ "episode": 33,
251
+ "seed": null,
252
+ "reward": 1.0,
253
+ "steps": 6,
254
+ "success": 1
255
+ },
256
+ {
257
+ "episode": 34,
258
+ "seed": null,
259
+ "reward": 1.0,
260
+ "steps": 6,
261
+ "success": 1
262
+ },
263
+ {
264
+ "episode": 35,
265
+ "seed": null,
266
+ "reward": 1.0,
267
+ "steps": 6,
268
+ "success": 1
269
+ },
270
+ {
271
+ "episode": 36,
272
+ "seed": null,
273
+ "reward": 1.0,
274
+ "steps": 6,
275
+ "success": 1
276
+ },
277
+ {
278
+ "episode": 37,
279
+ "seed": null,
280
+ "reward": 1.0,
281
+ "steps": 6,
282
+ "success": 1
283
+ },
284
+ {
285
+ "episode": 38,
286
+ "seed": null,
287
+ "reward": 1.0,
288
+ "steps": 6,
289
+ "success": 1
290
+ },
291
+ {
292
+ "episode": 39,
293
+ "seed": null,
294
+ "reward": 1.0,
295
+ "steps": 6,
296
+ "success": 1
297
+ },
298
+ {
299
+ "episode": 40,
300
+ "seed": null,
301
+ "reward": 1.0,
302
+ "steps": 6,
303
+ "success": 1
304
+ },
305
+ {
306
+ "episode": 41,
307
+ "seed": null,
308
+ "reward": 1.0,
309
+ "steps": 6,
310
+ "success": 1
311
+ },
312
+ {
313
+ "episode": 42,
314
+ "seed": null,
315
+ "reward": 1.0,
316
+ "steps": 6,
317
+ "success": 1
318
+ },
319
+ {
320
+ "episode": 43,
321
+ "seed": null,
322
+ "reward": 1.0,
323
+ "steps": 6,
324
+ "success": 1
325
+ },
326
+ {
327
+ "episode": 44,
328
+ "seed": null,
329
+ "reward": 1.0,
330
+ "steps": 6,
331
+ "success": 1
332
+ },
333
+ {
334
+ "episode": 45,
335
+ "seed": null,
336
+ "reward": 1.0,
337
+ "steps": 6,
338
+ "success": 1
339
+ },
340
+ {
341
+ "episode": 46,
342
+ "seed": null,
343
+ "reward": 1.0,
344
+ "steps": 6,
345
+ "success": 1
346
+ },
347
+ {
348
+ "episode": 47,
349
+ "seed": null,
350
+ "reward": 1.0,
351
+ "steps": 6,
352
+ "success": 1
353
+ },
354
+ {
355
+ "episode": 48,
356
+ "seed": null,
357
+ "reward": 1.0,
358
+ "steps": 6,
359
+ "success": 1
360
+ },
361
+ {
362
+ "episode": 49,
363
+ "seed": null,
364
+ "reward": 1.0,
365
+ "steps": 6,
366
+ "success": 1
367
+ },
368
+ {
369
+ "episode": 50,
370
+ "seed": null,
371
+ "reward": 1.0,
372
+ "steps": 6,
373
+ "success": 1
374
+ },
375
+ {
376
+ "episode": 51,
377
+ "seed": null,
378
+ "reward": 1.0,
379
+ "steps": 6,
380
+ "success": 1
381
+ },
382
+ {
383
+ "episode": 52,
384
+ "seed": null,
385
+ "reward": 1.0,
386
+ "steps": 6,
387
+ "success": 1
388
+ },
389
+ {
390
+ "episode": 53,
391
+ "seed": null,
392
+ "reward": 1.0,
393
+ "steps": 6,
394
+ "success": 1
395
+ },
396
+ {
397
+ "episode": 54,
398
+ "seed": null,
399
+ "reward": 1.0,
400
+ "steps": 6,
401
+ "success": 1
402
+ },
403
+ {
404
+ "episode": 55,
405
+ "seed": null,
406
+ "reward": 1.0,
407
+ "steps": 6,
408
+ "success": 1
409
+ },
410
+ {
411
+ "episode": 56,
412
+ "seed": null,
413
+ "reward": 1.0,
414
+ "steps": 6,
415
+ "success": 1
416
+ },
417
+ {
418
+ "episode": 57,
419
+ "seed": null,
420
+ "reward": 1.0,
421
+ "steps": 6,
422
+ "success": 1
423
+ },
424
+ {
425
+ "episode": 58,
426
+ "seed": null,
427
+ "reward": 1.0,
428
+ "steps": 6,
429
+ "success": 1
430
+ },
431
+ {
432
+ "episode": 59,
433
+ "seed": null,
434
+ "reward": 1.0,
435
+ "steps": 6,
436
+ "success": 1
437
+ },
438
+ {
439
+ "episode": 60,
440
+ "seed": null,
441
+ "reward": 1.0,
442
+ "steps": 6,
443
+ "success": 1
444
+ },
445
+ {
446
+ "episode": 61,
447
+ "seed": null,
448
+ "reward": 1.0,
449
+ "steps": 6,
450
+ "success": 1
451
+ },
452
+ {
453
+ "episode": 62,
454
+ "seed": null,
455
+ "reward": 1.0,
456
+ "steps": 6,
457
+ "success": 1
458
+ },
459
+ {
460
+ "episode": 63,
461
+ "seed": null,
462
+ "reward": 1.0,
463
+ "steps": 6,
464
+ "success": 1
465
+ },
466
+ {
467
+ "episode": 64,
468
+ "seed": null,
469
+ "reward": 1.0,
470
+ "steps": 6,
471
+ "success": 1
472
+ },
473
+ {
474
+ "episode": 65,
475
+ "seed": null,
476
+ "reward": 1.0,
477
+ "steps": 6,
478
+ "success": 1
479
+ },
480
+ {
481
+ "episode": 66,
482
+ "seed": null,
483
+ "reward": 1.0,
484
+ "steps": 6,
485
+ "success": 1
486
+ },
487
+ {
488
+ "episode": 67,
489
+ "seed": null,
490
+ "reward": 1.0,
491
+ "steps": 6,
492
+ "success": 1
493
+ },
494
+ {
495
+ "episode": 68,
496
+ "seed": null,
497
+ "reward": 1.0,
498
+ "steps": 6,
499
+ "success": 1
500
+ },
501
+ {
502
+ "episode": 69,
503
+ "seed": null,
504
+ "reward": 1.0,
505
+ "steps": 6,
506
+ "success": 1
507
+ },
508
+ {
509
+ "episode": 70,
510
+ "seed": null,
511
+ "reward": 1.0,
512
+ "steps": 6,
513
+ "success": 1
514
+ },
515
+ {
516
+ "episode": 71,
517
+ "seed": null,
518
+ "reward": 1.0,
519
+ "steps": 6,
520
+ "success": 1
521
+ },
522
+ {
523
+ "episode": 72,
524
+ "seed": null,
525
+ "reward": 1.0,
526
+ "steps": 6,
527
+ "success": 1
528
+ },
529
+ {
530
+ "episode": 73,
531
+ "seed": null,
532
+ "reward": 1.0,
533
+ "steps": 6,
534
+ "success": 1
535
+ },
536
+ {
537
+ "episode": 74,
538
+ "seed": null,
539
+ "reward": 1.0,
540
+ "steps": 6,
541
+ "success": 1
542
+ },
543
+ {
544
+ "episode": 75,
545
+ "seed": null,
546
+ "reward": 1.0,
547
+ "steps": 6,
548
+ "success": 1
549
+ },
550
+ {
551
+ "episode": 76,
552
+ "seed": null,
553
+ "reward": 1.0,
554
+ "steps": 6,
555
+ "success": 1
556
+ },
557
+ {
558
+ "episode": 77,
559
+ "seed": null,
560
+ "reward": 1.0,
561
+ "steps": 6,
562
+ "success": 1
563
+ },
564
+ {
565
+ "episode": 78,
566
+ "seed": null,
567
+ "reward": 1.0,
568
+ "steps": 6,
569
+ "success": 1
570
+ },
571
+ {
572
+ "episode": 79,
573
+ "seed": null,
574
+ "reward": 1.0,
575
+ "steps": 6,
576
+ "success": 1
577
+ },
578
+ {
579
+ "episode": 80,
580
+ "seed": null,
581
+ "reward": 1.0,
582
+ "steps": 6,
583
+ "success": 1
584
+ },
585
+ {
586
+ "episode": 81,
587
+ "seed": null,
588
+ "reward": 1.0,
589
+ "steps": 6,
590
+ "success": 1
591
+ },
592
+ {
593
+ "episode": 82,
594
+ "seed": null,
595
+ "reward": 1.0,
596
+ "steps": 6,
597
+ "success": 1
598
+ },
599
+ {
600
+ "episode": 83,
601
+ "seed": null,
602
+ "reward": 1.0,
603
+ "steps": 6,
604
+ "success": 1
605
+ },
606
+ {
607
+ "episode": 84,
608
+ "seed": null,
609
+ "reward": 1.0,
610
+ "steps": 6,
611
+ "success": 1
612
+ },
613
+ {
614
+ "episode": 85,
615
+ "seed": null,
616
+ "reward": 1.0,
617
+ "steps": 6,
618
+ "success": 1
619
+ },
620
+ {
621
+ "episode": 86,
622
+ "seed": null,
623
+ "reward": 1.0,
624
+ "steps": 6,
625
+ "success": 1
626
+ },
627
+ {
628
+ "episode": 87,
629
+ "seed": null,
630
+ "reward": 1.0,
631
+ "steps": 6,
632
+ "success": 1
633
+ },
634
+ {
635
+ "episode": 88,
636
+ "seed": null,
637
+ "reward": 1.0,
638
+ "steps": 6,
639
+ "success": 1
640
+ },
641
+ {
642
+ "episode": 89,
643
+ "seed": null,
644
+ "reward": 1.0,
645
+ "steps": 6,
646
+ "success": 1
647
+ },
648
+ {
649
+ "episode": 90,
650
+ "seed": null,
651
+ "reward": 1.0,
652
+ "steps": 6,
653
+ "success": 1
654
+ },
655
+ {
656
+ "episode": 91,
657
+ "seed": null,
658
+ "reward": 1.0,
659
+ "steps": 6,
660
+ "success": 1
661
+ },
662
+ {
663
+ "episode": 92,
664
+ "seed": null,
665
+ "reward": 1.0,
666
+ "steps": 6,
667
+ "success": 1
668
+ },
669
+ {
670
+ "episode": 93,
671
+ "seed": null,
672
+ "reward": 1.0,
673
+ "steps": 6,
674
+ "success": 1
675
+ },
676
+ {
677
+ "episode": 94,
678
+ "seed": null,
679
+ "reward": 1.0,
680
+ "steps": 6,
681
+ "success": 1
682
+ },
683
+ {
684
+ "episode": 95,
685
+ "seed": null,
686
+ "reward": 1.0,
687
+ "steps": 6,
688
+ "success": 1
689
+ },
690
+ {
691
+ "episode": 96,
692
+ "seed": null,
693
+ "reward": 1.0,
694
+ "steps": 6,
695
+ "success": 1
696
+ },
697
+ {
698
+ "episode": 97,
699
+ "seed": null,
700
+ "reward": 1.0,
701
+ "steps": 6,
702
+ "success": 1
703
+ },
704
+ {
705
+ "episode": 98,
706
+ "seed": null,
707
+ "reward": 1.0,
708
+ "steps": 6,
709
+ "success": 1
710
+ },
711
+ {
712
+ "episode": 99,
713
+ "seed": null,
714
+ "reward": 1.0,
715
+ "steps": 6,
716
+ "success": 1
717
+ },
718
+ {
719
+ "episode": 100,
720
+ "seed": null,
721
+ "reward": 1.0,
722
+ "steps": 6,
723
+ "success": 1
724
+ }
725
+ ],
726
+ "evaluation_seconds": 0.01
727
+ }
eval_results/eval_frozenlake4x4_slippery.json ADDED
@@ -0,0 +1,727 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "run": "frozenlake4x4_slippery",
3
+ "env_id": "FrozenLake-v1",
4
+ "env_kwargs": {
5
+ "map_name": "4x4",
6
+ "is_slippery": true
7
+ },
8
+ "n_training_episodes": 20000,
9
+ "n_episodes": 100,
10
+ "mean_reward": 0.74,
11
+ "std_reward": 0.4386342439892262,
12
+ "min_reward": 0.0,
13
+ "max_reward": 1.0,
14
+ "success_rate": 0.74,
15
+ "mean_steps": 39.41,
16
+ "policy": "greedy (argmax Q)",
17
+ "model_file": "models\\qtable_frozenlake4x4_slippery.pkl",
18
+ "qtable_shape": [
19
+ 16,
20
+ 4
21
+ ],
22
+ "qtable_nonzero": 44,
23
+ "evaluated_at": "2026-10-30T22:46:59",
24
+ "episodes": [
25
+ {
26
+ "episode": 1,
27
+ "seed": 0,
28
+ "reward": 0.0,
29
+ "steps": 54,
30
+ "success": 0
31
+ },
32
+ {
33
+ "episode": 2,
34
+ "seed": 1,
35
+ "reward": 1.0,
36
+ "steps": 41,
37
+ "success": 1
38
+ },
39
+ {
40
+ "episode": 3,
41
+ "seed": 2,
42
+ "reward": 0.0,
43
+ "steps": 99,
44
+ "success": 0
45
+ },
46
+ {
47
+ "episode": 4,
48
+ "seed": 3,
49
+ "reward": 1.0,
50
+ "steps": 27,
51
+ "success": 1
52
+ },
53
+ {
54
+ "episode": 5,
55
+ "seed": 4,
56
+ "reward": 1.0,
57
+ "steps": 98,
58
+ "success": 1
59
+ },
60
+ {
61
+ "episode": 6,
62
+ "seed": 5,
63
+ "reward": 1.0,
64
+ "steps": 78,
65
+ "success": 1
66
+ },
67
+ {
68
+ "episode": 7,
69
+ "seed": 6,
70
+ "reward": 1.0,
71
+ "steps": 24,
72
+ "success": 1
73
+ },
74
+ {
75
+ "episode": 8,
76
+ "seed": 7,
77
+ "reward": 1.0,
78
+ "steps": 16,
79
+ "success": 1
80
+ },
81
+ {
82
+ "episode": 9,
83
+ "seed": 8,
84
+ "reward": 1.0,
85
+ "steps": 42,
86
+ "success": 1
87
+ },
88
+ {
89
+ "episode": 10,
90
+ "seed": 9,
91
+ "reward": 1.0,
92
+ "steps": 23,
93
+ "success": 1
94
+ },
95
+ {
96
+ "episode": 11,
97
+ "seed": 10,
98
+ "reward": 1.0,
99
+ "steps": 96,
100
+ "success": 1
101
+ },
102
+ {
103
+ "episode": 12,
104
+ "seed": 11,
105
+ "reward": 0.0,
106
+ "steps": 30,
107
+ "success": 0
108
+ },
109
+ {
110
+ "episode": 13,
111
+ "seed": 12,
112
+ "reward": 1.0,
113
+ "steps": 17,
114
+ "success": 1
115
+ },
116
+ {
117
+ "episode": 14,
118
+ "seed": 13,
119
+ "reward": 0.0,
120
+ "steps": 13,
121
+ "success": 0
122
+ },
123
+ {
124
+ "episode": 15,
125
+ "seed": 14,
126
+ "reward": 1.0,
127
+ "steps": 18,
128
+ "success": 1
129
+ },
130
+ {
131
+ "episode": 16,
132
+ "seed": 15,
133
+ "reward": 1.0,
134
+ "steps": 25,
135
+ "success": 1
136
+ },
137
+ {
138
+ "episode": 17,
139
+ "seed": 16,
140
+ "reward": 1.0,
141
+ "steps": 37,
142
+ "success": 1
143
+ },
144
+ {
145
+ "episode": 18,
146
+ "seed": 17,
147
+ "reward": 1.0,
148
+ "steps": 24,
149
+ "success": 1
150
+ },
151
+ {
152
+ "episode": 19,
153
+ "seed": 18,
154
+ "reward": 1.0,
155
+ "steps": 22,
156
+ "success": 1
157
+ },
158
+ {
159
+ "episode": 20,
160
+ "seed": 19,
161
+ "reward": 0.0,
162
+ "steps": 76,
163
+ "success": 0
164
+ },
165
+ {
166
+ "episode": 21,
167
+ "seed": 20,
168
+ "reward": 1.0,
169
+ "steps": 34,
170
+ "success": 1
171
+ },
172
+ {
173
+ "episode": 22,
174
+ "seed": 21,
175
+ "reward": 1.0,
176
+ "steps": 26,
177
+ "success": 1
178
+ },
179
+ {
180
+ "episode": 23,
181
+ "seed": 22,
182
+ "reward": 1.0,
183
+ "steps": 19,
184
+ "success": 1
185
+ },
186
+ {
187
+ "episode": 24,
188
+ "seed": 23,
189
+ "reward": 1.0,
190
+ "steps": 23,
191
+ "success": 1
192
+ },
193
+ {
194
+ "episode": 25,
195
+ "seed": 24,
196
+ "reward": 1.0,
197
+ "steps": 22,
198
+ "success": 1
199
+ },
200
+ {
201
+ "episode": 26,
202
+ "seed": 25,
203
+ "reward": 1.0,
204
+ "steps": 17,
205
+ "success": 1
206
+ },
207
+ {
208
+ "episode": 27,
209
+ "seed": 26,
210
+ "reward": 1.0,
211
+ "steps": 23,
212
+ "success": 1
213
+ },
214
+ {
215
+ "episode": 28,
216
+ "seed": 27,
217
+ "reward": 0.0,
218
+ "steps": 30,
219
+ "success": 0
220
+ },
221
+ {
222
+ "episode": 29,
223
+ "seed": 28,
224
+ "reward": 1.0,
225
+ "steps": 9,
226
+ "success": 1
227
+ },
228
+ {
229
+ "episode": 30,
230
+ "seed": 29,
231
+ "reward": 1.0,
232
+ "steps": 32,
233
+ "success": 1
234
+ },
235
+ {
236
+ "episode": 31,
237
+ "seed": 30,
238
+ "reward": 0.0,
239
+ "steps": 31,
240
+ "success": 0
241
+ },
242
+ {
243
+ "episode": 32,
244
+ "seed": 31,
245
+ "reward": 0.0,
246
+ "steps": 10,
247
+ "success": 0
248
+ },
249
+ {
250
+ "episode": 33,
251
+ "seed": 32,
252
+ "reward": 0.0,
253
+ "steps": 46,
254
+ "success": 0
255
+ },
256
+ {
257
+ "episode": 34,
258
+ "seed": 33,
259
+ "reward": 0.0,
260
+ "steps": 99,
261
+ "success": 0
262
+ },
263
+ {
264
+ "episode": 35,
265
+ "seed": 34,
266
+ "reward": 1.0,
267
+ "steps": 16,
268
+ "success": 1
269
+ },
270
+ {
271
+ "episode": 36,
272
+ "seed": 35,
273
+ "reward": 0.0,
274
+ "steps": 69,
275
+ "success": 0
276
+ },
277
+ {
278
+ "episode": 37,
279
+ "seed": 36,
280
+ "reward": 1.0,
281
+ "steps": 13,
282
+ "success": 1
283
+ },
284
+ {
285
+ "episode": 38,
286
+ "seed": 37,
287
+ "reward": 0.0,
288
+ "steps": 30,
289
+ "success": 0
290
+ },
291
+ {
292
+ "episode": 39,
293
+ "seed": 38,
294
+ "reward": 1.0,
295
+ "steps": 27,
296
+ "success": 1
297
+ },
298
+ {
299
+ "episode": 40,
300
+ "seed": 39,
301
+ "reward": 1.0,
302
+ "steps": 26,
303
+ "success": 1
304
+ },
305
+ {
306
+ "episode": 41,
307
+ "seed": 40,
308
+ "reward": 1.0,
309
+ "steps": 9,
310
+ "success": 1
311
+ },
312
+ {
313
+ "episode": 42,
314
+ "seed": 41,
315
+ "reward": 1.0,
316
+ "steps": 37,
317
+ "success": 1
318
+ },
319
+ {
320
+ "episode": 43,
321
+ "seed": 42,
322
+ "reward": 1.0,
323
+ "steps": 7,
324
+ "success": 1
325
+ },
326
+ {
327
+ "episode": 44,
328
+ "seed": 43,
329
+ "reward": 1.0,
330
+ "steps": 47,
331
+ "success": 1
332
+ },
333
+ {
334
+ "episode": 45,
335
+ "seed": 44,
336
+ "reward": 1.0,
337
+ "steps": 48,
338
+ "success": 1
339
+ },
340
+ {
341
+ "episode": 46,
342
+ "seed": 45,
343
+ "reward": 1.0,
344
+ "steps": 53,
345
+ "success": 1
346
+ },
347
+ {
348
+ "episode": 47,
349
+ "seed": 46,
350
+ "reward": 1.0,
351
+ "steps": 27,
352
+ "success": 1
353
+ },
354
+ {
355
+ "episode": 48,
356
+ "seed": 47,
357
+ "reward": 1.0,
358
+ "steps": 27,
359
+ "success": 1
360
+ },
361
+ {
362
+ "episode": 49,
363
+ "seed": 48,
364
+ "reward": 1.0,
365
+ "steps": 16,
366
+ "success": 1
367
+ },
368
+ {
369
+ "episode": 50,
370
+ "seed": 49,
371
+ "reward": 1.0,
372
+ "steps": 90,
373
+ "success": 1
374
+ },
375
+ {
376
+ "episode": 51,
377
+ "seed": 50,
378
+ "reward": 1.0,
379
+ "steps": 48,
380
+ "success": 1
381
+ },
382
+ {
383
+ "episode": 52,
384
+ "seed": 51,
385
+ "reward": 0.0,
386
+ "steps": 17,
387
+ "success": 0
388
+ },
389
+ {
390
+ "episode": 53,
391
+ "seed": 52,
392
+ "reward": 1.0,
393
+ "steps": 88,
394
+ "success": 1
395
+ },
396
+ {
397
+ "episode": 54,
398
+ "seed": 53,
399
+ "reward": 1.0,
400
+ "steps": 25,
401
+ "success": 1
402
+ },
403
+ {
404
+ "episode": 55,
405
+ "seed": 54,
406
+ "reward": 0.0,
407
+ "steps": 25,
408
+ "success": 0
409
+ },
410
+ {
411
+ "episode": 56,
412
+ "seed": 55,
413
+ "reward": 1.0,
414
+ "steps": 18,
415
+ "success": 1
416
+ },
417
+ {
418
+ "episode": 57,
419
+ "seed": 56,
420
+ "reward": 1.0,
421
+ "steps": 46,
422
+ "success": 1
423
+ },
424
+ {
425
+ "episode": 58,
426
+ "seed": 57,
427
+ "reward": 1.0,
428
+ "steps": 29,
429
+ "success": 1
430
+ },
431
+ {
432
+ "episode": 59,
433
+ "seed": 58,
434
+ "reward": 1.0,
435
+ "steps": 43,
436
+ "success": 1
437
+ },
438
+ {
439
+ "episode": 60,
440
+ "seed": 59,
441
+ "reward": 1.0,
442
+ "steps": 46,
443
+ "success": 1
444
+ },
445
+ {
446
+ "episode": 61,
447
+ "seed": 60,
448
+ "reward": 0.0,
449
+ "steps": 99,
450
+ "success": 0
451
+ },
452
+ {
453
+ "episode": 62,
454
+ "seed": 61,
455
+ "reward": 1.0,
456
+ "steps": 22,
457
+ "success": 1
458
+ },
459
+ {
460
+ "episode": 63,
461
+ "seed": 62,
462
+ "reward": 1.0,
463
+ "steps": 7,
464
+ "success": 1
465
+ },
466
+ {
467
+ "episode": 64,
468
+ "seed": 63,
469
+ "reward": 1.0,
470
+ "steps": 29,
471
+ "success": 1
472
+ },
473
+ {
474
+ "episode": 65,
475
+ "seed": 64,
476
+ "reward": 0.0,
477
+ "steps": 26,
478
+ "success": 0
479
+ },
480
+ {
481
+ "episode": 66,
482
+ "seed": 65,
483
+ "reward": 1.0,
484
+ "steps": 26,
485
+ "success": 1
486
+ },
487
+ {
488
+ "episode": 67,
489
+ "seed": 66,
490
+ "reward": 1.0,
491
+ "steps": 33,
492
+ "success": 1
493
+ },
494
+ {
495
+ "episode": 68,
496
+ "seed": 67,
497
+ "reward": 1.0,
498
+ "steps": 55,
499
+ "success": 1
500
+ },
501
+ {
502
+ "episode": 69,
503
+ "seed": 68,
504
+ "reward": 1.0,
505
+ "steps": 19,
506
+ "success": 1
507
+ },
508
+ {
509
+ "episode": 70,
510
+ "seed": 69,
511
+ "reward": 1.0,
512
+ "steps": 17,
513
+ "success": 1
514
+ },
515
+ {
516
+ "episode": 71,
517
+ "seed": 70,
518
+ "reward": 1.0,
519
+ "steps": 84,
520
+ "success": 1
521
+ },
522
+ {
523
+ "episode": 72,
524
+ "seed": 71,
525
+ "reward": 0.0,
526
+ "steps": 42,
527
+ "success": 0
528
+ },
529
+ {
530
+ "episode": 73,
531
+ "seed": 72,
532
+ "reward": 0.0,
533
+ "steps": 62,
534
+ "success": 0
535
+ },
536
+ {
537
+ "episode": 74,
538
+ "seed": 73,
539
+ "reward": 0.0,
540
+ "steps": 99,
541
+ "success": 0
542
+ },
543
+ {
544
+ "episode": 75,
545
+ "seed": 74,
546
+ "reward": 1.0,
547
+ "steps": 39,
548
+ "success": 1
549
+ },
550
+ {
551
+ "episode": 76,
552
+ "seed": 75,
553
+ "reward": 1.0,
554
+ "steps": 16,
555
+ "success": 1
556
+ },
557
+ {
558
+ "episode": 77,
559
+ "seed": 76,
560
+ "reward": 1.0,
561
+ "steps": 21,
562
+ "success": 1
563
+ },
564
+ {
565
+ "episode": 78,
566
+ "seed": 77,
567
+ "reward": 0.0,
568
+ "steps": 20,
569
+ "success": 0
570
+ },
571
+ {
572
+ "episode": 79,
573
+ "seed": 78,
574
+ "reward": 1.0,
575
+ "steps": 14,
576
+ "success": 1
577
+ },
578
+ {
579
+ "episode": 80,
580
+ "seed": 79,
581
+ "reward": 1.0,
582
+ "steps": 23,
583
+ "success": 1
584
+ },
585
+ {
586
+ "episode": 81,
587
+ "seed": 80,
588
+ "reward": 0.0,
589
+ "steps": 12,
590
+ "success": 0
591
+ },
592
+ {
593
+ "episode": 82,
594
+ "seed": 81,
595
+ "reward": 1.0,
596
+ "steps": 58,
597
+ "success": 1
598
+ },
599
+ {
600
+ "episode": 83,
601
+ "seed": 82,
602
+ "reward": 1.0,
603
+ "steps": 12,
604
+ "success": 1
605
+ },
606
+ {
607
+ "episode": 84,
608
+ "seed": 83,
609
+ "reward": 1.0,
610
+ "steps": 31,
611
+ "success": 1
612
+ },
613
+ {
614
+ "episode": 85,
615
+ "seed": 84,
616
+ "reward": 0.0,
617
+ "steps": 11,
618
+ "success": 0
619
+ },
620
+ {
621
+ "episode": 86,
622
+ "seed": 85,
623
+ "reward": 0.0,
624
+ "steps": 73,
625
+ "success": 0
626
+ },
627
+ {
628
+ "episode": 87,
629
+ "seed": 86,
630
+ "reward": 1.0,
631
+ "steps": 14,
632
+ "success": 1
633
+ },
634
+ {
635
+ "episode": 88,
636
+ "seed": 87,
637
+ "reward": 0.0,
638
+ "steps": 99,
639
+ "success": 0
640
+ },
641
+ {
642
+ "episode": 89,
643
+ "seed": 88,
644
+ "reward": 1.0,
645
+ "steps": 82,
646
+ "success": 1
647
+ },
648
+ {
649
+ "episode": 90,
650
+ "seed": 89,
651
+ "reward": 0.0,
652
+ "steps": 99,
653
+ "success": 0
654
+ },
655
+ {
656
+ "episode": 91,
657
+ "seed": 90,
658
+ "reward": 1.0,
659
+ "steps": 14,
660
+ "success": 1
661
+ },
662
+ {
663
+ "episode": 92,
664
+ "seed": 91,
665
+ "reward": 0.0,
666
+ "steps": 72,
667
+ "success": 0
668
+ },
669
+ {
670
+ "episode": 93,
671
+ "seed": 92,
672
+ "reward": 1.0,
673
+ "steps": 59,
674
+ "success": 1
675
+ },
676
+ {
677
+ "episode": 94,
678
+ "seed": 93,
679
+ "reward": 1.0,
680
+ "steps": 76,
681
+ "success": 1
682
+ },
683
+ {
684
+ "episode": 95,
685
+ "seed": 94,
686
+ "reward": 1.0,
687
+ "steps": 26,
688
+ "success": 1
689
+ },
690
+ {
691
+ "episode": 96,
692
+ "seed": 95,
693
+ "reward": 1.0,
694
+ "steps": 43,
695
+ "success": 1
696
+ },
697
+ {
698
+ "episode": 97,
699
+ "seed": 96,
700
+ "reward": 1.0,
701
+ "steps": 40,
702
+ "success": 1
703
+ },
704
+ {
705
+ "episode": 98,
706
+ "seed": 97,
707
+ "reward": 1.0,
708
+ "steps": 44,
709
+ "success": 1
710
+ },
711
+ {
712
+ "episode": 99,
713
+ "seed": 98,
714
+ "reward": 1.0,
715
+ "steps": 47,
716
+ "success": 1
717
+ },
718
+ {
719
+ "episode": 100,
720
+ "seed": 99,
721
+ "reward": 1.0,
722
+ "steps": 68,
723
+ "success": 1
724
+ }
725
+ ],
726
+ "evaluation_seconds": 0.04
727
+ }
eval_results/eval_frozenlake8x8.json ADDED
@@ -0,0 +1,727 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "run": "frozenlake8x8",
3
+ "env_id": "FrozenLake-v1",
4
+ "env_kwargs": {
5
+ "map_name": "8x8",
6
+ "is_slippery": false
7
+ },
8
+ "n_training_episodes": 60000,
9
+ "n_episodes": 100,
10
+ "mean_reward": 1.0,
11
+ "std_reward": 0.0,
12
+ "min_reward": 1.0,
13
+ "max_reward": 1.0,
14
+ "success_rate": 1.0,
15
+ "mean_steps": 14.0,
16
+ "policy": "greedy (argmax Q)",
17
+ "model_file": "models\\qtable_frozenlake8x8.pkl",
18
+ "qtable_shape": [
19
+ 64,
20
+ 4
21
+ ],
22
+ "qtable_nonzero": 179,
23
+ "evaluated_at": "2026-10-30T22:47:24",
24
+ "episodes": [
25
+ {
26
+ "episode": 1,
27
+ "seed": 0,
28
+ "reward": 1.0,
29
+ "steps": 14,
30
+ "success": 1
31
+ },
32
+ {
33
+ "episode": 2,
34
+ "seed": 1,
35
+ "reward": 1.0,
36
+ "steps": 14,
37
+ "success": 1
38
+ },
39
+ {
40
+ "episode": 3,
41
+ "seed": 2,
42
+ "reward": 1.0,
43
+ "steps": 14,
44
+ "success": 1
45
+ },
46
+ {
47
+ "episode": 4,
48
+ "seed": 3,
49
+ "reward": 1.0,
50
+ "steps": 14,
51
+ "success": 1
52
+ },
53
+ {
54
+ "episode": 5,
55
+ "seed": 4,
56
+ "reward": 1.0,
57
+ "steps": 14,
58
+ "success": 1
59
+ },
60
+ {
61
+ "episode": 6,
62
+ "seed": 5,
63
+ "reward": 1.0,
64
+ "steps": 14,
65
+ "success": 1
66
+ },
67
+ {
68
+ "episode": 7,
69
+ "seed": 6,
70
+ "reward": 1.0,
71
+ "steps": 14,
72
+ "success": 1
73
+ },
74
+ {
75
+ "episode": 8,
76
+ "seed": 7,
77
+ "reward": 1.0,
78
+ "steps": 14,
79
+ "success": 1
80
+ },
81
+ {
82
+ "episode": 9,
83
+ "seed": 8,
84
+ "reward": 1.0,
85
+ "steps": 14,
86
+ "success": 1
87
+ },
88
+ {
89
+ "episode": 10,
90
+ "seed": 9,
91
+ "reward": 1.0,
92
+ "steps": 14,
93
+ "success": 1
94
+ },
95
+ {
96
+ "episode": 11,
97
+ "seed": 10,
98
+ "reward": 1.0,
99
+ "steps": 14,
100
+ "success": 1
101
+ },
102
+ {
103
+ "episode": 12,
104
+ "seed": 11,
105
+ "reward": 1.0,
106
+ "steps": 14,
107
+ "success": 1
108
+ },
109
+ {
110
+ "episode": 13,
111
+ "seed": 12,
112
+ "reward": 1.0,
113
+ "steps": 14,
114
+ "success": 1
115
+ },
116
+ {
117
+ "episode": 14,
118
+ "seed": 13,
119
+ "reward": 1.0,
120
+ "steps": 14,
121
+ "success": 1
122
+ },
123
+ {
124
+ "episode": 15,
125
+ "seed": 14,
126
+ "reward": 1.0,
127
+ "steps": 14,
128
+ "success": 1
129
+ },
130
+ {
131
+ "episode": 16,
132
+ "seed": 15,
133
+ "reward": 1.0,
134
+ "steps": 14,
135
+ "success": 1
136
+ },
137
+ {
138
+ "episode": 17,
139
+ "seed": 16,
140
+ "reward": 1.0,
141
+ "steps": 14,
142
+ "success": 1
143
+ },
144
+ {
145
+ "episode": 18,
146
+ "seed": 17,
147
+ "reward": 1.0,
148
+ "steps": 14,
149
+ "success": 1
150
+ },
151
+ {
152
+ "episode": 19,
153
+ "seed": 18,
154
+ "reward": 1.0,
155
+ "steps": 14,
156
+ "success": 1
157
+ },
158
+ {
159
+ "episode": 20,
160
+ "seed": 19,
161
+ "reward": 1.0,
162
+ "steps": 14,
163
+ "success": 1
164
+ },
165
+ {
166
+ "episode": 21,
167
+ "seed": 20,
168
+ "reward": 1.0,
169
+ "steps": 14,
170
+ "success": 1
171
+ },
172
+ {
173
+ "episode": 22,
174
+ "seed": 21,
175
+ "reward": 1.0,
176
+ "steps": 14,
177
+ "success": 1
178
+ },
179
+ {
180
+ "episode": 23,
181
+ "seed": 22,
182
+ "reward": 1.0,
183
+ "steps": 14,
184
+ "success": 1
185
+ },
186
+ {
187
+ "episode": 24,
188
+ "seed": 23,
189
+ "reward": 1.0,
190
+ "steps": 14,
191
+ "success": 1
192
+ },
193
+ {
194
+ "episode": 25,
195
+ "seed": 24,
196
+ "reward": 1.0,
197
+ "steps": 14,
198
+ "success": 1
199
+ },
200
+ {
201
+ "episode": 26,
202
+ "seed": 25,
203
+ "reward": 1.0,
204
+ "steps": 14,
205
+ "success": 1
206
+ },
207
+ {
208
+ "episode": 27,
209
+ "seed": 26,
210
+ "reward": 1.0,
211
+ "steps": 14,
212
+ "success": 1
213
+ },
214
+ {
215
+ "episode": 28,
216
+ "seed": 27,
217
+ "reward": 1.0,
218
+ "steps": 14,
219
+ "success": 1
220
+ },
221
+ {
222
+ "episode": 29,
223
+ "seed": 28,
224
+ "reward": 1.0,
225
+ "steps": 14,
226
+ "success": 1
227
+ },
228
+ {
229
+ "episode": 30,
230
+ "seed": 29,
231
+ "reward": 1.0,
232
+ "steps": 14,
233
+ "success": 1
234
+ },
235
+ {
236
+ "episode": 31,
237
+ "seed": 30,
238
+ "reward": 1.0,
239
+ "steps": 14,
240
+ "success": 1
241
+ },
242
+ {
243
+ "episode": 32,
244
+ "seed": 31,
245
+ "reward": 1.0,
246
+ "steps": 14,
247
+ "success": 1
248
+ },
249
+ {
250
+ "episode": 33,
251
+ "seed": 32,
252
+ "reward": 1.0,
253
+ "steps": 14,
254
+ "success": 1
255
+ },
256
+ {
257
+ "episode": 34,
258
+ "seed": 33,
259
+ "reward": 1.0,
260
+ "steps": 14,
261
+ "success": 1
262
+ },
263
+ {
264
+ "episode": 35,
265
+ "seed": 34,
266
+ "reward": 1.0,
267
+ "steps": 14,
268
+ "success": 1
269
+ },
270
+ {
271
+ "episode": 36,
272
+ "seed": 35,
273
+ "reward": 1.0,
274
+ "steps": 14,
275
+ "success": 1
276
+ },
277
+ {
278
+ "episode": 37,
279
+ "seed": 36,
280
+ "reward": 1.0,
281
+ "steps": 14,
282
+ "success": 1
283
+ },
284
+ {
285
+ "episode": 38,
286
+ "seed": 37,
287
+ "reward": 1.0,
288
+ "steps": 14,
289
+ "success": 1
290
+ },
291
+ {
292
+ "episode": 39,
293
+ "seed": 38,
294
+ "reward": 1.0,
295
+ "steps": 14,
296
+ "success": 1
297
+ },
298
+ {
299
+ "episode": 40,
300
+ "seed": 39,
301
+ "reward": 1.0,
302
+ "steps": 14,
303
+ "success": 1
304
+ },
305
+ {
306
+ "episode": 41,
307
+ "seed": 40,
308
+ "reward": 1.0,
309
+ "steps": 14,
310
+ "success": 1
311
+ },
312
+ {
313
+ "episode": 42,
314
+ "seed": 41,
315
+ "reward": 1.0,
316
+ "steps": 14,
317
+ "success": 1
318
+ },
319
+ {
320
+ "episode": 43,
321
+ "seed": 42,
322
+ "reward": 1.0,
323
+ "steps": 14,
324
+ "success": 1
325
+ },
326
+ {
327
+ "episode": 44,
328
+ "seed": 43,
329
+ "reward": 1.0,
330
+ "steps": 14,
331
+ "success": 1
332
+ },
333
+ {
334
+ "episode": 45,
335
+ "seed": 44,
336
+ "reward": 1.0,
337
+ "steps": 14,
338
+ "success": 1
339
+ },
340
+ {
341
+ "episode": 46,
342
+ "seed": 45,
343
+ "reward": 1.0,
344
+ "steps": 14,
345
+ "success": 1
346
+ },
347
+ {
348
+ "episode": 47,
349
+ "seed": 46,
350
+ "reward": 1.0,
351
+ "steps": 14,
352
+ "success": 1
353
+ },
354
+ {
355
+ "episode": 48,
356
+ "seed": 47,
357
+ "reward": 1.0,
358
+ "steps": 14,
359
+ "success": 1
360
+ },
361
+ {
362
+ "episode": 49,
363
+ "seed": 48,
364
+ "reward": 1.0,
365
+ "steps": 14,
366
+ "success": 1
367
+ },
368
+ {
369
+ "episode": 50,
370
+ "seed": 49,
371
+ "reward": 1.0,
372
+ "steps": 14,
373
+ "success": 1
374
+ },
375
+ {
376
+ "episode": 51,
377
+ "seed": 50,
378
+ "reward": 1.0,
379
+ "steps": 14,
380
+ "success": 1
381
+ },
382
+ {
383
+ "episode": 52,
384
+ "seed": 51,
385
+ "reward": 1.0,
386
+ "steps": 14,
387
+ "success": 1
388
+ },
389
+ {
390
+ "episode": 53,
391
+ "seed": 52,
392
+ "reward": 1.0,
393
+ "steps": 14,
394
+ "success": 1
395
+ },
396
+ {
397
+ "episode": 54,
398
+ "seed": 53,
399
+ "reward": 1.0,
400
+ "steps": 14,
401
+ "success": 1
402
+ },
403
+ {
404
+ "episode": 55,
405
+ "seed": 54,
406
+ "reward": 1.0,
407
+ "steps": 14,
408
+ "success": 1
409
+ },
410
+ {
411
+ "episode": 56,
412
+ "seed": 55,
413
+ "reward": 1.0,
414
+ "steps": 14,
415
+ "success": 1
416
+ },
417
+ {
418
+ "episode": 57,
419
+ "seed": 56,
420
+ "reward": 1.0,
421
+ "steps": 14,
422
+ "success": 1
423
+ },
424
+ {
425
+ "episode": 58,
426
+ "seed": 57,
427
+ "reward": 1.0,
428
+ "steps": 14,
429
+ "success": 1
430
+ },
431
+ {
432
+ "episode": 59,
433
+ "seed": 58,
434
+ "reward": 1.0,
435
+ "steps": 14,
436
+ "success": 1
437
+ },
438
+ {
439
+ "episode": 60,
440
+ "seed": 59,
441
+ "reward": 1.0,
442
+ "steps": 14,
443
+ "success": 1
444
+ },
445
+ {
446
+ "episode": 61,
447
+ "seed": 60,
448
+ "reward": 1.0,
449
+ "steps": 14,
450
+ "success": 1
451
+ },
452
+ {
453
+ "episode": 62,
454
+ "seed": 61,
455
+ "reward": 1.0,
456
+ "steps": 14,
457
+ "success": 1
458
+ },
459
+ {
460
+ "episode": 63,
461
+ "seed": 62,
462
+ "reward": 1.0,
463
+ "steps": 14,
464
+ "success": 1
465
+ },
466
+ {
467
+ "episode": 64,
468
+ "seed": 63,
469
+ "reward": 1.0,
470
+ "steps": 14,
471
+ "success": 1
472
+ },
473
+ {
474
+ "episode": 65,
475
+ "seed": 64,
476
+ "reward": 1.0,
477
+ "steps": 14,
478
+ "success": 1
479
+ },
480
+ {
481
+ "episode": 66,
482
+ "seed": 65,
483
+ "reward": 1.0,
484
+ "steps": 14,
485
+ "success": 1
486
+ },
487
+ {
488
+ "episode": 67,
489
+ "seed": 66,
490
+ "reward": 1.0,
491
+ "steps": 14,
492
+ "success": 1
493
+ },
494
+ {
495
+ "episode": 68,
496
+ "seed": 67,
497
+ "reward": 1.0,
498
+ "steps": 14,
499
+ "success": 1
500
+ },
501
+ {
502
+ "episode": 69,
503
+ "seed": 68,
504
+ "reward": 1.0,
505
+ "steps": 14,
506
+ "success": 1
507
+ },
508
+ {
509
+ "episode": 70,
510
+ "seed": 69,
511
+ "reward": 1.0,
512
+ "steps": 14,
513
+ "success": 1
514
+ },
515
+ {
516
+ "episode": 71,
517
+ "seed": 70,
518
+ "reward": 1.0,
519
+ "steps": 14,
520
+ "success": 1
521
+ },
522
+ {
523
+ "episode": 72,
524
+ "seed": 71,
525
+ "reward": 1.0,
526
+ "steps": 14,
527
+ "success": 1
528
+ },
529
+ {
530
+ "episode": 73,
531
+ "seed": 72,
532
+ "reward": 1.0,
533
+ "steps": 14,
534
+ "success": 1
535
+ },
536
+ {
537
+ "episode": 74,
538
+ "seed": 73,
539
+ "reward": 1.0,
540
+ "steps": 14,
541
+ "success": 1
542
+ },
543
+ {
544
+ "episode": 75,
545
+ "seed": 74,
546
+ "reward": 1.0,
547
+ "steps": 14,
548
+ "success": 1
549
+ },
550
+ {
551
+ "episode": 76,
552
+ "seed": 75,
553
+ "reward": 1.0,
554
+ "steps": 14,
555
+ "success": 1
556
+ },
557
+ {
558
+ "episode": 77,
559
+ "seed": 76,
560
+ "reward": 1.0,
561
+ "steps": 14,
562
+ "success": 1
563
+ },
564
+ {
565
+ "episode": 78,
566
+ "seed": 77,
567
+ "reward": 1.0,
568
+ "steps": 14,
569
+ "success": 1
570
+ },
571
+ {
572
+ "episode": 79,
573
+ "seed": 78,
574
+ "reward": 1.0,
575
+ "steps": 14,
576
+ "success": 1
577
+ },
578
+ {
579
+ "episode": 80,
580
+ "seed": 79,
581
+ "reward": 1.0,
582
+ "steps": 14,
583
+ "success": 1
584
+ },
585
+ {
586
+ "episode": 81,
587
+ "seed": 80,
588
+ "reward": 1.0,
589
+ "steps": 14,
590
+ "success": 1
591
+ },
592
+ {
593
+ "episode": 82,
594
+ "seed": 81,
595
+ "reward": 1.0,
596
+ "steps": 14,
597
+ "success": 1
598
+ },
599
+ {
600
+ "episode": 83,
601
+ "seed": 82,
602
+ "reward": 1.0,
603
+ "steps": 14,
604
+ "success": 1
605
+ },
606
+ {
607
+ "episode": 84,
608
+ "seed": 83,
609
+ "reward": 1.0,
610
+ "steps": 14,
611
+ "success": 1
612
+ },
613
+ {
614
+ "episode": 85,
615
+ "seed": 84,
616
+ "reward": 1.0,
617
+ "steps": 14,
618
+ "success": 1
619
+ },
620
+ {
621
+ "episode": 86,
622
+ "seed": 85,
623
+ "reward": 1.0,
624
+ "steps": 14,
625
+ "success": 1
626
+ },
627
+ {
628
+ "episode": 87,
629
+ "seed": 86,
630
+ "reward": 1.0,
631
+ "steps": 14,
632
+ "success": 1
633
+ },
634
+ {
635
+ "episode": 88,
636
+ "seed": 87,
637
+ "reward": 1.0,
638
+ "steps": 14,
639
+ "success": 1
640
+ },
641
+ {
642
+ "episode": 89,
643
+ "seed": 88,
644
+ "reward": 1.0,
645
+ "steps": 14,
646
+ "success": 1
647
+ },
648
+ {
649
+ "episode": 90,
650
+ "seed": 89,
651
+ "reward": 1.0,
652
+ "steps": 14,
653
+ "success": 1
654
+ },
655
+ {
656
+ "episode": 91,
657
+ "seed": 90,
658
+ "reward": 1.0,
659
+ "steps": 14,
660
+ "success": 1
661
+ },
662
+ {
663
+ "episode": 92,
664
+ "seed": 91,
665
+ "reward": 1.0,
666
+ "steps": 14,
667
+ "success": 1
668
+ },
669
+ {
670
+ "episode": 93,
671
+ "seed": 92,
672
+ "reward": 1.0,
673
+ "steps": 14,
674
+ "success": 1
675
+ },
676
+ {
677
+ "episode": 94,
678
+ "seed": 93,
679
+ "reward": 1.0,
680
+ "steps": 14,
681
+ "success": 1
682
+ },
683
+ {
684
+ "episode": 95,
685
+ "seed": 94,
686
+ "reward": 1.0,
687
+ "steps": 14,
688
+ "success": 1
689
+ },
690
+ {
691
+ "episode": 96,
692
+ "seed": 95,
693
+ "reward": 1.0,
694
+ "steps": 14,
695
+ "success": 1
696
+ },
697
+ {
698
+ "episode": 97,
699
+ "seed": 96,
700
+ "reward": 1.0,
701
+ "steps": 14,
702
+ "success": 1
703
+ },
704
+ {
705
+ "episode": 98,
706
+ "seed": 97,
707
+ "reward": 1.0,
708
+ "steps": 14,
709
+ "success": 1
710
+ },
711
+ {
712
+ "episode": 99,
713
+ "seed": 98,
714
+ "reward": 1.0,
715
+ "steps": 14,
716
+ "success": 1
717
+ },
718
+ {
719
+ "episode": 100,
720
+ "seed": 99,
721
+ "reward": 1.0,
722
+ "steps": 14,
723
+ "success": 1
724
+ }
725
+ ],
726
+ "evaluation_seconds": 0.02
727
+ }
eval_results/eval_taxi.json ADDED
@@ -0,0 +1,724 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "run": "taxi",
3
+ "env_id": "Taxi-v3",
4
+ "env_kwargs": {},
5
+ "n_training_episodes": 25000,
6
+ "n_episodes": 100,
7
+ "mean_reward": 7.56,
8
+ "std_reward": 2.706732347314747,
9
+ "min_reward": 3.0,
10
+ "max_reward": 15.0,
11
+ "success_rate": 1.0,
12
+ "mean_steps": 13.44,
13
+ "policy": "greedy (argmax Q)",
14
+ "model_file": "models\\qtable_taxi.pkl",
15
+ "qtable_shape": [
16
+ 500,
17
+ 6
18
+ ],
19
+ "qtable_nonzero": 2398,
20
+ "evaluated_at": "2026-10-30T22:47:35",
21
+ "episodes": [
22
+ {
23
+ "episode": 1,
24
+ "seed": 16,
25
+ "reward": 6.0,
26
+ "steps": 15,
27
+ "success": 1
28
+ },
29
+ {
30
+ "episode": 2,
31
+ "seed": 54,
32
+ "reward": 12.0,
33
+ "steps": 9,
34
+ "success": 1
35
+ },
36
+ {
37
+ "episode": 3,
38
+ "seed": 165,
39
+ "reward": 12.0,
40
+ "steps": 9,
41
+ "success": 1
42
+ },
43
+ {
44
+ "episode": 4,
45
+ "seed": 177,
46
+ "reward": 11.0,
47
+ "steps": 10,
48
+ "success": 1
49
+ },
50
+ {
51
+ "episode": 5,
52
+ "seed": 191,
53
+ "reward": 7.0,
54
+ "steps": 14,
55
+ "success": 1
56
+ },
57
+ {
58
+ "episode": 6,
59
+ "seed": 191,
60
+ "reward": 7.0,
61
+ "steps": 14,
62
+ "success": 1
63
+ },
64
+ {
65
+ "episode": 7,
66
+ "seed": 120,
67
+ "reward": 4.0,
68
+ "steps": 17,
69
+ "success": 1
70
+ },
71
+ {
72
+ "episode": 8,
73
+ "seed": 80,
74
+ "reward": 7.0,
75
+ "steps": 14,
76
+ "success": 1
77
+ },
78
+ {
79
+ "episode": 9,
80
+ "seed": 149,
81
+ "reward": 5.0,
82
+ "steps": 16,
83
+ "success": 1
84
+ },
85
+ {
86
+ "episode": 10,
87
+ "seed": 178,
88
+ "reward": 4.0,
89
+ "steps": 17,
90
+ "success": 1
91
+ },
92
+ {
93
+ "episode": 11,
94
+ "seed": 48,
95
+ "reward": 5.0,
96
+ "steps": 16,
97
+ "success": 1
98
+ },
99
+ {
100
+ "episode": 12,
101
+ "seed": 38,
102
+ "reward": 11.0,
103
+ "steps": 10,
104
+ "success": 1
105
+ },
106
+ {
107
+ "episode": 13,
108
+ "seed": 6,
109
+ "reward": 11.0,
110
+ "steps": 10,
111
+ "success": 1
112
+ },
113
+ {
114
+ "episode": 14,
115
+ "seed": 125,
116
+ "reward": 4.0,
117
+ "steps": 17,
118
+ "success": 1
119
+ },
120
+ {
121
+ "episode": 15,
122
+ "seed": 174,
123
+ "reward": 4.0,
124
+ "steps": 17,
125
+ "success": 1
126
+ },
127
+ {
128
+ "episode": 16,
129
+ "seed": 73,
130
+ "reward": 7.0,
131
+ "steps": 14,
132
+ "success": 1
133
+ },
134
+ {
135
+ "episode": 17,
136
+ "seed": 50,
137
+ "reward": 5.0,
138
+ "steps": 16,
139
+ "success": 1
140
+ },
141
+ {
142
+ "episode": 18,
143
+ "seed": 172,
144
+ "reward": 11.0,
145
+ "steps": 10,
146
+ "success": 1
147
+ },
148
+ {
149
+ "episode": 19,
150
+ "seed": 100,
151
+ "reward": 7.0,
152
+ "steps": 14,
153
+ "success": 1
154
+ },
155
+ {
156
+ "episode": 20,
157
+ "seed": 148,
158
+ "reward": 6.0,
159
+ "steps": 15,
160
+ "success": 1
161
+ },
162
+ {
163
+ "episode": 21,
164
+ "seed": 146,
165
+ "reward": 7.0,
166
+ "steps": 14,
167
+ "success": 1
168
+ },
169
+ {
170
+ "episode": 22,
171
+ "seed": 6,
172
+ "reward": 11.0,
173
+ "steps": 10,
174
+ "success": 1
175
+ },
176
+ {
177
+ "episode": 23,
178
+ "seed": 25,
179
+ "reward": 3.0,
180
+ "steps": 18,
181
+ "success": 1
182
+ },
183
+ {
184
+ "episode": 24,
185
+ "seed": 40,
186
+ "reward": 6.0,
187
+ "steps": 15,
188
+ "success": 1
189
+ },
190
+ {
191
+ "episode": 25,
192
+ "seed": 68,
193
+ "reward": 11.0,
194
+ "steps": 10,
195
+ "success": 1
196
+ },
197
+ {
198
+ "episode": 26,
199
+ "seed": 148,
200
+ "reward": 6.0,
201
+ "steps": 15,
202
+ "success": 1
203
+ },
204
+ {
205
+ "episode": 27,
206
+ "seed": 49,
207
+ "reward": 4.0,
208
+ "steps": 17,
209
+ "success": 1
210
+ },
211
+ {
212
+ "episode": 28,
213
+ "seed": 167,
214
+ "reward": 9.0,
215
+ "steps": 12,
216
+ "success": 1
217
+ },
218
+ {
219
+ "episode": 29,
220
+ "seed": 9,
221
+ "reward": 6.0,
222
+ "steps": 15,
223
+ "success": 1
224
+ },
225
+ {
226
+ "episode": 30,
227
+ "seed": 97,
228
+ "reward": 9.0,
229
+ "steps": 12,
230
+ "success": 1
231
+ },
232
+ {
233
+ "episode": 31,
234
+ "seed": 164,
235
+ "reward": 6.0,
236
+ "steps": 15,
237
+ "success": 1
238
+ },
239
+ {
240
+ "episode": 32,
241
+ "seed": 176,
242
+ "reward": 6.0,
243
+ "steps": 15,
244
+ "success": 1
245
+ },
246
+ {
247
+ "episode": 33,
248
+ "seed": 61,
249
+ "reward": 10.0,
250
+ "steps": 11,
251
+ "success": 1
252
+ },
253
+ {
254
+ "episode": 34,
255
+ "seed": 7,
256
+ "reward": 10.0,
257
+ "steps": 11,
258
+ "success": 1
259
+ },
260
+ {
261
+ "episode": 35,
262
+ "seed": 54,
263
+ "reward": 12.0,
264
+ "steps": 9,
265
+ "success": 1
266
+ },
267
+ {
268
+ "episode": 36,
269
+ "seed": 55,
270
+ "reward": 5.0,
271
+ "steps": 16,
272
+ "success": 1
273
+ },
274
+ {
275
+ "episode": 37,
276
+ "seed": 161,
277
+ "reward": 9.0,
278
+ "steps": 12,
279
+ "success": 1
280
+ },
281
+ {
282
+ "episode": 38,
283
+ "seed": 131,
284
+ "reward": 11.0,
285
+ "steps": 10,
286
+ "success": 1
287
+ },
288
+ {
289
+ "episode": 39,
290
+ "seed": 184,
291
+ "reward": 6.0,
292
+ "steps": 15,
293
+ "success": 1
294
+ },
295
+ {
296
+ "episode": 40,
297
+ "seed": 51,
298
+ "reward": 8.0,
299
+ "steps": 13,
300
+ "success": 1
301
+ },
302
+ {
303
+ "episode": 41,
304
+ "seed": 170,
305
+ "reward": 5.0,
306
+ "steps": 16,
307
+ "success": 1
308
+ },
309
+ {
310
+ "episode": 42,
311
+ "seed": 12,
312
+ "reward": 5.0,
313
+ "steps": 16,
314
+ "success": 1
315
+ },
316
+ {
317
+ "episode": 43,
318
+ "seed": 120,
319
+ "reward": 4.0,
320
+ "steps": 17,
321
+ "success": 1
322
+ },
323
+ {
324
+ "episode": 44,
325
+ "seed": 113,
326
+ "reward": 6.0,
327
+ "steps": 15,
328
+ "success": 1
329
+ },
330
+ {
331
+ "episode": 45,
332
+ "seed": 95,
333
+ "reward": 10.0,
334
+ "steps": 11,
335
+ "success": 1
336
+ },
337
+ {
338
+ "episode": 46,
339
+ "seed": 126,
340
+ "reward": 15.0,
341
+ "steps": 6,
342
+ "success": 1
343
+ },
344
+ {
345
+ "episode": 47,
346
+ "seed": 51,
347
+ "reward": 8.0,
348
+ "steps": 13,
349
+ "success": 1
350
+ },
351
+ {
352
+ "episode": 48,
353
+ "seed": 98,
354
+ "reward": 4.0,
355
+ "steps": 17,
356
+ "success": 1
357
+ },
358
+ {
359
+ "episode": 49,
360
+ "seed": 36,
361
+ "reward": 7.0,
362
+ "steps": 14,
363
+ "success": 1
364
+ },
365
+ {
366
+ "episode": 50,
367
+ "seed": 135,
368
+ "reward": 7.0,
369
+ "steps": 14,
370
+ "success": 1
371
+ },
372
+ {
373
+ "episode": 51,
374
+ "seed": 54,
375
+ "reward": 12.0,
376
+ "steps": 9,
377
+ "success": 1
378
+ },
379
+ {
380
+ "episode": 52,
381
+ "seed": 82,
382
+ "reward": 11.0,
383
+ "steps": 10,
384
+ "success": 1
385
+ },
386
+ {
387
+ "episode": 53,
388
+ "seed": 45,
389
+ "reward": 9.0,
390
+ "steps": 12,
391
+ "success": 1
392
+ },
393
+ {
394
+ "episode": 54,
395
+ "seed": 95,
396
+ "reward": 10.0,
397
+ "steps": 11,
398
+ "success": 1
399
+ },
400
+ {
401
+ "episode": 55,
402
+ "seed": 89,
403
+ "reward": 9.0,
404
+ "steps": 12,
405
+ "success": 1
406
+ },
407
+ {
408
+ "episode": 56,
409
+ "seed": 59,
410
+ "reward": 6.0,
411
+ "steps": 15,
412
+ "success": 1
413
+ },
414
+ {
415
+ "episode": 57,
416
+ "seed": 95,
417
+ "reward": 10.0,
418
+ "steps": 11,
419
+ "success": 1
420
+ },
421
+ {
422
+ "episode": 58,
423
+ "seed": 124,
424
+ "reward": 4.0,
425
+ "steps": 17,
426
+ "success": 1
427
+ },
428
+ {
429
+ "episode": 59,
430
+ "seed": 9,
431
+ "reward": 6.0,
432
+ "steps": 15,
433
+ "success": 1
434
+ },
435
+ {
436
+ "episode": 60,
437
+ "seed": 113,
438
+ "reward": 6.0,
439
+ "steps": 15,
440
+ "success": 1
441
+ },
442
+ {
443
+ "episode": 61,
444
+ "seed": 58,
445
+ "reward": 7.0,
446
+ "steps": 14,
447
+ "success": 1
448
+ },
449
+ {
450
+ "episode": 62,
451
+ "seed": 85,
452
+ "reward": 14.0,
453
+ "steps": 7,
454
+ "success": 1
455
+ },
456
+ {
457
+ "episode": 63,
458
+ "seed": 51,
459
+ "reward": 8.0,
460
+ "steps": 13,
461
+ "success": 1
462
+ },
463
+ {
464
+ "episode": 64,
465
+ "seed": 134,
466
+ "reward": 4.0,
467
+ "steps": 17,
468
+ "success": 1
469
+ },
470
+ {
471
+ "episode": 65,
472
+ "seed": 121,
473
+ "reward": 6.0,
474
+ "steps": 15,
475
+ "success": 1
476
+ },
477
+ {
478
+ "episode": 66,
479
+ "seed": 169,
480
+ "reward": 10.0,
481
+ "steps": 11,
482
+ "success": 1
483
+ },
484
+ {
485
+ "episode": 67,
486
+ "seed": 105,
487
+ "reward": 6.0,
488
+ "steps": 15,
489
+ "success": 1
490
+ },
491
+ {
492
+ "episode": 68,
493
+ "seed": 21,
494
+ "reward": 8.0,
495
+ "steps": 13,
496
+ "success": 1
497
+ },
498
+ {
499
+ "episode": 69,
500
+ "seed": 30,
501
+ "reward": 8.0,
502
+ "steps": 13,
503
+ "success": 1
504
+ },
505
+ {
506
+ "episode": 70,
507
+ "seed": 11,
508
+ "reward": 5.0,
509
+ "steps": 16,
510
+ "success": 1
511
+ },
512
+ {
513
+ "episode": 71,
514
+ "seed": 50,
515
+ "reward": 5.0,
516
+ "steps": 16,
517
+ "success": 1
518
+ },
519
+ {
520
+ "episode": 72,
521
+ "seed": 65,
522
+ "reward": 11.0,
523
+ "steps": 10,
524
+ "success": 1
525
+ },
526
+ {
527
+ "episode": 73,
528
+ "seed": 12,
529
+ "reward": 5.0,
530
+ "steps": 16,
531
+ "success": 1
532
+ },
533
+ {
534
+ "episode": 74,
535
+ "seed": 43,
536
+ "reward": 5.0,
537
+ "steps": 16,
538
+ "success": 1
539
+ },
540
+ {
541
+ "episode": 75,
542
+ "seed": 82,
543
+ "reward": 11.0,
544
+ "steps": 10,
545
+ "success": 1
546
+ },
547
+ {
548
+ "episode": 76,
549
+ "seed": 145,
550
+ "reward": 8.0,
551
+ "steps": 13,
552
+ "success": 1
553
+ },
554
+ {
555
+ "episode": 77,
556
+ "seed": 152,
557
+ "reward": 7.0,
558
+ "steps": 14,
559
+ "success": 1
560
+ },
561
+ {
562
+ "episode": 78,
563
+ "seed": 97,
564
+ "reward": 9.0,
565
+ "steps": 12,
566
+ "success": 1
567
+ },
568
+ {
569
+ "episode": 79,
570
+ "seed": 106,
571
+ "reward": 7.0,
572
+ "steps": 14,
573
+ "success": 1
574
+ },
575
+ {
576
+ "episode": 80,
577
+ "seed": 55,
578
+ "reward": 5.0,
579
+ "steps": 16,
580
+ "success": 1
581
+ },
582
+ {
583
+ "episode": 81,
584
+ "seed": 31,
585
+ "reward": 9.0,
586
+ "steps": 12,
587
+ "success": 1
588
+ },
589
+ {
590
+ "episode": 82,
591
+ "seed": 85,
592
+ "reward": 14.0,
593
+ "steps": 7,
594
+ "success": 1
595
+ },
596
+ {
597
+ "episode": 83,
598
+ "seed": 38,
599
+ "reward": 11.0,
600
+ "steps": 10,
601
+ "success": 1
602
+ },
603
+ {
604
+ "episode": 84,
605
+ "seed": 112,
606
+ "reward": 9.0,
607
+ "steps": 12,
608
+ "success": 1
609
+ },
610
+ {
611
+ "episode": 85,
612
+ "seed": 102,
613
+ "reward": 8.0,
614
+ "steps": 13,
615
+ "success": 1
616
+ },
617
+ {
618
+ "episode": 86,
619
+ "seed": 168,
620
+ "reward": 7.0,
621
+ "steps": 14,
622
+ "success": 1
623
+ },
624
+ {
625
+ "episode": 87,
626
+ "seed": 123,
627
+ "reward": 6.0,
628
+ "steps": 15,
629
+ "success": 1
630
+ },
631
+ {
632
+ "episode": 88,
633
+ "seed": 97,
634
+ "reward": 9.0,
635
+ "steps": 12,
636
+ "success": 1
637
+ },
638
+ {
639
+ "episode": 89,
640
+ "seed": 21,
641
+ "reward": 8.0,
642
+ "steps": 13,
643
+ "success": 1
644
+ },
645
+ {
646
+ "episode": 90,
647
+ "seed": 83,
648
+ "reward": 10.0,
649
+ "steps": 11,
650
+ "success": 1
651
+ },
652
+ {
653
+ "episode": 91,
654
+ "seed": 158,
655
+ "reward": 4.0,
656
+ "steps": 17,
657
+ "success": 1
658
+ },
659
+ {
660
+ "episode": 92,
661
+ "seed": 26,
662
+ "reward": 7.0,
663
+ "steps": 14,
664
+ "success": 1
665
+ },
666
+ {
667
+ "episode": 93,
668
+ "seed": 80,
669
+ "reward": 7.0,
670
+ "steps": 14,
671
+ "success": 1
672
+ },
673
+ {
674
+ "episode": 94,
675
+ "seed": 63,
676
+ "reward": 5.0,
677
+ "steps": 16,
678
+ "success": 1
679
+ },
680
+ {
681
+ "episode": 95,
682
+ "seed": 5,
683
+ "reward": 11.0,
684
+ "steps": 10,
685
+ "success": 1
686
+ },
687
+ {
688
+ "episode": 96,
689
+ "seed": 81,
690
+ "reward": 9.0,
691
+ "steps": 12,
692
+ "success": 1
693
+ },
694
+ {
695
+ "episode": 97,
696
+ "seed": 32,
697
+ "reward": 3.0,
698
+ "steps": 18,
699
+ "success": 1
700
+ },
701
+ {
702
+ "episode": 98,
703
+ "seed": 11,
704
+ "reward": 5.0,
705
+ "steps": 16,
706
+ "success": 1
707
+ },
708
+ {
709
+ "episode": 99,
710
+ "seed": 28,
711
+ "reward": 4.0,
712
+ "steps": 17,
713
+ "success": 1
714
+ },
715
+ {
716
+ "episode": 100,
717
+ "seed": 148,
718
+ "reward": 6.0,
719
+ "steps": 15,
720
+ "success": 1
721
+ }
722
+ ],
723
+ "evaluation_seconds": 0.02
724
+ }
logs/frozenlake4x4/progress.csv ADDED
The diff for this file is too large to render. See raw diff
 
logs/frozenlake4x4/train_console.log ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ==============================================================================
2
+ Q-Learning 训练 · FrozenLake-v1(4x4,is_slippery=False)
3
+ 运行名称: frozenlake4x4 开始时间: 2026-10-30 22:29:00
4
+ ==============================================================================
5
+
6
+ [环境自检]
7
+ env_id = FrozenLake-v1
8
+ env_kwargs = {'map_name': '4x4', 'is_slippery': False}
9
+ 观测空间 = Discrete(16) -> 16 个状态
10
+ 动作空间 = Discrete(4) -> 4 个动作
11
+ 一次 reset 的观测 = 0(整数 = 状态编号,4x4 地图按 row*4+col 编码)
12
+ 每回合最大步数 = 99
13
+
14
+ [Q 表初始化]
15
+ Q 表形状 = (16, 4)(= (状态数, 动作数)),初始值全为 0
16
+ 需要学习的参数个数 = 64
17
+
18
+ [超参数]
19
+ n_training_episodes = 10000
20
+ learning_rate = 0.7
21
+ gamma = 0.95
22
+ max_steps = 99
23
+ max_epsilon = 1.0
24
+ min_epsilon = 0.05
25
+ decay_rate = 0.0005
26
+ n_eval_episodes = 100
27
+ eval_seed = [](不指定)
28
+ train_seed = 0
29
+
30
+ [开始训练] 22:29:00 每 500 回合打印一行,每 1000 回合评估一次
31
+ 可复现性:每回合 env.reset(seed=0+episode),同 --seed 重跑分数一致
32
+ [train] ep= 1/10000 | eps=1.000 | 最近100回合平均回报= 0.00 | 成功率= 0.0% | 平均步数= 11.0 | Q非零项= 0 | 用时= 0.00min
33
+ [train] ep= 500/10000 | eps=0.790 | 最近100回合平均回报= 0.06 | 成功率= 6.0% | 平均步数= 7.6 | Q非零项= 35 | 用时= 0.00min
34
+ [train] ep= 1000/10000 | eps=0.626 | 最近100回合平均回报= 0.26 | 成功率= 26.0% | 平均步数= 7.4 | Q非零项= 35 | 用时= 0.00min
35
+ [eval ] ep= 1000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
36
+ [train] ep= 1500/10000 | eps=0.499 | 最近100回合平均回报= 0.47 | 成功率= 47.0% | 平均步数= 7.1 | Q非零项= 35 | 用时= 0.00min
37
+ [train] ep= 2000/10000 | eps=0.400 | 最近100回合平均回报= 0.54 | 成功率= 54.0% | 平均步数= 7.1 | Q非零项= 35 | 用时= 0.01min
38
+ [eval ] ep= 2000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
39
+ [train] ep= 2500/10000 | eps=0.322 | 最近100回合平均回报= 0.66 | 成功率= 66.0% | 平均步数= 7.0 | Q非零项= 35 | 用时= 0.01min
40
+ [train] ep= 3000/10000 | eps=0.262 | 最近100回合平均回报= 0.69 | 成功率= 69.0% | 平均步数= 6.7 | Q非零项= 35 | 用时= 0.01min
41
+ [eval ] ep= 3000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
42
+ [train] ep= 3500/10000 | eps=0.215 | 最近100回合平均回报= 0.77 | 成功率= 77.0% | 平均步数= 6.7 | Q非零项= 35 | 用时= 0.01min
43
+ [train] ep= 4000/10000 | eps=0.179 | 最近100回合平均回报= 0.84 | 成功率= 84.0% | 平均步数= 6.3 | Q非零项= 35 | 用时= 0.01min
44
+ [eval ] ep= 4000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
45
+ [train] ep= 4500/10000 | eps=0.150 | 最近100回合平均回报= 0.86 | 成功率= 86.0% | 平均步数= 6.5 | Q非零项= 35 | 用时= 0.01min
46
+ [train] ep= 5000/10000 | eps=0.128 | 最近100回合平均回报= 0.83 | 成功率= 83.0% | 平均步数= 6.2 | Q非零项= 35 | 用时= 0.01min
47
+ [eval ] ep= 5000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
48
+ [train] ep= 5500/10000 | eps=0.111 | 最近100回合平均回报= 0.85 | 成功率= 85.0% | 平均步数= 6.2 | Q非零项= 35 | 用时= 0.01min
49
+ [train] ep= 6000/10000 | eps=0.097 | 最近100回合平均回报= 0.94 | 成功率= 94.0% | 平均步数= 6.3 | Q非零项= 35 | 用时= 0.01min
50
+ [eval ] ep= 6000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
51
+ [train] ep= 6500/10000 | eps=0.087 | 最近100回合平均回报= 0.92 | 成功率= 92.0% | 平均步数= 6.1 | Q非零项= 35 | 用时= 0.02min
52
+ [train] ep= 7000/10000 | eps=0.079 | 最近100回合平均回报= 0.88 | 成功率= 88.0% | 平均步数= 6.2 | Q非零项= 35 | 用时= 0.02min
53
+ [eval ] ep= 7000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
54
+ [train] ep= 7500/10000 | eps=0.072 | 最近100回合平均回报= 0.92 | 成功率= 92.0% | 平均步数= 6.1 | Q非零项= 35 | 用时= 0.02min
55
+ [train] ep= 8000/10000 | eps=0.067 | 最近100回合平均回报= 0.88 | 成功率= 88.0% | 平均步数= 6.0 | Q非零项= 35 | 用时= 0.02min
56
+ [eval ] ep= 8000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
57
+ [train] ep= 8500/10000 | eps=0.064 | 最近100回合平均回报= 0.92 | 成功率= 92.0% | 平均步数= 6.2 | Q非零项= 35 | 用时= 0.02min
58
+ [train] ep= 9000/10000 | eps=0.061 | 最近100回合平均回报= 0.92 | 成功率= 92.0% | 平均步数= 6.0 | Q非零项= 35 | 用时= 0.02min
59
+ [eval ] ep= 9000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
60
+ [train] ep= 9500/10000 | eps=0.058 | 最近100回合平均回报= 0.92 | 成功率= 92.0% | 平均步数= 6.0 | Q非零项= 35 | 用时= 0.02min
61
+ [train] ep= 10000/10000 | eps=0.056 | 最近100回合平均回报= 0.93 | 成功率= 93.0% | 平均步数= 6.1 | Q非零项= 35 | 用时= 0.02min
62
+ [eval ] ep= 10000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
63
+
64
+ [训练完成] 22:29:01 总用时 = 0.02 分钟
65
+
66
+ [最终评估] 用贪婪策略跑 100 局(独立于训练)
67
+ Mean_reward = 1.00 +/- 0.00
68
+ 成功率 = 100%
69
+ 训练途中最好的周期评估:1.00 ± 0.00 @ 第 1000 回合
70
+ Q 表非零项 = 35 / 64
71
+ Q 表最大价值 = 1.0000
72
+
73
+ [输出] 训练曲线 = logs\frozenlake4x4\train_curve.png
74
+ [输出] 进度 CSV = logs\frozenlake4x4\progress.csv
75
+ [输出] 模型文件 = models\qtable_frozenlake4x4.pkl (1.1 KB)
76
+
77
+ ==============================================================================
78
+ 完成。下一步:python evaluate_and_record.py --model models/qtable_frozenlake4x4.pkl
79
+ ==============================================================================
logs/frozenlake4x4/train_curve.png ADDED

Git LFS Details

  • SHA256: 061796ef1ab4cc39bc9825ea03e9358fb2ec63c3fe02af24f3dae15d61678074
  • Pointer size: 131 Bytes
  • Size of remote file: 160 kB
logs/frozenlake4x4_slippery/progress.csv ADDED
The diff for this file is too large to render. See raw diff
 
logs/frozenlake4x4_slippery/train_console.log ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ==============================================================================
2
+ Q-Learning 训练 · FrozenLake-v1(4x4,is_slippery=True)
3
+ 运行名称: frozenlake4x4_slippery 开始时间: 2026-10-30 22:29:40
4
+ ==============================================================================
5
+
6
+ [环境自检]
7
+ env_id = FrozenLake-v1
8
+ env_kwargs = {'map_name': '4x4', 'is_slippery': True}
9
+ 观测空间 = Discrete(16) -> 16 个状态
10
+ 动作空间 = Discrete(4) -> 4 个动作
11
+ 一次 reset 的观测 = 0(整数 = 状态编号,4x4 地图按 row*4+col 编码)
12
+ 每回合最大步数 = 99
13
+
14
+ [Q 表初始化]
15
+ Q 表形状 = (16, 4)(= (状态数, 动作数)),初始值全为 0
16
+ 需要学习的参数个数 = 64
17
+
18
+ [超参数]
19
+ n_training_episodes = 20000
20
+ learning_rate = 0.7
21
+ gamma = 0.95
22
+ max_steps = 99
23
+ max_epsilon = 1.0
24
+ min_epsilon = 0.1
25
+ decay_rate = 0.0003
26
+ n_eval_episodes = 100
27
+ eval_seed = 官方 100 个种子
28
+ train_seed = 0
29
+
30
+ [开始训练] 22:29:40 每 1000 回合打印一行,每 2000 回合评估一次
31
+ 可复现性:每回合 env.reset(seed=0+episode),同 --seed 重跑分数一致
32
+ [train] ep= 1/20000 | eps=1.000 | 最近100回合平均回报= 0.00 | 成功率= 0.0% | 平均步数= 2.0 | Q非零项= 0 | 用时= 0.00min
33
+ [train] ep= 1000/20000 | eps=0.767 | 最近100回合平均回报= 0.01 | 成功率= 1.0% | 平均步数= 8.2 | Q非零项= 44 | 用时= 0.00min
34
+ [train] ep= 2000/20000 | eps=0.594 | 最近100回合平均回报= 0.05 | 成功率= 5.0% | 平均步数= 9.6 | Q非零项= 44 | 用时= 0.01min
35
+ [eval ] ep= 2000 | 贪婪策略 20 局: 回报= 0.00 ± 0.00 | 成功率= 0.0%
36
+ [train] ep= 3000/20000 | eps=0.466 | 最近100回合平均回报= 0.04 | 成功率= 4.0% | 平均步数= 10.5 | Q非零项= 44 | 用时= 0.01min
37
+ [train] ep= 4000/20000 | eps=0.371 | 最近100回合平均回报= 0.03 | 成功率= 3.0% | 平均步数= 11.7 | Q非零项= 44 | 用时= 0.01min
38
+ [eval ] ep= 4000 | 贪婪策略 20 局: 回报= 0.10 ± 0.30 | 成功率= 10.0%
39
+ [train] ep= 5000/20000 | eps=0.301 | 最近100回合平均回报= 0.04 | 成功率= 4.0% | 平均步数= 13.2 | Q非零项= 44 | 用时= 0.02min
40
+ [train] ep= 6000/20000 | eps=0.249 | 最近100回合平均回报= 0.06 | 成功率= 6.0% | 平均步数= 13.1 | Q非零项= 44 | 用时= 0.02min
41
+ [eval ] ep= 6000 | 贪婪策略 20 局: 回报= 0.30 ± 0.46 | 成功率= 30.0%
42
+ [train] ep= 7000/20000 | eps=0.210 | 最近100回合平均回报= 0.10 | 成功率= 10.0% | 平均步数= 15.9 | Q非零项= 44 | 用时= 0.02min
43
+ [train] ep= 8000/20000 | eps=0.182 | 最近100回合平均回报= 0.10 | 成功率= 10.0% | 平均步数= 17.3 | Q非零项= 44 | 用时= 0.03min
44
+ [eval ] ep= 8000 | 贪婪策略 20 局: 回报= 0.10 ± 0.30 | 成功率= 10.0%
45
+ [train] ep= 9000/20000 | eps=0.161 | 最近100回合平均回报= 0.09 | 成功率= 9.0% | 平均步数= 17.4 | Q非零项= 44 | 用时= 0.03min
46
+ [train] ep= 10000/20000 | eps=0.145 | 最近100回合平均回报= 0.12 | 成功率= 12.0% | 平均步数= 16.9 | Q非零项= 44 | 用时= 0.04min
47
+ [eval ] ep= 10000 | 贪婪策略 20 局: 回报= 0.65 ± 0.48 | 成功率= 65.0%
48
+ [train] ep= 11000/20000 | eps=0.133 | 最近100回合平均回报= 0.06 | 成功率= 6.0% | 平均步数= 16.5 | Q非零项= 44 | 用时= 0.04min
49
+ [train] ep= 12000/20000 | eps=0.125 | 最近100回合平均回报= 0.09 | 成功率= 9.0% | 平均步数= 17.4 | Q非零项= 44 | 用时= 0.05min
50
+ [eval ] ep= 12000 | 贪婪策略 20 局: 回报= 0.10 ± 0.30 | 成功率= 10.0%
51
+ [train] ep= 13000/20000 | eps=0.118 | 最近100回合平均回报= 0.09 | 成功率= 9.0% | 平均步数= 18.2 | Q非零项= 44 | 用时= 0.05min
52
+ [train] ep= 14000/20000 | eps=0.114 | 最近100回合平均回报= 0.22 | 成功率= 22.0% | 平均步数= 18.9 | Q非零项= 44 | 用时= 0.06min
53
+ [eval ] ep= 14000 | 贪婪策略 20 局: 回报= 0.65 ± 0.48 | 成功率= 65.0%
54
+ [train] ep= 15000/20000 | eps=0.110 | 最近100回合平均回报= 0.16 | 成功率= 16.0% | 平均步数= 19.4 | Q非零项= 44 | 用时= 0.07min
55
+ [train] ep= 16000/20000 | eps=0.107 | 最近100回合平均回报= 0.21 | 成功率= 21.0% | 平均步数= 21.4 | Q非零项= 44 | 用时= 0.07min
56
+ [eval ] ep= 16000 | 贪婪策略 20 局: 回报= 0.55 ± 0.50 | 成功率= 55.0%
57
+ [train] ep= 17000/20000 | eps=0.105 | 最近100回合平均回报= 0.23 | 成功率= 23.0% | 平均步数= 20.5 | Q非零项= 44 | 用时= 0.08min
58
+ [train] ep= 18000/20000 | eps=0.104 | 最近100回合平均回报= 0.24 | 成功率= 24.0% | 平均步数= 19.9 | Q非零项= 44 | 用时= 0.08min
59
+ [eval ] ep= 18000 | 贪婪策略 20 局: 回报= 0.65 ± 0.48 | 成功率= 65.0%
60
+ [train] ep= 19000/20000 | eps=0.103 | 最近100回合平均回报= 0.18 | 成功率= 18.0% | 平均步数= 20.9 | Q非零项= 44 | 用时= 0.09min
61
+ [train] ep= 20000/20000 | eps=0.102 | 最近100回合平均回报= 0.17 | 成功率= 17.0% | 平均步数= 19.4 | Q非零项= 44 | 用时= 0.09min
62
+ [eval ] ep= 20000 | 贪婪策略 20 局: 回报= 0.75 ± 0.43 | 成功率= 75.0%
63
+
64
+ [训练完成] 22:29:46 总用时 = 0.10 分钟
65
+
66
+ [最终评估] 用贪婪策略跑 100 局(独立于训练)
67
+ Mean_reward = 0.74 +/- 0.44
68
+ 成功率 = 74%
69
+ 训练途中最好的周期评估:0.75 ± 0.43 @ 第 20000 回合
70
+ Q 表非零项 = 44 / 64
71
+ Q 表最大价值 = 0.8458
72
+
73
+ [输出] 训练曲线 = logs\frozenlake4x4_slippery\train_curve.png
74
+ [输出] 进度 CSV = logs\frozenlake4x4_slippery\progress.csv
75
+ [输出] 模型文件 = models\qtable_frozenlake4x4_slippery.pkl (1.4 KB)
76
+
77
+ ==============================================================================
78
+ 完成。下一步:python evaluate_and_record.py --model models/qtable_frozenlake4x4_slippery.pkl
79
+ ==============================================================================
logs/frozenlake4x4_slippery/train_curve.png ADDED

Git LFS Details

  • SHA256: ab75cab760bd1b954b655d66cb6851b2bfb37b063be0a94122f29767da0b2151
  • Pointer size: 131 Bytes
  • Size of remote file: 165 kB
logs/frozenlake8x8/progress.csv ADDED
The diff for this file is too large to render. See raw diff
 
logs/frozenlake8x8/train_console.log ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ==============================================================================
2
+ Q-Learning 训练 · FrozenLake-v1(8x8,is_slippery=False)
3
+ 运行名称: frozenlake8x8 开始时间: 2026-10-30 22:30:02
4
+ ==============================================================================
5
+
6
+ [环境自检]
7
+ env_id = FrozenLake-v1
8
+ env_kwargs = {'map_name': '8x8', 'is_slippery': False}
9
+ 观测空间 = Discrete(64) -> 64 个状态
10
+ 动作空间 = Discrete(4) -> 4 个动作
11
+ 一次 reset 的观测 = 0(整数 = 状态编号,8x8 地图按 row*8+col 编码)
12
+ 每回合最大步数 = 200
13
+
14
+ [Q 表初始化]
15
+ Q 表形状 = (64, 4)(= (状态数, 动作数)),初始值全为 0
16
+ 需要学习的参数个数 = 256
17
+
18
+ [超参数]
19
+ n_training_episodes = 60000
20
+ learning_rate = 0.8
21
+ gamma = 0.95
22
+ max_steps = 200
23
+ max_epsilon = 1.0
24
+ min_epsilon = 0.3
25
+ decay_rate = 3e-05
26
+ n_eval_episodes = 100
27
+ eval_seed = 官方 100 个种子
28
+ train_seed = 0
29
+
30
+ [开始训练] 22:30:02 每 3000 回合打印一行,每 6000 回合评估一次
31
+ 可复现性:每回合 env.reset(seed=0+episode),同 --seed 重跑分数一致
32
+ [train] ep= 1/60000 | eps=1.000 | 最近100回合平均回报= 0.00 | 成功率= 0.0% | 平均步数= 42.0 | Q非零项= 0 | 用时= 0.00min
33
+ [train] ep= 3000/60000 | eps=0.940 | 最近100回合平均回报= 0.00 | 成功率= 0.0% | 平均步数= 23.9 | Q非零项= 161 | 用时= 0.02min
34
+ [train] ep= 6000/60000 | eps=0.885 | 最近100回合平均回报= 0.00 | 成功率= 0.0% | 平均步数= 23.6 | Q非零项= 171 | 用时= 0.05min
35
+ [eval ] ep= 6000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
36
+ [train] ep= 9000/60000 | eps=0.834 | 最近100回合平均回报= 0.02 | 成功率= 2.0% | 平均步数= 22.6 | Q非零项= 175 | 用时= 0.06min
37
+ [train] ep= 12000/60000 | eps=0.788 | 最近100回合平均回报= 0.02 | 成功率= 2.0% | 平均步数= 21.0 | Q非零项= 175 | 用时= 0.08min
38
+ [eval ] ep= 12000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
39
+ [train] ep= 15000/60000 | eps=0.746 | 最近100回合平均回报= 0.01 | 成功率= 1.0% | 平均步数= 18.5 | Q非零项= 175 | 用时= 0.10min
40
+ [train] ep= 18000/60000 | eps=0.708 | 最近100回合平均回报= 0.01 | 成功率= 1.0% | 平均步数= 18.1 | Q非零项= 175 | 用时= 0.11min
41
+ [eval ] ep= 18000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
42
+ [train] ep= 21000/60000 | eps=0.673 | 最近100回合平均回报= 0.04 | 成功率= 4.0% | 平均步数= 19.2 | Q非零项= 176 | 用时= 0.13min
43
+ [train] ep= 24000/60000 | eps=0.641 | 最近100回合平均回报= 0.03 | 成功率= 3.0% | 平均步数= 18.1 | Q非零项= 178 | 用时= 0.14min
44
+ [eval ] ep= 24000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
45
+ [train] ep= 27000/60000 | eps=0.611 | 最近100回合平均回报= 0.08 | 成功率= 8.0% | 平均步数= 17.6 | Q非零项= 179 | 用时= 0.16min
46
+ [train] ep= 30000/60000 | eps=0.585 | 最近100回合平均回报= 0.08 | 成功率= 8.0% | 平均步数= 16.5 | Q非零项= 179 | 用时= 0.17min
47
+ [eval ] ep= 30000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
48
+ [train] ep= 33000/60000 | eps=0.560 | 最近100回合平均回报= 0.10 | 成功率= 10.0% | 平均步数= 17.6 | Q非零项= 179 | 用时= 0.18min
49
+ [train] ep= 36000/60000 | eps=0.538 | 最近100回合平均回报= 0.07 | 成功率= 7.0% | 平均步数= 14.5 | Q非零项= 179 | 用时= 0.20min
50
+ [eval ] ep= 36000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
51
+ [train] ep= 39000/60000 | eps=0.517 | 最近100回合平均回报= 0.08 | 成功率= 8.0% | 平均步数= 16.6 | Q非零项= 179 | 用时= 0.21min
52
+ [train] ep= 42000/60000 | eps=0.499 | 最近100回合平均回报= 0.16 | 成功率= 16.0% | 平均步数= 16.7 | Q非零项= 179 | 用时= 0.23min
53
+ [eval ] ep= 42000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
54
+ [train] ep= 45000/60000 | eps=0.481 | 最近100回合平均回报= 0.16 | 成功率= 16.0% | 平均步数= 16.2 | Q非零项= 179 | 用时= 0.24min
55
+ [train] ep= 48000/60000 | eps=0.466 | 最近100回合平均回报= 0.12 | 成功率= 12.0% | 平均步数= 15.4 | Q非零项= 179 | 用时= 0.26min
56
+ [eval ] ep= 48000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
57
+ [train] ep= 51000/60000 | eps=0.452 | 最近100回合平均回报= 0.14 | 成功率= 14.0% | 平均步数= 15.0 | Q非零项= 179 | 用时= 0.27min
58
+ [train] ep= 54000/60000 | eps=0.439 | 最近100回合平均回报= 0.17 | 成功率= 17.0% | 平均步数= 14.4 | Q非零项= 179 | 用时= 0.28min
59
+ [eval ] ep= 54000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
60
+ [train] ep= 57000/60000 | eps=0.427 | 最近100回合平均回报= 0.25 | 成功率= 25.0% | 平均步数= 14.7 | Q非零项= 179 | ��时= 0.30min
61
+ [train] ep= 60000/60000 | eps=0.416 | 最近100回合平均回报= 0.22 | 成功率= 22.0% | 平均步数= 16.1 | Q非零项= 179 | 用时= 0.31min
62
+ [eval ] ep= 60000 | 贪婪策略 20 局: 回报= 1.00 ± 0.00 | 成功率=100.0%
63
+
64
+ [训练完成] 22:30:20 总用时 = 0.31 分钟
65
+
66
+ [最终评估] 用贪婪策略跑 100 局(独立于训练)
67
+ Mean_reward = 1.00 +/- 0.00
68
+ 成功率 = 100%
69
+ 训练途中最好的周期评估:1.00 ± 0.00 @ 第 6000 回合
70
+ Q 表非零项 = 179 / 256
71
+ Q 表最大价值 = 1.0000
72
+
73
+ [输出] 训练曲线 = logs\frozenlake8x8\train_curve.png
74
+ [输出] 进度 CSV = logs\frozenlake8x8\progress.csv
75
+ [输出] 模型文件 = models\qtable_frozenlake8x8.pkl (2.8 KB)
76
+
77
+ ==============================================================================
78
+ 完成。下一步:python evaluate_and_record.py --model models/qtable_frozenlake8x8.pkl
79
+ ==============================================================================
logs/frozenlake8x8/train_curve.png ADDED

Git LFS Details

  • SHA256: 472274e30623d1452b5fe79eb00763adc4d9e72fe858d8b456293400a35bb521
  • Pointer size: 131 Bytes
  • Size of remote file: 136 kB
logs/taxi/progress.csv ADDED
The diff for this file is too large to render. See raw diff
 
logs/taxi/train_console.log ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ==============================================================================
2
+ Q-Learning 训练 · Taxi-v3
3
+ 运行名称: taxi 开始时间: 2026-10-30 22:45:31
4
+ ==============================================================================
5
+
6
+ [环境自检]
7
+ env_id = Taxi-v3
8
+ env_kwargs = {}
9
+ 观测空间 = Discrete(500) -> 500 个状态
10
+ 动作空间 = Discrete(6) -> 6 个动作
11
+ 一次 reset 的观测 = 314(整数 = 状态编号,共 500 个)
12
+ 每回合最大步数 = 99
13
+
14
+ [Q 表初始化]
15
+ Q 表形状 = (500, 6)(= (状态数, 动作数)),初始值全为 0
16
+ 需要学习的参数个数 = 3000
17
+
18
+ [超参数]
19
+ n_training_episodes = 25000
20
+ learning_rate = 0.7
21
+ gamma = 0.95
22
+ max_steps = 99
23
+ max_epsilon = 1.0
24
+ min_epsilon = 0.05
25
+ decay_rate = 0.005
26
+ n_eval_episodes = 100
27
+ eval_seed = 官方 100 个种子
28
+ train_seed = 0
29
+
30
+ [开始训练] 22:45:31 每 1000 回合打印一行,每 2500 回合评估一次
31
+ 可复现性:每回合 env.reset(seed=0+episode),同 --seed 重跑分数一致
32
+ [train] ep= 1/25000 | eps=1.000 | 最近100回合平均回报=-405.00 | 成功率= 0.0% | 平均步数= 99.0 | Q非零项= 29 | 用时= 0.00min
33
+ [train] ep= 1000/25000 | eps=0.056 | 最近100回合平均回报= 4.96 | 成功率=100.0% | 平均步数= 14.1 | Q非零项= 2393 | 用时= 0.01min
34
+ [train] ep= 2000/25000 | eps=0.050 | 最近100回合平均回报= 4.12 | 成功率=100.0% | 平均步数= 14.5 | Q非零项= 2393 | 用时= 0.01min
35
+ [eval ] ep= 2500 | 贪婪策略 20 局: 回报= 7.20 ± 2.93 | 成功率=100.0%
36
+ [train] ep= 3000/25000 | eps=0.050 | 最近100回合平均回报= 5.34 | 成功率=100.0% | 平均步数= 13.8 | Q非零项= 2393 | 用时= 0.02min
37
+ [train] ep= 4000/25000 | eps=0.050 | 最近100回合平均回报= 4.83 | 成功率=100.0% | 平均步数= 14.1 | Q非零项= 2393 | 用时= 0.02min
38
+ [train] ep= 5000/25000 | eps=0.050 | 最近100回合平均回报= 4.12 | 成功率=100.0% | 平均步数= 14.7 | Q非零项= 2393 | 用时= 0.03min
39
+ [eval ] ep= 5000 | 贪婪策略 20 局: 回报= 7.30 ± 2.85 | 成功率=100.0%
40
+ [train] ep= 6000/25000 | eps=0.050 | 最近100回合平均回报= 4.99 | 成功率=100.0% | 平均步数= 13.8 | Q非零项= 2393 | 用时= 0.03min
41
+ [train] ep= 7000/25000 | eps=0.050 | 最近100回合平均回报= 4.21 | 成功率=100.0% | 平均步数= 14.3 | Q非零项= 2393 | 用时= 0.04min
42
+ [eval ] ep= 7500 | 贪婪策略 20 局: 回报= 7.30 ± 2.85 | 成功率=100.0%
43
+ [train] ep= 8000/25000 | eps=0.050 | 最近100回合平均回报= 4.63 | 成功率=100.0% | 平均步数= 13.9 | Q非零项= 2393 | 用时= 0.04min
44
+ [train] ep= 9000/25000 | eps=0.050 | 最近100回合平均回报= 5.26 | 成功率=100.0% | 平均步数= 13.8 | Q非零项= 2393 | 用时= 0.05min
45
+ [train] ep= 10000/25000 | eps=0.050 | 最近100回合平均回报= 4.50 | 成功率=100.0% | 平均步数= 14.0 | Q非零项= 2394 | 用时= 0.05min
46
+ [eval ] ep= 10000 | 贪婪策略 20 局: 回报= 7.30 ± 2.85 | 成功率=100.0%
47
+ [train] ep= 11000/25000 | eps=0.050 | 最近100回合平均回报= 5.30 | 成功率=100.0% | 平均步数= 13.6 | Q非零项= 2394 | 用时= 0.06min
48
+ [train] ep= 12000/25000 | eps=0.050 | 最近100回合平均回报= 5.68 | 成功率=100.0% | 平均步数= 13.9 | Q非零项= 2394 | 用时= 0.06min
49
+ [eval ] ep= 12500 | 贪婪策略 20 局: 回报= 7.30 ± 2.85 | 成功率=100.0%
50
+ [train] ep= 13000/25000 | eps=0.050 | 最近100回合平均回报= 6.05 | 成功率=100.0% | 平均步数= 13.6 | Q非零项= 2394 | 用时= 0.07min
51
+ [train] ep= 14000/25000 | eps=0.050 | 最近100回合平均回报= 5.30 | 成功率=100.0% | 平均步数= 13.7 | Q非零项= 2394 | 用时= 0.07min
52
+ [train] ep= 15000/25000 | eps=0.050 | 最近100回合平均回报= 5.02 | 成功率=100.0% | 平均步数= 13.9 | Q非零项= 2396 | 用时= 0.07min
53
+ [eval ] ep= 15000 | 贪婪策略 20 局: 回报= 7.30 ± 2.85 | 成功率=100.0%
54
+ [train] ep= 16000/25000 | eps=0.050 | 最近100回合平均回报= 5.16 | 成功率=100.0% | 平均步数= 13.6 | Q非零项= 2396 | 用时= 0.08min
55
+ [train] ep= 17000/25000 | eps=0.050 | 最近100回合平均回报= 4.64 | 成功率=100.0% | 平均步数= 14.0 | Q非零项= 2396 | 用时= 0.08min
56
+ [eval ] ep= 17500 | 贪婪策略 20 局: 回报= 7.30 ± 2.85 | 成功率=100.0%
57
+ [train] ep= 18000/25000 | eps=0.050 | 最近100回合平均回报= 4.51 | 成功率=100.0% | 平均步数= 14.2 | Q非零项= 2397 | 用时= 0.09min
58
+ [train] ep= 19000/25000 | eps=0.050 | 最近100回合平均回报= 6.06 | 成功率=100.0% | 平均步数= 13.8 | Q非零项= 2397 | 用时= 0.09min
59
+ [train] ep= 20000/25000 | eps=0.050 | 最近100回合平均回报= 4.73 | 成功率=100.0% | 平均步数= 13.8 | Q非零项= 2398 | 用时= 0.10min
60
+ [eval ] ep= 20000 | 贪婪策略 20 局: 回报= 7.30 ± 2.85 | 成功率=100.0%
61
+ [train] ep= 21000/25000 | eps=0.050 | 最近100回合平均回报= 4.93 | 成功率=100.0% | 平均步数= 14.1 | Q非零项= 2398 | 用时= 0.10min
62
+ [train] ep= 22000/25000 | eps=0.050 | 最近100回合平均回报= 5.22 | 成功率=100.0% | 平均步数= 14.4 | Q非零项= 2398 | 用时= 0.11min
63
+ [eval ] ep= 22500 | 贪婪策略 20 局: 回报= 7.30 ± 2.85 | 成功率=100.0%
64
+ [train] ep= 23000/25000 | eps=0.050 | 最近100回合平均回报= 5.73 | 成功率=100.0% | 平均步数= 13.5 | Q非零项= 2398 | 用时= 0.11min
65
+ [train] ep= 24000/25000 | eps=0.050 | 最近100回合平均回报= 5.22 | 成功率=100.0% | 平均步数= 14.0 | Q非零项= 2398 | 用时= 0.12min
66
+ [train] ep= 25000/25000 | eps=0.050 | 最近100回合平均回报= 4.43 | 成功率=100.0% | 平均步数= 14.0 | Q非零项= 2398 | 用时= 0.12min
67
+ [eval ] ep= 25000 | 贪婪策略 20 局: 回报= 7.30 ± 2.85 | 成功率=100.0%
68
+
69
+ [训练完成] 22:45:38 总用时 = 0.12 分钟
70
+
71
+ [最终评估] 用贪婪策略跑 100 局(独立于训练)
72
+ Mean_reward = 7.56 +/- 2.71
73
+ 成功率 = 100%
74
+ 训练途中最好的周期评估:7.30 ± 2.85 @ 第 5000 回合
75
+ Q 表非零项 = 2398 / 3000
76
+ Q 表最大价值 = 20.0000
77
+
78
+ [输出] 训练曲线 = logs\taxi\train_curve.png
79
+ [输出] 进度 CSV = logs\taxi\progress.csv
80
+ [输出] 模型文件 = models\qtable_taxi.pkl (24.2 KB)
81
+
82
+ ==============================================================================
83
+ 完成。下一步:python evaluate_and_record.py --model models/qtable_taxi.pkl
84
+ ==============================================================================
logs/taxi/train_curve.png ADDED

Git LFS Details

  • SHA256: 9560d52c889dcaf0e8db6e0d0972dd43e0428da3668d60ed9594d9877ec74523
  • Pointer size: 131 Bytes
  • Size of remote file: 105 kB
metrics.json ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "frozenlake4x4": {
3
+ "env_id": "FrozenLake-v1",
4
+ "n_episodes": 100,
5
+ "mean_reward": 1.0,
6
+ "std_reward": 0.0,
7
+ "success_rate": 1.0,
8
+ "mean_steps": 6.0,
9
+ "qtable_shape": [
10
+ 16,
11
+ 4
12
+ ],
13
+ "qtable_nonzero": 35,
14
+ "evaluated_at": "2026-10-30T22:46:48"
15
+ },
16
+ "frozenlake4x4_slippery": {
17
+ "env_id": "FrozenLake-v1",
18
+ "n_episodes": 100,
19
+ "mean_reward": 0.74,
20
+ "std_reward": 0.4386342439892262,
21
+ "success_rate": 0.74,
22
+ "mean_steps": 39.41,
23
+ "qtable_shape": [
24
+ 16,
25
+ 4
26
+ ],
27
+ "qtable_nonzero": 44,
28
+ "evaluated_at": "2026-10-30T22:46:59"
29
+ },
30
+ "frozenlake8x8": {
31
+ "env_id": "FrozenLake-v1",
32
+ "n_episodes": 100,
33
+ "mean_reward": 1.0,
34
+ "std_reward": 0.0,
35
+ "success_rate": 1.0,
36
+ "mean_steps": 14.0,
37
+ "qtable_shape": [
38
+ 64,
39
+ 4
40
+ ],
41
+ "qtable_nonzero": 179,
42
+ "evaluated_at": "2026-10-30T22:47:24"
43
+ },
44
+ "taxi": {
45
+ "env_id": "Taxi-v3",
46
+ "n_episodes": 100,
47
+ "mean_reward": 7.56,
48
+ "std_reward": 2.706732347314747,
49
+ "success_rate": 1.0,
50
+ "mean_steps": 13.44,
51
+ "qtable_shape": [
52
+ 500,
53
+ 6
54
+ ],
55
+ "qtable_nonzero": 2398,
56
+ "evaluated_at": "2026-10-30T22:47:35"
57
+ }
58
+ }
models/qtable_frozenlake4x4.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:15783f0650b3ecdbf19a79372c3e8e505de805e775f3f3b3283ce4cd2485b011
3
+ size 1175
models/qtable_frozenlake4x4_slippery.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:355c6ee182cb716493c9f7ea9518986b11b9c211cdd07e5564690ca995b863b5
3
+ size 1386
models/qtable_frozenlake8x8.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:56b229bccb1b7ae4b5518dd78e0aae8ededf2b766f7d832393a51f4fca295b09
3
+ size 2914
models/qtable_taxi.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:42512445ebaa03b152ca4b9f5dc084f363b6a16c5a9d70db18f084625a0ab390
3
+ size 24775
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gymnasium>=1.0
2
+ numpy>=1.24
3
+ imageio>=2.31
4
+ imageio-ffmpeg>=0.4
5
+ Pillow>=9.0
6
+ matplotlib>=3.7
7
+ tqdm>=4.65
videos/replay_frozenlake4x4.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8f02fda66c2195fd108a330c70e8da165ff0ed144bedffc7f2a4ab5723824e5e
3
+ size 970642
videos/replay_frozenlake4x4_slippery.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a84a49c1451716a0315a6b2620eec47809bbd98030e2c70978d2af1686e915ac
3
+ size 1963828
videos/replay_frozenlake8x8.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5a170d8acedfb336a15dc6c1ba368a22681ef21d46ab59f6f7a5e264822be340
3
+ size 1624987
videos/replay_taxi.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b80e69b55c62c9007c2086b6a651a4f9871a2b9fd7ac5220152aa8af9d815c35
3
+ size 1973514
videos/replay_value_based_all.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5bba48c8275925470d1dd4873246b6ed7318a642b4159af1af1224df78f385d7
3
+ size 4782880