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.
- mcode_neural/__init__.py +183 -0
- mcode_neural/data.py +773 -0
- mcode_neural/fonts/MicroCODE-Mono/MicroCODE-Mono.ttf +0 -0
- mcode_neural/fonts/MicroCODE-Mono/OFL.txt +128 -0
- mcode_neural/fonts/RobotoMono/OFL.txt +93 -0
- mcode_neural/fonts/RobotoMono/RobotoMono-Bold.ttf +0 -0
- mcode_neural/fonts/RobotoMono/RobotoMono-Bold.woff2 +0 -0
- mcode_neural/fonts/RobotoMono/RobotoMono-Regular.ttf +0 -0
- mcode_neural/fonts/RobotoMono/RobotoMono-Regular.woff2 +0 -0
- mcode_neural/layer.py +589 -0
- mcode_neural/model.py +2092 -0
- mcode_neural/network.py +447 -0
- mcode_neural/neuron.py +233 -0
- mcode_neural/predict.py +360 -0
- mcode_neural/show.py +6076 -0
- mcode_neural/train.py +756 -0
- mcode_neural-0.5.0.dist-info/METADATA +767 -0
- mcode_neural-0.5.0.dist-info/RECORD +22 -0
- mcode_neural-0.5.0.dist-info/WHEEL +4 -0
- mcode_neural-0.5.0.dist-info/licenses/LICENSE +21 -0
- mcode_neural-0.5.0.dist-info/licenses/src/mcode_neural/fonts/MicroCODE-Mono/OFL.txt +128 -0
- mcode_neural-0.5.0.dist-info/licenses/src/mcode_neural/fonts/RobotoMono/OFL.txt +93 -0
mcode_neural/predict.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# region F I L E
|
|
2
|
+
# <copyright file="mcode-neural/src/mcode_neural/predict.py" company="MicroCODE Incorporated">Copyright © 2026 MicroCODE, Inc. Troy, MI</copyright><author>Timothy J. McGuire</author>
|
|
3
|
+
# region M O D U L E
|
|
4
|
+
# region D O C U M E N T A T I O N
|
|
5
|
+
"""
|
|
6
|
+
Project: MicroCODE Python Packages
|
|
7
|
+
Customer: Internal + MIT xPRO Course
|
|
8
|
+
@module 'mcode_neural.predict'
|
|
9
|
+
@memberof mcode_neural
|
|
10
|
+
@created October 2026
|
|
11
|
+
@author Timothy McGuire, MicroCODE, Inc.
|
|
12
|
+
@description >
|
|
13
|
+
MicroCODE Shared Prediction Library
|
|
14
|
+
|
|
15
|
+
LICENSE:
|
|
16
|
+
--------
|
|
17
|
+
MIT License: MicroCODE.mcode-neural
|
|
18
|
+
|
|
19
|
+
Copyright (c) 2026 Timothy McGuire, MicroCODE, Inc.
|
|
20
|
+
|
|
21
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
22
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
23
|
+
in the Software without restriction, including without limitation the rights
|
|
24
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
25
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
26
|
+
furnished to do so, subject to the following conditions:
|
|
27
|
+
|
|
28
|
+
The above copyright notice and this permission notice shall be included in all
|
|
29
|
+
copies or substantial portions of the Software.
|
|
30
|
+
|
|
31
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
32
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
33
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
34
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
35
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
36
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
37
|
+
SOFTWARE.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
DESCRIPTION:
|
|
41
|
+
------------
|
|
42
|
+
This module implements MicroCODE's predictions from our trained models, a
|
|
43
|
+
single neuron or a network: a classifier's labels and class probabilities, a
|
|
44
|
+
regressor's values, and the metrics of either one or of an autoencoder, each scored on one phase of the
|
|
45
|
+
model's own data, without changing the model.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
REFERENCES:
|
|
49
|
+
-----------
|
|
50
|
+
1. MIT xPRO Course: AI and Deep Learning: Neural Network Applications
|
|
51
|
+
|
|
52
|
+
2. PyTorch -- torch.no_grad and torch.nn.Module.eval
|
|
53
|
+
https://pytorch.org/docs/stable/generated/torch.no_grad.html
|
|
54
|
+
https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.eval
|
|
55
|
+
|
|
56
|
+
3. Fawcett, T. -- An introduction to ROC analysis, Pattern Recognition Letters 27 (2006)
|
|
57
|
+
https://doi.org/10.1016/j.patrec.2005.10.010
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
# endregion
|
|
61
|
+
|
|
62
|
+
# region I M P O R T S
|
|
63
|
+
|
|
64
|
+
import numpy as np
|
|
65
|
+
|
|
66
|
+
import torch
|
|
67
|
+
from torch.utils.data import DataLoader
|
|
68
|
+
|
|
69
|
+
from .model import _article, _check_model, _out_of_memory, _score_batch_size
|
|
70
|
+
|
|
71
|
+
# endregion
|
|
72
|
+
|
|
73
|
+
# region P R I V A T E F U N C T I O N S
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _check_kind(model, kind, name):
|
|
77
|
+
"""
|
|
78
|
+
Raises a TypeError unless 'model' is a mcode-neural Model, and a ValueError naming 'name' unless it is of 'kind'.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
model: the caller's model.
|
|
82
|
+
kind: the kind the caller needs, "classification", "regression", or "autoencoder".
|
|
83
|
+
name: the calling function, e.g. "predict_classes".
|
|
84
|
+
"""
|
|
85
|
+
_check_model(model)
|
|
86
|
+
if model.kind != kind:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"'{name}()' needs {_article(kind)} model; 'model' is {_article(model.kind)} model"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _phase_scores(model, phase, score):
|
|
93
|
+
"""
|
|
94
|
+
Returns 'score(X)' for every sample of one phase, scored batch by batch, and the phase's labels.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
model: a Model, its data loaded.
|
|
98
|
+
phase: a phase of 'model.data', e.g. "test".
|
|
99
|
+
score: score(X) -> a NumPy array, or a tuple of them, one row per sample of the batch X,
|
|
100
|
+
a NumPy array, e.g. 'model.classify'.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
(scores, labels): scores as 'score()' returns them, each array gathered over every batch
|
|
104
|
+
in phase order; labels the phase's labels as a NumPy array, or None for a phase with none.
|
|
105
|
+
|
|
106
|
+
Note:
|
|
107
|
+
The phase is read through a loader of '_score_batch_size(model)' samples, so neither
|
|
108
|
+
system RAM nor the GPU holds it whole. A GPU out-of-memory error names the phase and
|
|
109
|
+
the batch size.
|
|
110
|
+
"""
|
|
111
|
+
dataset = model.data._dataset(phase)
|
|
112
|
+
batch_size = _score_batch_size(model)
|
|
113
|
+
parts, labels = [], []
|
|
114
|
+
try:
|
|
115
|
+
for batch in DataLoader(dataset, batch_size=batch_size):
|
|
116
|
+
scores = score(batch[0].numpy())
|
|
117
|
+
parts.append(scores if isinstance(scores, tuple) else (scores,))
|
|
118
|
+
if len(batch) == 2:
|
|
119
|
+
labels.append(batch[1].numpy())
|
|
120
|
+
except torch.OutOfMemoryError as error:
|
|
121
|
+
device = next(model.parameters()).device
|
|
122
|
+
raise _out_of_memory(phase, batch_size, device) from error
|
|
123
|
+
gathered = tuple(np.concatenate(arrays) for arrays in zip(*parts))
|
|
124
|
+
if not isinstance(scores, tuple):
|
|
125
|
+
gathered = gathered[0]
|
|
126
|
+
return gathered, (np.concatenate(labels) if labels else None)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _roc_curve(is_up, scores):
|
|
130
|
+
"""
|
|
131
|
+
Returns a binary classifier's ROC curve: its false and true positive rates at every threshold.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
is_up: every sample's truth as a bool array, True for the higher (positive) class.
|
|
135
|
+
scores: every sample's probability of the higher class.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
(fpr, tpr) arrays from (0, 0) to (1, 1), one point per distinct score, highest first.
|
|
139
|
+
|
|
140
|
+
Note:
|
|
141
|
+
Fawcett's algorithm (REFERENCES 3): samples tied on a score pass the threshold together,
|
|
142
|
+
so a tie of positives and negatives steps diagonally.
|
|
143
|
+
"""
|
|
144
|
+
order = np.argsort(-scores, kind='stable')
|
|
145
|
+
ranked, is_up = scores[order], is_up[order]
|
|
146
|
+
# the last sample of each run of equal scores, where the threshold lets the whole run pass
|
|
147
|
+
last = np.r_[np.flatnonzero(np.diff(ranked)), len(ranked) - 1]
|
|
148
|
+
true_positives = np.cumsum(is_up)[last]
|
|
149
|
+
false_positives = np.cumsum(~is_up)[last]
|
|
150
|
+
fpr = np.r_[0, false_positives / false_positives[-1]]
|
|
151
|
+
tpr = np.r_[0, true_positives / true_positives[-1]]
|
|
152
|
+
return fpr, tpr
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _fit_scores(prediction, actual):
|
|
156
|
+
"""
|
|
157
|
+
Returns a regressor's fit: its mean absolute error, root mean squared error, and R².
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
prediction: every sample's predicted value.
|
|
161
|
+
actual: every sample's target value, in the same order; not constant.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
{"mae": float, "rmse": float, "r2": float}
|
|
165
|
+
"""
|
|
166
|
+
error = prediction - actual
|
|
167
|
+
squared = error**2
|
|
168
|
+
spread = ((actual - actual.mean()) ** 2).sum()
|
|
169
|
+
return {
|
|
170
|
+
'mae': float(np.abs(error).mean()),
|
|
171
|
+
'rmse': float(np.sqrt(squared.mean())),
|
|
172
|
+
'r2': float(1 - squared.sum() / spread),
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _classifier_scores(model, phase):
|
|
177
|
+
"""
|
|
178
|
+
Returns a classifier's accuracy, and its ROC AUC when it has two classes.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
model: a trained classifier with one label per sample.
|
|
182
|
+
phase: a labeled phase of 'model.data'.
|
|
183
|
+
"""
|
|
184
|
+
labels = sorted(model.data.classes)
|
|
185
|
+
(columns, probabilities), Y = _phase_scores(model, phase, model.classify)
|
|
186
|
+
actual = np.ravel(Y)
|
|
187
|
+
# k sigmoid outputs are k labels per sample, not one class to score
|
|
188
|
+
if columns.ndim != 1:
|
|
189
|
+
raise ValueError(
|
|
190
|
+
f"'model' has {columns.shape[1]} outputs per sample; its metrics need one label per sample"
|
|
191
|
+
)
|
|
192
|
+
if probabilities.shape[1] != len(labels):
|
|
193
|
+
raise ValueError(
|
|
194
|
+
f"'model' outputs {probabilities.shape[1]} classes; 'model.data.classes' has {len(labels)}"
|
|
195
|
+
)
|
|
196
|
+
scores = {'accuracy': float((np.asarray(labels)[columns] == actual).mean())}
|
|
197
|
+
# a ROC curve ranks one score, the higher class's probability, so only two classes have an AUC
|
|
198
|
+
if len(labels) == 2:
|
|
199
|
+
is_up = actual == labels[1]
|
|
200
|
+
if is_up.all() or not is_up.any():
|
|
201
|
+
raise ValueError(
|
|
202
|
+
"the phase needs both classes for a ROC curve's AUC; it holds only one"
|
|
203
|
+
)
|
|
204
|
+
fpr, tpr = _roc_curve(is_up, probabilities[:, 1])
|
|
205
|
+
scores['auc'] = float(np.trapezoid(tpr, fpr))
|
|
206
|
+
return scores
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _regressor_scores(model, phase):
|
|
210
|
+
"""
|
|
211
|
+
Returns a one-target regressor's MAE, RMSE, and R².
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
model: a trained regressor with one target.
|
|
215
|
+
phase: a labeled phase of 'model.data'.
|
|
216
|
+
"""
|
|
217
|
+
prediction, Y = _phase_scores(model, phase, model.regress)
|
|
218
|
+
actual = np.ravel(Y)
|
|
219
|
+
if prediction.ndim != 1:
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"'model' has {prediction.shape[1]} outputs; its metrics need one target"
|
|
222
|
+
)
|
|
223
|
+
if np.ptp(actual) == 0:
|
|
224
|
+
raise ValueError("the phase's targets are constant, so R² is undefined")
|
|
225
|
+
return _fit_scores(prediction, actual)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _reconstruction_errors(model, X):
|
|
229
|
+
"""
|
|
230
|
+
Returns an autoencoder's reconstruction of every sample, and each sample's mean squared error.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
model: a trained autoencoder.
|
|
234
|
+
X: the input samples, (N, ...).
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
(rebuilt, errors): rebuilt (N, in_features), each sample encoded and decoded; errors (N,),
|
|
238
|
+
each sample's mean of (rebuilt - input)² over its values.
|
|
239
|
+
"""
|
|
240
|
+
rebuilt = model.reconstruct(X)
|
|
241
|
+
errors = ((rebuilt - X.reshape(len(X), -1)) ** 2).mean(axis=1)
|
|
242
|
+
return rebuilt, errors
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _autoencoder_scores(model, phase):
|
|
246
|
+
"""
|
|
247
|
+
Returns an autoencoder's mean squared error over every value of every sample, and its root.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
model: a trained autoencoder.
|
|
251
|
+
phase: a labeled phase of 'model.data'; its labels are not read, an autoencoder is
|
|
252
|
+
scored against its input.
|
|
253
|
+
"""
|
|
254
|
+
errors, _ = _phase_scores(
|
|
255
|
+
model, phase, lambda X: _reconstruction_errors(model, X)[1]
|
|
256
|
+
)
|
|
257
|
+
# every sample holds the same number of values, so the mean of the means is nn.MSELoss
|
|
258
|
+
mse = float(errors.mean())
|
|
259
|
+
return {'mse': mse, 'rmse': float(np.sqrt(mse))}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# endregion
|
|
263
|
+
|
|
264
|
+
# region P U B L I C F U N C T I O N S
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def predict_classes(model, phase):
|
|
268
|
+
"""
|
|
269
|
+
Returns a classifier's predicted column and class probabilities for every sample of one phase.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
model: a trained classifier, a single neuron or a network, e.g.
|
|
273
|
+
LogisticClassifierNeuronModel or ClassificationModel, its data loaded.
|
|
274
|
+
phase: the phase of 'model.data' to predict, e.g. "test", or one added by
|
|
275
|
+
'data_frame_phase()'; its labels, if any, are not read.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
(columns, probabilities) as NumPy arrays, columns int64 and probabilities float64:
|
|
279
|
+
one label per sample: (N,) columns and (N, k) probabilities, each row summing to 1;
|
|
280
|
+
a binary network's k labels: (N, k) columns, one 0 / 1 per output, and (N, k)
|
|
281
|
+
probabilities, each output's probability of 1.
|
|
282
|
+
|
|
283
|
+
Note:
|
|
284
|
+
A column is a position in the sorted labels of 'model.data.classes', from 0, as the
|
|
285
|
+
model was trained; 'sorted(model.data.classes)[column]' is its label. A network is
|
|
286
|
+
scored in eval() mode, without gradients, on its own device; a neuron does not touch
|
|
287
|
+
its cached z, a, grad_w, or grad_w_0. Neither one's weights or mode change.
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
>>> columns, probabilities = predict_classes(model, "test")
|
|
291
|
+
"""
|
|
292
|
+
_check_kind(model, 'classification', 'predict_classes')
|
|
293
|
+
(columns, probabilities), _ = _phase_scores(model, phase, model.classify)
|
|
294
|
+
return columns, probabilities
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def predict_values(model, phase):
|
|
298
|
+
"""
|
|
299
|
+
Returns a regressor's predicted value(s) for every sample of one phase.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
model: a trained regressor, a single neuron or a network, e.g. RegressorNeuronModel
|
|
303
|
+
or RegressionModel, its data loaded.
|
|
304
|
+
phase: the phase of 'model.data' to predict, e.g. "test", or one added by
|
|
305
|
+
'data_frame_phase()'; its targets, if any, are not read.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
a float64 NumPy array: (N,) for one target, as the targets are, or (N, k) for k targets.
|
|
309
|
+
|
|
310
|
+
Note:
|
|
311
|
+
A network is scored in eval() mode, without gradients, on its own device; a neuron
|
|
312
|
+
does not touch its cached z, a, grad_w, or grad_w_0. Neither one's weights or mode change.
|
|
313
|
+
|
|
314
|
+
Example:
|
|
315
|
+
>>> values = predict_values(model, "test")
|
|
316
|
+
"""
|
|
317
|
+
_check_kind(model, 'regression', 'predict_values')
|
|
318
|
+
values, _ = _phase_scores(model, phase, model.regress)
|
|
319
|
+
return values
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def predict_metrics(model, phase):
|
|
323
|
+
"""
|
|
324
|
+
Returns a trained model's metrics on one labeled phase of its data, as plain numbers.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
model: a trained model, a single neuron or a network, its data loaded.
|
|
328
|
+
phase: a phase of 'model.data' that has labels, e.g. "test".
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
a classifier's {"accuracy": float}, plus "auc", its ROC AUC, when it has two classes;
|
|
332
|
+
a one-target regressor's {"mae": float, "rmse": float, "r2": float}; an autoencoder's
|
|
333
|
+
{"mse": float, "rmse": float}.
|
|
334
|
+
|
|
335
|
+
Note:
|
|
336
|
+
The same model is scored as 'predict_classes()' or 'predict_values()' score it; the AUC
|
|
337
|
+
ranks each sample's probability of the higher label, as 'show_model_metrics()' charts it.
|
|
338
|
+
An autoencoder's mse is over every value of every reconstructed sample, the loss
|
|
339
|
+
'train_model()' prints for the phase; its labels are required but not read.
|
|
340
|
+
|
|
341
|
+
Example:
|
|
342
|
+
>>> predict_metrics(model, "test")
|
|
343
|
+
{'accuracy': 0.9615, 'auc': 0.9912}
|
|
344
|
+
"""
|
|
345
|
+
scorers = {
|
|
346
|
+
'classification': _classifier_scores,
|
|
347
|
+
'regression': _regressor_scores,
|
|
348
|
+
'autoencoder': _autoencoder_scores,
|
|
349
|
+
}
|
|
350
|
+
_check_model(model)
|
|
351
|
+
if not model.data._is_labeled(phase):
|
|
352
|
+
raise ValueError(
|
|
353
|
+
f"'model.data' phase {phase!r} has no labels to score the model against"
|
|
354
|
+
)
|
|
355
|
+
return scorers[model.kind](model, phase)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# endregion
|
|
359
|
+
# endregion
|
|
360
|
+
# endregion
|