| import os |
| import glob |
| import pandas as pd |
| import matplotlib.pyplot as plt |
| import seaborn as sns |
| from tensorboard.backend.event_processing import event_accumulator |
|
|
| |
| sns.set_theme(style="whitegrid", palette="muted") |
| plt.rcParams.update({ |
| 'font.size': 12, |
| 'axes.titlesize': 14, |
| 'axes.labelsize': 12, |
| 'figure.autolayout': True, |
| 'figure.dpi': 300 |
| }) |
|
|
| def extract_tb_data(log_dir): |
| """从 TensorBoard 目录中提取所有 Scalar 数据为 Pandas DataFrame""" |
| event_files = glob.glob(os.path.join(log_dir, 'events.out.tfevents.*')) |
| if not event_files: |
| return None |
| |
| |
| ea = event_accumulator.EventAccumulator(log_dir, size_guidance={event_accumulator.SCALARS: 0}) |
| ea.Reload() |
| |
| extracted_data = {} |
| if 'scalars' not in ea.Tags(): |
| return None |
| |
| for tag in ea.Tags()['scalars']: |
| events = ea.Scalars(tag) |
| |
| extracted_data[tag] = pd.DataFrame( |
| [(e.step, e.value) for e in events], |
| columns=['step', tag] |
| ).set_index('step') |
| |
| if not extracted_data: |
| return None |
| |
| |
| df = pd.concat(extracted_data.values(), axis=1).reset_index() |
| return df |
|
|
| def main(): |
| outputs_dir = os.path.join(os.path.dirname(__file__), "..", "outputs") |
| plots_dir = os.path.join(os.path.dirname(__file__), "..", "plots") |
| os.makedirs(plots_dir, exist_ok=True) |
| |
| all_data = [] |
| |
| print("🔍 [Harvester] 正在扫描 TensorBoard 日志...") |
| for folder in os.listdir(outputs_dir): |
| log_dir = os.path.join(outputs_dir, folder) |
| |
| if not os.path.isdir(log_dir) or folder == "pathology_archive": |
| continue |
| |
| df = extract_tb_data(log_dir) |
| if df is not None: |
| |
| parts = folder.split('_', 1) |
| method = parts[0] if len(parts) > 1 else folder |
| |
| df['method'] = method |
| df['experiment'] = folder |
| all_data.append(df) |
| print(f" ✅ 成功提取: {folder} ({len(df)} 条时间序列记录)") |
| |
| if not all_data: |
| print("⚠️ 未找到任何有效的 TensorBoard 数据!") |
| return |
| |
| master_df = pd.concat(all_data, ignore_index=True) |
| |
| |
| time_col = 'hardware/cumulative_time_sec' if 'hardware/cumulative_time_sec' in master_df.columns else 'step' |
| n_col = 'geometry/N_gaussians' |
| loss_col = 'train/loss' |
| cos_col = 'dynamics_decoupled/cos_similarity' |
| gamma_col = 'pathology/gamma_median' |
| |
| print(f"\n📊 [Harvester] 正在使用 Matplotlib 绘制动力学大图,目标目录: {plots_dir}/") |
| |
| |
| |
| |
| if n_col in master_df.columns and loss_col in master_df.columns: |
| plt.figure(figsize=(10, 6)) |
| ax1 = plt.gca() |
| ax2 = ax1.twinx() |
| |
| colors = sns.color_palette("husl", len(master_df['experiment'].unique())) |
| |
| for idx, exp in enumerate(master_df['experiment'].unique()): |
| exp_df = master_df[master_df['experiment'] == exp].dropna(subset=[time_col, n_col, loss_col]) |
| if exp_df.empty: continue |
| |
| color = colors[idx] |
| |
| ax1.plot(exp_df[time_col], exp_df[n_col], linestyle='-', linewidth=2.5, color=color, label=f"{exp} (N)") |
| |
| ax2.plot(exp_df[time_col], exp_df[loss_col], linestyle='--', linewidth=2, color=color, alpha=0.6) |
| |
| xlabel = 'Cumulative GPU Time (Seconds)' if time_col != 'step' else 'Training Steps' |
| ax1.set_xlabel(xlabel, fontweight='bold') |
| ax1.set_ylabel('Number of Gaussians (N)', color='black', fontweight='bold') |
| ax2.set_ylabel('Training Loss (L1 + SSIM)', color='gray', fontweight='bold') |
| |
| plt.title('Densification Dynamics & Loss Convergence', pad=20, fontweight='bold') |
| ax1.legend(loc='upper left', bbox_to_anchor=(1.15, 1)) |
| |
| out_path1 = os.path.join(plots_dir, 'fig1_marginal_return.png') |
| plt.savefig(out_path1, bbox_inches='tight') |
| plt.close() |
| print(f" 📸 论文大图 1 已生成: {out_path1}") |
|
|
| |
| |
| |
| if cos_col in master_df.columns and gamma_col in master_df.columns: |
| plot_df = master_df.dropna(subset=[cos_col, gamma_col]).copy() |
| if not plot_df.empty: |
| plt.figure(figsize=(9, 7)) |
| |
| |
| scatter = sns.scatterplot( |
| data=plot_df, |
| x=cos_col, |
| y=gamma_col, |
| hue='method', |
| size='step', |
| sizes=(20, 200), |
| alpha=0.8, |
| palette='Set1', |
| edgecolor='black' |
| ) |
| |
| |
| plt.axvline(x=0, color='gray', linestyle='--', alpha=0.5, label='Orthogonal Gradients (0)') |
| plt.axhline(y=18, color='red', linestyle=':', linewidth=2, alpha=0.7, label=r'Pathology Threshold ($\gamma=18$)') |
| |
| plt.xlabel(r'Gradient Cosine Similarity $\cos(\theta)$ (Target vs Parasitic)', fontweight='bold') |
| plt.ylabel(r'Anisotropy Distortion $\gamma$ (Max/Min Scale)', fontweight='bold') |
| plt.title('Photometric-Geometric Compensation Phase Space', pad=20, fontweight='bold') |
| |
| plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left') |
| |
| out_path2 = os.path.join(plots_dir, 'fig2_compensation_scatter.png') |
| plt.savefig(out_path2, bbox_inches='tight') |
| plt.close() |
| print(f" 📸 论文大图 2 已生成: {out_path2}") |
|
|
| print("✅ [Harvester] 您的核心科研图表已就绪!") |
|
|
| if __name__ == "__main__": |
| main() |
|
|