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.
Files changed (28) hide show
  1. {neuralplex-0.3.2 → neuralplex-0.4.0}/PKG-INFO +8 -10
  2. {neuralplex-0.3.2 → neuralplex-0.4.0}/README.md +7 -9
  3. {neuralplex-0.3.2 → neuralplex-0.4.0}/docs/README.md +2 -3
  4. {neuralplex-0.3.2 → neuralplex-0.4.0}/docs/generate_images.py +11 -13
  5. neuralplex-0.4.0/images/digits_results.png +0 -0
  6. neuralplex-0.4.0/src/neuralplex/__about__.py +1 -0
  7. {neuralplex-0.3.2 → neuralplex-0.4.0}/src/neuralplex/network.py +0 -14
  8. {neuralplex-0.3.2 → neuralplex-0.4.0}/tests/test.py +32 -27
  9. neuralplex-0.3.2/images/digits_confusion_matrix.png +0 -0
  10. neuralplex-0.3.2/images/digits_predictions.png +0 -0
  11. neuralplex-0.3.2/src/neuralplex/__about__.py +0 -1
  12. {neuralplex-0.3.2 → neuralplex-0.4.0}/.gitignore +0 -0
  13. {neuralplex-0.3.2 → neuralplex-0.4.0}/LICENSE +0 -0
  14. {neuralplex-0.3.2 → neuralplex-0.4.0}/docs/pyproject.toml +0 -0
  15. {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_digits_experiment/README.md +0 -0
  16. {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_digits_experiment/main.ipynb +0 -0
  17. {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_digits_experiment/pyproject.toml +0 -0
  18. {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_digits_experiment/visualizations.py +0 -0
  19. {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_nibble_challenge/README.md +0 -0
  20. {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_nibble_challenge/main.ipynb +0 -0
  21. {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_nibble_challenge/pyproject.toml +0 -0
  22. {neuralplex-0.3.2 → neuralplex-0.4.0}/examples/the_nibble_challenge/visualizations.py +0 -0
  23. {neuralplex-0.3.2 → neuralplex-0.4.0}/images/nibble_multiweight.png +0 -0
  24. {neuralplex-0.3.2 → neuralplex-0.4.0}/pyproject.toml +0 -0
  25. {neuralplex-0.3.2 → neuralplex-0.4.0}/src/neuralplex/__init__.py +0 -0
  26. {neuralplex-0.3.2 → neuralplex-0.4.0}/src/neuralplex/layer.py +0 -0
  27. {neuralplex-0.3.2 → neuralplex-0.4.0}/src/neuralplex/neuron.py +0 -0
  28. {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.2
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, with manual backpropagation. The Neural-pleX 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.
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. To explore a whole hidden neuron, `n1.detach(neuron)` removes it from its layer and disconnects all its incoming and outgoing edges. Save those weights before detaching if you want to restore it, then add the neuron back to its layer and reconnect its edges. Its bias, activation, and learning rate remain on the object.
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
- The notebook imports `visualize_network` from [its visualization module](https://github.com/adamjpatterson/neuralplex/blob/main/examples/the_nibble_challenge/visualizations.py).
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
- ![Digits predictions](https://raw.githubusercontent.com/adamjpatterson/neuralplex/main/images/digits_predictions.png)
183
-
184
- ![Digits confusion matrix](https://raw.githubusercontent.com/adamjpatterson/neuralplex/main/images/digits_confusion_matrix.png)
182
+ ![Digits predictions and confusion matrix](https://raw.githubusercontent.com/adamjpatterson/neuralplex/main/images/digits_results.png)
185
183
 
186
184
  ## Tests
187
185
 
188
- The suite in `tests/test.py` checks forward calculations, manual gradients, parameter updates, detachment and reconnection, and input validation.
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, digits predictions, and confusion matrix to `images/`.
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, with manual backpropagation. The Neural-pleX 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.
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. To explore a whole hidden neuron, `n1.detach(neuron)` removes it from its layer and disconnects all its incoming and outgoing edges. Save those weights before detaching if you want to restore it, then add the neuron back to its layer and reconnect its edges. Its bias, activation, and learning rate remain on the object.
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
- The notebook imports `visualize_network` from [its visualization module](https://github.com/adamjpatterson/neuralplex/blob/main/examples/the_nibble_challenge/visualizations.py).
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
- ![Digits predictions](https://raw.githubusercontent.com/adamjpatterson/neuralplex/main/images/digits_predictions.png)
169
-
170
- ![Digits confusion matrix](https://raw.githubusercontent.com/adamjpatterson/neuralplex/main/images/digits_confusion_matrix.png)
168
+ ![Digits predictions and confusion matrix](https://raw.githubusercontent.com/adamjpatterson/neuralplex/main/images/digits_results.png)
171
169
 
172
170
  ## Tests
173
171
 
174
- The suite in `tests/test.py` checks forward calculations, manual gradients, parameter updates, detachment and reconnection, and input validation.
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, digits predictions, and confusion matrix to `images/`.
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 three README illustrations.
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/digits_predictions.png`
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
- figure, axes = plt.subplots(rows, columns, figsize=(2 * columns, 2 * rows), squeeze=False)
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
- figure, axis = plt.subplots(figsize=(7, 6))
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 = plot_predictions(digits.images[test[:12]], truth[:12], predictions[:12],
156
- title=f'Neural-pleX: held-out digits (overall accuracy {accuracy:.1%})')
157
- figure.savefig(output / 'digits_predictions.png', dpi=150)
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
- figure = plot_confusion_matrix(matrix)
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__':
@@ -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 test_detach_and_reattach(self):
60
- inputs = [Neuron(name=f'i{i}') for i in range(3)]
61
- hidden, output = Neuron(name='h'), Neuron(name='o')
62
- model = Network([Layer(inputs), Layer([hidden], step=0.01), Layer([output], step=0.01)], seed=42)
63
- incoming = dict(hidden.weights)
64
- outgoing = output.weights[hidden]
65
- before = model.predict([1, 2, 3])
66
- model.detach(hidden)
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.assertTrue(all(not n.neuronsRHS for n in inputs))
71
- self.assertEqual(model.predict([1, 2, 3]), [0])
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(model.predict([1, 2, 3]), before)
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
- for source in inputs:
82
- self.assertEqual(source.neuronsRHS, [hidden])
83
- inputs[0].disconnectRHS(hidden)
84
- inputs[0].disconnectRHS(hidden)
85
- model.train([1, 2, 3], [1])
86
- with self.assertRaises(ValueError):
87
- model.detach(output)
88
- output.connectRHS(inputs[0])
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, 2, 3])
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')]:
@@ -1 +0,0 @@
1
- __version__ = "0.3.2"
File without changes
File without changes
File without changes
File without changes