neuralplex 0.3.2__tar.gz → 0.4.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.
- {neuralplex-0.3.2 → neuralplex-0.4.0}/PKG-INFO +8 -10
- {neuralplex-0.3.2 → neuralplex-0.4.0}/README.md +7 -9
- {neuralplex-0.3.2 → neuralplex-0.4.0}/docs/README.md +2 -3
- {neuralplex-0.3.2 → neuralplex-0.4.0}/docs/generate_images.py +11 -13
- neuralplex-0.4.0/images/digits_results.png +0 -0
- neuralplex-0.4.0/src/neuralplex/__about__.py +1 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/src/neuralplex/network.py +0 -14
- {neuralplex-0.3.2 → neuralplex-0.4.0}/tests/test.py +32 -27
- neuralplex-0.3.2/images/digits_confusion_matrix.png +0 -0
- neuralplex-0.3.2/images/digits_predictions.png +0 -0
- neuralplex-0.3.2/src/neuralplex/__about__.py +0 -1
- {neuralplex-0.3.2 → neuralplex-0.4.0}/.gitignore +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/LICENSE +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/docs/pyproject.toml +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_digits_experiment/README.md +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_digits_experiment/main.ipynb +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_digits_experiment/pyproject.toml +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_digits_experiment/visualizations.py +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_nibble_challenge/README.md +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_nibble_challenge/main.ipynb +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_nibble_challenge/pyproject.toml +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_nibble_challenge/visualizations.py +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/images/nibble_multiweight.png +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/pyproject.toml +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/src/neuralplex/__init__.py +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/src/neuralplex/layer.py +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/src/neuralplex/neuron.py +0 -0
- {neuralplex-0.3.2 → neuralplex-0.4.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: neuralplex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A neural network implementation built as an explicit graph of neurons and weighted connections.
|
|
5
5
|
Project-URL: Homepage, https://github.com/adamjpatterson/neuralplex
|
|
6
6
|
Project-URL: Issues, https://github.com/adamjpatterson/neuralplex/issues
|
|
@@ -20,7 +20,7 @@ A neural network implementation built as an explicit graph of neurons and weight
|
|
|
20
20
|
|
|
21
21
|
## Introduction
|
|
22
22
|
|
|
23
|
-
**Neural-pleX** represents individual neurons and their connections explicitly
|
|
23
|
+
**Neural-pleX** represents individual neurons and their connections explicitly. Backpropagation is done using explicitly computed gradients. The object-oriented API consists of Network, Layer, and Neuron constructors. The networks can be easily [visualized](#visualizations-of-the-network-before-and-after-training) using a visualization library.
|
|
24
24
|
|
|
25
25
|
## Table of contents
|
|
26
26
|
|
|
@@ -113,7 +113,7 @@ print("Without the connection:", after)
|
|
|
113
113
|
source.connectRHS(receiver, weight=weight)
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
Try several inputs: a connection's effect can depend on the input.
|
|
116
|
+
Try several inputs: a connection's effect can depend on the input.
|
|
117
117
|
|
|
118
118
|
You can also change an existing weight through `receiver.weights[source]`. Use the connection methods to add or remove edges so incoming weights and outgoing connections stay consistent.
|
|
119
119
|
|
|
@@ -139,7 +139,7 @@ pip install .
|
|
|
139
139
|
|
|
140
140
|
Open [the nibble challenge notebook](https://github.com/adamjpatterson/neuralplex/blob/main/examples/the_nibble_challenge/main.ipynb). It introduces all sixteen four-bit inputs, constructs a 4 → 8 → 1 network, compares initial and trained predictions, and lets you change an input to inspect weights and activations. With seed 42, a step of 0.01, and 10,000 training examples, the displayed R² rounds to 1.000000000.
|
|
141
141
|
|
|
142
|
-
Training and evaluation both use all sixteen nibbles.
|
|
142
|
+
Training and evaluation both use all sixteen nibbles; this experiment provides no evidence of generalization. The notebook imports `visualize_network` from [its visualization module](https://github.com/adamjpatterson/neuralplex/blob/main/examples/the_nibble_challenge/visualizations.py).
|
|
143
143
|
|
|
144
144
|
##### Visualizations of the network before and after training
|
|
145
145
|
|
|
@@ -149,7 +149,7 @@ Training and evaluation both use all sixteen nibbles.
|
|
|
149
149
|
- Node area above a small visibility floor is proportional to absolute activation. Green nodes are inputs, red nodes are hidden, and yellow is the output.
|
|
150
150
|
- Both panels use the same input, positions, and scales. Node size represents activity, not importance, and does not encode the activation's sign.
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
This visualization demonstrates that an irregular-looking network can learn a simple mapping almost perfectly: individual connections can reinforce or cancel one another.
|
|
153
153
|
|
|
154
154
|
### The digits experiment
|
|
155
155
|
|
|
@@ -179,13 +179,11 @@ The notebook imports `plot_predictions` and `plot_confusion_matrix` from [its vi
|
|
|
179
179
|
|
|
180
180
|
The prediction figure shows the first twelve test images. The confusion matrix includes all 450 test images; rows show the true digit and columns show the predicted digit.
|
|
181
181
|
|
|
182
|
-

|
|
182
|
+

|
|
185
183
|
|
|
186
184
|
## Tests
|
|
187
185
|
|
|
188
|
-
The suite in `tests/test.py` checks forward calculations, manual gradients, parameter updates,
|
|
186
|
+
The suite in `tests/test.py` checks forward calculations, manual gradients, parameter updates, connection removal and restoration, and input validation.
|
|
189
187
|
|
|
190
188
|
#### Run all tests.
|
|
191
189
|
|
|
@@ -203,7 +201,7 @@ The [documentation-image project](https://github.com/adamjpatterson/neuralplex/b
|
|
|
203
201
|
python generate_images.py
|
|
204
202
|
```
|
|
205
203
|
|
|
206
|
-
It writes the nibble network image
|
|
204
|
+
It writes the nibble network image and the combined digits predictions and confusion matrix image to `images/`.
|
|
207
205
|
|
|
208
206
|
## Support
|
|
209
207
|
|
|
@@ -6,7 +6,7 @@ A neural network implementation built as an explicit graph of neurons and weight
|
|
|
6
6
|
|
|
7
7
|
## Introduction
|
|
8
8
|
|
|
9
|
-
**Neural-pleX** represents individual neurons and their connections explicitly
|
|
9
|
+
**Neural-pleX** represents individual neurons and their connections explicitly. Backpropagation is done using explicitly computed gradients. The object-oriented API consists of Network, Layer, and Neuron constructors. The networks can be easily [visualized](#visualizations-of-the-network-before-and-after-training) using a visualization library.
|
|
10
10
|
|
|
11
11
|
## Table of contents
|
|
12
12
|
|
|
@@ -99,7 +99,7 @@ print("Without the connection:", after)
|
|
|
99
99
|
source.connectRHS(receiver, weight=weight)
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
Try several inputs: a connection's effect can depend on the input.
|
|
102
|
+
Try several inputs: a connection's effect can depend on the input.
|
|
103
103
|
|
|
104
104
|
You can also change an existing weight through `receiver.weights[source]`. Use the connection methods to add or remove edges so incoming weights and outgoing connections stay consistent.
|
|
105
105
|
|
|
@@ -125,7 +125,7 @@ pip install .
|
|
|
125
125
|
|
|
126
126
|
Open [the nibble challenge notebook](https://github.com/adamjpatterson/neuralplex/blob/main/examples/the_nibble_challenge/main.ipynb). It introduces all sixteen four-bit inputs, constructs a 4 → 8 → 1 network, compares initial and trained predictions, and lets you change an input to inspect weights and activations. With seed 42, a step of 0.01, and 10,000 training examples, the displayed R² rounds to 1.000000000.
|
|
127
127
|
|
|
128
|
-
Training and evaluation both use all sixteen nibbles.
|
|
128
|
+
Training and evaluation both use all sixteen nibbles; this experiment provides no evidence of generalization. The notebook imports `visualize_network` from [its visualization module](https://github.com/adamjpatterson/neuralplex/blob/main/examples/the_nibble_challenge/visualizations.py).
|
|
129
129
|
|
|
130
130
|
##### Visualizations of the network before and after training
|
|
131
131
|
|
|
@@ -135,7 +135,7 @@ Training and evaluation both use all sixteen nibbles.
|
|
|
135
135
|
- Node area above a small visibility floor is proportional to absolute activation. Green nodes are inputs, red nodes are hidden, and yellow is the output.
|
|
136
136
|
- Both panels use the same input, positions, and scales. Node size represents activity, not importance, and does not encode the activation's sign.
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
This visualization demonstrates that an irregular-looking network can learn a simple mapping almost perfectly: individual connections can reinforce or cancel one another.
|
|
139
139
|
|
|
140
140
|
### The digits experiment
|
|
141
141
|
|
|
@@ -165,13 +165,11 @@ The notebook imports `plot_predictions` and `plot_confusion_matrix` from [its vi
|
|
|
165
165
|
|
|
166
166
|
The prediction figure shows the first twelve test images. The confusion matrix includes all 450 test images; rows show the true digit and columns show the predicted digit.
|
|
167
167
|
|
|
168
|
-

|
|
168
|
+

|
|
171
169
|
|
|
172
170
|
## Tests
|
|
173
171
|
|
|
174
|
-
The suite in `tests/test.py` checks forward calculations, manual gradients, parameter updates,
|
|
172
|
+
The suite in `tests/test.py` checks forward calculations, manual gradients, parameter updates, connection removal and restoration, and input validation.
|
|
175
173
|
|
|
176
174
|
#### Run all tests.
|
|
177
175
|
|
|
@@ -189,7 +187,7 @@ The [documentation-image project](https://github.com/adamjpatterson/neuralplex/b
|
|
|
189
187
|
python generate_images.py
|
|
190
188
|
```
|
|
191
189
|
|
|
192
|
-
It writes the nibble network image
|
|
190
|
+
It writes the nibble network image and the combined digits predictions and confusion matrix image to `images/`.
|
|
193
191
|
|
|
194
192
|
## Support
|
|
195
193
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# README image generation
|
|
2
2
|
|
|
3
|
-
This project generates the repository's
|
|
3
|
+
This project generates the repository's two README illustrations.
|
|
4
4
|
|
|
5
5
|
## Setup and run
|
|
6
6
|
|
|
@@ -26,8 +26,7 @@ The generator uses seed **42** with these settings:
|
|
|
26
26
|
The script prints the nibble R² and digits accuracy, and overwrites:
|
|
27
27
|
|
|
28
28
|
- `../images/nibble_multiweight.png`
|
|
29
|
-
- `../images/
|
|
30
|
-
- `../images/digits_confusion_matrix.png`
|
|
29
|
+
- `../images/digits_results.png`
|
|
31
30
|
|
|
32
31
|
Review the regenerated images and update any reported README results when changing these settings.
|
|
33
32
|
|
|
@@ -68,14 +68,14 @@ def visualize_network(before, after, inputs):
|
|
|
68
68
|
return figure
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def plot_predictions(images, labels, predictions=None, title='Sample digits'):
|
|
71
|
+
def plot_predictions(figure, images, labels, predictions=None, title='Sample digits'):
|
|
72
72
|
"""Return a figure of the first twelve supplied images, without selecting successes."""
|
|
73
73
|
count = min(12, len(images))
|
|
74
74
|
if count == 0:
|
|
75
75
|
raise ValueError('Supply at least one image')
|
|
76
76
|
columns = min(4, count)
|
|
77
77
|
rows = (count + columns - 1) // columns
|
|
78
|
-
|
|
78
|
+
axes = figure.subplots(rows, columns, squeeze=False)
|
|
79
79
|
for index, axis in enumerate(axes.flat):
|
|
80
80
|
axis.axis('off')
|
|
81
81
|
if index >= len(images):
|
|
@@ -86,13 +86,12 @@ def plot_predictions(images, labels, predictions=None, title='Sample digits'):
|
|
|
86
86
|
label += f' Predicted: {predictions[index]}'
|
|
87
87
|
axis.set_title(label, fontsize=10)
|
|
88
88
|
figure.suptitle(title)
|
|
89
|
-
figure.tight_layout()
|
|
90
89
|
return figure
|
|
91
90
|
|
|
92
91
|
|
|
93
|
-
def plot_confusion_matrix(matrix):
|
|
92
|
+
def plot_confusion_matrix(figure, matrix):
|
|
94
93
|
"""Return a figure; rows are true digits and columns are predicted digits."""
|
|
95
|
-
|
|
94
|
+
axis = figure.subplots()
|
|
96
95
|
heatmap = axis.imshow(matrix, cmap='Blues')
|
|
97
96
|
axis.set(xticks=range(10), yticks=range(10), xlabel='Predicted digit',
|
|
98
97
|
ylabel='True digit', title='Neural-pleX: held-out confusion matrix')
|
|
@@ -101,7 +100,6 @@ def plot_confusion_matrix(matrix):
|
|
|
101
100
|
axis.text(column, row, str(matrix[row, column]), ha='center', va='center',
|
|
102
101
|
color='white' if matrix[row, column] > matrix.max() / 2 else 'black')
|
|
103
102
|
figure.colorbar(heatmap, ax=axis)
|
|
104
|
-
figure.tight_layout()
|
|
105
103
|
return figure
|
|
106
104
|
|
|
107
105
|
|
|
@@ -152,14 +150,14 @@ def main():
|
|
|
152
150
|
accuracy = accuracy_score(truth, predictions)
|
|
153
151
|
matrix = confusion_matrix(truth, predictions, labels=list(range(10)))
|
|
154
152
|
print(f'Digits accuracy: {accuracy:.2%} ({len(train)} train, {len(test)} test)')
|
|
155
|
-
figure =
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
figure = plt.figure(figsize=(15, 6), layout='constrained')
|
|
154
|
+
predictions_panel, matrix_panel = figure.subfigures(1, 2, width_ratios=[8, 7])
|
|
155
|
+
plot_predictions(predictions_panel, digits.images[test[:12]], truth[:12], predictions[:12],
|
|
156
|
+
title=f'Neural-pleX: held-out digits (overall accuracy {accuracy:.1%})')
|
|
157
|
+
plot_confusion_matrix(matrix_panel, matrix)
|
|
158
|
+
figure.savefig(output / 'digits_results.png', dpi=150)
|
|
158
159
|
plt.close(figure)
|
|
159
|
-
|
|
160
|
-
figure.savefig(output / 'digits_confusion_matrix.png', dpi=150)
|
|
161
|
-
plt.close(figure)
|
|
162
|
-
print(f'Wrote three README images to {output}')
|
|
160
|
+
print(f'Wrote two README images to {output}')
|
|
163
161
|
|
|
164
162
|
|
|
165
163
|
if __name__ == '__main__':
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0"
|
|
@@ -72,17 +72,3 @@ class Network:
|
|
|
72
72
|
for source in neuron.weights:
|
|
73
73
|
neuron.weights[source] -= neuron.step * neuron.delta * source.value
|
|
74
74
|
neuron.bias -= neuron.step * neuron.delta
|
|
75
|
-
|
|
76
|
-
def detach(self, neuron: Neuron) -> None:
|
|
77
|
-
"""Remove a hidden neuron and its edges, between training steps."""
|
|
78
|
-
if neuron in self.input_layer.neurons or neuron in self.output_layer.neurons:
|
|
79
|
-
raise ValueError("Input and output neurons cannot be detached")
|
|
80
|
-
for layer in self.layers[1:-1]:
|
|
81
|
-
if neuron in layer.neurons:
|
|
82
|
-
for source in list(neuron.neuronsLHS):
|
|
83
|
-
neuron.disconnectLHS(source)
|
|
84
|
-
for receiver in list(neuron.neuronsRHS):
|
|
85
|
-
neuron.disconnectRHS(receiver)
|
|
86
|
-
layer.neurons.remove(neuron)
|
|
87
|
-
return
|
|
88
|
-
raise ValueError("Neuron is not a hidden member of this network")
|
|
@@ -56,38 +56,43 @@ class TestCorrectness(unittest.TestCase):
|
|
|
56
56
|
model.train([1, 2], [1])
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
before = model.predict([1
|
|
66
|
-
|
|
59
|
+
def test_disconnect_and_reconnect(self):
|
|
60
|
+
source, hidden, output = Neuron(), Neuron(), Neuron()
|
|
61
|
+
model = Network([Layer([source]), Layer([hidden], step=0.01),
|
|
62
|
+
Layer([output], step=0.01)], seed=42)
|
|
63
|
+
hidden.weights[source] = 2.0
|
|
64
|
+
output.weights[hidden] = 3.0
|
|
65
|
+
before = model.predict([1])
|
|
66
|
+
self.assertEqual(before, [6.0])
|
|
67
|
+
|
|
68
|
+
weight = output.weights[hidden]
|
|
69
|
+
hidden.disconnectRHS(output)
|
|
70
|
+
hidden.disconnectRHS(output) # Removing an absent edge is harmless.
|
|
67
71
|
self.assertEqual(output.weights, {})
|
|
68
|
-
self.assertEqual(hidden.weights, {})
|
|
69
72
|
self.assertEqual(hidden.neuronsRHS, [])
|
|
70
|
-
self.
|
|
71
|
-
|
|
72
|
-
model.layers[1].neurons.append(hidden)
|
|
73
|
-
for source, weight in incoming.items():
|
|
74
|
-
source.connectRHS(hidden, weight)
|
|
75
|
-
hidden.connectRHS(output, outgoing)
|
|
73
|
+
self.assertEqual(model.predict([1]), [0.0])
|
|
74
|
+
hidden.connectRHS(output, weight)
|
|
76
75
|
hidden.connectRHS(output, 999) # Existing edges keep their weights.
|
|
77
|
-
self.assertEqual(
|
|
78
|
-
self.assertEqual(hidden.weights, incoming)
|
|
79
|
-
self.assertEqual(output.weights, {hidden: outgoing})
|
|
76
|
+
self.assertEqual(output.weights, {hidden: weight})
|
|
80
77
|
self.assertEqual(hidden.neuronsRHS, [output])
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
self.assertEqual(model.predict([1]), before)
|
|
79
|
+
|
|
80
|
+
weight = hidden.weights[source]
|
|
81
|
+
hidden.disconnectLHS(source)
|
|
82
|
+
hidden.disconnectLHS(source)
|
|
83
|
+
self.assertEqual(hidden.weights, {})
|
|
84
|
+
self.assertEqual(source.neuronsRHS, [])
|
|
85
|
+
self.assertEqual(model.predict([1]), [0.0])
|
|
86
|
+
hidden.connectLHS(source, weight)
|
|
87
|
+
self.assertEqual(hidden.weights, {source: weight})
|
|
88
|
+
self.assertEqual(source.neuronsRHS, [hidden])
|
|
89
|
+
self.assertEqual(model.predict([1]), before)
|
|
90
|
+
model.train([1], [5])
|
|
91
|
+
self.assertLess(abs(model.predict([1])[0] - 5), abs(before[0] - 5))
|
|
92
|
+
|
|
93
|
+
output.connectRHS(source)
|
|
89
94
|
with self.assertRaises(ValueError):
|
|
90
|
-
model.predict([1
|
|
95
|
+
model.predict([1])
|
|
91
96
|
|
|
92
97
|
def test_invalid_learning_rates(self):
|
|
93
98
|
for step in [0, -0.1, float('nan'), float('inf'), float('-inf')]:
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|