Source code for evolib.representation.netvector
# SPDX-License-Identifier: MIT
"""
NetVector – Interpreter for flat Vectors as feedforward neural networks.
This module provides a lightweight helper class that interprets a flat Vector
(e.g. created via dim_type='net') as a fully connected feedforward network with
arbitrary layer structure and activation function.
NetVector does not contain any trainable parameters or evolutionary logic itself.
Instead, it unpacks and applies a flat vector (weights + biases) to input data.
Typical use case:
- Use Vector as evolvable parameter container (mutation, crossover etc.)
- Use NetVector to define the network structure and perform forward evaluations
Example:
para = Vector(...) # created via normal_initializer_net
net = NetVector(dim=[1, 8, 1], activation="tanh")
y = net.forward(x, para.vector)
"""
from typing import Callable
import numpy as np
from evolib.config.schema import FullConfig
ACTIVATIONS: dict[str, Callable[[np.ndarray], np.ndarray]] = {
"tanh": np.tanh,
"relu": lambda x: np.maximum(0, x),
"linear": lambda x: x,
}
[docs]
class NetVector:
def __init__(self, dim: list[int], activation: str = "tanh") -> None:
if not isinstance(dim, list) or not all(isinstance(d, int) for d in dim):
raise ValueError(f"dim must be list[int], but got: {dim} ({type(dim)})")
if len(dim) < 2:
raise ValueError("dim must include at least input and output layers")
self.dim = dim
self.n_layers = len(dim) - 1
self.activation_fn = ACTIVATIONS[activation]
self.weight_shapes = [(dim[i + 1], dim[i]) for i in range(self.n_layers)]
self.bias_shapes = [(dim[i + 1],) for i in range(self.n_layers)]
self.n_parameters = sum(
np.prod(s) for s in self.weight_shapes + self.bias_shapes
)
[docs]
def forward(self, x: np.ndarray, vector: np.ndarray) -> np.ndarray:
"""
Evaluates the network on input x using the flat parameter vector. Activation is
applied after all but the last layer.
Args:
x (np.ndarray): Input vector (shape: [input_dim] or [batch, input_dim])
vector (np.ndarray): Flat parameter vector with correct dimension
Returns:
np.ndarray: Output of the network
"""
weights, biases = self._unpack_parameters(vector)
h = x
for i in range(self.n_layers):
h = weights[i] @ h + biases[i]
if i < self.n_layers - 1:
h = self.activation_fn(h)
return h
def _unpack_parameters(
self, vector: np.ndarray
) -> tuple[list[np.ndarray], list[np.ndarray]]:
"""
Decomposes the flat vector into per-layer weights and biases.
Args:
vector (np.ndarray): Flat parameter vector
Returns:
Tuple of two lists: weights and biases
"""
i = 0
weights, biases = [], []
for w_shape, b_shape in zip(self.weight_shapes, self.bias_shapes):
w_size = int(np.prod(w_shape))
b_size = int(np.prod(b_shape))
weights.append(vector[i : i + w_size].reshape(w_shape))
i += w_size
biases.append(vector[i : i + b_size].reshape(b_shape))
i += b_size
return weights, biases
[docs]
@classmethod
def from_config(cls, cfg: FullConfig, module: str) -> "NetVector":
mod = cfg.modules[module]
if not isinstance(mod.dim, list):
raise TypeError(f"Module '{module}': expected dim as list[int]")
return cls(dim=mod.dim, activation=mod.activation or "tanh")