evonet 0.1.0a0.dev1__tar.gz → 0.1.0a0.dev2__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.0a0.dev1 → evonet-0.1.0a0.dev2}/PKG-INFO +38 -3
- evonet-0.1.0a0.dev2/README.md +47 -0
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet/activation.py +3 -4
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet/connection.py +4 -5
- evonet-0.1.0a0.dev2/evonet/core.py +154 -0
- evonet-0.1.0a0.dev1/evonet/types.py → evonet-0.1.0a0.dev2/evonet/enums.py +1 -0
- evonet-0.1.0a0.dev2/evonet/layer.py +10 -0
- evonet-0.1.0a0.dev2/evonet/mutation.py +137 -0
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet/neuron.py +21 -9
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet.egg-info/PKG-INFO +38 -3
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet.egg-info/SOURCES.txt +2 -1
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/pyproject.toml +1 -1
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/tests/test_activation.py +3 -3
- evonet-0.1.0a0.dev2/tests/test_core.py +75 -0
- evonet-0.1.0a0.dev1/README.md +0 -12
- evonet-0.1.0a0.dev1/evonet/core.py +0 -117
- evonet-0.1.0a0.dev1/evonet/mutation.py +0 -59
- evonet-0.1.0a0.dev1/tests/test_core.py +0 -42
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/LICENSE +0 -0
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet/__init__.py +0 -0
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet/io.py +0 -0
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet/utils.py +0 -0
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet/visualize.py +0 -0
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet.egg-info/dependency_links.txt +0 -0
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet.egg-info/requires.txt +0 -0
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/evonet.egg-info/top_level.txt +0 -0
- {evonet-0.1.0a0.dev1 → evonet-0.1.0a0.dev2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evonet
|
|
3
|
-
Version: 0.1.0a0.
|
|
3
|
+
Version: 0.1.0a0.dev2
|
|
4
4
|
Summary: Evolvable neural network core for integration with EvoLib
|
|
5
5
|
Author-email: EvoLib <evolib@dismail.de>
|
|
6
6
|
License: MIT License
|
|
@@ -52,8 +52,43 @@ Dynamic: license-file
|
|
|
52
52
|
[](LICENSE)
|
|
53
53
|
[](https://github.com/EvoLib/evo-net)
|
|
54
54
|
|
|
55
|
-
**EvoNet** is a modular
|
|
56
|
-
It supports dynamic topologies, recurrent connections, and
|
|
55
|
+
**EvoNet** is a modular and evolvable neural network core designed for integration with [EvoLib](https://github.com/EvoLib/evo-lib).
|
|
56
|
+
It supports dynamic topologies, recurrent connections, per-neuron activation, and structural evolution – with a strong emphasis on **clarity**, **transparency**, and **didactic value**.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🔧 Features
|
|
61
|
+
|
|
62
|
+
- **Layer-based but flexible** – allows skip connections, cycles, and recurrent paths
|
|
63
|
+
- **Typed neuron roles and connection types** (`NeuronRole`, `ConnectionType`)
|
|
64
|
+
- **Topology-aware mutation system** – add/remove neurons and connections, mutate weights, change activations
|
|
65
|
+
- **Per-neuron activation functions** – configurable, extensible, evolvable
|
|
66
|
+
- **1-step recurrent state logic** – avoids multi-pass stabilization
|
|
67
|
+
- **Topology can grow at runtime** – with `add_neuron`, `add_connection`, `split_connection`
|
|
68
|
+
- **Debug-friendly architecture** – explicit IDs, labels, roles, directional graphs
|
|
69
|
+
- **Designed for evolutionary learning** – mutation, crossover, speciation ready
|
|
70
|
+
- **Lightweight & extensible** – pure Python, NumPy-based, no hard dependencies
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
> ⚠️ **This project is in early development (alpha)**. Interfaces and structure may change.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 🚀 Quick Example
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from evonet.core import Nnet
|
|
82
|
+
|
|
83
|
+
net = Nnet()
|
|
84
|
+
net.add_layer() # Input
|
|
85
|
+
net.add_layer() # Output
|
|
86
|
+
|
|
87
|
+
net.add_neuron(layer_idx=0, activation="linear", lable="in")
|
|
88
|
+
net.add_neuron(layer_idx=1, activation="linear", bias=0.5, lable="out", connect_layer=True)
|
|
89
|
+
|
|
90
|
+
print(net.calc([1.0]))
|
|
91
|
+
```
|
|
57
92
|
|
|
58
93
|
## 🪪 License
|
|
59
94
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# EvoNet
|
|
2
|
+
[](https://github.com/EvoLib/evo-net/actions/workflows/ci.yml)
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://github.com/EvoLib/evo-net)
|
|
5
|
+
|
|
6
|
+
**EvoNet** is a modular and evolvable neural network core designed for integration with [EvoLib](https://github.com/EvoLib/evo-lib).
|
|
7
|
+
It supports dynamic topologies, recurrent connections, per-neuron activation, and structural evolution – with a strong emphasis on **clarity**, **transparency**, and **didactic value**.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🔧 Features
|
|
12
|
+
|
|
13
|
+
- **Layer-based but flexible** – allows skip connections, cycles, and recurrent paths
|
|
14
|
+
- **Typed neuron roles and connection types** (`NeuronRole`, `ConnectionType`)
|
|
15
|
+
- **Topology-aware mutation system** – add/remove neurons and connections, mutate weights, change activations
|
|
16
|
+
- **Per-neuron activation functions** – configurable, extensible, evolvable
|
|
17
|
+
- **1-step recurrent state logic** – avoids multi-pass stabilization
|
|
18
|
+
- **Topology can grow at runtime** – with `add_neuron`, `add_connection`, `split_connection`
|
|
19
|
+
- **Debug-friendly architecture** – explicit IDs, labels, roles, directional graphs
|
|
20
|
+
- **Designed for evolutionary learning** – mutation, crossover, speciation ready
|
|
21
|
+
- **Lightweight & extensible** – pure Python, NumPy-based, no hard dependencies
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
> ⚠️ **This project is in early development (alpha)**. Interfaces and structure may change.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 🚀 Quick Example
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from evonet.core import Nnet
|
|
33
|
+
|
|
34
|
+
net = Nnet()
|
|
35
|
+
net.add_layer() # Input
|
|
36
|
+
net.add_layer() # Output
|
|
37
|
+
|
|
38
|
+
net.add_neuron(layer_idx=0, activation="linear", lable="in")
|
|
39
|
+
net.add_neuron(layer_idx=1, activation="linear", bias=0.5, lable="out", connect_layer=True)
|
|
40
|
+
|
|
41
|
+
print(net.calc([1.0]))
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 🪪 License
|
|
45
|
+
|
|
46
|
+
This project is licensed under the [MIT License](https://github.com/EvoLib/evo-net/tree/main/LICENSE).
|
|
47
|
+
|
|
@@ -154,14 +154,13 @@ def softmax(values: Union[List[float], Tuple[float], np.ndarray]) -> np.ndarray:
|
|
|
154
154
|
return exp_x / np.sum(exp_x)
|
|
155
155
|
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def random_function() -> str:
|
|
157
|
+
def random_function_name() -> str:
|
|
161
158
|
"""Returns a random activation function name from the registry."""
|
|
162
159
|
return np.random.choice(list(ACTIVATIONS.keys()))
|
|
163
160
|
|
|
164
161
|
|
|
162
|
+
# Registry
|
|
163
|
+
|
|
165
164
|
ACTIVATIONS: dict[str, Callable] = {
|
|
166
165
|
"tanh": tanh,
|
|
167
166
|
"ntanh": ntanh,
|
|
@@ -6,11 +6,9 @@ A connection links a source neuron to a target neuron with a weight. Supports op
|
|
|
6
6
|
connection types for future use (e.g. inhibitory).
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
9
|
from typing import TYPE_CHECKING
|
|
12
10
|
|
|
13
|
-
from evonet.
|
|
11
|
+
from evonet.enums import ConnectionType
|
|
14
12
|
|
|
15
13
|
if TYPE_CHECKING:
|
|
16
14
|
from evonet.neuron import Neuron
|
|
@@ -30,12 +28,13 @@ class Connection:
|
|
|
30
28
|
|
|
31
29
|
def __init__(
|
|
32
30
|
self,
|
|
33
|
-
source: Neuron,
|
|
34
|
-
target: Neuron,
|
|
31
|
+
source: "Neuron",
|
|
32
|
+
target: "Neuron",
|
|
35
33
|
weight: float = 1.0,
|
|
36
34
|
delay: int = 0,
|
|
37
35
|
conn_type: ConnectionType = ConnectionType.STANDARD,
|
|
38
36
|
) -> None:
|
|
37
|
+
|
|
39
38
|
self.source = source
|
|
40
39
|
self.target = target
|
|
41
40
|
self.weight = weight
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Core class for evolvable neural networks.
|
|
4
|
+
|
|
5
|
+
Manages neurons, connections, and forward computation. Prepares mutation, crossover, and
|
|
6
|
+
export interfaces.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from evonet.connection import Connection
|
|
14
|
+
from evonet.enums import ConnectionType, NeuronRole
|
|
15
|
+
from evonet.layer import Layer
|
|
16
|
+
from evonet.neuron import Neuron
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Nnet:
|
|
20
|
+
"""
|
|
21
|
+
Evolvable neural network with explicit topology.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
connections (list[Connection]): All directed, weighted edges.
|
|
25
|
+
input_neurons (list[Neuron]): Subset of neurons used as input nodes.
|
|
26
|
+
hidden_neurons (list[Neuron]): Subset of neurons used as hidden nodes.
|
|
27
|
+
output_neurons (list[Neuron]): Subset of neurons used as output nodes.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
self.layers: list[Layer] = []
|
|
32
|
+
|
|
33
|
+
def add_layer(self, count: int = 1) -> int:
|
|
34
|
+
"""
|
|
35
|
+
Add a layer to the network.
|
|
36
|
+
|
|
37
|
+
Parameter:
|
|
38
|
+
count (int): Number of layers to add
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
if count <= 0:
|
|
42
|
+
raise ValueError("Number of layers must be greater then zero")
|
|
43
|
+
|
|
44
|
+
for _ in range(count):
|
|
45
|
+
self.layers.append(Layer())
|
|
46
|
+
|
|
47
|
+
return len(self.layers) - 1
|
|
48
|
+
|
|
49
|
+
def add_neuron(
|
|
50
|
+
self,
|
|
51
|
+
layer_idx: int | None = None,
|
|
52
|
+
activation: str = "tanh",
|
|
53
|
+
bias: float = 0.0,
|
|
54
|
+
lable: str = "",
|
|
55
|
+
role: NeuronRole = NeuronRole.HIDDEN,
|
|
56
|
+
count: int = 1,
|
|
57
|
+
connect_layer: bool = True,
|
|
58
|
+
) -> Neuron:
|
|
59
|
+
|
|
60
|
+
if layer_idx is None:
|
|
61
|
+
layer_idx = len(self.layers) - 1 # Add neuron to last layer
|
|
62
|
+
|
|
63
|
+
if layer_idx < 0:
|
|
64
|
+
raise ValueError(f"Expected positiv layerindex: got {layer_idx}")
|
|
65
|
+
if layer_idx >= len(self.layers):
|
|
66
|
+
raise ValueError("Layerindex out off scope: got {layer_idx}")
|
|
67
|
+
|
|
68
|
+
for _ in range(count):
|
|
69
|
+
neuron = Neuron(activation=activation, bias=bias)
|
|
70
|
+
neuron.role = role
|
|
71
|
+
neuron.lable = lable
|
|
72
|
+
|
|
73
|
+
self.layers[layer_idx].neurons.append(neuron)
|
|
74
|
+
|
|
75
|
+
if connect_layer and layer_idx > 0:
|
|
76
|
+
# Finde letzten nicht-leeren Layer vor diesem
|
|
77
|
+
for prev_idx in range(layer_idx - 1, -1, -1):
|
|
78
|
+
prev_layer = self.layers[prev_idx]
|
|
79
|
+
if prev_layer.neurons:
|
|
80
|
+
for prev_neuron in prev_layer.neurons:
|
|
81
|
+
self.add_connection(prev_neuron, neuron)
|
|
82
|
+
break
|
|
83
|
+
return neuron
|
|
84
|
+
|
|
85
|
+
def add_connection(
|
|
86
|
+
self,
|
|
87
|
+
source: Neuron,
|
|
88
|
+
target: Neuron,
|
|
89
|
+
weight: float | None = None,
|
|
90
|
+
conn_type: ConnectionType = ConnectionType.STANDARD,
|
|
91
|
+
) -> None:
|
|
92
|
+
|
|
93
|
+
if weight is None:
|
|
94
|
+
weight = np.random.randn() * 0.5
|
|
95
|
+
|
|
96
|
+
conn = Connection(source, target, weight=weight, conn_type=conn_type)
|
|
97
|
+
source.outgoing.append(conn)
|
|
98
|
+
target.incoming.append(conn)
|
|
99
|
+
|
|
100
|
+
def reset(self) -> None:
|
|
101
|
+
"""Resets all neurons."""
|
|
102
|
+
for layer in self.layers:
|
|
103
|
+
for neuron in layer.neurons:
|
|
104
|
+
neuron.reset()
|
|
105
|
+
|
|
106
|
+
def calc(self, input_values: list[float]) -> list[float]:
|
|
107
|
+
|
|
108
|
+
self.reset()
|
|
109
|
+
|
|
110
|
+
# Set Inputs
|
|
111
|
+
input_layer = self.layers[0]
|
|
112
|
+
assert len(input_layer.neurons) == len(input_values)
|
|
113
|
+
for idx, neuron in enumerate(input_layer.neurons):
|
|
114
|
+
neuron.input = float(input_values[idx])
|
|
115
|
+
|
|
116
|
+
# Recurrent
|
|
117
|
+
for layer in self.layers:
|
|
118
|
+
for neuron in layer.neurons:
|
|
119
|
+
for conn in neuron.incoming:
|
|
120
|
+
if conn.type.name.lower() == "recurrent":
|
|
121
|
+
neuron.input += conn.source.last_output * conn.weight
|
|
122
|
+
else:
|
|
123
|
+
neuron.input += conn.source.output * conn.weight
|
|
124
|
+
|
|
125
|
+
for layer in self.layers:
|
|
126
|
+
for neuron in layer.neurons:
|
|
127
|
+
total = neuron.input + neuron.bias
|
|
128
|
+
neuron.output = neuron.activation(total)
|
|
129
|
+
for conn in neuron.outgoing:
|
|
130
|
+
conn.target.input += conn.weight * neuron.output
|
|
131
|
+
|
|
132
|
+
# Return Output
|
|
133
|
+
return [n.output for n in self.layers[-1].neurons]
|
|
134
|
+
|
|
135
|
+
def get_all_neurons(self) -> list[Neuron]:
|
|
136
|
+
return [n for layer in self.layers for n in layer.neurons]
|
|
137
|
+
|
|
138
|
+
def get_all_connections(self) -> list[Connection]:
|
|
139
|
+
return [c for n in self.get_all_neurons() for c in n.outgoing]
|
|
140
|
+
|
|
141
|
+
def __repr__(self) -> str:
|
|
142
|
+
total_neurons = sum(len(layer.neurons) for layer in self.layers)
|
|
143
|
+
input_neurons = len(self.layers[0].neurons) if self.layers else 0
|
|
144
|
+
output_neurons = len(self.layers[-1].neurons) if len(self.layers) > 1 else 0
|
|
145
|
+
hidden_neurons = total_neurons - input_neurons - output_neurons
|
|
146
|
+
|
|
147
|
+
total_connections = len(self.get_all_connections())
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
f"<Nnet | {len(self.layers)} layers, "
|
|
151
|
+
f"{total_neurons} neurons (I:{input_neurons} H:{hidden_neurons} "
|
|
152
|
+
f"O:{output_neurons}), "
|
|
153
|
+
f"{total_connections} connections "
|
|
154
|
+
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Basic mutation operations for evolvable neural networks.
|
|
4
|
+
|
|
5
|
+
Supports weight, bias, and structural mutations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import random
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from evonet.activation import ACTIVATIONS, random_function_name
|
|
13
|
+
from evonet.connection import Connection
|
|
14
|
+
from evonet.core import Nnet
|
|
15
|
+
from evonet.enums import ConnectionType, NeuronRole
|
|
16
|
+
from evonet.neuron import Neuron
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def mutate_activation(neuron: Neuron, std: float = 0.1) -> None:
|
|
20
|
+
neuron.activation_name = random_function_name()
|
|
21
|
+
neuron.activation = ACTIVATIONS[neuron.activation_name]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def mutate_activations(net: Nnet, probability: float = 1.0, std: float = 0.1) -> None:
|
|
25
|
+
for neuron in net.get_all_neurons():
|
|
26
|
+
if neuron.role != NeuronRole.INPUT and np.random.rand() < probability:
|
|
27
|
+
mutate_activation(neuron)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def mutate_weight(conn: Connection, std: float = 0.1) -> None:
|
|
31
|
+
conn.weight += np.random.normal(0, std)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def mutate_weights(net: Nnet, probability: float = 1.0, std: float = 0.1) -> None:
|
|
35
|
+
"""Applies Gaussian noise to all connection weights."""
|
|
36
|
+
|
|
37
|
+
for conn in net.get_all_connections():
|
|
38
|
+
if np.random.rand() < probability:
|
|
39
|
+
mutate_weight(conn, std)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def mutate_bias(neuron: Neuron, std: float = 0.1) -> None:
|
|
43
|
+
neuron.bias += np.random.normal(0, std)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def mutate_biases(net: Nnet, probability: float = 1.0, std: float = 0.1) -> None:
|
|
47
|
+
"""Applies Gaussian noise to all neuron biases (except input neurons)."""
|
|
48
|
+
for neuron in net.get_all_neurons():
|
|
49
|
+
if neuron.role != NeuronRole.INPUT and np.random.rand() < probability:
|
|
50
|
+
mutate_bias(neuron, std)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def add_random_connection(net: Nnet) -> None:
|
|
54
|
+
"""Creates a new connection between two random Neuronen."""
|
|
55
|
+
|
|
56
|
+
all_neurons = net.get_all_neurons()
|
|
57
|
+
if len(all_neurons) < 2:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
src = random.choice(all_neurons)
|
|
61
|
+
dst = random.choice(all_neurons)
|
|
62
|
+
|
|
63
|
+
if src == dst:
|
|
64
|
+
conn_type = ConnectionType.RECURRENT
|
|
65
|
+
else:
|
|
66
|
+
conn_type = ConnectionType.STANDARD
|
|
67
|
+
|
|
68
|
+
# Prüfen, ob Verbindung bereits existiert
|
|
69
|
+
if any(conn.target == dst for conn in src.outgoing):
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
net.add_connection(src, dst, conn_type=conn_type)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def remove_random_connection(net: Nnet) -> None:
|
|
76
|
+
"""Entfernt zufällig eine bestehende Verbindung."""
|
|
77
|
+
all_connections = net.get_all_connections()
|
|
78
|
+
if not all_connections:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
conn = random.choice(all_connections)
|
|
82
|
+
conn.source.outgoing.remove(conn)
|
|
83
|
+
conn.target.incoming.remove(conn)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def add_random_neuron(net: Nnet) -> None:
|
|
87
|
+
"""Fügt ein neues Hidden-Neuron in ein zufälliges Layer ein und verbindet es mit
|
|
88
|
+
vorhandenen Neuronen."""
|
|
89
|
+
if len(net.layers) < 2:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# Ziel-Layer wählen (nicht Input, nicht Output)
|
|
93
|
+
candidate_layers = net.layers[1:-1]
|
|
94
|
+
if not candidate_layers:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
layer = random.choice(candidate_layers)
|
|
98
|
+
net.add_neuron(
|
|
99
|
+
layer_idx=net.layers.index(layer),
|
|
100
|
+
activation="tanh",
|
|
101
|
+
role=NeuronRole.HIDDEN,
|
|
102
|
+
connect_layer=True,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def split_connection(net: Nnet, activation: str = "tanh", noise: float = 0.1) -> None:
|
|
107
|
+
"""Add Neuron between connection."""
|
|
108
|
+
all_connections = net.get_all_connections()
|
|
109
|
+
if not all_connections:
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
conn = random.choice(all_connections)
|
|
113
|
+
src, dst = conn.source, conn.target
|
|
114
|
+
|
|
115
|
+
insert_idx = None
|
|
116
|
+
for idx, layer in enumerate(net.layers):
|
|
117
|
+
if src in layer.neurons:
|
|
118
|
+
insert_idx = idx + 1
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
if insert_idx is None or insert_idx >= len(net.layers):
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
new_neuron = net.add_neuron(
|
|
125
|
+
layer_idx=insert_idx,
|
|
126
|
+
role=NeuronRole.HIDDEN,
|
|
127
|
+
activation=activation,
|
|
128
|
+
connect_layer=False,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Set new connections
|
|
132
|
+
src.outgoing.remove(conn)
|
|
133
|
+
dst.incoming.remove(conn)
|
|
134
|
+
|
|
135
|
+
weight = 1.0 + np.random.normal(0, noise)
|
|
136
|
+
net.add_connection(src, new_neuron, weight=weight)
|
|
137
|
+
net.add_connection(new_neuron, dst, weight=conn.weight)
|
|
@@ -6,12 +6,12 @@ Each neuron holds its activation function, input/output connections, bias value,
|
|
|
6
6
|
cached output from the last forward pass.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
9
|
from typing import Callable
|
|
12
10
|
from uuid import uuid4
|
|
13
11
|
|
|
14
12
|
from evonet.activation import ACTIVATIONS
|
|
13
|
+
from evonet.connection import Connection
|
|
14
|
+
from evonet.enums import NeuronRole
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class Neuron:
|
|
@@ -19,32 +19,44 @@ class Neuron:
|
|
|
19
19
|
Represents a single neuron in the network.
|
|
20
20
|
|
|
21
21
|
Attributes:
|
|
22
|
-
id (str): Unique identifier for tracking
|
|
22
|
+
id (str): Unique identifier for tracking.
|
|
23
23
|
activation_name (str): Name of the activation function.
|
|
24
24
|
bias (float): Bias value added to incoming inputs.
|
|
25
25
|
incoming (list): Incoming connections (to be filled externally).
|
|
26
26
|
outgoing (list): Outgoing connections (to be filled externally).
|
|
27
27
|
output (float): Cached result after activation.
|
|
28
|
+
lable (str): An optional lable
|
|
28
29
|
"""
|
|
29
30
|
|
|
30
|
-
def __init__(
|
|
31
|
+
def __init__(
|
|
32
|
+
self, activation: str = "tanh", lable: str = "", bias: float = 0.0
|
|
33
|
+
) -> None:
|
|
34
|
+
|
|
31
35
|
if activation not in ACTIVATIONS:
|
|
32
36
|
raise ValueError(f"Unknown activation function: '{activation}'")
|
|
33
37
|
self.id: str = str(uuid4())
|
|
38
|
+
self.role: NeuronRole = NeuronRole.HIDDEN
|
|
34
39
|
self.activation_name: str = activation
|
|
35
40
|
self.activation: Callable[[float], float] = ACTIVATIONS[activation]
|
|
36
41
|
self.bias: float = bias
|
|
37
|
-
self.incoming: list = []
|
|
38
|
-
self.outgoing: list = []
|
|
42
|
+
self.incoming: list[Connection] = []
|
|
43
|
+
self.outgoing: list[Connection] = []
|
|
44
|
+
self.input: float = 0.0
|
|
39
45
|
self.output: float = 0.0
|
|
46
|
+
self.last_output: float = 0.0
|
|
47
|
+
self.lable = lable
|
|
40
48
|
|
|
41
49
|
def reset(self) -> None:
|
|
42
|
-
|
|
50
|
+
self.last_output = self.output
|
|
43
51
|
self.output = 0.0
|
|
52
|
+
self.input = 0.0
|
|
44
53
|
|
|
45
54
|
def __repr__(self) -> str:
|
|
46
55
|
return (
|
|
47
|
-
f"
|
|
56
|
+
f"Neuron id={self.id[:6]} "
|
|
48
57
|
f"act={self.activation_name} "
|
|
49
|
-
f"
|
|
58
|
+
f"role={self.role} "
|
|
59
|
+
f"bias={self.bias:.2f} "
|
|
60
|
+
f"input={self.input:0.5f} "
|
|
61
|
+
f"output={self.output:0.5f}"
|
|
50
62
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evonet
|
|
3
|
-
Version: 0.1.0a0.
|
|
3
|
+
Version: 0.1.0a0.dev2
|
|
4
4
|
Summary: Evolvable neural network core for integration with EvoLib
|
|
5
5
|
Author-email: EvoLib <evolib@dismail.de>
|
|
6
6
|
License: MIT License
|
|
@@ -52,8 +52,43 @@ Dynamic: license-file
|
|
|
52
52
|
[](LICENSE)
|
|
53
53
|
[](https://github.com/EvoLib/evo-net)
|
|
54
54
|
|
|
55
|
-
**EvoNet** is a modular
|
|
56
|
-
It supports dynamic topologies, recurrent connections, and
|
|
55
|
+
**EvoNet** is a modular and evolvable neural network core designed for integration with [EvoLib](https://github.com/EvoLib/evo-lib).
|
|
56
|
+
It supports dynamic topologies, recurrent connections, per-neuron activation, and structural evolution – with a strong emphasis on **clarity**, **transparency**, and **didactic value**.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🔧 Features
|
|
61
|
+
|
|
62
|
+
- **Layer-based but flexible** – allows skip connections, cycles, and recurrent paths
|
|
63
|
+
- **Typed neuron roles and connection types** (`NeuronRole`, `ConnectionType`)
|
|
64
|
+
- **Topology-aware mutation system** – add/remove neurons and connections, mutate weights, change activations
|
|
65
|
+
- **Per-neuron activation functions** – configurable, extensible, evolvable
|
|
66
|
+
- **1-step recurrent state logic** – avoids multi-pass stabilization
|
|
67
|
+
- **Topology can grow at runtime** – with `add_neuron`, `add_connection`, `split_connection`
|
|
68
|
+
- **Debug-friendly architecture** – explicit IDs, labels, roles, directional graphs
|
|
69
|
+
- **Designed for evolutionary learning** – mutation, crossover, speciation ready
|
|
70
|
+
- **Lightweight & extensible** – pure Python, NumPy-based, no hard dependencies
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
> ⚠️ **This project is in early development (alpha)**. Interfaces and structure may change.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 🚀 Quick Example
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from evonet.core import Nnet
|
|
82
|
+
|
|
83
|
+
net = Nnet()
|
|
84
|
+
net.add_layer() # Input
|
|
85
|
+
net.add_layer() # Output
|
|
86
|
+
|
|
87
|
+
net.add_neuron(layer_idx=0, activation="linear", lable="in")
|
|
88
|
+
net.add_neuron(layer_idx=1, activation="linear", bias=0.5, lable="out", connect_layer=True)
|
|
89
|
+
|
|
90
|
+
print(net.calc([1.0]))
|
|
91
|
+
```
|
|
57
92
|
|
|
58
93
|
## 🪪 License
|
|
59
94
|
|
|
@@ -6,10 +6,11 @@ evonet/__init__.py
|
|
|
6
6
|
evonet/activation.py
|
|
7
7
|
evonet/connection.py
|
|
8
8
|
evonet/core.py
|
|
9
|
+
evonet/enums.py
|
|
9
10
|
evonet/io.py
|
|
11
|
+
evonet/layer.py
|
|
10
12
|
evonet/mutation.py
|
|
11
13
|
evonet/neuron.py
|
|
12
|
-
evonet/types.py
|
|
13
14
|
evonet/utils.py
|
|
14
15
|
evonet/visualize.py
|
|
15
16
|
evonet.egg-info/PKG-INFO
|
|
@@ -23,8 +23,8 @@ def test_softmax_sum_to_one() -> None:
|
|
|
23
23
|
np.testing.assert_almost_equal(np.sum(output), 1.0, decimal=6)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def
|
|
27
|
-
"""
|
|
26
|
+
def test_random_function_name_exists_in_registry() -> None:
|
|
27
|
+
"""random_function_name must return a valid name in the registry."""
|
|
28
28
|
for _ in range(10):
|
|
29
|
-
name = activation.
|
|
29
|
+
name = activation.random_function_name()
|
|
30
30
|
assert name in activation.ACTIVATIONS
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from evonet.core import Nnet
|
|
2
|
+
from evonet.enums import NeuronRole
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_forward_pass_identity() -> None:
|
|
6
|
+
"""Tests a minimal feedforward network with identity mapping."""
|
|
7
|
+
net = Nnet()
|
|
8
|
+
net.add_layer() # Input layer
|
|
9
|
+
net.add_layer() # Output layer
|
|
10
|
+
|
|
11
|
+
net.add_neuron(
|
|
12
|
+
layer_idx=0,
|
|
13
|
+
activation="linear",
|
|
14
|
+
role=NeuronRole.INPUT,
|
|
15
|
+
lable="in",
|
|
16
|
+
connect_layer=False,
|
|
17
|
+
)
|
|
18
|
+
net.add_neuron(
|
|
19
|
+
layer_idx=1,
|
|
20
|
+
activation="linear",
|
|
21
|
+
role=NeuronRole.OUTPUT,
|
|
22
|
+
lable="out",
|
|
23
|
+
connect_layer=False,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
src = net.layers[0].neurons[0]
|
|
27
|
+
dst = net.layers[1].neurons[0]
|
|
28
|
+
|
|
29
|
+
net.add_connection(src, dst, weight=1.0)
|
|
30
|
+
|
|
31
|
+
# Gewicht prüfen
|
|
32
|
+
assert abs(src.outgoing[0].weight - 1.0) < 1e-6
|
|
33
|
+
|
|
34
|
+
result = net.calc([0.75])
|
|
35
|
+
|
|
36
|
+
assert isinstance(result, list)
|
|
37
|
+
assert len(result) == 1
|
|
38
|
+
assert abs(result[0] - 0.75) < 1e-6
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_forward_pass_with_bias() -> None:
|
|
42
|
+
"""Tests a simple net with bias on the output neuron."""
|
|
43
|
+
net = Nnet()
|
|
44
|
+
net.add_layer()
|
|
45
|
+
net.add_layer()
|
|
46
|
+
|
|
47
|
+
net.add_neuron(
|
|
48
|
+
layer_idx=0,
|
|
49
|
+
activation="linear",
|
|
50
|
+
role=NeuronRole.INPUT,
|
|
51
|
+
lable="in",
|
|
52
|
+
connect_layer=False,
|
|
53
|
+
)
|
|
54
|
+
net.add_neuron(
|
|
55
|
+
layer_idx=1,
|
|
56
|
+
activation="linear",
|
|
57
|
+
role=NeuronRole.OUTPUT,
|
|
58
|
+
bias=0.5,
|
|
59
|
+
lable="out",
|
|
60
|
+
connect_layer=False,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
src = net.layers[0].neurons[0]
|
|
64
|
+
dst = net.layers[1].neurons[0]
|
|
65
|
+
|
|
66
|
+
net.add_connection(src, dst, weight=2.0)
|
|
67
|
+
|
|
68
|
+
# Gewicht prüfen
|
|
69
|
+
assert abs(src.outgoing[0].weight - 2.0) < 1e-6
|
|
70
|
+
|
|
71
|
+
result = net.calc([1.0])
|
|
72
|
+
|
|
73
|
+
assert isinstance(result, list)
|
|
74
|
+
assert len(result) == 1
|
|
75
|
+
assert abs(result[0] - 2.5) < 1e-6 # (1.0 * 2.0) + 0.5 = 2.5
|
evonet-0.1.0a0.dev1/README.md
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# EvoNet
|
|
2
|
-
[](https://github.com/EvoLib/evo-net/actions/workflows/ci.yml)
|
|
3
|
-
[](LICENSE)
|
|
4
|
-
[](https://github.com/EvoLib/evo-net)
|
|
5
|
-
|
|
6
|
-
**EvoNet** is a modular, evolvable neural network core designed for integration with [EvoLib](https://github.com/EvoLib/evo-lib).
|
|
7
|
-
It supports dynamic topologies, recurrent connections, and is optimized for mutation, crossover, and structural evolution.
|
|
8
|
-
|
|
9
|
-
## 🪪 License
|
|
10
|
-
|
|
11
|
-
This project is licensed under the [MIT License](https://github.com/EvoLib/evo-net/tree/main/LICENSE).
|
|
12
|
-
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
"""
|
|
3
|
-
Core class for evolvable neural networks.
|
|
4
|
-
|
|
5
|
-
Manages neurons, connections, and forward computation. Prepares mutation, crossover, and
|
|
6
|
-
export interfaces.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import numpy as np
|
|
12
|
-
|
|
13
|
-
from evonet.connection import Connection
|
|
14
|
-
from evonet.neuron import Neuron
|
|
15
|
-
from evonet.types import NeuronRole
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Nnet:
|
|
19
|
-
"""
|
|
20
|
-
Evolvable neural network with explicit topology.
|
|
21
|
-
|
|
22
|
-
Attributes:
|
|
23
|
-
neurons (list[Neuron]): All neurons in the network.
|
|
24
|
-
connections (list[Connection]): All directed, weighted edges.
|
|
25
|
-
input_neurons (list[Neuron]): Subset of neurons used as input nodes.
|
|
26
|
-
output_neurons (list[Neuron]): Subset of neurons used as output nodes.
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
def __init__(self) -> None:
|
|
30
|
-
self.neurons: list[Neuron] = []
|
|
31
|
-
self.connections: list[Connection] = []
|
|
32
|
-
self.input_neurons: list[Neuron] = []
|
|
33
|
-
self.output_neurons: list[Neuron] = []
|
|
34
|
-
|
|
35
|
-
def add_neuron(self, neuron: Neuron, role: NeuronRole = NeuronRole.HIDDEN) -> None:
|
|
36
|
-
"""Adds a neuron to the network and assigns its functional role."""
|
|
37
|
-
self.neurons.append(neuron)
|
|
38
|
-
if role == NeuronRole.INPUT:
|
|
39
|
-
self.input_neurons.append(neuron)
|
|
40
|
-
elif role == NeuronRole.OUTPUT:
|
|
41
|
-
self.output_neurons.append(neuron)
|
|
42
|
-
|
|
43
|
-
def add_connection(self, conn: Connection) -> None:
|
|
44
|
-
"""Adds a connection and updates neuron references."""
|
|
45
|
-
self.connections.append(conn)
|
|
46
|
-
conn.target.incoming.append(conn)
|
|
47
|
-
conn.source.outgoing.append(conn)
|
|
48
|
-
|
|
49
|
-
def reset(self) -> None:
|
|
50
|
-
"""Resets output values of all neurons (before forward pass)."""
|
|
51
|
-
for neuron in self.neurons:
|
|
52
|
-
neuron.reset()
|
|
53
|
-
|
|
54
|
-
def calc(self, input_values: list[float]) -> list[float]:
|
|
55
|
-
"""
|
|
56
|
-
Forward pass through the network.
|
|
57
|
-
|
|
58
|
-
Args:
|
|
59
|
-
input_values: values to assign to input neurons
|
|
60
|
-
|
|
61
|
-
Returns:
|
|
62
|
-
outputs from output neurons (after activation)
|
|
63
|
-
"""
|
|
64
|
-
assert len(input_values) == len(self.input_neurons), "Input size mismatch"
|
|
65
|
-
self.reset()
|
|
66
|
-
|
|
67
|
-
# Assign inputs
|
|
68
|
-
for i, value in enumerate(input_values):
|
|
69
|
-
self.input_neurons[i].output = float(value)
|
|
70
|
-
|
|
71
|
-
# Topological forward computation (assumes acyclic)
|
|
72
|
-
visited = set(self.input_neurons)
|
|
73
|
-
queue = [n for n in self.neurons if n not in visited]
|
|
74
|
-
|
|
75
|
-
while queue:
|
|
76
|
-
progressed = False
|
|
77
|
-
for neuron in queue[:]:
|
|
78
|
-
if all(src.source in visited for src in neuron.incoming):
|
|
79
|
-
total = sum(c.get_signal() for c in neuron.incoming) + neuron.bias
|
|
80
|
-
neuron.output = neuron.activation(total)
|
|
81
|
-
visited.add(neuron)
|
|
82
|
-
queue.remove(neuron)
|
|
83
|
-
progressed = True
|
|
84
|
-
if not progressed:
|
|
85
|
-
break # Prevent infinite loop
|
|
86
|
-
|
|
87
|
-
return [n.output for n in self.output_neurons]
|
|
88
|
-
|
|
89
|
-
def __repr__(self) -> str:
|
|
90
|
-
return (
|
|
91
|
-
f"<Nnet | {len(self.neurons)} neurons, "
|
|
92
|
-
f"{len(self.connections)} connections>"
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
def get_weights(self) -> np.ndarray:
|
|
96
|
-
"""Returns all connection weights as a flat NumPy array."""
|
|
97
|
-
import numpy as np
|
|
98
|
-
|
|
99
|
-
return np.array([c.weight for c in self.connections], dtype=float)
|
|
100
|
-
|
|
101
|
-
def set_weights(self, vector: np.ndarray) -> None:
|
|
102
|
-
"""Assigns connection weights from a flat NumPy array."""
|
|
103
|
-
assert len(vector) == len(self.connections), "Weight vector length mismatch"
|
|
104
|
-
for i, c in enumerate(self.connections):
|
|
105
|
-
c.weight = float(vector[i])
|
|
106
|
-
|
|
107
|
-
def get_biases(self) -> np.ndarray:
|
|
108
|
-
"""Returns all neuron biases as a flat NumPy array."""
|
|
109
|
-
import numpy as np
|
|
110
|
-
|
|
111
|
-
return np.array([n.bias for n in self.neurons], dtype=float)
|
|
112
|
-
|
|
113
|
-
def set_biases(self, vector: np.ndarray) -> None:
|
|
114
|
-
"""Assigns neuron biases from a flat NumPy array."""
|
|
115
|
-
assert len(vector) == len(self.neurons), "Bias vector length mismatch"
|
|
116
|
-
for i, n in enumerate(self.neurons):
|
|
117
|
-
n.bias = float(vector[i])
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
"""
|
|
3
|
-
Mutation operators for evolvable neural networks.
|
|
4
|
-
|
|
5
|
-
Includes mutations for weights, biases, and (optionally) activation functions. Structure
|
|
6
|
-
mutations will be implemented separately.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import random
|
|
10
|
-
|
|
11
|
-
from evonet.core import Nnet
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def mutate_weights(
|
|
15
|
-
net: Nnet,
|
|
16
|
-
std: float = 0.1,
|
|
17
|
-
mutation_rate: float = 1.0,
|
|
18
|
-
weight_min: float = -5.0,
|
|
19
|
-
weight_max: float = 5.0,
|
|
20
|
-
) -> None:
|
|
21
|
-
"""
|
|
22
|
-
Applies Gaussian noise to connection weights.
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
net (Nnet): The network to mutate.
|
|
26
|
-
std (float): Standard deviation of noise.
|
|
27
|
-
mutation_rate (float): Probability per connection to mutate.
|
|
28
|
-
weight_min (float): Lower bound for weights.
|
|
29
|
-
weight_max (float): Upper bound for weights.
|
|
30
|
-
"""
|
|
31
|
-
for conn in net.connections:
|
|
32
|
-
if random.random() < mutation_rate:
|
|
33
|
-
noise = random.gauss(0.0, std)
|
|
34
|
-
conn.weight += noise
|
|
35
|
-
conn.weight = max(min(conn.weight, weight_max), weight_min)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def mutate_biases(
|
|
39
|
-
net: Nnet,
|
|
40
|
-
std: float = 0.1,
|
|
41
|
-
mutation_rate: float = 1.0,
|
|
42
|
-
bias_min: float = -5.0,
|
|
43
|
-
bias_max: float = 5.0,
|
|
44
|
-
) -> None:
|
|
45
|
-
"""
|
|
46
|
-
Applies Gaussian noise to neuron biases.
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
net (Nnet): The network to mutate.
|
|
50
|
-
std (float): Standard deviation of noise.
|
|
51
|
-
mutation_rate (float): Probability per neuron to mutate.
|
|
52
|
-
bias_min (float): Lower bound for biases.
|
|
53
|
-
bias_max (float): Upper bound for biases.
|
|
54
|
-
"""
|
|
55
|
-
for neuron in net.neurons:
|
|
56
|
-
if random.random() < mutation_rate:
|
|
57
|
-
noise = random.gauss(0.0, std)
|
|
58
|
-
neuron.bias += noise
|
|
59
|
-
neuron.bias = max(min(neuron.bias, bias_max), bias_min)
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from evonet.connection import Connection
|
|
2
|
-
from evonet.core import Nnet
|
|
3
|
-
from evonet.neuron import Neuron
|
|
4
|
-
from evonet.types import NeuronRole
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def test_forward_pass_identity() -> None:
|
|
8
|
-
"""Testet ein einfaches Netz mit einem Input und einem Output."""
|
|
9
|
-
net = Nnet()
|
|
10
|
-
|
|
11
|
-
# Neuronen erstellen
|
|
12
|
-
n_input = Neuron(activation="linear")
|
|
13
|
-
n_output = Neuron(activation="linear")
|
|
14
|
-
|
|
15
|
-
# Netz aufbauen
|
|
16
|
-
net.add_neuron(n_input, role=NeuronRole.INPUT)
|
|
17
|
-
net.add_neuron(n_output, role=NeuronRole.OUTPUT)
|
|
18
|
-
net.add_connection(Connection(n_input, n_output, weight=1.0))
|
|
19
|
-
|
|
20
|
-
# Eingabe --> Ausgabe testen
|
|
21
|
-
x = [0.75]
|
|
22
|
-
y = net.calc(x)
|
|
23
|
-
|
|
24
|
-
assert isinstance(y, list)
|
|
25
|
-
assert len(y) == 1
|
|
26
|
-
assert abs(y[0] - 0.75) < 1e-6
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def test_forward_pass_with_bias() -> None:
|
|
30
|
-
"""Testet Netz mit Bias am Output-Neuron."""
|
|
31
|
-
net = Nnet()
|
|
32
|
-
|
|
33
|
-
n_input = Neuron(activation="linear")
|
|
34
|
-
n_output = Neuron(activation="linear", bias=0.5)
|
|
35
|
-
|
|
36
|
-
net.add_neuron(n_input, role=NeuronRole.INPUT)
|
|
37
|
-
net.add_neuron(n_output, role=NeuronRole.OUTPUT)
|
|
38
|
-
net.add_connection(Connection(n_input, n_output, weight=2.0))
|
|
39
|
-
|
|
40
|
-
y = net.calc([1.0])
|
|
41
|
-
|
|
42
|
-
assert abs(y[0] - 2.5) < 1e-6 # (1.0 * 2.0) + 0.5 = 2.5
|
|
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
|