mcode-neural 0.5.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.
@@ -0,0 +1,767 @@
1
+ Metadata-Version: 2.5
2
+ Name: mcode-neural
3
+ Version: 0.5.0
4
+ Summary: Our AI and Neural Network tools. Clean, aligned, readable views of PyTorch tensors, single neuron models, and multi-layer networks, for Jupyter Notebooks and the console.
5
+ Project-URL: Homepage, https://www.mcode.com/products-python-packages.html
6
+ Author: Timothy McGuire
7
+ License-Expression: MIT AND OFL-1.1
8
+ License-File: LICENSE
9
+ License-File: src/mcode_neural/fonts/MicroCODE-Mono/OFL.txt
10
+ License-File: src/mcode_neural/fonts/RobotoMono/OFL.txt
11
+ Keywords: AI,Debugging,Display,Jupyter,MicroCODE,Neural Network,Notebook,PyTorch,Tensor
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Framework :: Jupyter
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Education
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.12
23
+ Requires-Dist: ipython
24
+ Requires-Dist: ipywidgets
25
+ Requires-Dist: matplotlib
26
+ Requires-Dist: numpy
27
+ Requires-Dist: pandas
28
+ Requires-Dist: torch
29
+ Requires-Dist: varname
30
+ Provides-Extra: dev
31
+ Requires-Dist: build; extra == 'dev'
32
+ Requires-Dist: nbconvert; extra == 'dev'
33
+ Requires-Dist: poethepoet; extra == 'dev'
34
+ Requires-Dist: pytest; extra == 'dev'
35
+ Requires-Dist: ruff~=0.17.3; extra == 'dev'
36
+ Requires-Dist: twine; extra == 'dev'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # MicroCODE's 'mcode-neural' package
40
+
41
+ A public PyPI Package of our internal Neural Network tools for Jupyter Notebooks and the console.
42
+
43
+ It includes our PyTorch Tensor display functions, our NumPy single neuron models, and our PyTorch multi-layer networks for regression and classification.
44
+
45
+ It is a 'light weight' package built only on the standard Notebook stack: PyTorch, NumPy, pandas, Matplotlib, IPython, ipywidgets, and 'varname'.
46
+
47
+ ## Description
48
+
49
+ This is our internal tensor display code. It replaces the use of "print(tensor)" with "show_tensor(tensor)"
50
+ which shows **everything you need to know about a tensor in one aligned block**.
51
+
52
+ - **show_tensor()** - prints a tensor's Name, Shape, Datatype, Device, and Values with right-aligned labels.
53
+
54
+ ## show_tensor() vs. print()
55
+
56
+ Why did we write this package? We were unhappy with how much work it took to inspect a tensor while learning
57
+ and debugging. A plain **print(tensor)** shows only the values, wrapped in 'tensor(...)' and extra brackets,
58
+ and you must make separate calls to see its shape, dtype, and device.
59
+
60
+ - **print()**...
61
+
62
+ ```
63
+ tensor([[1, 2, 3],
64
+ [4, 5, 6],
65
+ [7, 8, 9]])
66
+ ```
67
+
68
+ - **show_tensor()**...
69
+
70
+ ```
71
+ Name: tp_data
72
+ Shape: (3, 3)
73
+ Datatype: int64
74
+ Device: cpu
75
+ Tensor: [1, 2, 3]
76
+ [4, 5, 6]
77
+ [7, 8, 9]
78
+ ```
79
+
80
+ Features:
81
+
82
+ 1. The **Name** is taken automatically from the expression passed in, or can be given explicitly.
83
+ 2. The 'torch.' prefix is removed from Datatypes and 'torch.Size' from Shapes.
84
+ 3. The 'tensor(...)' wrapper and the outermost brackets are removed, rows stay aligned under the label.
85
+ 4. Scalars (0-D), Vectors (1-D), Matrices (2-D), and higher dimensions are all handled.
86
+ 5. Every tensor in a cell is shown, not just the last one Jupyter auto-displays.
87
+
88
+ ## Dependencies
89
+
90
+ - **Production**
91
+
92
+ 1. Python 3.12+ - required for our f-string quoting style
93
+ 2. PyTorch - the tensors being displayed
94
+ 3. NumPy - the arrays used by the single neuron models
95
+ 4. varname - used to retrieve the caller's variable name
96
+ 5. pandas - the tables shown by the model views
97
+ 6. Matplotlib - the bar charts drawn by the model views
98
+ 7. IPython - displays the model views' tables and Markdown in a Notebook
99
+ 8. ipywidgets - the slider that browses the trace sample by sample
100
+
101
+ - **Development**
102
+
103
+ 1. pytest - our preferred Python testing framework
104
+ 2. build - builds the distributable wheel and source archive
105
+ 3. twine - uploads the distributables to PyPI
106
+
107
+ ## Fonts
108
+
109
+ Two monospace fonts ship inside the package, so nothing has to be installed on your machine:
110
+
111
+ - **Roboto Mono** (v3.001, Regular and Bold) - every model view's table is drawn in it. The first table each time a
112
+ Notebook cell runs carries the font inside its own HTML, so the tables read the same on every machine.
113
+ - **MicroCODE Mono** (v1.0.83) - MicroCODE's own monospace font, derived from ProFont, with complete Box Drawing and
114
+ Block Elements coverage.
115
+
116
+ Both are registered with Matplotlib when 'mcode_neural' is imported, so your own charts can use either by name; the
117
+ package's charts keep their Arial text.
118
+
119
+ ```python
120
+ import matplotlib.pyplot as plt
121
+ import mcode_neural
122
+
123
+ plt.text(0.1, 0.5, "┌──┐ MicroCODE Mono", family="MicroCODE Mono")
124
+ ```
125
+
126
+ ## Light Mode and Dark Mode
127
+
128
+ Every view draws in Dark Mode by default: near-black charts and dark gray tables, for an editor in a dark theme. For a
129
+ light theme, call show_mode("light") once, near the top of the Notebook, before any view:
130
+
131
+ ```python
132
+ from mcode_neural import show_mode
133
+
134
+ show_mode("light")
135
+ ```
136
+
137
+ - Light Mode draws white charts, light gray tables, and dark text. The data colors (class colors, the weights' signs,
138
+ the color maps) are the same in both modes.
139
+ - Keep one mode per Notebook. A table's and a slider's styles apply to every output in the Notebook, so the last mode
140
+ set restyles every table and widget already shown. A chart keeps the mode it was drawn in.
141
+ - An unknown mode raises a ValueError naming the two modes, and the mode stays as it was.
142
+ - With '%autoreload 2', editing the package reloads it in Dark Mode; run the show_mode() cell again.
143
+
144
+ ## GPU (CUDA)
145
+
146
+ - Nothing in the package changes for a GPU: the multi-layer networks train and predict on whatever device the network
147
+ is on. train_model() moves each batch to the network's device, and predict_classes() / predict_values() return NumPy
148
+ arrays from the CPU.
149
+ - 'pip install torch' on Windows installs a CPU-only build. Replace it with the CUDA build that matches your NVIDIA
150
+ driver, from the [PyTorch install selector](https://pytorch.org/get-started/locally/); uninstall first, as pip treats
151
+ the CPU build of the same version as already installed...
152
+
153
+ ```
154
+ pip uninstall torch
155
+ pip install torch --index-url https://download.pytorch.org/whl/cu128 # e.g. CUDA 12.8
156
+ ```
157
+
158
+ - Move a network to the GPU when you create it, and again after model_load(), which loads every model on the CPU so a
159
+ model saved on a GPU loads on any machine (a wrapped Model loads where the networks given to it are):
160
+
161
+ ```python
162
+ device = model_device() # CUDA when available, else the CPU; prints the GPU, its memory, and the cap
163
+ model = ClassificationModel(in_features=4, hidden_size=(64, 32), out_features=2).to(device)
164
+ model = model_load("wine_network.pt").to(device)
165
+ ```
166
+
167
+ - model_device() caps this process's GPU memory at 75% of the GPU's (model_device(fraction=0.5) for half), leaving the
168
+ rest to the desktop. On Windows the NVIDIA driver otherwise lets a batch too big for the GPU spill into shared system
169
+ RAM, where it runs many times slower with no message. With the cap it fails at once, and train_model() names it:
170
+ "phase 'train' at batch_size=100 needs more than the 6.00 GiB cap; try a smaller batch_size".
171
+
172
+ - train_probe() finds the batch size that fits before a long run: it trains two batches at each size, smallest first,
173
+ prints each peak, stops at the first that does not fit, and leaves the model and optimizer as they were.
174
+ - train_model() records each run's device, cap, and peak GPU memory in its history, and prints the peak and what the
175
+ process still holds when it ends. However it ends, its gradients and PyTorch's cached blocks go back to the GPU. An
176
+ interrupted run (Interrupt Kernel, or an out-of-memory error) records nothing and puts back the weights the model had
177
+ when it started. The optimizer's state is yours: 'del optimizer', or build a new one for the next phase.
178
+ - checkpoint= saves the model at each epoch whose val score is the best so far, so a kernel restart keeps it;
179
+ model_load() reads it, and its history shows the run "In process..." until the run ends:
180
+
181
+ ```python
182
+ optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
183
+ train_probe(model, optimizer, [32, 48, 64, 100]) # batch_size 64: 5.00 GiB allocated, 5.56 GiB reserved of the 6.00 GiB cap
184
+ model = train_model(model, optimizer, None, num_epochs=10, batch_size=64, checkpoint="../Models/vgg11.pt")
185
+ # Training complete in 4m 12s
186
+ # Peak GPU memory: 5.00 GiB allocated, 5.56 GiB reserved of the 6.00 GiB cap
187
+ # GPU memory held: 0.55 GiB
188
+ ```
189
+
190
+ - Every prediction, metric, and view scores its phase batch by batch, in batches of the model's latest training's
191
+ batch_size (64 before any training), so a phase of any size fits the GPU memory one training batch did.
192
+ model_test() keeps the scoring's peak GPU memory beside its metrics in the history.
193
+ - The single neuron models are NumPy, and always run on the CPU.
194
+ - A small network on tabular data can train slower on a GPU, as every batch is copied to it; the GPU pays off for
195
+ larger networks, larger batches, and image data such as MNIST.
196
+
197
+ ## Usage
198
+
199
+ ```python
200
+ import torch as tp
201
+ from mcode_neural import show_tensor
202
+
203
+ x_rand = tp.rand(3, 2)
204
+ show_tensor(x_rand) # Name: x_rand
205
+ show_tensor(x_rand, 'Random Tensor') # Name: Random Tensor
206
+ ```
207
+
208
+ - Every model is **self-describing**: it knows its kind (model.kind, "classification" or "regression"), the arguments
209
+ it was built with, its data (model.data, its datasets by phase with their feature names, classes, and scaling), and
210
+ its history (model.history: how it was created, given its data, trained, and tested; model.curves and
211
+ model.best_epoch read its latest training). So nearly every function takes only the model, and a phase of its data
212
+ by name: create it, load its data, train it, validate it, evaluate it, examine it, look inside it, and read how it
213
+ came to be:
214
+
215
+ ```python
216
+ import torch
217
+ from mcode_neural import (LogisticClassifierNeuronModel, NeuronOptimizer, data_frame_split, train_model,
218
+ show_training_curves, model_test, show_model_metrics, show_misclassified,
219
+ show_neuron_trace, show_model_history)
220
+
221
+ wine_classes = {0: ("goldenrod", "white wine"), 1: ("red", "red wine")}
222
+
223
+ model = LogisticClassifierNeuronModel(in_features=len(input_columns)) # create it
224
+ model.data = data_frame_split(wine_df, input_columns, "color", torch.int64, # load its data
225
+ input_columns, wine_classes, seed=42)
226
+ model = train_model(model, NeuronOptimizer(lr=0.01), None, 10, batch_size=1) # train and validate it
227
+ show_training_curves(model)
228
+ model_test(model, "test") # evaluate it
229
+ show_model_metrics(model, "test") # examine it
230
+ show_misclassified(model, "test")
231
+ show_neuron_trace(model, "test", i=10) # look inside it
232
+ show_model_history(model) # how it came to be
233
+ ```
234
+
235
+ ```python
236
+ import numpy as np
237
+ from mcode_neural import RegressorNeuronModel
238
+
239
+ x = np.random.randn(100, 1)
240
+ y = 3.0 * x[:, 0] + 1.0
241
+
242
+ model = RegressorNeuronModel(in_features=1)
243
+ for x_i, y_i in zip(x, y):
244
+ model.forward(x_i)
245
+ model.gradient(x_i)
246
+ model.update(grad_loss=2 * (model.a - y_i), learning_rate=0.05) # Squared Error loss
247
+ ```
248
+
249
+ - The multi-layer networks are PyTorch modules sized by their data: **hidden_size** lists each hidden layer's width,
250
+ so (64, 32) is a two hidden layer network; **dropout** adds a Dropout after each hidden ReLU.
251
+
252
+ ```python
253
+ import torch
254
+ from mcode_neural import ClassificationModel, RegressionModel
255
+
256
+ model = ClassificationModel(in_features=4, hidden_size=(64, 32), out_features=2, dropout=0.25)
257
+ print(model.classifier) # Linear, ReLU, Dropout, ... Linear
258
+
259
+ criterion = torch.nn.CrossEntropyLoss() # the outputs are logits, one per class
260
+ optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
261
+ predicted = model(inputs).argmax(1) # the class with the highest logit
262
+
263
+ regressor = RegressionModel(in_features=4, hidden_size=(64, 64, 64), out_features=1)
264
+ ```
265
+
266
+ - BinaryClassificationModel has sigmoid outputs, each one yes / no logit, trained on nn.BCEWithLogitsLoss:
267
+ **out_features=1** is one two-class label, **out_features=k** is k independent labels (multi-label):
268
+
269
+ ```python
270
+ from mcode_neural import BinaryClassificationModel
271
+
272
+ binary = BinaryClassificationModel(in_features=4, hidden_size=(64, 32), out_features=1)
273
+ predicted = binary(inputs) > 0 # yes where the logit is above 0, sigmoid 0.5
274
+ ```
275
+
276
+ - AutoencoderModel encodes each sample to a **latent_size** code and decodes it back, its decoder the mirror of
277
+ **encoder_size**. **output_activation** fits the output to the inputs' range: "linear" (any value), "sigmoid"
278
+ (0 to 1), or "tanh" (-1 to 1), the entries of AUTOENCODER_OUTPUTS. train_model() trains it on its own inputs as
279
+ targets; its data's classes are optional, and only color its latent view:
280
+
281
+ ```python
282
+ from mcode_neural import AutoencoderModel, show_latent_space, show_reconstructions
283
+
284
+ model = AutoencoderModel(in_features=784, encoder_size=(128, 64), latent_size=2)
285
+ model.data = data_dataset_split(mnist_train, mnist_test, 0.25, digit_classes, seed=0)
286
+ model = train_model(model, optimizer, scheduler, num_epochs=5, batch_size=100)
287
+
288
+ codes = model.encode(X) # (N, 2), each sample's latent code
289
+ show_latent_space(model, "test") # one dot per sample, by class or by value
290
+ show_reconstructions(model, "test", n=6) # each input beside its reconstruction
291
+ ```
292
+
293
+ - show_3d_latent_space() draws the same dots in 3D: a latent size of 3 as it is on z1 / z2 / z3, a wider code on its
294
+ 3 principal components; show_3d_slider() spins it, chosen by the model's kind:
295
+
296
+ ```python
297
+ from mcode_neural import show_3d_latent_space, show_3d_slider
298
+
299
+ model = AutoencoderModel(in_features=784, encoder_size=(128, 64), latent_size=32)
300
+ show_3d_latent_space(model, "test") # the 32-value codes on their 3 principal components
301
+ show_3d_slider(model, "test") # the same chart, turned by the sliders
302
+ ```
303
+
304
+ - Every network takes **convolution**, the convolution stages before its Linear layers, for images read in their 2D
305
+ shape: each stage is a dict of "channels", "kernel" (odd, so the image keeps its size), and "pool" (1 for no
306
+ pooling), and **in_shape** is one image's (channels, height, width). An autoencoder's decoder mirrors its stages
307
+ back to the image. train_model() and the predictors shape each sample to **in_shape**, so images and flattened rows
308
+ score the same:
309
+
310
+ ```python
311
+ from mcode_neural import AutoencoderModel, ClassificationModel
312
+
313
+ stages = ({"channels": 8, "kernel": 3, "pool": 2},) # Conv2d, ReLU, MaxPool2d: (8, 14, 14)
314
+ model = ClassificationModel(784, (64,), 10, in_shape=(1, 28, 28), convolution=stages)
315
+ print(model.classifier) # Conv2d, ReLU, MaxPool2d, Flatten, Linear, ReLU, Linear
316
+ autoencoder = AutoencoderModel(784, (), 32, in_shape=(1, 28, 28), convolution=stages)
317
+ print(autoencoder.hidden_size) # ((8, 14, 14), 32, (8, 14, 14))
318
+ ```
319
+
320
+ ```python
321
+ from mcode_neural import show_misclassified, show_model_trace, show_neuron_trace
322
+
323
+ show_model_trace(model, "test") # a neuron's weights, or a network's PyTorch modules
324
+ show_neuron_trace(model, "test", i=10)
325
+ show_misclassified(model, "test") # a single neuron or a network
326
+ ```
327
+
328
+ - `show_model_trace()` also shows an external network, any `torch.nn.Module`, with no `phase`: it has no
329
+ `model.data` to score, so its chart has no score line.
330
+
331
+ ```python
332
+ from torchvision import models
333
+ from torchvision.models import VGG16_Weights
334
+
335
+ show_model_trace(models.vgg16(weights=VGG16_Weights.IMAGENET1K_V1)) # 'features', 'avgpool', 'classifier'
336
+ ```
337
+
338
+ - Every model is **Networks of Layers of Neurons**: model.network lists its Networks, each one named network of the
339
+ model and what it reads (its source: the data, or an earlier network, as a decoder reads its encoder); each
340
+ Network's layer lists its Layers, each one weight module (Linear, Conv2d, or ConvTranspose2d) with the modules after
341
+ it, e.g. ReLU and Dropout; and each Layer's neuron lists its Neurons, one per output. A single NumPy neuron is one
342
+ Network of one Layer of one Neuron. All three are views of the trained weights, so they always read the model as it
343
+ is. An indexed name is singular, so model.layer[2].neuron[5] reads as one neuron. A model of one network reads
344
+ model.layer for model.network[0].layer, and its labels drop "network[0]."; an autoencoder's read in full, e.g.
345
+ network[1].layer[0]. Zoom in from the model to one neuron, or draw the model once and click into each view:
346
+
347
+ ```python
348
+ from mcode_neural import show_layer_internals, show_layer_trace, show_model, show_model_internals, show_network_internals, show_neuron_internals
349
+
350
+ show_model(model, "test") # the network as a diagram; a click opens each view below
351
+ show_model_internals(model, "test") # one row per Layer, a mean |w| bar each
352
+ show_network_internals(autoencoder.network[1], "test") # the decoder: its source, shapes, and Layers
353
+ show_layer_internals(model.layer[0], "test") # one row per Neuron, a mean a bar each
354
+ show_neuron_internals(model.layer[0].neuron[5], "test", n=10) # its 10 largest weights, a sample slider
355
+ show_layer_trace(model, "test", i=10) # sample 10 through every Layer, its a per Layer
356
+
357
+ layer = model.layer[0]
358
+ layer.kind, layer.activation, layer.in_shape, layer.out_shape # ('linear', 'relu', (11,), (64,))
359
+ z, a = layer.neuron[5].outputs(X) # its z and a for every sample of X
360
+ decoder = autoencoder.network[1]
361
+ decoder.name, decoder.source, decoder.in_shape # ('decoder', ('encoder',), (2,))
362
+ ```
363
+
364
+ - A regression model's data has **classes=None**; a classifier's data names every label in its classes.
365
+
366
+ - Pass **chart=False** or **tables=False** to show_neuron_trace() to show one part alone, e.g. its tables
367
+ above a Notebook slider and its chart below it.
368
+
369
+ - show_neuron_slider() browses the trace sample by sample, its tables above a slider and its chart below; the "-" and
370
+ "+" at the ends of its track step 1 sample a click, and a sample number typed in the box after them moves it there:
371
+
372
+ ```python
373
+ from mcode_neural import show_neuron_slider
374
+
375
+ show_neuron_slider(model, "test")
376
+ ```
377
+
378
+ - show_confusion_matrix() charts a classifier's counts by true and predicted class, a single neuron or a network,
379
+ each square's rate (TNR, FPR / FNR, TPR) in its upper right corner:
380
+
381
+ ```python
382
+ from mcode_neural import show_confusion_matrix
383
+
384
+ show_confusion_matrix(model, "test")
385
+ ```
386
+
387
+ - show_2d_scores() charts a classifier's score z by class either side of the decision boundary z = 0;
388
+ show_3d_results() charts its samples in 3D around its decision plane, on its three largest |w| features:
389
+
390
+ ```python
391
+ from mcode_neural import show_2d_scores, show_3d_results
392
+
393
+ show_2d_scores(model, "test")
394
+ show_3d_results(model, "test")
395
+ ```
396
+
397
+ - show_3d_slider() spins the same 3D chart with Elevation, Azimuth, and Roll sliders below it, each track between a
398
+ "-" and a "+" button that turn it 1 degree a click, and an angle typed in the box after them turns it there; an
399
+ autoencoder's slider spins show_3d_latent_space() instead.
400
+ show_3d_results() takes the same three angles, in degrees, for a still view:
401
+
402
+ ```python
403
+ from mcode_neural import show_3d_results, show_3d_slider
404
+
405
+ show_3d_slider(model, "test")
406
+ show_3d_results(model, "test", elev=0, azim=90)
407
+ ```
408
+
409
+ - show_model_metrics() scores a two-class model itself, a single neuron or a network (two softmax outputs, or one
410
+ sigmoid output), and charts its ROC curve, the AUC under it, and the threshold 0.5 point, then its confusion matrix
411
+ at that point. For a one-target regression model it charts every sample's prediction against its actual value
412
+ around the dashed y = x line, with R² and RMSE in the header. Either way the model's weights and train() / eval()
413
+ mode are left as they were:
414
+
415
+ ```python
416
+ from mcode_neural import show_model_metrics
417
+
418
+ show_model_metrics(model, "test")
419
+ show_model_metrics(regressor, "test") # predicted vs actual, R², RMSE
420
+ ```
421
+
422
+ - For a regression model the Model chart shows R², RMSE, and predicted vs actual, the trace shows the
423
+ sample's error, and show_largest_errors() lists the worst predictions:
424
+
425
+ ```python
426
+ from mcode_neural import show_largest_errors
427
+
428
+ show_largest_errors(model, "test", n=20) # a single neuron or a network
429
+ ```
430
+
431
+ - show_training_curves() charts how a model's training went, one chart per metric and one line per phase, with a
432
+ dashed line labeled "best" at the epoch whose weights the model kept. It reads model.curves
433
+ ({"train_loss": [...], "val_acc": [...], ...}) and model.best_epoch, which train_model() records;
434
+ a model trained by another loop sets both itself:
435
+
436
+ ```python
437
+ from mcode_neural import show_training_curves
438
+
439
+ show_training_curves(model) # loss and acc charts; a regressor's curves hold only loss
440
+ ```
441
+
442
+ - data_subset_split() holds out a validation subset of any PyTorch dataset, optionally stratified by class:
443
+
444
+ ```python
445
+ from mcode_neural import data_subset_split
446
+
447
+ train, validate = data_subset_split(mnist_train, validate_split=1/6, seed=0, stratify=True)
448
+ ```
449
+
450
+ - data_dataset_split() makes that split a model's data: the train and val subsets of a PyTorch training dataset, and
451
+ its test dataset as it is, in a "dataset" Data to set as model.data, with the classes that name every label; each
452
+ dataset keeps its own transform:
453
+
454
+ ```python
455
+ from mcode_neural import data_dataset_split
456
+
457
+ digit_classes = {label: (f"C{label}", name) for label, name in enumerate(mnist_test.classes)}
458
+ model.data = data_dataset_split(mnist_train, mnist_test, 1/6, digit_classes, seed=0, stratify=True)
459
+ ```
460
+
461
+ - data_labels() reads every label of a dataset without loading its inputs: from where torchvision keeps them
462
+ (targets, labels, \_labels, y, or GTSRB's \_samples), through the dataset's target_transform, and for a Subset from
463
+ its parent's at its indices. Both splits read their labels with it; only a dataset that keeps no labels is read item
464
+ by item:
465
+
466
+ ```python
467
+ from mcode_neural import data_labels
468
+
469
+ labels = data_labels(gtsrb_train) # 26,640 labels, not one image loaded
470
+ print(labels.bincount()) # the datapoints of each of its 43 classes
471
+ ```
472
+
473
+ - data_frame_split() splits a pandas DataFrame 60 / 20 / 20 into train, val, and test datasets, standardizing the
474
+ columns you name from the train rows only; features keep the order you list them in, and labels take the dtype you
475
+ give. It returns a "frame" Data to set as model.data: the datasets by phase, and the contract that names and scales
476
+ them (the feature names, the classes, and each standardized column's mean and std). data_loaders() makes one
477
+ DataLoader per phase from any dict of datasets:
478
+
479
+ ```python
480
+ from mcode_neural import data_frame_split, data_loaders
481
+
482
+ model.data = data_frame_split(encoded_df, encoded_feature_columns, "Old", torch.int64,
483
+ numerical_feature_columns, abalone_classes, seed=42)
484
+ loaders = data_loaders(model.data.datasets, batch_size=10)
485
+ ```
486
+
487
+ - data_frame_phase() adds new raw rows to the model's data as a named phase, scaled by the model's own contract, so
488
+ every view and predict function takes them by name; rows without the label column are a phase with no labels:
489
+
490
+ ```python
491
+ from mcode_neural import data_frame_phase, predict_classes
492
+
493
+ data_frame_phase(model, "new", new_wines)
494
+ columns, probabilities = predict_classes(model, "new")
495
+ ```
496
+
497
+ - Pass a list of label columns for a multi-label model: the labels are (N, k), one column per name, where a single
498
+ name gives (N,).
499
+
500
+ - train_model() trains any Model on its own data, in batches of batch_size, on the model's own device: each epoch
501
+ trains on "train" and scores every other phase, printed from epoch 0, and the best "val" epoch's weights are kept
502
+ (a classifier's highest accuracy, a regressor's lowest loss). Each batch runs through the Model's own train_step(),
503
+ which makes its loss and steps its optimizer; a network's loss comes from its class (nn.CrossEntropyLoss for a
504
+ classifier, nn.BCEWithLogitsLoss for a binary classifier, whose 0 / 1 labels it casts to float, nn.MSELoss for a
505
+ regressor). A Model of several networks, e.g. a GAN, names its optimizer_keys and takes a dict of one optimizer per
506
+ key, and a dict of one scheduler per key or None. It returns the model, with the curves show_training_curves()
507
+ charts in model.curves and the kept epoch in model.best_epoch. Each run adds a "train" event to model.history: the
508
+ optimizer by its constructor's names (optimizer.lr, and the settings left at their defaults, kept apart), the
509
+ scheduler, num_epochs, batch_size, and the run's results; training again adds another, so every run is kept:
510
+
511
+ ```python
512
+ from mcode_neural import train_model
513
+
514
+ optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
515
+ scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.95) # or None
516
+ model = train_model(model, optimizer, scheduler, num_epochs=50, batch_size=10)
517
+ show_training_curves(model)
518
+ ```
519
+
520
+ - A single neuron trains through the same train_model(), stepped by a NeuronOptimizer that holds its learning rate, on
521
+ the neuron's own loss: the squared error for a regressor, the binary cross-entropy for a sigmoid classifier, and the
522
+ binary cross-entropy of (a + 1) / 2 for a tanh classifier with -1 / +1 labels. batch_size=1 steps once per sample,
523
+ as the course's loops do; a larger batch steps once by the mean of its samples' updates. A NumPy neuron has no GPU,
524
+ so its run records no device or memory:
525
+
526
+ ```python
527
+ from mcode_neural import NeuronOptimizer, train_model
528
+
529
+ model = train_model(model, NeuronOptimizer(lr=0.01), None, num_epochs=10, batch_size=1)
530
+ show_training_curves(model)
531
+ ```
532
+
533
+ - predict_classes() and predict_values() predict for every sample of one phase, a single neuron or a network; a
534
+ network is scored in eval() mode without gradients on its own device, its train() / eval() mode left as it was. A
535
+ classifier returns its output columns (the k-th lowest label is column k) and class probabilities, a regressor its
536
+ values shaped like its targets, all as NumPy arrays. predict_metrics() returns a model's metrics on a labeled phase
537
+ as plain numbers:
538
+
539
+ ```python
540
+ from mcode_neural import predict_classes, predict_metrics, predict_values
541
+
542
+ columns, probabilities = predict_classes(model, "test") # (N,) columns and (N, k) probabilities
543
+ values = predict_values(regressor, "test") # (N,) for one target, (N, k) for k
544
+ predict_metrics(model, "test") # {'accuracy': 0.9615, 'auc': 0.9912}
545
+ predict_metrics(regressor, "test") # {'mae': ..., 'rmse': ..., 'r2': ...}
546
+ predict_metrics(autoencoder, "test") # {'mse': ..., 'rmse': ...}
547
+ ```
548
+
549
+ - model_test() returns the same metrics and keeps them in the model's latest training, so its history says how the
550
+ weights it kept scored; testing the same phase again replaces them:
551
+
552
+ ```python
553
+ from mcode_neural import model_test
554
+
555
+ model_test(model, "test") # {'accuracy': 0.9615, 'auc': 0.9912}, kept
556
+ ```
557
+
558
+ - show_model_comparison() shows several models' metrics side by side, one row per model, each scored on the named
559
+ phase of its own data; each row starts with the model's layers (a single neuron is 1) and hidden sizes:
560
+
561
+ ```python
562
+ from mcode_neural import show_model_comparison
563
+
564
+ show_model_comparison({"neuron": neuron, "2 layers": network}, "test")
565
+ ```
566
+
567
+ - For autoencoders of images, pass **n** to also chart the phase's first n samples, each beside every model's
568
+ reconstruction, one column per model:
569
+
570
+ ```python
571
+ show_model_comparison({"linear": model, "tanh": tanh_model}, "test", n=6)
572
+ ```
573
+
574
+ - model_save() saves a model with what it knows about itself: its class, weights, data contract (and how its data was
575
+ split), and history (not its datasets). model_load() builds it again, ready to score new rows, and adds a "load"
576
+ event to its history; a file saved by a pre-release build that kept no history loads with a history built from
577
+ what it held:
578
+
579
+ ```python
580
+ from mcode_neural import model_load, model_save
581
+
582
+ model_save(model, "wine_neuron.pt")
583
+ model = model_load("wine_neuron.pt")
584
+ data_frame_phase(model, "new", new_wines)
585
+ ```
586
+
587
+ - show_model_history() shows how a model came to be, oldest first, one block of rows per event: "create" (its
588
+ arguments and seed, a default marked "(default)", and the mcode-neural and torch versions), "data"
589
+ (how it was split, and the samples per phase),
590
+ each "train" (the trainer's settings, a default marked "(default)", and its best epoch, val score, seconds, and
591
+ tests), and "load". Each event's time reads as a local 12-hour clock, e.g. "2026-10-31 8:16:52 AM".
592
+ Pass **seed** to any model to build the same starting weights again:
593
+
594
+ ```python
595
+ from mcode_neural import ClassificationModel, show_model_history
596
+
597
+ model = ClassificationModel(11, (16, 8), 2, dropout=0.25, seed=7) # the same weights for seed=7
598
+ show_model_history(model)
599
+ ```
600
+
601
+ - The **classes** keys are the labels in Y: {0: ..., 1: ...} for the sigmoid model, {-1: ..., 1: ...}
602
+ for the tanh model; the lower label is the class below the threshold.
603
+
604
+ - Pass **n** to show_misclassified() to list only the n most confident misses, as in .head(n);
605
+ leave it out to list every miss.
606
+
607
+ ## Development
608
+
609
+ ### Installing
610
+
611
+ - Use "pip install" to load the package...
612
+
613
+ ```
614
+ pip install mcode-neural
615
+ ```
616
+
617
+ - To use local modifications to the package (the Python equivalent of 'npm link')...
618
+ - From the **mcode-neural** package directory, with your project's virtual environment active...
619
+
620
+ ```
621
+ pip install -e .[dev]
622
+ ```
623
+
624
+ ...edits to the source are used immediately, restart your Jupyter kernel to pick them up.
625
+
626
+ ### Testing
627
+
628
+ - From the **mcode-neural** package directory...
629
+
630
+ ```
631
+ pytest
632
+ ```
633
+
634
+ ## Included Functions
635
+
636
+ | Function | Description |
637
+ | ------------------------ | ----------------------------------------------------------------------------------- |
638
+ | data_classes() | Returns a classifier's classes, a color and name per label, for any class count. |
639
+ | data_dataset_split() | Splits a PyTorch training dataset, beside its test set, into a model's data. |
640
+ | data_frame_phase() | Adds new raw rows to a model's data as a named phase, scaled by its contract. |
641
+ | data_frame_split() | Splits a DataFrame into a model's train, val, and test data, standardized. |
642
+ | data_labels() | Reads every label of a dataset, or a subset of one, without loading its inputs. |
643
+ | data_loaders() | Makes one DataLoader per phase from a dict of datasets. |
644
+ | data_subset_split() | Splits a PyTorch dataset into train and validate subsets, optionally stratified. |
645
+ | model_device() | Returns the device to train on, CUDA or the CPU, its GPU memory capped; prints it. |
646
+ | model_freeze() | Freezes a network, or named parts of it, for transfer learning; prints what trains. |
647
+ | model_load() | Builds a saved model again; a wrapped Model takes its networks by name. |
648
+ | model_replace_output() | Replaces a network's output layer, its last Linear, with one of new out_features. |
649
+ | model_save() | Saves a model with its class, weights, data contract, and history. |
650
+ | model_test() | Scores a trained model on a phase, and keeps the metrics in its history. |
651
+ | model_unfreeze() | Unfreezes a network, or named parts of it, to fine-tune; prints what trains. |
652
+ | predict_classes() | Predicts a classifier's columns and class probabilities for every sample. |
653
+ | predict_metrics() | Returns a model's metrics on a phase: accuracy, AUC; MAE, RMSE, R²; or MSE, RMSE. |
654
+ | predict_values() | Predicts a regressor's value(s) for every sample, shaped like its targets. |
655
+ | show_2d_scores() | Charts a single neuron classifier's score z by class, either side of z = 0. |
656
+ | show_3d_latent_space() | Charts the same latent codes in 3D: a latent size of 3 as it is, or on 3 PCs. |
657
+ | show_3d_results() | Charts a single neuron classifier's samples in 3D around its decision plane. |
658
+ | show_3d_slider() | Spins a 3D chart, results or latent space by kind, with "-" / "+" stepped sliders. |
659
+ | show_confusion_matrix() | Charts a classifier's counts and rates by true and predicted class. |
660
+ | show_largest_errors() | Lists the n samples a regressor predicts worst, largest error first. |
661
+ | show_latent_space() | Charts an autoencoder's latent code of each sample, colored by its class or value. |
662
+ | show_layer_internals() | Shows one Layer: one row per Neuron, and a bar of each neuron's mean activation. |
663
+ | show_layer_trace() | Traces one sample through every Layer of a model: its z and range of a per Layer. |
664
+ | show_misclassified() | Lists every sample a classifier gets wrong, most confident first. |
665
+ | show_mode() | Sets the mode every later view draws in: "dark", the default, or "light". |
666
+ | show_model() | Draws a model as a network diagram; a click on any part opens its view below it. |
667
+ | show_model_comparison() | Shows several models' metrics side by side, one row per model. |
668
+ | show_model_history() | Shows how a model came to be: each event's settings and results, oldest first. |
669
+ | show_model_internals() | Shows a model as its Layers: one row and a mean \|w\| bar per Layer. |
670
+ | show_model_metrics() | Charts a classifier's ROC curve and confusion matrix, or a regressor's fit. |
671
+ | show_model_trace() | Shows a model's internals: a neuron's weights, or a network's PyTorch modules. |
672
+ | show_network_internals() | Shows one Network of a model: its source and shapes, one row and a bar per Layer. |
673
+ | show_neuron_internals() | Shows one Neuron: its weights (bars or grids), its a by class, and a sample slider. |
674
+ | show_neuron_slider() | Browses a single neuron model's trace sample by sample with a slider. |
675
+ | show_neuron_trace() | Traces one sample through a single neuron model, charting each term of its score z. |
676
+ | show_reconstructions() | Charts the first n samples beside an autoencoder's reconstruction of each. |
677
+ | show_samples() | Shows a model's first n samples, of each class if named: images, or DataFrame rows. |
678
+ | show_tensor() | Prints a tensor's Name, Shape, Datatype, Device, and Values, aligned. |
679
+ | show_training_curves() | Charts each training metric by epoch, one line per phase (train, val, test). |
680
+ | train_model() | Trains any Model, scoring each phase per epoch; keeps its best val weights. |
681
+ | train_probe() | Measures a network's peak GPU memory at each batch size; leaves it as it was. |
682
+
683
+ ## Included Classes
684
+
685
+ | Class | Description |
686
+ | ----------------------------- | ----------------------------------------------------------------------------------- |
687
+ | Model | Self-describing model: its kind, data, history, and networks (model.network). |
688
+ | Data | A model's datasets by phase, feature names (data.feature), classes, scaling, split. |
689
+ | Network | One network of a model: its name, what it reads (source), shapes, and its Layers. |
690
+ | Layer | A model's weight module and the modules after it; its neurons, inputs, z, and a. |
691
+ | Neuron | One output of a Layer: its weights (neuron.weight), bias, z, and a. |
692
+ | NeuronModel | Abstract single neuron; subclasses set activation_name, a key of NEURON_KINDS. |
693
+ | RegressorNeuronModel | Single neuron regressor, a linear activation, for any real target. |
694
+ | LogisticClassifierNeuronModel | Single neuron logistic classifier, a sigmoid activation, for 0 / 1 labels. |
695
+ | BipolarClassifierNeuronModel | Single neuron bipolar classifier, a tanh activation, for -1 / +1 labels. |
696
+ | NeuronOptimizer | A single neuron's optimizer for train_model(): its learning rate, lr. |
697
+ | FeedforwardModel | Abstract network: conv stages (optional), ReLU hidden layers, a Linear output. |
698
+ | ClassificationModel | Multi-layer network for classification, one logit per class. |
699
+ | BinaryClassificationModel | Multi-layer network for binary or multi-label classification, one logit per label. |
700
+ | RegressionModel | Multi-layer network for regression, one value per target. |
701
+ | AutoencoderModel | Autoencoder: a ReLU encoder (conv stages optional) to a latent code, mirrored back. |
702
+ | ExternalClassificationModel | A classifier built elsewhere, e.g. torchvision's VGG11, as a ClassificationModel. |
703
+ | ExternalRegressionModel | A regressor built elsewhere, as a RegressionModel. |
704
+ | ExternalAutoencoderModel | An encoder and decoder built elsewhere, as an AutoencoderModel. |
705
+
706
+ | Table | Description |
707
+ | ------------------- | ---------------------------------------------------------------------------------------- |
708
+ | MODEL_KINDS | Each model kind's metrics and best-epoch rule: classification, regression, autoencoder. |
709
+ | NETWORK_KINDS | How each network kind reads layers, weights, z, inputs, outputs, device: torch, numpy. |
710
+ | LAYER_KINDS | Each layer kind's weight module, fields, and neuron axis: linear, conv, deconv. |
711
+ | NEURON_KINDS | Each neuron kind's label, module, range, activation, slope, curve: linear ... softmax. |
712
+ | DATA_KINDS | What each kind of data holds: "frame" names and scales its features, "dataset" does not. |
713
+ | AUTOENCODER_OUTPUTS | The neuron kinds an autoencoder's output may take: linear, sigmoid, tanh. |
714
+
715
+ ## Help
716
+
717
+ Contact Timothy McGuire, support@mcode.com.
718
+
719
+ ## Terminology
720
+
721
+ | Word or Acronym | Description/Definition |
722
+ | --------------- | -------------------------------------------------------------------------------------------- |
723
+ | **PyPI** | The Python Package Index, the Python equivalent of the npm registry. |
724
+ | **pip** | The Python package installer, the Python equivalent of 'npm install'. |
725
+ | **PyTorch** | An optimized tensor library for deep learning using GPUs and CPUs. |
726
+ | **Tensor** | A multi-dimensional array, like a NumPy 'ndarray', that can run on GPUs and track gradients. |
727
+ | **CUDA** | NVIDIA's GPU computing platform; a CUDA build of PyTorch runs tensors on an NVIDIA GPU. |
728
+ | **Jupyter** | An interactive Notebook environment mixing code, output, and Markdown documentation. |
729
+
730
+ ## Authors
731
+
732
+ Contributor's names and contact info...
733
+
734
+ - Timothy McGuire [@TimothyMcGuire](https://x.com/TimothyMcGuire) - Founder, President-CEO of MicroCODE, Inc. a software and controls engineering company in Detroit, Michigan USA.
735
+
736
+ ## Version History
737
+
738
+ - v0.5.0
739
+ - The first release published to PyPI; earlier version numbers were pre-release builds, never published.
740
+ - 'show_tensor()': a PyTorch tensor printed as a readable table, in Dark Mode or Light Mode ('show_mode()'), in the bundled Roboto Mono and MicroCODE Mono fonts.
741
+ - Self-describing models: NumPy single neurons ('RegressorNeuronModel', 'LogisticClassifierNeuronModel', 'BipolarClassifierNeuronModel') and PyTorch networks ('FeedforwardModel', 'ClassificationModel', 'BinaryClassificationModel', 'RegressionModel', 'AutoencoderModel'), with optional convolution stages; 'ExternalClassificationModel', 'ExternalRegressionModel', and 'ExternalAutoencoderModel' wrap a network built elsewhere, e.g. torchvision's VGG11. Every model knows its kind, its data, and its history, and reads as Networks of Layers of Neurons.
742
+ - Data, training, and prediction: 'data_frame_split()', 'data_dataset_split()', 'data_loaders()', 'data_classes()', and 'data_labels()'; 'train_model()', 'train_probe()', and 'model_device()'; 'predict_classes()', 'predict_values()', 'predict_metrics()', and 'model_test()'; 'model_save()' and 'model_load()'; 'model_freeze()', 'model_unfreeze()', and 'model_replace_output()' for transfer learning.
743
+ - Views: 'show_model()', a clickable network diagram; the internals views from a model down to one neuron; traces, metrics, confusion matrices, training curves, latent spaces, reconstructions, samples, comparisons, and a model's history.
744
+
745
+ ## Future Development
746
+
747
+ - v0.6.\*
748
+ - Any additional core code we develop for general PyTorch and Neural Network coding, debug, and support.
749
+
750
+ ## License
751
+
752
+ This project is licensed under the MIT License - see the LICENSE file for details
753
+
754
+ The bundled fonts are licensed under the SIL Open Font License, Version 1.1 - see the 'OFL.txt' beside each font in
755
+ 'mcode_neural/fonts/'. Roboto Mono is © 2015 The Roboto Mono Project Authors; MicroCODE Mono is © 2025 MicroCODE, Inc.,
756
+ with "MicroCODE Mono" as a Reserved Font Name, and its 'OFL.txt' carries the MIT notice of ProFont, from which it is
757
+ derived.
758
+
759
+ ## MicroCODE Mantra
760
+
761
+ MicroCODE, Inc. was founded in 1987 as a controls engineering and software development company.<br>
762
+ We specialize in manufacturing and quality control applications that must run 24x7x365 for years at a time.
763
+
764
+ Our slogan, distilled from over three decades of developing, testing, installing, and supporting 24x7x365
765
+ manufacturing applications, is..
766
+
767
+ **"Hail CAESAR"**℠ — _"Code like a Machine: Consistently and Explicitly, Simply and for Readability."_