neuralplex 0.3.0__tar.gz → 0.3.1__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 (27) hide show
  1. {neuralplex-0.3.0 → neuralplex-0.3.1}/PKG-INFO +30 -7
  2. {neuralplex-0.3.0 → neuralplex-0.3.1}/README.md +29 -6
  3. neuralplex-0.3.1/src/neuralplex/__about__.py +1 -0
  4. {neuralplex-0.3.0 → neuralplex-0.3.1}/src/neuralplex/layer.py +3 -2
  5. {neuralplex-0.3.0 → neuralplex-0.3.1}/src/neuralplex/network.py +3 -2
  6. {neuralplex-0.3.0 → neuralplex-0.3.1}/src/neuralplex/neuron.py +3 -3
  7. {neuralplex-0.3.0 → neuralplex-0.3.1}/tests/test.py +16 -0
  8. neuralplex-0.3.0/src/neuralplex/__about__.py +0 -1
  9. {neuralplex-0.3.0 → neuralplex-0.3.1}/.gitignore +0 -0
  10. {neuralplex-0.3.0 → neuralplex-0.3.1}/LICENSE +0 -0
  11. {neuralplex-0.3.0 → neuralplex-0.3.1}/docs/README.md +0 -0
  12. {neuralplex-0.3.0 → neuralplex-0.3.1}/docs/generate_images.py +0 -0
  13. {neuralplex-0.3.0 → neuralplex-0.3.1}/docs/pyproject.toml +0 -0
  14. {neuralplex-0.3.0 → neuralplex-0.3.1}/examples/the_digits_experiment/README.md +0 -0
  15. {neuralplex-0.3.0 → neuralplex-0.3.1}/examples/the_digits_experiment/main.ipynb +0 -0
  16. {neuralplex-0.3.0 → neuralplex-0.3.1}/examples/the_digits_experiment/pyproject.toml +0 -0
  17. {neuralplex-0.3.0 → neuralplex-0.3.1}/examples/the_digits_experiment/visualizations.py +0 -0
  18. {neuralplex-0.3.0 → neuralplex-0.3.1}/examples/the_nibble_challenge/README.md +0 -0
  19. {neuralplex-0.3.0 → neuralplex-0.3.1}/examples/the_nibble_challenge/main.ipynb +0 -0
  20. {neuralplex-0.3.0 → neuralplex-0.3.1}/examples/the_nibble_challenge/pyproject.toml +0 -0
  21. {neuralplex-0.3.0 → neuralplex-0.3.1}/examples/the_nibble_challenge/visualizations.py +0 -0
  22. {neuralplex-0.3.0 → neuralplex-0.3.1}/images/digits_confusion_matrix.png +0 -0
  23. {neuralplex-0.3.0 → neuralplex-0.3.1}/images/digits_predictions.png +0 -0
  24. {neuralplex-0.3.0 → neuralplex-0.3.1}/images/nibble_multiweight.png +0 -0
  25. {neuralplex-0.3.0 → neuralplex-0.3.1}/pyproject.toml +0 -0
  26. {neuralplex-0.3.0 → neuralplex-0.3.1}/src/neuralplex/__init__.py +0 -0
  27. {neuralplex-0.3.0 → neuralplex-0.3.1}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: neuralplex
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: An object oriented neural network implementation.
5
5
  Project-URL: Homepage, https://github.com/adamjpatterson/neuralplex
6
6
  Project-URL: Issues, https://github.com/adamjpatterson/neuralplex/issues
@@ -36,13 +36,23 @@ An object oriented educational/experimental neural network implementation.
36
36
 
37
37
  ## Installation
38
38
 
39
+ To use the published package:
40
+
41
+ ```bash
42
+ python -m pip install neuralplex
43
+ ```
44
+
45
+ To explore the notebooks or modify the package, clone the repository and install
46
+ the checkout:
47
+
39
48
  ```bash
40
49
  git clone https://github.com/adamjpatterson/neuralplex.git
41
50
  cd neuralplex
42
51
  python -m pip install -e .
43
52
  ```
44
53
 
45
- Install from this checkout to use the implementation documented below.
54
+ Follow each example’s setup instructions below to install its notebook and
55
+ visualization dependencies.
46
56
 
47
57
  ## How it works
48
58
 
@@ -58,6 +68,12 @@ from neuralplex import Network, Layer, Neuron
58
68
 
59
69
  #### Set a step.
60
70
 
71
+ `step` is the learning rate: it controls the size of weight and bias updates.
72
+ Each update subtracts `step` times the corresponding gradient.
73
+ `Layer(step=...)` supplies an initial default only to neurons whose `step` is
74
+ `None`. Changing `layer.step` later does not change the neurons; update each
75
+ `neuron.step` directly to change its learning rate.
76
+
61
77
  ```python
62
78
  STEP = 0.01
63
79
  ```
@@ -97,9 +113,11 @@ hidden layers. Linear layers are sufficient for the nibble conversion task.
97
113
 
98
114
  `train` minimizes half the sum of squared output errors. It runs the forward
99
115
  pass, accumulates gradients backward through every connection, and only then
100
- updates weights and biases. A weight's gradient is `delta * source.value`;
116
+ updates weights and biases. `delta` is the derivative of the loss with respect
117
+ to the neuron's weighted sum plus bias, before activation.
118
+ A weight's gradient is `delta * source.value`;
101
119
  the bias gradient is `delta`. The code uses explicit loops, not automatic
102
- differentiation. Every non-input neuron needs a positive step.
120
+ differentiation. Every non-input neuron needs a finite, positive `step` for training.
103
121
 
104
122
  `Neuron(bias=...)` sets the bias. Each incoming connection has its own weight,
105
123
  stored in `receiver.weights[source]`. `Network(..., seed=42)` initializes
@@ -110,6 +128,11 @@ weights. Visualization modules read connections directly from
110
128
 
111
129
  ### Detach and reconnect between training steps
112
130
 
131
+ You can change an existing connection weight through `receiver.weights[source]`.
132
+ To add or remove connections, use `connectRHS`/`connectLHS` and
133
+ `disconnectRHS`/`disconnectLHS` rather than inserting or deleting dictionary
134
+ entries. These methods keep incoming weights and outgoing connections consistent.
135
+
113
136
  ```python
114
137
  hidden = n1.layers[1].neurons[0]
115
138
  incoming = dict(hidden.weights)
@@ -126,9 +149,9 @@ for target, weight in outgoing.items():
126
149
 
127
150
  The neuron's bias, activation, and learning rate remain on the object. For one
128
151
  edge, use `source.disconnectRHS(target)` and `source.connectRHS(target, weight)`.
129
- Removing an edge does not cascade. A computational node without inputs uses
130
- its bias; an empty hidden layer left by detachment is allowed. Connections must
131
- run from earlier to later layers; input/output neurons cannot be detached.
152
+ Removing an edge does not cascade. A non-input neuron without incoming
153
+ connections outputs `activation(bias)`; an empty hidden layer left by detachment
154
+ is allowed. Connections must run from earlier to later layers; input/output neurons cannot be detached.
132
155
  Change structure only between training steps, not concurrently with training.
133
156
 
134
157
  ## Examples
@@ -22,13 +22,23 @@ An object oriented educational/experimental neural network implementation.
22
22
 
23
23
  ## Installation
24
24
 
25
+ To use the published package:
26
+
27
+ ```bash
28
+ python -m pip install neuralplex
29
+ ```
30
+
31
+ To explore the notebooks or modify the package, clone the repository and install
32
+ the checkout:
33
+
25
34
  ```bash
26
35
  git clone https://github.com/adamjpatterson/neuralplex.git
27
36
  cd neuralplex
28
37
  python -m pip install -e .
29
38
  ```
30
39
 
31
- Install from this checkout to use the implementation documented below.
40
+ Follow each example’s setup instructions below to install its notebook and
41
+ visualization dependencies.
32
42
 
33
43
  ## How it works
34
44
 
@@ -44,6 +54,12 @@ from neuralplex import Network, Layer, Neuron
44
54
 
45
55
  #### Set a step.
46
56
 
57
+ `step` is the learning rate: it controls the size of weight and bias updates.
58
+ Each update subtracts `step` times the corresponding gradient.
59
+ `Layer(step=...)` supplies an initial default only to neurons whose `step` is
60
+ `None`. Changing `layer.step` later does not change the neurons; update each
61
+ `neuron.step` directly to change its learning rate.
62
+
47
63
  ```python
48
64
  STEP = 0.01
49
65
  ```
@@ -83,9 +99,11 @@ hidden layers. Linear layers are sufficient for the nibble conversion task.
83
99
 
84
100
  `train` minimizes half the sum of squared output errors. It runs the forward
85
101
  pass, accumulates gradients backward through every connection, and only then
86
- updates weights and biases. A weight's gradient is `delta * source.value`;
102
+ updates weights and biases. `delta` is the derivative of the loss with respect
103
+ to the neuron's weighted sum plus bias, before activation.
104
+ A weight's gradient is `delta * source.value`;
87
105
  the bias gradient is `delta`. The code uses explicit loops, not automatic
88
- differentiation. Every non-input neuron needs a positive step.
106
+ differentiation. Every non-input neuron needs a finite, positive `step` for training.
89
107
 
90
108
  `Neuron(bias=...)` sets the bias. Each incoming connection has its own weight,
91
109
  stored in `receiver.weights[source]`. `Network(..., seed=42)` initializes
@@ -96,6 +114,11 @@ weights. Visualization modules read connections directly from
96
114
 
97
115
  ### Detach and reconnect between training steps
98
116
 
117
+ You can change an existing connection weight through `receiver.weights[source]`.
118
+ To add or remove connections, use `connectRHS`/`connectLHS` and
119
+ `disconnectRHS`/`disconnectLHS` rather than inserting or deleting dictionary
120
+ entries. These methods keep incoming weights and outgoing connections consistent.
121
+
99
122
  ```python
100
123
  hidden = n1.layers[1].neurons[0]
101
124
  incoming = dict(hidden.weights)
@@ -112,9 +135,9 @@ for target, weight in outgoing.items():
112
135
 
113
136
  The neuron's bias, activation, and learning rate remain on the object. For one
114
137
  edge, use `source.disconnectRHS(target)` and `source.connectRHS(target, weight)`.
115
- Removing an edge does not cascade. A computational node without inputs uses
116
- its bias; an empty hidden layer left by detachment is allowed. Connections must
117
- run from earlier to later layers; input/output neurons cannot be detached.
138
+ Removing an edge does not cascade. A non-input neuron without incoming
139
+ connections outputs `activation(bias)`; an empty hidden layer left by detachment
140
+ is allowed. Connections must run from earlier to later layers; input/output neurons cannot be detached.
118
141
  Change structure only between training steps, not concurrently with training.
119
142
 
120
143
  ## Examples
@@ -0,0 +1 @@
1
+ __version__ = "0.3.1"
@@ -1,3 +1,4 @@
1
+ from math import isfinite
1
2
  from typing import Self
2
3
  from random import Random
3
4
 
@@ -6,8 +7,8 @@ from neuralplex.neuron import Neuron
6
7
 
7
8
  class Layer:
8
9
  def __init__(self, neurons: list[Neuron], step: float | None = None):
9
- if step is not None and step <= 0:
10
- raise ValueError("step must be positive")
10
+ if step is not None and (not isfinite(step) or step <= 0):
11
+ raise ValueError("step must be finite and positive")
11
12
  self.neurons = neurons
12
13
  self.step = step
13
14
  for neuron in neurons:
@@ -1,3 +1,4 @@
1
+ from math import isfinite
1
2
  from random import Random
2
3
 
3
4
  from neuralplex.layer import Layer
@@ -48,8 +49,8 @@ class Network:
48
49
  raise ValueError("Supply one target value per output neuron")
49
50
  for layer in self.layers[1:]:
50
51
  for neuron in layer.neurons:
51
- if neuron.step is None or neuron.step <= 0:
52
- raise ValueError("Every computational neuron needs a positive step")
52
+ if neuron.step is None or not isfinite(neuron.step) or neuron.step <= 0:
53
+ raise ValueError("Every computational neuron needs a finite, positive step")
53
54
  self.predict(X_train)
54
55
 
55
56
  # Clear all gradients, including those accumulated at input neurons.
@@ -1,4 +1,4 @@
1
- from math import tanh
1
+ from math import isfinite, tanh
2
2
  from random import uniform
3
3
  from typing import Self
4
4
 
@@ -10,8 +10,8 @@ class Neuron:
10
10
  name=None, activation: str = "linear"):
11
11
  if activation not in ("linear", "tanh"):
12
12
  raise ValueError("activation must be 'linear' or 'tanh'")
13
- if step is not None and step <= 0:
14
- raise ValueError("step must be positive")
13
+ if step is not None and (not isfinite(step) or step <= 0):
14
+ raise ValueError("step must be finite and positive")
15
15
  self.bias = bias
16
16
  self.step = step
17
17
  self.name = name
@@ -89,6 +89,22 @@ class TestCorrectness(unittest.TestCase):
89
89
  with self.assertRaises(ValueError):
90
90
  model.predict([1, 2, 3])
91
91
 
92
+ def test_invalid_learning_rates(self):
93
+ for step in [0, -0.1, float('nan'), float('inf'), float('-inf')]:
94
+ with self.subTest(step=step):
95
+ with self.assertRaises(ValueError):
96
+ Neuron(step=step)
97
+ with self.assertRaises(ValueError):
98
+ Layer([Neuron()], step=step)
99
+
100
+ model = Network([Layer([Neuron()]), Layer([Neuron()], step=0.1)], seed=42)
101
+ output = model.output_layer.neurons[0]
102
+ before = (dict(output.weights), output.bias)
103
+ output.step = step # Manual changes must also be checked before training.
104
+ with self.assertRaises(ValueError):
105
+ model.train([1], [1])
106
+ self.assertEqual((output.weights, output.bias), before)
107
+
92
108
  def test_missing_step_and_external_connections(self):
93
109
  a, b, outside = Neuron(), Neuron(), Neuron()
94
110
  model = Network([Layer([a]), Layer([b])])
@@ -1 +0,0 @@
1
- __version__ = "0.3.0"
File without changes
File without changes
File without changes
File without changes
File without changes