tekkmaven commited on
Commit
4f61f94
·
verified ·
1 Parent(s): 52eb540

Upload visualize.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. visualize.py +330 -0
visualize.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Visualization for Representation Learning Dynamics experiment.
3
+ ================================================================
4
+ Generates publication-quality figures from experiment results.
5
+ """
6
+
7
+ import json
8
+ import numpy as np
9
+ import matplotlib
10
+ matplotlib.use('Agg')
11
+ import matplotlib.pyplot as plt
12
+ import matplotlib.gridspec as gridspec
13
+ from pathlib import Path
14
+ from typing import Dict, List, Optional
15
+ import argparse
16
+
17
+
18
+ def load_results(results_path: str) -> Dict:
19
+ with open(results_path) as f:
20
+ return json.load(f)
21
+
22
+
23
+ def extract_metric_series(history: List[Dict], metric_name: str) -> tuple:
24
+ """Extract (steps, values) for a metric from history."""
25
+ steps = [h['step'] for h in history if metric_name in h]
26
+ values = [h[metric_name] for h in history if metric_name in h]
27
+ return np.array(steps), np.array(values)
28
+
29
+
30
+ def plot_training_curves(results: Dict, output_dir: str):
31
+ """Plot training loss and task accuracies across all phases."""
32
+ fig, axes = plt.subplots(2, 2, figsize=(14, 10))
33
+
34
+ # Phase 1
35
+ p1 = results['phase1_history']
36
+ steps_p1 = [h['step'] for h in p1]
37
+ loss_p1 = [h['train_loss'] for h in p1]
38
+ acc_add_p1 = [h.get('eval/add_test_acc', 0) for h in p1]
39
+ acc_sub_p1 = [h.get('eval/subtract_test_acc', 0) for h in p1]
40
+
41
+ # Phase 2 A→A
42
+ p2aa = results['phase2_aa_history']
43
+ steps_aa = [h['step'] + steps_p1[-1] for h in p2aa] if p2aa else []
44
+ loss_aa = [h['train_loss'] for h in p2aa]
45
+ acc_add_aa = [h.get('eval/add_test_acc', 0) for h in p2aa]
46
+ acc_sub_aa = [h.get('eval/subtract_test_acc', 0) for h in p2aa]
47
+
48
+ # Phase 2 A→B
49
+ p2ab = results['phase2_ab_history']
50
+ steps_ab = [h['step'] + steps_p1[-1] for h in p2ab] if p2ab else []
51
+ loss_ab = [h['train_loss'] for h in p2ab]
52
+ acc_add_ab = [h.get('eval/add_test_acc', 0) for h in p2ab]
53
+ acc_sub_ab = [h.get('eval/subtract_test_acc', 0) for h in p2ab]
54
+
55
+ # Training loss
56
+ ax = axes[0, 0]
57
+ ax.plot(steps_p1, loss_p1, 'k-', label='Phase 1 (Add)', linewidth=2)
58
+ if steps_aa:
59
+ ax.plot(steps_aa, loss_aa, 'b-', label='A→A (Continue Add)', linewidth=2)
60
+ if steps_ab:
61
+ ax.plot(steps_ab, loss_ab, 'r-', label='A→B (Switch to Sub)', linewidth=2)
62
+ ax.axvline(x=steps_p1[-1] if steps_p1 else 0, color='gray', linestyle='--',
63
+ alpha=0.5, label='Phase transition')
64
+ ax.set_xlabel('Training Step')
65
+ ax.set_ylabel('Loss')
66
+ ax.set_title('Training Loss')
67
+ ax.legend()
68
+ ax.set_yscale('log')
69
+
70
+ # Addition accuracy
71
+ ax = axes[0, 1]
72
+ ax.plot(steps_p1, acc_add_p1, 'k-', label='Phase 1', linewidth=2)
73
+ if steps_aa:
74
+ ax.plot(steps_aa, acc_add_aa, 'b-', label='A→A', linewidth=2)
75
+ if steps_ab:
76
+ ax.plot(steps_ab, acc_add_ab, 'r-', label='A→B', linewidth=2)
77
+ ax.axvline(x=steps_p1[-1] if steps_p1 else 0, color='gray',
78
+ linestyle='--', alpha=0.5)
79
+ ax.set_xlabel('Training Step')
80
+ ax.set_ylabel('Accuracy')
81
+ ax.set_title('Task A (Addition) Accuracy')
82
+ ax.legend()
83
+ ax.set_ylim(-0.05, 1.05)
84
+
85
+ # Subtraction accuracy
86
+ ax = axes[1, 0]
87
+ ax.plot(steps_p1, acc_sub_p1, 'k-', label='Phase 1', linewidth=2)
88
+ if steps_aa:
89
+ ax.plot(steps_aa, acc_sub_aa, 'b-', label='A→A', linewidth=2)
90
+ if steps_ab:
91
+ ax.plot(steps_ab, acc_sub_ab, 'r-', label='A→B', linewidth=2)
92
+ ax.axvline(x=steps_p1[-1] if steps_p1 else 0, color='gray',
93
+ linestyle='--', alpha=0.5)
94
+ ax.set_xlabel('Training Step')
95
+ ax.set_ylabel('Accuracy')
96
+ ax.set_title('Task B (Subtraction) Accuracy')
97
+ ax.legend()
98
+ ax.set_ylim(-0.05, 1.05)
99
+
100
+ # Gradient alignment
101
+ ax = axes[1, 1]
102
+ ga_p1 = [h.get('gradient_alignment_a_vs_b', 0) for h in p1]
103
+ ga_aa = [h.get('gradient_alignment_a_vs_b', 0) for h in p2aa]
104
+ ga_ab = [h.get('gradient_alignment_a_vs_b', 0) for h in p2ab]
105
+ ax.plot(steps_p1, ga_p1, 'k-', label='Phase 1', linewidth=2)
106
+ if steps_aa:
107
+ ax.plot(steps_aa, ga_aa, 'b-', label='A→A', linewidth=2)
108
+ if steps_ab:
109
+ ax.plot(steps_ab, ga_ab, 'r-', label='A→B', linewidth=2)
110
+ ax.axvline(x=steps_p1[-1] if steps_p1 else 0, color='gray',
111
+ linestyle='--', alpha=0.5)
112
+ ax.axhline(y=0, color='gray', linestyle=':', alpha=0.3)
113
+ ax.set_xlabel('Training Step')
114
+ ax.set_ylabel('Cosine Similarity')
115
+ ax.set_title('Gradient Alignment (Task A vs Task B)')
116
+ ax.legend()
117
+
118
+ plt.tight_layout()
119
+ plt.savefig(f'{output_dir}/training_curves.png', dpi=150, bbox_inches='tight')
120
+ plt.close()
121
+ print(f"Saved: {output_dir}/training_curves.png")
122
+
123
+
124
+ def plot_cka_dynamics(results: Dict, output_dir: str):
125
+ """Plot CKA drift from Phase 1 end across all layers."""
126
+ fig, axes = plt.subplots(1, 2, figsize=(14, 5))
127
+
128
+ n_layers = results['config']['n_layers'] + 1
129
+
130
+ for layer_idx in range(n_layers):
131
+ metric = f'layer_{layer_idx}/cka_vs_phase1'
132
+
133
+ # A→A branch
134
+ p2aa = results['phase2_aa_history']
135
+ steps_aa = [h['step'] for h in p2aa if metric in h]
136
+ vals_aa = [h[metric] for h in p2aa if metric in h]
137
+
138
+ # A→B branch
139
+ p2ab = results['phase2_ab_history']
140
+ steps_ab = [h['step'] for h in p2ab if metric in h]
141
+ vals_ab = [h[metric] for h in p2ab if metric in h]
142
+
143
+ label = f'Layer {layer_idx}' if layer_idx > 0 else 'Embedding'
144
+ axes[0].plot(steps_aa, vals_aa, '-', label=label, linewidth=1.5)
145
+ axes[1].plot(steps_ab, vals_ab, '-', label=label, linewidth=1.5)
146
+
147
+ axes[0].set_title('Branch A→A: CKA vs Phase 1 End')
148
+ axes[0].set_xlabel('Training Step')
149
+ axes[0].set_ylabel('CKA Similarity')
150
+ axes[0].legend()
151
+ axes[0].set_ylim(0, 1.05)
152
+
153
+ axes[1].set_title('Branch A→B: CKA vs Phase 1 End')
154
+ axes[1].set_xlabel('Training Step')
155
+ axes[1].set_ylabel('CKA Similarity')
156
+ axes[1].legend()
157
+ axes[1].set_ylim(0, 1.05)
158
+
159
+ plt.tight_layout()
160
+ plt.savefig(f'{output_dir}/cka_dynamics.png', dpi=150, bbox_inches='tight')
161
+ plt.close()
162
+ print(f"Saved: {output_dir}/cka_dynamics.png")
163
+
164
+
165
+ def plot_attention_entropy(results: Dict, output_dir: str):
166
+ """Plot attention entropy per head over training."""
167
+ n_layers = results['config']['n_layers']
168
+ n_heads = results['config']['n_heads']
169
+
170
+ fig, axes = plt.subplots(n_layers, 2, figsize=(14, 4 * n_layers))
171
+ if n_layers == 1:
172
+ axes = axes.reshape(1, 2)
173
+
174
+ for layer_idx in range(n_layers):
175
+ for head_idx in range(n_heads):
176
+ metric = f'layer_{layer_idx+1}/head_{head_idx}_entropy'
177
+
178
+ # A→A
179
+ p2aa = results['phase2_aa_history']
180
+ steps_aa = [h['step'] for h in p2aa if metric in h]
181
+ vals_aa = [h[metric] for h in p2aa if metric in h]
182
+ axes[layer_idx, 0].plot(steps_aa, vals_aa, label=f'Head {head_idx}')
183
+
184
+ # A→B
185
+ p2ab = results['phase2_ab_history']
186
+ steps_ab = [h['step'] for h in p2ab if metric in h]
187
+ vals_ab = [h[metric] for h in p2ab if metric in h]
188
+ axes[layer_idx, 1].plot(steps_ab, vals_ab, label=f'Head {head_idx}')
189
+
190
+ axes[layer_idx, 0].set_title(f'Layer {layer_idx+1} — A→A')
191
+ axes[layer_idx, 0].set_ylabel('Entropy (bits)')
192
+ axes[layer_idx, 0].legend()
193
+ axes[layer_idx, 1].set_title(f'Layer {layer_idx+1} — A→B')
194
+ axes[layer_idx, 1].legend()
195
+
196
+ axes[-1, 0].set_xlabel('Training Step')
197
+ axes[-1, 1].set_xlabel('Training Step')
198
+
199
+ plt.tight_layout()
200
+ plt.savefig(f'{output_dir}/attention_entropy.png', dpi=150, bbox_inches='tight')
201
+ plt.close()
202
+ print(f"Saved: {output_dir}/attention_entropy.png")
203
+
204
+
205
+ def plot_cka_heatmaps(results: Dict, output_dir: str):
206
+ """Plot CKA cross-layer heatmaps for final model comparisons."""
207
+ heatmaps = results['cka_heatmaps']
208
+
209
+ fig, axes = plt.subplots(1, 3, figsize=(18, 5))
210
+
211
+ titles = ['A→A vs A→B', 'A→A vs Phase 1 End', 'A→B vs Phase 1 End']
212
+ keys = ['aa_vs_ab', 'aa_vs_p1', 'ab_vs_p1']
213
+
214
+ for ax, title, key in zip(axes, titles, keys):
215
+ hm = np.array(heatmaps[key])
216
+ im = ax.imshow(hm, cmap='viridis', vmin=0, vmax=1, aspect='auto')
217
+ ax.set_title(title)
218
+ ax.set_xlabel('Layer (model 2)')
219
+ ax.set_ylabel('Layer (model 1)')
220
+ # Add text annotations
221
+ for i in range(hm.shape[0]):
222
+ for j in range(hm.shape[1]):
223
+ color = 'white' if hm[i, j] < 0.5 else 'black'
224
+ ax.text(j, i, f'{hm[i,j]:.2f}', ha='center', va='center',
225
+ fontsize=8, color=color)
226
+ plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
227
+
228
+ plt.tight_layout()
229
+ plt.savefig(f'{output_dir}/cka_heatmaps.png', dpi=150, bbox_inches='tight')
230
+ plt.close()
231
+ print(f"Saved: {output_dir}/cka_heatmaps.png")
232
+
233
+
234
+ def plot_subspace_angles(results: Dict, output_dir: str):
235
+ """Plot subspace angle divergence between branches."""
236
+ n_layers = results['config']['n_layers'] + 1
237
+
238
+ fig, ax = plt.subplots(figsize=(10, 5))
239
+
240
+ for layer_idx in range(n_layers):
241
+ metric = f'layer_{layer_idx}/subspace_angle_vs_phase1'
242
+
243
+ p2aa = results['phase2_aa_history']
244
+ steps_aa = [h['step'] for h in p2aa if metric in h]
245
+ vals_aa = [h[metric] for h in p2aa if metric in h]
246
+
247
+ p2ab = results['phase2_ab_history']
248
+ steps_ab = [h['step'] for h in p2ab if metric in h]
249
+ vals_ab = [h[metric] for h in p2ab if metric in h]
250
+
251
+ label = f'Layer {layer_idx}' if layer_idx > 0 else 'Embedding'
252
+ if steps_aa:
253
+ ax.plot(steps_aa, vals_aa, '--', label=f'{label} (A→A)',
254
+ alpha=0.7, linewidth=1.5)
255
+ if steps_ab:
256
+ ax.plot(steps_ab, vals_ab, '-', label=f'{label} (A→B)',
257
+ linewidth=2)
258
+
259
+ ax.set_xlabel('Training Step')
260
+ ax.set_ylabel('Mean Subspace Angle (degrees)')
261
+ ax.set_title('Subspace Angle Drift from Phase 1 End')
262
+ ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
263
+ plt.tight_layout()
264
+ plt.savefig(f'{output_dir}/subspace_angles.png', dpi=150, bbox_inches='tight')
265
+ plt.close()
266
+ print(f"Saved: {output_dir}/subspace_angles.png")
267
+
268
+
269
+ def plot_weight_changes(results: Dict, output_dir: str):
270
+ """Plot weight change magnitude per block."""
271
+ n_blocks = results['config']['n_layers']
272
+
273
+ fig, axes = plt.subplots(1, 2, figsize=(14, 5))
274
+
275
+ for block_idx in range(n_blocks):
276
+ metric_init = f'block_{block_idx}/weight_change_from_init'
277
+ metric_p1 = f'block_{block_idx}/weight_change_from_phase1'
278
+
279
+ # A→A
280
+ p2aa = results['phase2_aa_history']
281
+ steps = [h['step'] for h in p2aa if metric_p1 in h]
282
+ vals = [h[metric_p1] for h in p2aa if metric_p1 in h]
283
+ axes[0].plot(steps, vals, label=f'Block {block_idx}', linewidth=2)
284
+
285
+ # A→B
286
+ p2ab = results['phase2_ab_history']
287
+ steps = [h['step'] for h in p2ab if metric_p1 in h]
288
+ vals = [h[metric_p1] for h in p2ab if metric_p1 in h]
289
+ axes[1].plot(steps, vals, label=f'Block {block_idx}', linewidth=2)
290
+
291
+ axes[0].set_title('A→A: Weight Change from Phase 1')
292
+ axes[0].set_xlabel('Training Step')
293
+ axes[0].set_ylabel('L2 Norm of Weight Delta')
294
+ axes[0].legend()
295
+
296
+ axes[1].set_title('A→B: Weight Change from Phase 1')
297
+ axes[1].set_xlabel('Training Step')
298
+ axes[1].set_ylabel('L2 Norm of Weight Delta')
299
+ axes[1].legend()
300
+
301
+ plt.tight_layout()
302
+ plt.savefig(f'{output_dir}/weight_changes.png', dpi=150, bbox_inches='tight')
303
+ plt.close()
304
+ print(f"Saved: {output_dir}/weight_changes.png")
305
+
306
+
307
+ def generate_all_plots(results_path: str, output_dir: str = None):
308
+ """Generate all visualization plots from experiment results."""
309
+ results = load_results(results_path)
310
+ if output_dir is None:
311
+ output_dir = str(Path(results_path).parent)
312
+
313
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
314
+
315
+ plot_training_curves(results, output_dir)
316
+ plot_cka_dynamics(results, output_dir)
317
+ plot_attention_entropy(results, output_dir)
318
+ plot_cka_heatmaps(results, output_dir)
319
+ plot_subspace_angles(results, output_dir)
320
+ plot_weight_changes(results, output_dir)
321
+
322
+ print(f"\nAll plots saved to {output_dir}/")
323
+
324
+
325
+ if __name__ == '__main__':
326
+ parser = argparse.ArgumentParser()
327
+ parser.add_argument('--results', type=str, default='results/experiment_results.json')
328
+ parser.add_argument('--output-dir', type=str, default=None)
329
+ args = parser.parse_args()
330
+ generate_all_plots(args.results, args.output_dir)