neuroplot 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
neuroplot/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # NeuroPlot 🧠📊
2
+ A lightweight, real-time training telemetry and visualization library for PyTorch.
3
+
4
+ ## Features
5
+ - Live multi-panel monitoring (Decision boundaries, latent space representations, loss, and accuracy).
6
+ - Automatic GIF compilation of training progression.
7
+ - Best-model checkpointing.
8
+ - Designed for mechanistic interpretability and deep learning research.
9
+
10
+ ## Installation
11
+ ```bash
12
+ pip install neuroplot
neuroplot/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from neuroplot.visualizer import LiveVisualizer
@@ -0,0 +1,231 @@
1
+ # import necessary libraries
2
+ import torch
3
+ import torch.nn as nn
4
+ import matplotlib.pyplot as plt
5
+ import numpy as np
6
+ import os
7
+ import tempfile
8
+
9
+ try:
10
+ import imageio
11
+ IMAGEIO_AVAILABLE = True
12
+ except ImportError:
13
+ IMAGEIO_AVAILABLE = False
14
+
15
+ class LiveVisualizer:
16
+ def __init__(self, plots, model=None, data=None, update_every=50,
17
+ save_gif=False, gif_name="training_progress.gif",
18
+ save_best_model=False, checkpoint_path="best_model.pth",
19
+ smooth_alpha=0.1):
20
+ self.plots = plots
21
+ self.model = model
22
+ self.data = data
23
+ self.update_every = update_every
24
+ self.save_gif = save_gif
25
+ self.gif_name = gif_name
26
+ self.save_best_model = save_best_model
27
+ self.checkpoint_path = checkpoint_path
28
+ self.smooth_alpha = smooth_alpha
29
+
30
+ # Dynamic history dictionary
31
+ self.history = {plot_type: [] for plot_type in plots if plot_type not in ["boundary", "gradients", "latent"]}
32
+ self.smoothed_history = {plot_type: [] for plot_type in plots if plot_type not in ["boundary", "gradients", "latent"]}
33
+ self.custom_plots = {}
34
+
35
+ # Best metric trackers
36
+ self.best_loss = float('inf')
37
+ self.best_acc = 0.0
38
+
39
+ # GIF frame storage
40
+ self.frames = []
41
+ if self.save_gif and not IMAGEIO_AVAILABLE:
42
+ print("Warning: 'imageio' package not found. GIF saving will be skipped. Install via 'pip install imageio'.")
43
+ self.save_gif = False
44
+
45
+ if self.save_gif:
46
+ self.temp_dir = tempfile.TemporaryDirectory()
47
+ self.frame_count = 0
48
+
49
+ # Grid precomputation for decision boundary
50
+ self.grid_tensor = None
51
+ self.xx = None
52
+ self.yy = None
53
+
54
+ # Set up dynamic subplot grid
55
+ num_plots = len(self.plots)
56
+ cols = min(num_plots, 3)
57
+ rows = (num_plots + cols - 1) // cols
58
+
59
+ plt.ion()
60
+ self.fig, self.axes = plt.subplots(rows, cols, figsize=(5 * cols, 4 * rows))
61
+
62
+ if num_plots == 1:
63
+ self.axes = [self.axes]
64
+ else:
65
+ self.axes = self.axes.flatten() if isinstance(self.axes, np.ndarray) else [self.axes]
66
+
67
+ for j in range(num_plots, len(self.axes)):
68
+ self.fig.delaxes(self.axes[j])
69
+
70
+ if "boundary" in self.plots and self.data is not None:
71
+ X, _ = self.data
72
+ x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
73
+ y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
74
+ xx, yy = torch.meshgrid(torch.linspace(x_min, x_max, 100), torch.linspace(y_min, y_max, 100), indexing='ij')
75
+ self.grid_tensor = torch.stack([xx.ravel(), yy.ravel()], dim=1)
76
+ self.xx, self.yy = xx, yy
77
+
78
+ def add_custom_plot(self, name, func):
79
+ self.custom_plots[name] = func
80
+
81
+ def _extract_hidden_activations(self, x):
82
+ """Helper to extract hidden layer representations for mechanistic tasks."""
83
+ if self.model is None:
84
+ return None
85
+
86
+ activation = {}
87
+ def get_activation(name):
88
+ def hook(model, input, output):
89
+ activation[name] = output.detach()
90
+ return hook
91
+
92
+ # Automatically find the second-to-last linear layer or hook a fallback
93
+ target_layer = None
94
+ for name, module in self.model.named_modules():
95
+ if isinstance(module, nn.Linear):
96
+ target_layer = module
97
+
98
+ if target_layer is not None:
99
+ handle = target_layer.register_forward_hook(get_activation('hidden'))
100
+ with torch.no_grad():
101
+ self.model(x)
102
+ handle.remove()
103
+ return activation.get('hidden', None)
104
+ return None
105
+
106
+ def step(self, epoch, **metrics):
107
+ if epoch % self.update_every != 0:
108
+ return
109
+
110
+ for key, value in metrics.items():
111
+ if key in self.history:
112
+ self.history[key].append(value)
113
+ prev_smooth = self.smoothed_history[key][-1] if self.smoothed_history[key] else value
114
+ smoothed_val = self.smooth_alpha * value + (1 - self.smooth_alpha) * prev_smooth
115
+ self.smoothed_history[key].append(smoothed_val)
116
+
117
+ if key == "loss" and value < self.best_loss:
118
+ self.best_loss = value
119
+ if self.save_best_model and self.model is not None:
120
+ torch.save(self.model.state_dict(), self.checkpoint_path)
121
+ elif key == "accuracy" and value > self.best_acc:
122
+ self.best_acc = value
123
+ if self.save_best_model and self.model is not None:
124
+ torch.save(self.model.state_dict(), self.checkpoint_path)
125
+
126
+ for i, plot_type in enumerate(self.plots):
127
+ ax = self.axes[i]
128
+ ax.clear()
129
+
130
+ # 1. Metric Curves with EMA Trendlines
131
+ if plot_type in self.history:
132
+ raw_values = self.history[plot_type]
133
+ smooth_values = self.smoothed_history[plot_type]
134
+
135
+ ax.plot(raw_values, color='gray', alpha=0.3, lw=1, label='Raw')
136
+ ax.plot(smooth_values, color='red' if plot_type == 'loss' else 'green', lw=2, label='EMA Trend')
137
+ ax.set_title(f"{plot_type.capitalize()} Curve")
138
+ ax.set_xlabel("Update Step")
139
+ ax.set_ylabel(plot_type.capitalize())
140
+ ax.grid(True, linestyle='--', alpha=0.6)
141
+
142
+ if plot_type == "loss" and raw_values:
143
+ best_idx = np.argmin(raw_values)
144
+ ax.scatter(best_idx, raw_values[best_idx], color='gold', s=100, zorder=5, label=f'Best: {raw_values[best_idx]:.4f}')
145
+ ax.legend(loc='upper right', fontsize=7)
146
+ elif plot_type == "accuracy" and raw_values:
147
+ best_idx = np.argmax(raw_values)
148
+ ax.scatter(best_idx, raw_values[best_idx], color='gold', s=100, zorder=5, label=f'Best: {raw_values[best_idx]:.4f}')
149
+ ax.legend(loc='lower right', fontsize=7)
150
+
151
+ # 2. Decision Boundary Plot
152
+ elif plot_type == "boundary" and self.model is not None and self.data is not None:
153
+ X, y = self.data
154
+ if self.grid_tensor is not None:
155
+ self.model.eval()
156
+ with torch.no_grad():
157
+ Z = self.model(self.grid_tensor).detach().cpu().numpy()
158
+ self.model.train()
159
+ Z = Z.reshape(self.xx.shape)
160
+ ax.contourf(self.xx, self.yy, Z, levels=50, cmap=plt.cm.Spectral, alpha=0.7)
161
+
162
+ ax.scatter(X[:, 0], X[:, 1], c=y.squeeze(), cmap=plt.cm.Spectral, edgecolors='k')
163
+ ax.set_title(f"Decision Boundary (Epoch {epoch})")
164
+
165
+ # 3. Mechanistic Latent Space / Internal Representation Warp Plot
166
+ elif plot_type == "latent" and self.model is not None and self.data is not None:
167
+ X, y = self.data
168
+ hidden_acts = self._extract_hidden_activations(X)
169
+ if hidden_acts is not None:
170
+ h = hidden_acts.cpu().numpy()
171
+ # If hidden dimension > 2, take the first 2 principal components or dimensions for visualization
172
+ h_x = h[:, 0]
173
+ h_y = h[:, 1] if h.shape[1] > 1 else np.zeros_like(h_x)
174
+
175
+ ax.scatter(h_x, h_y, c=y.squeeze(), cmap=plt.cm.Spectral, edgecolors='k', s=40)
176
+ ax.set_title(f"Internal Latent Space (Epoch {epoch})")
177
+ ax.set_xlabel("Hidden Dim 1")
178
+ ax.set_ylabel("Hidden Dim 2")
179
+ ax.grid(True, linestyle='--', alpha=0.6)
180
+
181
+ # 4. Gradient Flow Plot
182
+ elif plot_type == "gradients" and self.model is not None:
183
+ ave_grads = []
184
+ layers = []
185
+ for n, p in self.model.named_parameters():
186
+ if p.requires_grad and ("bias" not in n) and (p.grad is not None):
187
+ layers.append(n.split(".")[0])
188
+ ave_grads.append(p.grad.abs().mean().item())
189
+
190
+ if ave_grads:
191
+ ax.plot(ave_grads, alpha=0.7, color="blue", lw=2)
192
+ ax.bar(range(len(ave_grads)), ave_grads, alpha=0.3, color="blue")
193
+ ax.set_xticks(range(len(layers)))
194
+ ax.set_xticklabels(layers, rotation=30, ha='right', fontsize=8)
195
+ ax.set_title("Gradient Flow (Layer-wise)")
196
+ ax.set_xlabel("Layers")
197
+ ax.set_ylabel("Avg Gradient Magnitude")
198
+ ax.set_yscale("log")
199
+ ax.grid(True, linestyle='--', alpha=0.6)
200
+
201
+ # 5. Custom Callbacks
202
+ elif plot_type in self.custom_plots:
203
+ self.custom_plots[plot_type](ax, self.model, self.data)
204
+
205
+ plt.tight_layout()
206
+ plt.draw()
207
+ self.fig.canvas.flush_events()
208
+ plt.pause(0.001)
209
+
210
+ if self.save_gif and IMAGEIO_AVAILABLE:
211
+ frame_path = os.path.join(self.temp_dir.name, f"frame_{self.frame_count:04d}.png")
212
+ self.fig.savefig(frame_path, dpi=100)
213
+ self.frames.append(frame_path)
214
+ self.frame_count += 1
215
+
216
+ def close(self):
217
+ plt.ioff()
218
+
219
+ if self.save_best_model:
220
+ abs_ckpt = os.path.abspath(self.checkpoint_path)
221
+ print(f"Best model automatically saved to:\n---> {abs_ckpt}")
222
+
223
+ if self.save_gif and IMAGEIO_AVAILABLE and self.frames:
224
+ abs_path = os.path.abspath(self.gif_name)
225
+ print(f"Compiling training progress into GIF...")
226
+ images = [imageio.v3.imread(f) for f in self.frames]
227
+ imageio.mimsave(self.gif_name, images, duration=200, loop=0)
228
+ self.temp_dir.cleanup()
229
+ print(f"GIF successfully compiled and saved at:\n---> {abs_path}")
230
+
231
+ plt.show()
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: neuroplot
3
+ Version: 0.1.0
4
+ Summary: A lightweight, modular real-time visualization and diagnostics library for PyTorch.
5
+ Description-Content-Type: text/markdown
6
+ Requires-Dist: torch
7
+ Requires-Dist: matplotlib
8
+ Requires-Dist: numpy
9
+ Requires-Dist: imageio
10
+
11
+ # NeuroPlot 🧠📊
12
+ A lightweight, real-time training telemetry and visualization library for PyTorch.
13
+
14
+ ## Features
15
+ - Live multi-panel monitoring (Decision boundaries, latent space representations, loss, and accuracy).
16
+ - Automatic GIF compilation of training progression.
17
+ - Best-model checkpointing.
18
+ - Designed for mechanistic interpretability and deep learning research.
19
+
20
+ ## Installation
21
+ ```bash
22
+ pip install neuroplot
@@ -0,0 +1,7 @@
1
+ neuroplot/README.md,sha256=6G8cdIOg5GYuZJj2F_t4II3hoh6TRFApDr56axngfFE,430
2
+ neuroplot/__init__.py,sha256=fV4kUAsXMqIFLYodP7T89IDIEd0s33q0QqXYFGc3L0k,47
3
+ neuroplot/visualizer.py,sha256=1wWutIqbyV9Qo7Qqk2ZCoTANk_zCqyWUHMW9lAjf-OM,10409
4
+ neuroplot-0.1.0.dist-info/METADATA,sha256=KgAQgQHJUMeUKmTPbawz-45EUZryX7GUnJ6BqCB1NUM,720
5
+ neuroplot-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
6
+ neuroplot-0.1.0.dist-info/top_level.txt,sha256=a2CUIuz5rJW95nNhxyAg-F54lk8abNmkoC1TiaYnevk,10
7
+ neuroplot-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ neuroplot