evonet 0.1.0.dev4__tar.gz → 0.1.0.dev6__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.
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/PKG-INFO +1 -1
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/core.py +77 -35
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/mutation.py +71 -10
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/neuron.py +14 -7
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet.egg-info/PKG-INFO +1 -1
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/pyproject.toml +1 -1
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/tests/test_recurrent_dynamics.py +17 -14
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/LICENSE +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/README.md +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/__init__.py +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/activation.py +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/connection.py +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/enums.py +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/io.py +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/layer.py +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/utils.py +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet/visualize.py +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet.egg-info/SOURCES.txt +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet.egg-info/dependency_links.txt +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet.egg-info/requires.txt +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/evonet.egg-info/top_level.txt +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/setup.cfg +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/tests/test_activation.py +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/tests/test_core.py +0 -0
- {evonet-0.1.0.dev4 → evonet-0.1.0.dev6}/tests/test_nnet_io_and_forward.py +0 -0
|
@@ -9,11 +9,13 @@ export interfaces.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
12
14
|
import graphviz
|
|
13
15
|
import numpy as np
|
|
14
16
|
|
|
15
17
|
from evonet.connection import Connection
|
|
16
|
-
from evonet.enums import ConnectionType, NeuronRole
|
|
18
|
+
from evonet.enums import ConnectionType, NeuronRole, RecurrentKind
|
|
17
19
|
from evonet.layer import Layer
|
|
18
20
|
from evonet.neuron import Neuron
|
|
19
21
|
|
|
@@ -99,24 +101,23 @@ class Nnet:
|
|
|
99
101
|
role: NeuronRole = NeuronRole.HIDDEN,
|
|
100
102
|
count: int = 1,
|
|
101
103
|
connect_layer: bool = True,
|
|
102
|
-
|
|
104
|
+
recurrent: Optional[set[RecurrentKind]] = None,
|
|
105
|
+
) -> list[Neuron]:
|
|
103
106
|
"""
|
|
104
|
-
Add one or more neurons to
|
|
107
|
+
Add one or more neurons to the network.
|
|
105
108
|
|
|
106
109
|
Args:
|
|
107
|
-
layer_idx (int | None):
|
|
110
|
+
layer_idx (int | None): Target layer index. Defaults to last layer.
|
|
108
111
|
activation (str): Activation function name.
|
|
109
112
|
bias (float): Initial bias value.
|
|
110
|
-
label (str): Optional label
|
|
113
|
+
label (str): Optional label.
|
|
111
114
|
role (NeuronRole): Role of the neuron (INPUT, HIDDEN, OUTPUT).
|
|
112
|
-
count (int): Number of neurons to add.
|
|
113
|
-
connect_layer (bool):
|
|
115
|
+
count (int): Number of neurons to add (default: 1).
|
|
116
|
+
connect_layer (bool): If True, auto-connect to adjacent layers.
|
|
117
|
+
recurrent (set[RecurrentKind] | None): Optional recurrent connection types.
|
|
114
118
|
|
|
115
119
|
Returns:
|
|
116
|
-
Neuron:
|
|
117
|
-
|
|
118
|
-
Raises:
|
|
119
|
-
ValueError: If layer index is invalid.
|
|
120
|
+
list[Neuron]: List of added neurons.
|
|
120
121
|
"""
|
|
121
122
|
|
|
122
123
|
if layer_idx is None:
|
|
@@ -127,31 +128,67 @@ class Nnet:
|
|
|
127
128
|
if layer_idx >= len(self.layers):
|
|
128
129
|
raise ValueError(f"Layer index out of bounds: {layer_idx}")
|
|
129
130
|
|
|
130
|
-
|
|
131
|
+
target_layer = self.layers[layer_idx]
|
|
132
|
+
new_neurons: list[Neuron] = []
|
|
133
|
+
|
|
134
|
+
# Create neurons without connections
|
|
135
|
+
for i in range(count):
|
|
131
136
|
neuron = Neuron(activation=activation, bias=bias)
|
|
132
137
|
neuron.role = role
|
|
133
138
|
neuron.label = label
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
139
|
+
target_layer.neurons.append(neuron)
|
|
140
|
+
new_neurons.append(neuron)
|
|
141
|
+
|
|
142
|
+
# Connect to previous layer (feedforward)
|
|
143
|
+
if connect_layer and layer_idx > 0:
|
|
144
|
+
for prev_neuron in self.layers[layer_idx - 1].neurons:
|
|
145
|
+
for n in new_neurons:
|
|
146
|
+
self.add_connection(prev_neuron, n)
|
|
147
|
+
|
|
148
|
+
# Connect to next layer (feedforward)
|
|
149
|
+
if (
|
|
150
|
+
connect_layer
|
|
151
|
+
and role == NeuronRole.HIDDEN
|
|
152
|
+
and layer_idx < len(self.layers) - 1
|
|
153
|
+
):
|
|
154
|
+
|
|
155
|
+
for next_neuron in self.layers[layer_idx + 1].neurons:
|
|
156
|
+
for n in new_neurons:
|
|
157
|
+
self.add_connection(n, next_neuron)
|
|
158
|
+
|
|
159
|
+
# Recurrent connections
|
|
160
|
+
if recurrent:
|
|
161
|
+
if RecurrentKind.DIRECT in recurrent:
|
|
162
|
+
for n in new_neurons:
|
|
163
|
+
if n.role == NeuronRole.HIDDEN:
|
|
164
|
+
self.add_connection(n, n, conn_type=ConnectionType.RECURRENT)
|
|
165
|
+
|
|
166
|
+
if RecurrentKind.LATERAL in recurrent:
|
|
167
|
+
# All neurons in this layer (old + new)
|
|
168
|
+
full_layer = list(self.layers[layer_idx].neurons)
|
|
169
|
+
|
|
170
|
+
for src in full_layer:
|
|
171
|
+
for dst in new_neurons:
|
|
172
|
+
if src is not dst:
|
|
173
|
+
self.add_connection(
|
|
174
|
+
src, dst, conn_type=ConnectionType.RECURRENT
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if RecurrentKind.INDIRECT in recurrent:
|
|
178
|
+
for src in new_neurons:
|
|
179
|
+
for lower_layer in self.layers[1:layer_idx]:
|
|
180
|
+
for dst in lower_layer.neurons:
|
|
181
|
+
self.add_connection(
|
|
182
|
+
src, dst, conn_type=ConnectionType.RECURRENT
|
|
183
|
+
)
|
|
184
|
+
for higher_layer in self.layers[layer_idx + 1 :]:
|
|
185
|
+
for src in higher_layer.neurons:
|
|
186
|
+
for dst in new_neurons:
|
|
187
|
+
self.add_connection(
|
|
188
|
+
src, dst, conn_type=ConnectionType.RECURRENT
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return new_neurons
|
|
155
192
|
|
|
156
193
|
def add_connection(
|
|
157
194
|
self,
|
|
@@ -177,11 +214,11 @@ class Nnet:
|
|
|
177
214
|
source.outgoing.append(conn)
|
|
178
215
|
target.incoming.append(conn)
|
|
179
216
|
|
|
180
|
-
def reset(self) -> None:
|
|
217
|
+
def reset(self, full: bool = False) -> None:
|
|
181
218
|
"""Reset all neurons (clears input, output, and caches)."""
|
|
182
219
|
for layer in self.layers:
|
|
183
220
|
for neuron in layer.neurons:
|
|
184
|
-
neuron.reset()
|
|
221
|
+
neuron.reset(full=full)
|
|
185
222
|
|
|
186
223
|
def calc(self, input_values: list[float]) -> list[float]:
|
|
187
224
|
"""
|
|
@@ -197,6 +234,11 @@ class Nnet:
|
|
|
197
234
|
AssertionError: If input size does not match input layer.
|
|
198
235
|
"""
|
|
199
236
|
|
|
237
|
+
# Save LAST OUTPUT
|
|
238
|
+
for layer in self.layers:
|
|
239
|
+
for neuron in layer.neurons:
|
|
240
|
+
neuron.last_output = neuron.output
|
|
241
|
+
|
|
200
242
|
self.reset()
|
|
201
243
|
|
|
202
244
|
# Set inputs
|
|
@@ -10,7 +10,7 @@ Includes:
|
|
|
10
10
|
|
|
11
11
|
import random
|
|
12
12
|
from collections.abc import Collection
|
|
13
|
-
from typing import Optional
|
|
13
|
+
from typing import Literal, Optional, Union, cast
|
|
14
14
|
|
|
15
15
|
import numpy as np
|
|
16
16
|
|
|
@@ -20,6 +20,8 @@ from evonet.core import Nnet
|
|
|
20
20
|
from evonet.enums import ConnectionType, NeuronRole, RecurrentKind
|
|
21
21
|
from evonet.neuron import Neuron
|
|
22
22
|
|
|
23
|
+
ALL_ACTIVATIONS = "all"
|
|
24
|
+
|
|
23
25
|
|
|
24
26
|
def mutate_activation(neuron: Neuron, activations: list[str] | None = None) -> None:
|
|
25
27
|
"""
|
|
@@ -34,19 +36,78 @@ def mutate_activation(neuron: Neuron, activations: list[str] | None = None) -> N
|
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
def mutate_activations(
|
|
37
|
-
net: Nnet,
|
|
39
|
+
net: Nnet,
|
|
40
|
+
probability: float = 1.0,
|
|
41
|
+
activations: Optional[list[str]] = None,
|
|
42
|
+
layers: Optional[dict[int, Union[list[str], Literal["all"]]]] = None,
|
|
38
43
|
) -> None:
|
|
39
44
|
"""
|
|
40
|
-
Mutate the activation functions of
|
|
45
|
+
Mutate the activation functions of neurons with optional global or layer-specific
|
|
46
|
+
control.
|
|
47
|
+
|
|
48
|
+
Modes:
|
|
49
|
+
A) Default: Hidden layers are mutated using all available activation functions.
|
|
50
|
+
B) With `activations`: Hidden layers use a restricted set of activation
|
|
51
|
+
functions.
|
|
52
|
+
C) With `layers`: Only specified layers are mutated, each with their own
|
|
53
|
+
activation sets. This mode overrides the `activations` argument.
|
|
41
54
|
|
|
42
55
|
Args:
|
|
43
|
-
net (Nnet): The target network.
|
|
44
|
-
probability (float): Mutation probability per neuron.
|
|
45
|
-
activations (list[str] | None): Optional
|
|
56
|
+
net (Nnet): The target neural network.
|
|
57
|
+
probability (float): Mutation probability per neuron (must be in [0, 1]).
|
|
58
|
+
activations (list[str] | None): Optional list of allowed activation functions
|
|
59
|
+
(used in mode B).
|
|
60
|
+
layers (dict[int, list[str] | Literal["all"]] | None):
|
|
61
|
+
Optional per-layer activation sets (mode C).
|
|
62
|
+
If specified, overrides the `activations` argument.
|
|
63
|
+
|
|
64
|
+
Notes:
|
|
65
|
+
- Input and output layers are not mutated unless explicitly included
|
|
66
|
+
in `layers`.
|
|
67
|
+
- The function relies on `numpy.random.rand()` for random number generation.
|
|
68
|
+
- The `mutate_activation` function is called to perform the actual mutation of
|
|
69
|
+
a neuron's activation function.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If `probability` is not in [0, 1] or
|
|
73
|
+
if `layers` contains invalid layer indices.
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
>>> net = Nnet(...) # Assume a neural network with 3 layers
|
|
77
|
+
>>> mutate_activations(net, probability=0.5, activations=["relu", "sigmoid"])
|
|
78
|
+
>>> mutate_activations(net, layers={1: ["relu"], 2: "all"}, probability=0.3)
|
|
46
79
|
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
80
|
+
|
|
81
|
+
if not 0 <= probability <= 1:
|
|
82
|
+
raise ValueError("Probability must be between 0 and 1.")
|
|
83
|
+
|
|
84
|
+
if layers is not None:
|
|
85
|
+
max_layer = len(net.layers)
|
|
86
|
+
invalid_layers = [i for i in layers if i < 0 or i >= max_layer]
|
|
87
|
+
if invalid_layers:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"Invalid layer indices: {invalid_layers}."
|
|
90
|
+
f"Must be between 0 and {max_layer - 1}."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
for layer_idx, layer in enumerate(net.layers):
|
|
94
|
+
if layers is not None:
|
|
95
|
+
if layer_idx not in layers:
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
if layers[layer_idx] != ALL_ACTIVATIONS:
|
|
99
|
+
layer_activations = cast(list[str], layers[layer_idx])
|
|
100
|
+
else:
|
|
101
|
+
layer_activations = None # None: mutate_activation uses all activations
|
|
102
|
+
|
|
103
|
+
elif layer_idx == 0 or layer_idx == len(net.layers) - 1:
|
|
104
|
+
continue
|
|
105
|
+
else:
|
|
106
|
+
layer_activations = activations
|
|
107
|
+
|
|
108
|
+
for neuron in layer.neurons:
|
|
109
|
+
if np.random.rand() < probability:
|
|
110
|
+
mutate_activation(neuron, layer_activations)
|
|
50
111
|
|
|
51
112
|
|
|
52
113
|
def mutate_weight(conn: Connection, std: float = 0.1) -> None:
|
|
@@ -267,7 +328,7 @@ def split_connection(net: Nnet, activation: str = "tanh", noise: float = 0.1) ->
|
|
|
267
328
|
role=NeuronRole.HIDDEN,
|
|
268
329
|
activation=activation,
|
|
269
330
|
connect_layer=False,
|
|
270
|
-
)
|
|
331
|
+
)[0]
|
|
271
332
|
|
|
272
333
|
# Set new connections
|
|
273
334
|
src.outgoing.remove(conn)
|
|
@@ -55,18 +55,25 @@ class Neuron:
|
|
|
55
55
|
self.last_output: float = 0.0
|
|
56
56
|
self.label = label
|
|
57
57
|
|
|
58
|
-
def reset(self) -> None:
|
|
58
|
+
def reset(self, full: bool = False) -> None:
|
|
59
59
|
"""
|
|
60
|
-
|
|
60
|
+
Clear current neuron state for a new forward pass.
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
Args:
|
|
63
|
+
full (bool): If True, also clears `last_output` (used for recurrent memory).
|
|
64
|
+
|
|
65
|
+
This method:
|
|
66
|
+
- Always resets `input` and `output`.
|
|
67
|
+
- Only resets `last_output` if `full=True`.
|
|
68
|
+
|
|
69
|
+
Use `full=True` if the neuron should forget its prior state
|
|
70
|
+
(e.g. after fitness evaluation).
|
|
66
71
|
"""
|
|
67
|
-
|
|
72
|
+
|
|
68
73
|
self.output = 0.0
|
|
69
74
|
self.input = 0.0
|
|
75
|
+
if full:
|
|
76
|
+
self.last_output = 0.0
|
|
70
77
|
|
|
71
78
|
def __repr__(self) -> str:
|
|
72
79
|
"""
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
|
|
3
3
|
from evonet.core import Nnet
|
|
4
|
-
from evonet.enums import
|
|
4
|
+
from evonet.enums import ConnectionType, NeuronRole
|
|
5
|
+
from evonet.neuron import Neuron
|
|
5
6
|
|
|
6
7
|
|
|
7
|
-
def _mk_linear_ih_h_o() -> tuple[Nnet,
|
|
8
|
+
def _mk_linear_ih_h_o() -> tuple[Nnet, Neuron, Neuron, Neuron]:
|
|
8
9
|
"""
|
|
9
10
|
Build a minimal 3-layer net (I-H-O), all linear activations, no auto-connections.
|
|
10
11
|
|
|
@@ -13,18 +14,20 @@ def _mk_linear_ih_h_o() -> tuple[Nnet, object, object, object]:
|
|
|
13
14
|
net = Nnet()
|
|
14
15
|
net.add_layer(3) # input, hidden, output
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
neurons: list[Neuron] = net.add_neuron(
|
|
18
|
+
layer_idx=0, role=NeuronRole.INPUT, activation="linear", connect_layer=False
|
|
19
|
+
)
|
|
20
|
+
n_in = neurons[0]
|
|
21
|
+
|
|
22
|
+
neurons = net.add_neuron(
|
|
23
|
+
layer_idx=1, role=NeuronRole.HIDDEN, activation="linear", connect_layer=False
|
|
24
|
+
)
|
|
25
|
+
n_hid = neurons[0]
|
|
26
|
+
|
|
27
|
+
neurons = net.add_neuron(
|
|
28
|
+
layer_idx=2, role=NeuronRole.OUTPUT, activation="linear", connect_layer=False
|
|
29
|
+
)
|
|
30
|
+
n_out = neurons[0]
|
|
28
31
|
|
|
29
32
|
return net, n_in, n_hid, n_out
|
|
30
33
|
|
|
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
|
|
File without changes
|