| # ShinkaEvolve: results_dir 传递流程详解 |
| |
| ## 🎯 问题 |
| 当运行 ShinkaEvolve 时,评估脚本(evaluate.py)是如何知道要把结果保存到指定的 `results_dir` 的? |
|
|
| ## 📊 完整数据流程 |
|
|
| ``` |
| ┌─────────────────────────────────────────────────────────────────────────┐ |
| │ 第 1 步: 用户配置 (run_circle_packing_*.py) │ |
| ├─────────────────────────────────────────────────────────────────────────┤ |
| │ │ |
| │ evo_config = EvolutionConfig( │ |
| │ results_dir="examples/circle_packing/results/results_exp_20260129", │ |
| │ ... │ |
| │ ) │ |
| │ │ |
| └────────────────────────────┬────────────────────────────────────────────┘ |
| │ |
| ▼ |
| ┌─────────────────────────────────────────────────────────────────────────┐ |
| │ 第 2 步: EvolutionRunner 初始化 (shinka/core/runner.py:90-108) │ |
| ├─────────────────────────────────────────────────────────────────────────┤ |
| │ │ |
| │ class EvolutionRunner: │ |
| │ def __init__(self, evo_config, ...): │ |
| │ if evo_config.results_dir is None: │ |
| │ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") │ |
| │ self.results_dir = f"results_{timestamp}" │ |
| │ else: │ |
| │ self.results_dir = Path(evo_config.results_dir) ← 使用配置 │ |
| │ │ |
| │ # 创建目录 │ |
| │ Path(self.results_dir).mkdir(parents=True, exist_ok=True) │ |
| │ │ |
| │ 结果: self.results_dir = "examples/.../results_exp_20260129" │ |
| │ │ |
| └────────────────────────────┬────────────────────────────────────────────┘ |
| │ |
| ▼ |
| ┌─────────────────────────────────────────────────────────────────────────┐ |
| │ 第 3 步: 运行每一代 (shinka/core/runner.py:461-496) │ |
| ├─────────────────────────────────────────────────────────────────────────┤ |
| │ │ |
| │ def _run_generation_0(self): │ |
| │ # 构建当前代的目录路径 │ |
| │ initial_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0" │ |
| │ # 即: "examples/.../results_exp_20260129/gen_0" │ |
| │ │ |
| │ Path(initial_dir).mkdir(parents=True, exist_ok=True) │ |
| │ exec_fname = f"{initial_dir}/main.py" │ |
| │ │ |
| │ # 构建结果子目录 │ |
| │ results_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0/results" │ |
| │ # 即: "examples/.../results_exp_20260129/gen_0/results" │ |
| │ │ |
| │ Path(results_dir).mkdir(parents=True, exist_ok=True) │ |
| │ │ |
| │ # 运行评估,传递 results_dir │ |
| │ results, rtime = self.scheduler.run(exec_fname, results_dir) ←关键 │ |
| │ │ |
| └────────────────────────────┬────────────────────────────────────────────┘ |
| │ |
| ▼ |
| ┌─────────────────────────────────────────────────────────────────────────┐ |
| │ 第 4 步: JobScheduler 构建命令 (shinka/launch/scheduler.py:98-148) │ |
| ├─────────────────────────────────────────────────────────────────────────┤ |
| │ │ |
| │ def _build_command(self, exec_fname_t, results_dir_t): │ |
| │ cmd = [ │ |
| │ "python", │ |
| │ f"{self.config.eval_program_path}", ← 评估脚本路径 │ |
| │ "--program_path", │ |
| │ f"{exec_fname_t}", ← 被评估的程序 │ |
| │ "--results_dir", │ |
| │ results_dir_t, ← 结果目录! │ |
| │ ] │ |
| │ │ |
| │ 实际命令: │ |
| │ python examples/circle_packing/evaluate_with_auxiliary.py \ │ |
| │ --program_path examples/.../gen_0/main.py \ │ |
| │ --results_dir examples/.../gen_0/results │ |
| │ │ |
| └────────────────────────────┬────────────────────────────────────────────┘ |
| │ |
| ▼ |
| ┌─────────────────────────────────────────────────────────────────────────┐ |
| │ 第 5 步: 评估脚本接收参数 (evaluate_with_auxiliary.py:260-295) │ |
| ├─────────────────────────────────────────────────────────────────────────┤ |
| │ │ |
| │ if __name__ == "__main__": │ |
| │ parser = argparse.ArgumentParser(...) │ |
| │ parser.add_argument( │ |
| │ "--program_path", │ |
| │ type=str, │ |
| │ default="initial.py", │ |
| │ ) │ |
| │ parser.add_argument( │ |
| │ "--results_dir", ← 接收命令行参数 │ |
| │ type=str, │ |
| │ default="results", │ |
| │ ) │ |
| │ │ |
| │ args = parser.parse_args() │ |
| │ │ |
| │ main( │ |
| │ program_path=args.program_path, │ |
| │ results_dir=args.results_dir, ← 传递给 main 函数 │ |
| │ ) │ |
| │ │ |
| └────────────────────────────┬────────────────────────────────────────────┘ |
| │ |
| ▼ |
| ┌─────────────────────────────────────────────────────────────────────────┐ |
| │ 第 6 步: 评估执行和结果保存 (evaluate_with_auxiliary.py:161-256) │ |
| ├─────────────────────────────────────────────────────────────────────────┤ |
| │ │ |
| │ def main(program_path, results_dir, ...): │ |
| │ # 确保目录存在 │ |
| │ os.makedirs(results_dir, exist_ok=True) │ |
| │ │ |
| │ # 运行评估 │ |
| │ metrics, correct, error = run_shinka_eval( │ |
| │ program_path=program_path, │ |
| │ results_dir=results_dir, ← 传递给核心评估函数 │ |
| │ ... │ |
| │ ) │ |
| │ │ |
| └────────────────────────────┬────────────────────────────────────────────┘ |
| │ |
| ▼ |
| ┌─────────────────────────────────────────────────────────────────────────┐ |
| │ 第 7 步: 核心评估函数保存结果 (shinka/core/wrap_eval.py:54-193) │ |
| ├─────────────────────────────────────────────────────────────────────────┤ |
| │ │ |
| │ def run_shinka_eval(program_path, results_dir, ...): │ |
| │ # ... 运行实验 ... │ |
| │ │ |
| │ # 保存额外数据 │ |
| │ if "extra_data" in metrics: │ |
| │ os.makedirs(results_dir, exist_ok=True) │ |
| │ extra_file = os.path.join(results_dir, "extra.pkl") │ |
| │ with open(extra_file, "wb") as f: │ |
| │ pickle.dump(extra_data, f) │ |
| │ │ |
| │ # 保存主要结果 │ |
| │ save_json_results(results_dir, metrics, correct, error) │ |
| │ │ |
| │ def save_json_results(results_dir, metrics, correct, error): │ |
| │ os.makedirs(results_dir, exist_ok=True) │ |
| │ metrics_file = os.path.join(results_dir, "metrics.json") │ |
| │ correct_file = os.path.join(results_dir, "correct.json") │ |
| │ │ |
| │ # 保存文件 │ |
| │ with open(metrics_file, "w") as f: │ |
| │ json.dump(metrics, f, indent=4) │ |
| │ with open(correct_file, "w") as f: │ |
| │ json.dump({"correct": correct, "error": error}, f, indent=4) │ |
| │ │ |
| │ 结果文件: │ |
| │ - examples/.../gen_0/results/metrics.json │ |
| │ - examples/.../gen_0/results/correct.json │ |
| │ - examples/.../gen_0/results/extra.pkl (如果有) │ |
| │ │ |
| └─────────────────────────────────────────────────────────────────────────┘ |
| ``` |
|
|
| ## 🔍 关键代码位置 |
|
|
| ### 1. EvolutionConfig 定义 (shinka/core/runner.py:38-64) |
|
|
| ```python |
| @dataclass |
| class EvolutionConfig: |
| # ... 其他配置 ... |
| results_dir: Optional[str] = None # 第59行:results_dir 配置项 |
| # ... 其他配置 ... |
| ``` |
|
|
| ### 2. EvolutionRunner 使用配置 (shinka/core/runner.py:104-108) |
|
|
| ```python |
| if evo_config.results_dir is None: |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
| self.results_dir = f"results_{timestamp}" |
| else: |
| self.results_dir = Path(evo_config.results_dir) # 使用用户配置 |
| ``` |
|
|
| ### 3. 构建每一代的路径 (shinka/core/runner.py:461-496) |
|
|
| ```python |
| def _run_generation_0(self): |
| initial_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0" |
| # ... |
| results_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0/results" |
| Path(results_dir).mkdir(parents=True, exist_ok=True) |
| |
| # 运行评估 |
| results, rtime = self.scheduler.run(exec_fname, results_dir) |
| ``` |
|
|
| ### 4. JobScheduler 构建命令 (shinka/launch/scheduler.py:98-148) |
|
|
| ```python |
| def _build_command(self, exec_fname_t: str, results_dir_t: str) -> List[str]: |
| cmd = [ |
| "python", |
| f"{self.config.eval_program_path}", |
| "--program_path", |
| f"{exec_fname_t}", |
| "--results_dir", |
| results_dir_t, # ← results_dir 作为命令行参数 |
| ] |
| return cmd |
| ``` |
|
|
| ### 5. 评估脚本接收参数 (evaluate_with_auxiliary.py:260-295) |
|
|
| ```python |
| if __name__ == "__main__": |
| parser = argparse.ArgumentParser(...) |
| parser.add_argument( |
| "--program_path", |
| type=str, |
| default="initial.py", |
| ) |
| parser.add_argument( |
| "--results_dir", # ← 接收命令行参数 |
| type=str, |
| default="results", |
| ) |
| args = parser.parse_args() |
| |
| main( |
| program_path=args.program_path, |
| results_dir=args.results_dir, # ← 传递给 main |
| ) |
| ``` |
|
|
| ### 6. 保存结果 (shinka/core/wrap_eval.py:33-51) |
| |
| ```python |
| def save_json_results( |
| results_dir: str, # ← 接收 results_dir |
| metrics: Dict[str, Any], |
| correct: bool, |
| error: Optional[str] = None, |
| ) -> None: |
| os.makedirs(results_dir, exist_ok=True) |
| metrics_file = os.path.join(results_dir, "metrics.json") |
| correct_file = os.path.join(results_dir, "correct.json") |
| |
| with open(metrics_file, "w", encoding="utf-8") as f: |
| json.dump(metrics, f, indent=4) |
| |
| with open(correct_file, "w", encoding="utf-8") as f: |
| json.dump({"correct": correct, "error": error}, f, indent=4) |
| ``` |
| |
| ## 📁 实际目录结构示例 |
|
|
| ``` |
| examples/circle_packing/results/results_exp_20260129_150530/ |
| │ |
| ├── evolution_db_exp_20260129_150530.sqlite ← 数据库 |
| ├── evolution_run.log ← 日志 |
| ├── experiment_config.yaml ← 配置 |
| │ |
| ├── gen_0/ ← 第 0 代 |
| │ ├── main.py ← 被评估的程序 |
| │ └── results/ ← 评估结果目录 |
| │ ├── metrics.json ← 性能指标 |
| │ ├── correct.json ← 正确性标志 |
| │ ├── extra.npz ← 额外数据 |
| │ ├── packing_viz.png ← 可视化 |
| │ └── auxiliary_analysis.json ← 辅助分析 |
| │ |
| ├── gen_1/ |
| │ ├── main.py |
| │ └── results/ |
| │ ├── metrics.json |
| │ └── ... |
| │ |
| ├── gen_2/ |
| │ └── ... |
| │ |
| └── best/ ← 最佳结果(符号链接) |
| └── results/ |
| ├── metrics.json |
| └── ... |
| ``` |
|
|
| ## 🔧 关键设计特点 |
|
|
| ### 1. 层次化的目录结构 |
|
|
| ``` |
| {results_dir}/ |
| ├── gen_0/results/ ← EvolutionRunner 创建 |
| ├── gen_1/results/ ← EvolutionRunner 创建 |
| └── gen_2/results/ ← EvolutionRunner 创建 |
| └── metrics.json ← 评估脚本保存 |
| ``` |
|
|
| ### 2. 参数传递链 |
|
|
| ``` |
| 用户配置 → EvolutionConfig.results_dir |
| → EvolutionRunner.results_dir |
| → JobScheduler.run(results_dir) |
| → 命令行参数 --results_dir |
| → argparse 解析 |
| → main(results_dir) |
| → save_json_results(results_dir) |
| → 文件保存 |
| ``` |
|
|
| ### 3. 职责分离 |
|
|
| | 组件 | 职责 | |
| |-----|------| |
| | **EvolutionRunner** | 创建顶层目录和每代的子目录 | |
| | **JobScheduler** | 构建命令并传递 results_dir 参数 | |
| | **评估脚本** | 接收参数并执行评估 | |
| | **run_shinka_eval** | 保存评估结果到指定目录 | |
| |
| ## 💡 为什么这样设计? |
| |
| ### 优点 |
| |
| 1. **解耦**: 评估脚本不需要知道 ShinkaEvolve 的目录结构 |
| 2. **灵活**: 可以独立运行评估脚本进行测试 |
| 3. **标准化**: 所有评估脚本使用相同的参数接口 |
| 4. **可追溯**: 每一代都有独立的结果目录 |
| |
| ### 示例:独立运行评估脚本 |
| |
| ```bash |
| # 可以完全独立于 ShinkaEvolve 运行评估 |
| python examples/circle_packing/evaluate_with_auxiliary.py \ |
| --program_path my_solution.py \ |
| --results_dir my_custom_results/ |
| |
| # 结果会保存在 my_custom_results/ 而不是 ShinkaEvolve 的目录结构中 |
| ``` |
| |
| ## 🎯 总结 |
| |
| **评估脚本通过命令行参数 `--results_dir` 知道保存位置!** |
| |
| 整个流程是: |
| |
| 1. 用户在 `EvolutionConfig` 中设置 `results_dir` |
| 2. `EvolutionRunner` 读取配置并创建目录结构 |
| 3. 对于每一代,构建 `{results_dir}/gen_X/results` 路径 |
| 4. `JobScheduler` 将这个路径作为 `--results_dir` 参数传递给评估脚本 |
| 5. 评估脚本通过 `argparse` 解析参数 |
| 6. `run_shinka_eval` 和 `save_json_results` 使用这个路径保存结果 |
| |
| 这是一个**标准的命令行参数传递机制**,而不是硬编码或环境变量。 |
| |
| --- |
| |
| **创建时间**: 2026-01-29 |
| **相关文件**: |
| - `shinka/core/runner.py` - EvolutionRunner 和 EvolutionConfig |
| - `shinka/launch/scheduler.py` - JobScheduler 命令构建 |
| - `shinka/core/wrap_eval.py` - run_shinka_eval 和结果保存 |
| - `examples/circle_packing/evaluate_with_auxiliary.py` - 评估脚本示例 |
| |