evonet 0.1.0.dev7__tar.gz → 0.1.0.dev9__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.dev7 → evonet-0.1.0.dev9}/PKG-INFO +1 -1
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/activation.py +1 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/core.py +67 -30
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/mutation.py +2 -2
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet.egg-info/PKG-INFO +1 -1
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/pyproject.toml +1 -1
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/tests/test_core.py +4 -4
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/tests/test_nnet_io_and_forward.py +5 -5
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/tests/test_recurrent_dynamics.py +3 -3
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/LICENSE +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/README.md +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/__init__.py +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/connection.py +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/enums.py +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/io.py +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/layer.py +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/neuron.py +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/utils.py +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/visualize.py +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet.egg-info/SOURCES.txt +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet.egg-info/dependency_links.txt +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet.egg-info/requires.txt +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet.egg-info/top_level.txt +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/setup.cfg +0 -0
- {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/tests/test_activation.py +0 -0
|
@@ -9,11 +9,12 @@ export interfaces.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
-
from typing import Optional
|
|
12
|
+
from typing import Literal, Optional
|
|
13
13
|
|
|
14
14
|
import graphviz
|
|
15
15
|
import numpy as np
|
|
16
16
|
|
|
17
|
+
from evonet.activation import softmax as softmax_vec
|
|
17
18
|
from evonet.connection import Connection
|
|
18
19
|
from evonet.enums import ConnectionType, NeuronRole, RecurrentKind
|
|
19
20
|
from evonet.layer import Layer
|
|
@@ -100,31 +101,33 @@ class Nnet:
|
|
|
100
101
|
label: str = "",
|
|
101
102
|
role: NeuronRole = NeuronRole.HIDDEN,
|
|
102
103
|
count: int = 1,
|
|
103
|
-
|
|
104
|
+
connection_init: Literal["random", "zero", "none"] = "random",
|
|
104
105
|
recurrent: Optional[set[RecurrentKind]] = None,
|
|
105
106
|
) -> list[Neuron]:
|
|
106
107
|
"""
|
|
107
108
|
Add one or more neurons to the network.
|
|
108
109
|
|
|
109
110
|
Args:
|
|
110
|
-
layer_idx
|
|
111
|
-
activation
|
|
112
|
-
bias
|
|
113
|
-
label
|
|
114
|
-
role
|
|
115
|
-
count
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
layer_idx: Target layer index. Defaults to last layer.
|
|
112
|
+
activation: Activation function name.
|
|
113
|
+
bias: Initial bias value.
|
|
114
|
+
label: Optional label.
|
|
115
|
+
role: Role of the neuron (INPUT, HIDDEN, OUTPUT).
|
|
116
|
+
count: Number of neurons to add (default: 1).
|
|
117
|
+
connection_init:
|
|
118
|
+
"random" – connect with random weights (feedforward + recurrent)
|
|
119
|
+
"zero" – connect with weight 0.0 (feedforward + recurrent)
|
|
120
|
+
"none" – do not create connections (feedforward + recurrent)
|
|
121
|
+
recurrent: Optional recurrent connection types.
|
|
118
122
|
|
|
119
123
|
Returns:
|
|
120
124
|
list[Neuron]: List of added neurons.
|
|
121
125
|
"""
|
|
122
|
-
|
|
123
126
|
if layer_idx is None:
|
|
124
127
|
layer_idx = len(self.layers) - 1 # Add neuron to last layer
|
|
125
128
|
|
|
126
129
|
if layer_idx < 0:
|
|
127
|
-
raise ValueError(f"
|
|
130
|
+
raise ValueError(f"Layer index must be >= 0 (got {layer_idx})")
|
|
128
131
|
if layer_idx >= len(self.layers):
|
|
129
132
|
raise ValueError(f"Layer index out of bounds: {layer_idx}")
|
|
130
133
|
|
|
@@ -132,46 +135,56 @@ class Nnet:
|
|
|
132
135
|
new_neurons: list[Neuron] = []
|
|
133
136
|
|
|
134
137
|
# Create neurons without connections
|
|
135
|
-
for
|
|
138
|
+
for _ in range(count):
|
|
136
139
|
neuron = Neuron(activation=activation, bias=bias)
|
|
137
140
|
neuron.role = role
|
|
138
141
|
neuron.label = label
|
|
139
142
|
target_layer.neurons.append(neuron)
|
|
140
143
|
new_neurons.append(neuron)
|
|
141
144
|
|
|
142
|
-
#
|
|
143
|
-
|
|
145
|
+
# Weights based on init mode
|
|
146
|
+
weight_map = {"random": None, "zero": 0.0, "none": None}
|
|
147
|
+
if connection_init not in weight_map:
|
|
148
|
+
raise ValueError(f"Invalid connection_init: {connection_init}")
|
|
149
|
+
weight = weight_map[connection_init]
|
|
150
|
+
|
|
151
|
+
skip_connections = connection_init == "none"
|
|
152
|
+
|
|
153
|
+
# Connect to previous layer
|
|
154
|
+
if not skip_connections and layer_idx > 0:
|
|
144
155
|
for prev_neuron in self.layers[layer_idx - 1].neurons:
|
|
145
156
|
for n in new_neurons:
|
|
146
|
-
self.add_connection(prev_neuron, n)
|
|
157
|
+
self.add_connection(prev_neuron, n, weight=weight)
|
|
147
158
|
|
|
148
|
-
# Connect to next layer (
|
|
159
|
+
# Connect to next layer (hidden only)
|
|
149
160
|
if (
|
|
150
|
-
|
|
161
|
+
not skip_connections
|
|
151
162
|
and role == NeuronRole.HIDDEN
|
|
152
163
|
and layer_idx < len(self.layers) - 1
|
|
153
164
|
):
|
|
154
|
-
|
|
155
165
|
for next_neuron in self.layers[layer_idx + 1].neurons:
|
|
156
166
|
for n in new_neurons:
|
|
157
|
-
self.add_connection(n, next_neuron)
|
|
167
|
+
self.add_connection(n, next_neuron, weight=weight)
|
|
158
168
|
|
|
159
169
|
# Recurrent connections
|
|
160
|
-
if recurrent:
|
|
170
|
+
if recurrent and not skip_connections:
|
|
161
171
|
if RecurrentKind.DIRECT in recurrent:
|
|
162
172
|
for n in new_neurons:
|
|
163
173
|
if n.role == NeuronRole.HIDDEN:
|
|
164
|
-
self.add_connection(
|
|
174
|
+
self.add_connection(
|
|
175
|
+
n, n, weight=weight, conn_type=ConnectionType.RECURRENT
|
|
176
|
+
)
|
|
165
177
|
|
|
166
178
|
if RecurrentKind.LATERAL in recurrent:
|
|
167
|
-
# All neurons in this layer (old + new)
|
|
168
179
|
full_layer = list(self.layers[layer_idx].neurons)
|
|
169
|
-
|
|
170
180
|
for src in full_layer:
|
|
171
181
|
for dst in new_neurons:
|
|
172
182
|
if src is not dst:
|
|
173
183
|
self.add_connection(
|
|
174
|
-
src,
|
|
184
|
+
src,
|
|
185
|
+
dst,
|
|
186
|
+
weight=weight,
|
|
187
|
+
conn_type=ConnectionType.RECURRENT,
|
|
175
188
|
)
|
|
176
189
|
|
|
177
190
|
if RecurrentKind.INDIRECT in recurrent:
|
|
@@ -179,13 +192,19 @@ class Nnet:
|
|
|
179
192
|
for lower_layer in self.layers[1:layer_idx]:
|
|
180
193
|
for dst in lower_layer.neurons:
|
|
181
194
|
self.add_connection(
|
|
182
|
-
src,
|
|
195
|
+
src,
|
|
196
|
+
dst,
|
|
197
|
+
weight=weight,
|
|
198
|
+
conn_type=ConnectionType.RECURRENT,
|
|
183
199
|
)
|
|
184
200
|
for higher_layer in self.layers[layer_idx + 1 :]:
|
|
185
201
|
for src in higher_layer.neurons:
|
|
186
202
|
for dst in new_neurons:
|
|
187
203
|
self.add_connection(
|
|
188
|
-
src,
|
|
204
|
+
src,
|
|
205
|
+
dst,
|
|
206
|
+
weight=weight,
|
|
207
|
+
conn_type=ConnectionType.RECURRENT,
|
|
189
208
|
)
|
|
190
209
|
|
|
191
210
|
return new_neurons
|
|
@@ -256,10 +275,28 @@ class Nnet:
|
|
|
256
275
|
|
|
257
276
|
# Feed-forward by layers: activate first, then propagate non-recurrent edges
|
|
258
277
|
for layer in self.layers:
|
|
278
|
+
|
|
279
|
+
# Apply softmax to all neurons with activation_name == "softmax"
|
|
280
|
+
softmax_neurons = [
|
|
281
|
+
n for n in layer.neurons if n.activation_name == "softmax"
|
|
282
|
+
]
|
|
283
|
+
if softmax_neurons:
|
|
284
|
+
if len(softmax_neurons) >= 2:
|
|
285
|
+
# Normal softmax behaviour
|
|
286
|
+
totals = [n.input + n.bias for n in softmax_neurons]
|
|
287
|
+
probabilities = softmax_vec(totals)
|
|
288
|
+
for n, p in zip(softmax_neurons, probabilities):
|
|
289
|
+
n.output = float(p)
|
|
290
|
+
else:
|
|
291
|
+
# Fallback: single softmax neuron acts like identity
|
|
292
|
+
n = softmax_neurons[0]
|
|
293
|
+
n.output = n.input + n.bias
|
|
294
|
+
|
|
259
295
|
# Activate all neurons in this layer
|
|
260
296
|
for n in layer.neurons:
|
|
261
|
-
|
|
262
|
-
|
|
297
|
+
if n.activation_name != "softmax":
|
|
298
|
+
total = n.input + n.bias
|
|
299
|
+
n.output = n.activation(total)
|
|
263
300
|
|
|
264
301
|
# Propagate to targets (exclude recurrent edges)
|
|
265
302
|
for n in layer.neurons:
|
|
@@ -326,7 +363,7 @@ class Nnet:
|
|
|
326
363
|
ratio="fill",
|
|
327
364
|
splines="spline",
|
|
328
365
|
size="6.68,5!",
|
|
329
|
-
dpi="
|
|
366
|
+
dpi="600",
|
|
330
367
|
)
|
|
331
368
|
dot.node_attr.update(
|
|
332
369
|
shape="circle", style="filled", fixedsize="shape", width="1.8"
|
|
@@ -270,7 +270,7 @@ def add_random_neuron(net: Nnet, activations: list[str] | None = None) -> None:
|
|
|
270
270
|
layer_idx=net.layers.index(layer),
|
|
271
271
|
activation=random_function_name(activations),
|
|
272
272
|
role=NeuronRole.HIDDEN,
|
|
273
|
-
|
|
273
|
+
connection_init="random",
|
|
274
274
|
)
|
|
275
275
|
|
|
276
276
|
|
|
@@ -332,7 +332,7 @@ def split_connection(net: Nnet, activation: str = "tanh", noise: float = 0.1) ->
|
|
|
332
332
|
layer_idx=insert_idx,
|
|
333
333
|
role=NeuronRole.HIDDEN,
|
|
334
334
|
activation=activation,
|
|
335
|
-
|
|
335
|
+
connection_init="none",
|
|
336
336
|
)[0]
|
|
337
337
|
|
|
338
338
|
# Set new connections
|
|
@@ -13,14 +13,14 @@ def test_forward_pass_identity() -> None:
|
|
|
13
13
|
activation="linear",
|
|
14
14
|
role=NeuronRole.INPUT,
|
|
15
15
|
label="in",
|
|
16
|
-
|
|
16
|
+
connection_init="none",
|
|
17
17
|
)
|
|
18
18
|
net.add_neuron(
|
|
19
19
|
layer_idx=1,
|
|
20
20
|
activation="linear",
|
|
21
21
|
role=NeuronRole.OUTPUT,
|
|
22
22
|
label="out",
|
|
23
|
-
|
|
23
|
+
connection_init="none",
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
src = net.layers[0].neurons[0]
|
|
@@ -49,7 +49,7 @@ def test_forward_pass_with_bias() -> None:
|
|
|
49
49
|
activation="linear",
|
|
50
50
|
role=NeuronRole.INPUT,
|
|
51
51
|
label="in",
|
|
52
|
-
|
|
52
|
+
connection_init="none",
|
|
53
53
|
)
|
|
54
54
|
net.add_neuron(
|
|
55
55
|
layer_idx=1,
|
|
@@ -57,7 +57,7 @@ def test_forward_pass_with_bias() -> None:
|
|
|
57
57
|
role=NeuronRole.OUTPUT,
|
|
58
58
|
bias=0.5,
|
|
59
59
|
label="out",
|
|
60
|
-
|
|
60
|
+
connection_init="none",
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
src = net.layers[0].neurons[0]
|
|
@@ -29,21 +29,21 @@ def build_simple_ff_net() -> Nnet:
|
|
|
29
29
|
role=NeuronRole.INPUT,
|
|
30
30
|
activation="linear",
|
|
31
31
|
count=2,
|
|
32
|
-
|
|
32
|
+
connection_init="none",
|
|
33
33
|
)
|
|
34
34
|
net.add_neuron(
|
|
35
35
|
layer_idx=1,
|
|
36
36
|
role=NeuronRole.HIDDEN,
|
|
37
37
|
activation="linear",
|
|
38
38
|
count=3,
|
|
39
|
-
|
|
39
|
+
connection_init="random",
|
|
40
40
|
)
|
|
41
41
|
net.add_neuron(
|
|
42
42
|
layer_idx=2,
|
|
43
43
|
role=NeuronRole.OUTPUT,
|
|
44
44
|
activation="linear",
|
|
45
45
|
count=1,
|
|
46
|
-
|
|
46
|
+
connection_init="random",
|
|
47
47
|
)
|
|
48
48
|
return net
|
|
49
49
|
|
|
@@ -117,14 +117,14 @@ def test_forward_sanity_linear_1x1() -> None:
|
|
|
117
117
|
role=NeuronRole.INPUT,
|
|
118
118
|
activation="linear",
|
|
119
119
|
count=1,
|
|
120
|
-
|
|
120
|
+
connection_init="none",
|
|
121
121
|
)
|
|
122
122
|
net.add_neuron(
|
|
123
123
|
layer_idx=1,
|
|
124
124
|
role=NeuronRole.OUTPUT,
|
|
125
125
|
activation="linear",
|
|
126
126
|
count=1,
|
|
127
|
-
|
|
127
|
+
connection_init="random",
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
# There should be exactly one connection L0(0) -> L1(0). Force known params:
|
|
@@ -15,17 +15,17 @@ def _mk_linear_ih_h_o() -> tuple[Nnet, Neuron, Neuron, Neuron]:
|
|
|
15
15
|
net.add_layer(3) # input, hidden, output
|
|
16
16
|
|
|
17
17
|
neurons: list[Neuron] = net.add_neuron(
|
|
18
|
-
layer_idx=0, role=NeuronRole.INPUT, activation="linear",
|
|
18
|
+
layer_idx=0, role=NeuronRole.INPUT, activation="linear", connection_init="none"
|
|
19
19
|
)
|
|
20
20
|
n_in = neurons[0]
|
|
21
21
|
|
|
22
22
|
neurons = net.add_neuron(
|
|
23
|
-
layer_idx=1, role=NeuronRole.HIDDEN, activation="linear",
|
|
23
|
+
layer_idx=1, role=NeuronRole.HIDDEN, activation="linear", connection_init="none"
|
|
24
24
|
)
|
|
25
25
|
n_hid = neurons[0]
|
|
26
26
|
|
|
27
27
|
neurons = net.add_neuron(
|
|
28
|
-
layer_idx=2, role=NeuronRole.OUTPUT, activation="linear",
|
|
28
|
+
layer_idx=2, role=NeuronRole.OUTPUT, activation="linear", connection_init="none"
|
|
29
29
|
)
|
|
30
30
|
n_out = neurons[0]
|
|
31
31
|
|
|
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
|