neuroplot 0.1.0__tar.gz
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-0.1.0/PKG-INFO +22 -0
- neuroplot-0.1.0/README.md +9 -0
- neuroplot-0.1.0/neuroplot/README.md +12 -0
- neuroplot-0.1.0/neuroplot/__init__.py +1 -0
- neuroplot-0.1.0/neuroplot/visualizer.py +231 -0
- neuroplot-0.1.0/neuroplot.egg-info/PKG-INFO +22 -0
- neuroplot-0.1.0/neuroplot.egg-info/SOURCES.txt +10 -0
- neuroplot-0.1.0/neuroplot.egg-info/dependency_links.txt +1 -0
- neuroplot-0.1.0/neuroplot.egg-info/requires.txt +4 -0
- neuroplot-0.1.0/neuroplot.egg-info/top_level.txt +1 -0
- neuroplot-0.1.0/pyproject.toml +15 -0
- neuroplot-0.1.0/setup.cfg +4 -0
neuroplot-0.1.0/PKG-INFO
ADDED
|
@@ -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,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
|
|
@@ -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,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
neuroplot/README.md
|
|
4
|
+
neuroplot/__init__.py
|
|
5
|
+
neuroplot/visualizer.py
|
|
6
|
+
neuroplot.egg-info/PKG-INFO
|
|
7
|
+
neuroplot.egg-info/SOURCES.txt
|
|
8
|
+
neuroplot.egg-info/dependency_links.txt
|
|
9
|
+
neuroplot.egg-info/requires.txt
|
|
10
|
+
neuroplot.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
neuroplot
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "neuroplot"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A lightweight, modular real-time visualization and diagnostics library for PyTorch."
|
|
9
|
+
readme = "neuroplot/README.md"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"torch",
|
|
12
|
+
"matplotlib",
|
|
13
|
+
"numpy",
|
|
14
|
+
"imageio"
|
|
15
|
+
]
|