visualization_direction9.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. #!/usr/bin/env python
  2. """
  3. 方向 9 实验可视化模块
  4. 功能:
  5. - 绘制 MMLU vs r_eff 散点图
  6. - 绘制 RS_cross 跨域分层图
  7. - 绘制 SPD 模型间谱距离热力图
  8. - 绘制逐层谱收敛轮廓 (LCP)
  9. - 绘制能力涌现 r_eff 曲线
  10. 用法:
  11. python experiments/visualization_direction9.py --input-dir output/cross_model
  12. """
  13. import os
  14. import json
  15. import numpy as np
  16. from pathlib import Path
  17. from typing import Dict, List, Optional, Any
  18. from datetime import datetime
  19. import matplotlib
  20. matplotlib.use('Agg') # 非交互式后端
  21. import matplotlib.pyplot as plt
  22. import seaborn as sns
  23. class Direction9Visualizer:
  24. """方向 9 实验可视化器"""
  25. def __init__(self, output_dir: str = "experiments/output/figures"):
  26. self.output_dir = Path(output_dir)
  27. self.output_dir.mkdir(parents=True, exist_ok=True)
  28. # 设置样式
  29. sns.set_style("whitegrid")
  30. sns.set_context("paper", font_scale=1.2)
  31. # 中文支持
  32. plt.rcParams['font.sans-serif'] = ['DejaVu Sans']
  33. plt.rcParams['axes.unicode_minus'] = False
  34. def plot_mmlu_vs_r_eff(
  35. self,
  36. r_effs: Dict[str, Dict[str, float]],
  37. model_mmlu: Dict[str, float],
  38. output_name: str = "mmlu_vs_r_eff.png"
  39. ) -> str:
  40. """
  41. 绘制 MMLU vs r_eff 散点图
  42. Args:
  43. r_effs: {dataset: {model: r_eff}}
  44. model_mmlu: {model: mmlu_score}
  45. """
  46. fig, axes = plt.subplots(2, 2, figsize=(12, 10))
  47. axes = axes.flatten()
  48. datasets = list(r_effs.keys())
  49. colors = {'llama3.2-3b-instruct': '#1f77b4', 'Mistral-7B-v0.3': '#ff7f0e',
  50. 'llama3-8b': '#2ca02c', 'Qwen2.5-7B-Instruct': '#d62728',
  51. 'gemma-2-9b-it': '#9467bd'}
  52. for idx, dataset in enumerate(datasets):
  53. ax = axes[idx]
  54. data = r_effs[dataset]
  55. models = list(data.keys())
  56. mmlu_vals = [model_mmlu.get(m, 0) for m in models]
  57. r_eff_vals = [data[m] for m in models]
  58. for model in models:
  59. ax.scatter(
  60. model_mmlu.get(model, 0),
  61. data[model],
  62. c=[colors.get(model, '#333333')],
  63. s=100, alpha=0.7,
  64. label=model
  65. )
  66. # 拟合趋势线
  67. if len(mmlu_vals) > 1:
  68. z = np.polyfit(mmlu_vals, r_eff_vals, 1)
  69. p = np.poly1d(z)
  70. ax.plot(sorted(mmlu_vals), p(sorted(mmlu_vals)),
  71. '--', color='gray', alpha=0.5,
  72. label=f'Trend (τ={np.corrcoef(mmlu_vals, r_eff_vals)[0,1]:.2f})')
  73. ax.set_xlabel('MMLU Score (%)')
  74. ax.set_ylabel('Effective Rank (r_eff)')
  75. ax.set_title(f'{dataset}')
  76. ax.legend(fontsize=8)
  77. plt.tight_layout()
  78. output_path = self.output_dir / output_name
  79. plt.savefig(output_path, dpi=150, bbox_inches='tight')
  80. plt.close()
  81. print(f"已保存:{output_path}")
  82. return str(output_path)
  83. def plot_rs_cross_comparison(
  84. self,
  85. rs_cross: Dict[str, float],
  86. output_name: str = "rs_cross_comparison.png"
  87. ) -> str:
  88. """绘制 RS_cross 跨域分层比较图"""
  89. fig, ax = plt.subplots(figsize=(10, 6))
  90. datasets = list(rs_cross.keys())
  91. values = list(rs_cross.values())
  92. # 按 RS_cross 排序
  93. sorted_idx = np.argsort(values)
  94. datasets = [datasets[i] for i in sorted_idx]
  95. values = [values[i] for i in sorted_idx]
  96. colors = plt.cm.OrRd(np.linspace(0.3, 0.9, len(datasets)))
  97. bars = ax.bar(range(len(datasets)), values, color=colors)
  98. ax.set_xticks(range(len(datasets)))
  99. ax.set_xticklabels(datasets, rotation=45, ha='right')
  100. ax.set_ylabel('RS_cross (跨模型谱方差)')
  101. ax.set_title('跨域谱分层比较\n(越小表示跨模型谱收敛越好)')
  102. # 添加数值标签
  103. for bar, val in zip(bars, values):
  104. ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01,
  105. f'{val:.3f}', ha='center', va='bottom', fontsize=10)
  106. plt.tight_layout()
  107. output_path = self.output_dir / output_name
  108. plt.savefig(output_path, dpi=150, bbox_inches='tight')
  109. plt.close()
  110. print(f"已保存:{output_path}")
  111. return str(output_path)
  112. def plot_spd_heatmap(
  113. self,
  114. spd_matrix: List[List[float]],
  115. model_names: List[str],
  116. dataset_name: str = "all",
  117. output_name: str = "spd_heatmap.png"
  118. ) -> str:
  119. """绘制 SPD 模型间谱距离热力图"""
  120. spd = np.array(spd_matrix)
  121. fig, ax = plt.subplots(figsize=(10, 8))
  122. # 使用 seaborn 热力图
  123. sns.heatmap(spd, annot=True, fmt='.2f', cmap='YlOrRd',
  124. xticklabels=model_names, yticklabels=model_names,
  125. ax=ax, cbar_kws={'label': 'Wasserstein-1 Distance'})
  126. ax.set_title(f'SPD 模型间谱距离矩阵\n({dataset_name})')
  127. ax.set_xlabel('Model')
  128. ax.set_ylabel('Model')
  129. plt.tight_layout()
  130. output_path = self.output_dir / output_name
  131. plt.savefig(output_path, dpi=150, bbox_inches='tight')
  132. plt.close()
  133. print(f"已保存:{output_path}")
  134. return str(output_path)
  135. def plot_lcp_profile(
  136. self,
  137. lcp_data: Dict[str, Any],
  138. output_name: str = "lcp_profile.png"
  139. ) -> str:
  140. """绘制逐层谱收敛轮廓 (LCP)"""
  141. layer_indices = lcp_data.get('layer_indices', [])
  142. lcp_values = lcp_data.get('lcp_values', [])
  143. dataset = lcp_data.get('dataset', 'unknown')
  144. fig, ax = plt.subplots(figsize=(10, 6))
  145. ax.plot(layer_indices, lcp_values, 'o-', linewidth=2, markersize=8)
  146. ax.fill_between(layer_indices, lcp_values, alpha=0.3)
  147. ax.set_xlabel('Layer Index')
  148. ax.set_ylabel('RS_cross')
  149. ax.set_title(f'逐层谱收敛轮廓\n({dataset})')
  150. ax.grid(True, alpha=0.3)
  151. # 标注最小值和最大值
  152. min_idx = np.argmin(lcp_values)
  153. max_idx = np.argmax(lcp_values)
  154. ax.annotate(f'Min: {lcp_values[min_idx]:.3f}',
  155. xy=(layer_indices[min_idx], lcp_values[min_idx]),
  156. xytext=(10, 10), textcoords='offset points',
  157. bbox=dict(boxstyle='round', fc='w', ec='gray'))
  158. plt.tight_layout()
  159. output_path = self.output_dir / output_name
  160. plt.savefig(output_path, dpi=150, bbox_inches='tight')
  161. plt.close()
  162. print(f"已保存:{output_path}")
  163. return str(output_path)
  164. def plot_emergence_curve(
  165. self,
  166. emergence_results: Dict[str, Any],
  167. output_name: str = "emergence_curves.png"
  168. ) -> str:
  169. """绘制能力涌现 r_eff 曲线"""
  170. n_abilities = len(emergence_results)
  171. fig, axes = plt.subplots(2, 2, figsize=(14, 10))
  172. axes = axes.flatten()
  173. colors = plt.cm.viridis(np.linspace(0.1, 0.9, n_abilities))
  174. for idx, (ability, data) in enumerate(emergence_results.items()):
  175. ax = axes[idx]
  176. r_eff_curve = data.get('r_eff_curve', [])
  177. model_order = data.get('model_order', [])
  178. model_mmlu = data.get('model_mmlu', [])
  179. max_jump = data.get('max_jump', {})
  180. changepoints = data.get('changepoints', [])
  181. ax.plot(range(len(r_eff_curve)), r_eff_curve, 'o-',
  182. linewidth=2, markersize=8, color=colors[idx])
  183. # 标注变化点
  184. for cp in changepoints:
  185. ax.axvline(x=cp, color='red', linestyle='--', alpha=0.5)
  186. ax.scatter([cp], [r_eff_curve[cp] if cp < len(r_eff_curve) else 0],
  187. color='red', s=100, zorder=5)
  188. # 标注最大跃变点
  189. jump_idx = max_jump.get('model_index', 0)
  190. if jump_idx < len(r_eff_curve):
  191. ax.scatter([jump_idx], [r_eff_curve[jump_idx]],
  192. color='orange', s=150, marker='*', zorder=5)
  193. ax.set_xlabel('Model Index (sorted by MMLU)')
  194. ax.set_ylabel('Effective Rank (r_eff)')
  195. ax.set_title(f'{ability}\nJump@{max_jump.get("model_name", "N/A")} '
  196. f'(MMLU={max_jump.get("mmlu_at_jump", 0):.1f}%)')
  197. ax.set_xticks(range(len(model_order)))
  198. ax.set_xticklabels([m.split('-')[0] for m in model_order],
  199. rotation=45, ha='right', fontsize=8)
  200. ax.grid(True, alpha=0.3)
  201. plt.tight_layout()
  202. output_path = self.output_dir / output_name
  203. plt.savefig(output_path, dpi=150, bbox_inches='tight')
  204. plt.close()
  205. print(f"已保存:{output_path}")
  206. return str(output_path)
  207. def create_summary_dashboard(
  208. self,
  209. e1_result: Dict[str, Any],
  210. e3_result: Dict[str, Any],
  211. output_name: str = "summary_dashboard.png"
  212. ) -> str:
  213. """创建实验摘要仪表盘"""
  214. fig = plt.figure(figsize=(16, 12))
  215. # 子图布局
  216. gs = plt.GridSpec(2, 2, figure=fig)
  217. ax1 = fig.add_subplot(gs[0, 0])
  218. ax2 = fig.add_subplot(gs[0, 1])
  219. ax3 = fig.add_subplot(gs[1, :])
  220. # 1. RS_cross 柱状图
  221. rs_cross = e1_result.get('rs_cross', {})
  222. if rs_cross:
  223. datasets = list(rs_cross.keys())
  224. values = list(rs_cross.values())
  225. ax1.bar(range(len(datasets)), values, color='steelblue')
  226. ax1.set_xticks(range(len(datasets)))
  227. ax1.set_xticklabels(datasets, rotation=45, ha='right')
  228. ax1.set_ylabel('RS_cross')
  229. ax1.set_title('跨域谱分层')
  230. # 2. 能力涌现显著性
  231. if e3_result:
  232. abilities = list(e3_result.keys())
  233. significance = [e3_result[a].get('jump_significance', 0) for a in abilities]
  234. colors = ['green' if s > 2 else 'orange' if s > 1 else 'red' for s in significance]
  235. ax2.bar(range(len(abilities)), significance, color=colors)
  236. ax2.set_xticks(range(len(abilities)))
  237. ax2.set_xticklabels(abilities, rotation=45, ha='right')
  238. ax2.set_ylabel('Jump Significance (σ)')
  239. ax2.set_title('能力涌现显著性\n(绿>2σ, 橙>1σ, 红<1σ)')
  240. ax2.axhline(y=2, color='green', linestyle='--', alpha=0.5)
  241. ax2.axhline(y=1, color='orange', linestyle='--', alpha=0.5)
  242. # 3. 模型 r_eff 对比表
  243. ax3.axis('off')
  244. r_effs = e1_result.get('r_effs', {})
  245. if r_effs:
  246. table_data = []
  247. headers = ['Dataset'] + list(list(r_effs.values())[0].keys())
  248. for ds, r_dict in r_effs.items():
  249. row = [ds] + [f'{r_dict.get(m, 0):.1f}' for m in headers[1:]]
  250. table_data.append(row)
  251. table = ax3.table(
  252. cellText=table_data,
  253. colLabels=headers,
  254. loc='center',
  255. cellLoc='center',
  256. colColours=['lightblue'] * len(headers)
  257. )
  258. table.auto_set_font_size(False)
  259. table.set_fontsize(10)
  260. table.scale(1.2, 1.5)
  261. plt.suptitle(f'方向 9 实验摘要仪表盘\n生成时间:{datetime.now().strftime("%Y-%m-%d %H:%M")}',
  262. fontsize=14, y=0.98)
  263. plt.tight_layout()
  264. output_path = self.output_dir / output_name
  265. plt.savefig(output_path, dpi=150, bbox_inches='tight')
  266. plt.close()
  267. print(f"已保存:{output_path}")
  268. return str(output_path)
  269. def main():
  270. import argparse
  271. parser = argparse.ArgumentParser(description="方向 9 实验可视化")
  272. parser.add_argument("--input-dir", type=str,
  273. default="experiments/output/cross_model",
  274. help="实验结果输入目录")
  275. parser.add_argument("--output-dir", type=str,
  276. default="experiments/output/figures",
  277. help="图片输出目录")
  278. args = parser.parse_args()
  279. visualizer = Direction9Visualizer(args.output_dir)
  280. # 加载 E1 结果
  281. e1_result_file = Path(args.input_dir) / "e1_result.json"
  282. if e1_result_file.exists():
  283. with open(e1_result_file) as f:
  284. e1_result = json.load(f)
  285. # 绘制 MMLU vs r_eff
  286. # 需要 model_mmlu 数据
  287. model_mmlu = {
  288. "llama3.2-3b-instruct": 58.0,
  289. "Mistral-7B-v0.3": 62.5,
  290. "llama3-8b": 68.4,
  291. "Qwen2.5-7B-Instruct": 86.3,
  292. "gemma-2-9b-it": 82.0
  293. }
  294. # 绘制 RS_cross 比较图
  295. if 'rs_cross' in e1_result:
  296. visualizer.plot_rs_cross_comparison(e1_result['rs_cross'])
  297. # 绘制 SPD 热力图
  298. if 'spd_matrices' in e1_result:
  299. for dataset, spd in e1_result['spd_matrices'].items():
  300. model_names = list(e1_result['r_effs'].get(dataset, {}).keys())
  301. if model_names:
  302. visualizer.plot_spd_heatmap(spd, model_names, dataset)
  303. # 加载 E3 结果
  304. e3_result_file = Path(args.input_dir).parent / "emergence/emergence_results.json"
  305. if e3_result_file.exists():
  306. with open(e3_result_file) as f:
  307. e3_result = json.load(f)
  308. visualizer.plot_emergence_curve(e3_result)
  309. print("\n可视化完成!")
  310. if __name__ == "__main__":
  311. main()