value-based-qlearning / code /make_combined_video.py
lin267's picture
value-based 实践:Q-Learning 智能体(FrozenLake + Taxi) + 回放视频 + 评估分数
c3d6fc9 verified
Raw History Blame Contribute Delete
5.48 kB
# -*- coding: utf-8 -*-
"""
把三个智能体的回放视频拼成一支"总回放"视频(方便上传 Hub / 交作业)。
==============================================================
输入:videos/replay_frozenlake4x4.mp4、replay_frozenlake8x8.mp4、replay_taxi.mp4
输出:videos/replay_value_based_all.mp4(1280x720 / 30fps,开头有总览卡,结尾有分数汇总卡)
用法:
D:\\study\\robot\\pyenv\\DRL\\python.exe make_combined_video.py
"""
import json
import os
import imageio.v2 as imageio
import numpy as np
import evaluate_and_record as ev # 复用录像脚本里的画布/字体/配色
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
VIDEO_DIR = os.path.join(BASE_DIR, "videos")
EVAL_DIR = os.path.join(BASE_DIR, "eval_results")
FPS = ev.FPS
RUNS = [
("frozenlake4x4", "FrozenLake-v1 · 4x4 不滑", "PPT 第一部分(10,000 回合)"),
("frozenlake8x8", "FrozenLake-v1 · 8x8", "进阶:地图变大 + 探索调度"),
("taxi", "Taxi-v3", "PPT 第二部分(25,000 回合)"),
]
def load_scores():
scores = {}
for run, _, _ in RUNS:
path = os.path.join(EVAL_DIR, f"eval_{run}.json")
if os.path.exists(path):
with open(path, encoding="utf-8") as f:
scores[run] = json.load(f)
return scores
def intro_card(scores):
canvas, draw = ev.base_canvas()
ev.draw_text(draw, (ev.WIDTH // 2, 110), "value-based 实践 · Q-Learning 智能体回放合集",
ev.FONT_TITLE, fill=ev.ACCENT, anchor="mm")
ev.draw_text(draw, (ev.WIDTH // 2, 165),
"从零手写 Q 表 + epsilon-greedy + Bellman 更新(PPT 第 27–40 页)",
ev.FONT_BODY, fill=ev.TEXT, anchor="mm")
draw.line([120, 205, ev.WIDTH - 120, 205], fill=ev.PANEL_EDGE, width=2)
y = 265
for i, (run, name, note) in enumerate(RUNS):
s = scores.get(run)
ev.draw_text(draw, (110, y), f"{i + 1}. {name}", ev.FONT_H2, fill=ev.TEXT)
ev.draw_text(draw, (110, y + 34), note, ev.FONT_SMALL, fill=ev.MUTED)
if s:
score_text = f"{s['mean_reward']:.2f} ± {s['std_reward']:.2f}"
ev.draw_text(draw, (ev.WIDTH - 110, y + 16), score_text, ev.FONT_H1,
fill=ev.GREEN if s["mean_reward"] > 0 else ev.RED, anchor="rm")
ev.draw_text(draw, (ev.WIDTH - 110, y + 46),
f"成功率 {s['success_rate'] * 100:.0f}% "
f"平均步数 {s['mean_steps']:.1f}",
ev.FONT_SMALL, fill=ev.MUTED, anchor="rm")
y += 95
ev.draw_text(draw, (ev.WIDTH // 2, 620),
"每段回放都标注了步数 / 动作 / Q 值条形图,片尾有该智能体的评估分数",
ev.FONT_SMALL, fill=ev.MUTED, anchor="mm")
return canvas
def end_card(scores):
canvas, draw = ev.base_canvas()
ev.draw_text(draw, (ev.WIDTH // 2, 105), "三个智能体评估分数汇总", ev.FONT_TITLE,
fill=ev.ACCENT, anchor="mm")
ev.draw_text(draw, (ev.WIDTH // 2, 155),
"独立环境 · 贪婪策略 argmax_a Q(s,a) · 100 局", ev.FONT_BODY,
fill=ev.MUTED, anchor="mm")
draw.line([120, 195, ev.WIDTH - 120, 195], fill=ev.PANEL_EDGE, width=2)
y = 265
for run, name, _ in RUNS:
s = scores.get(run)
if not s:
continue
ev.draw_text(draw, (120, y), name, ev.FONT_H2, fill=ev.TEXT)
ev.draw_text(draw, (560, y), f"回报 {s['mean_reward']:.2f} ± {s['std_reward']:.2f}",
ev.FONT_H2, fill=ev.GREEN if s["mean_reward"] > 0 else ev.RED)
ev.draw_text(draw, (880, y), f"成功率 {s['success_rate'] * 100:.0f}%",
ev.FONT_H2, fill=ev.ACCENT)
y += 90
ev.draw_text(draw, (ev.WIDTH // 2, 610),
"模型文件(Q 表 pkl)+ 训练代码 + 日志 已打包上传 Hub",
ev.FONT_BODY, fill=ev.TEXT, anchor="mm")
return canvas
def main():
scores = load_scores()
clips = []
for run, _, _ in RUNS:
path = os.path.join(VIDEO_DIR, f"replay_{run}.mp4")
if not os.path.exists(path):
print(f"[跳过] 找不到 {path},请先跑 evaluate_and_record.py --model models/qtable_{run}.pkl")
continue
clips.append(path)
if not clips:
raise SystemExit("没有可拼接的视频。")
out_path = os.path.join(VIDEO_DIR, "replay_value_based_all.mp4")
writer = imageio.get_writer(out_path, fps=FPS, codec="libx264", quality=8,
macro_block_size=2)
frames = 0
def push(img, repeat=1):
nonlocal frames
arr = np.asarray(img, dtype=np.uint8)
for _ in range(repeat):
writer.append_data(arr)
frames += 1
push(intro_card(scores), int(3.0 * FPS))
for path in clips:
reader = imageio.get_reader(path)
n = 0
for frame in reader:
writer.append_data(frame)
frames += 1
n += 1
reader.close()
print(f" [拼接] {os.path.basename(path)}:{n} 帧")
push(end_card(scores), int(5.0 * FPS))
writer.close()
size_mb = os.path.getsize(out_path) / (1024 * 1024)
print(f"[输出] 总回放视频 = videos/{os.path.basename(out_path)} "
f"({frames} 帧 / {frames / FPS:.1f} 秒 / {size_mb:.1f} MB)")
if __name__ == "__main__":
main()