Skip to content

neural_fields

NeuralField(input_size, hidden_size, output_size=None, input_embedding=None, output_embedding=None, activation_nonlin=torch.sigmoid, mirrored_conv_weights=True, conv_kernel_size=None, conv_padding_mode='circular', conv_out_channels=1, conv_pooling_norm=1, tau_init=10, tau_learnable=True, kappa_init=1e-05, kappa_learnable=True, potentials_init=None, init_param_kwargs=None, device='cpu', dtype=None)

Bases: PotentialBased

A potential-based recurrent neural network according to [Amari, 1977].

See Also

[Amari, 1977] S.-I. Amari, "Dynamics of Pattern Formation in Lateral-Inhibition Type Neural Fields", Biological Cybernetics, 1977.

hidden_size: Number of neurons with potential in the (single) hidden layer.
output_size: Number of output dimensions. By default, the number of outputs is equal to the number of
    hidden neurons.
input_embedding: Optional (custom) [Module][torch.nn.Module] to extract features from the inputs.
    This module must transform the inputs such that the dimensionality matches the number of
    neurons of the neural field, i.e., `hidden_size`. By default, a [linear layer][torch.nn.Linear]
    without biases is used.
output_embedding: Optional (custom) [Module][torch.nn.Module] to compute the outputs from the activations.
    This module must map the activations of shape (`hidden_size`,) to the outputs of shape (`output_size`,)
    By default, a [linear layer][torch.nn.Linear] without biases is used.
activation_nonlin: Nonlinearity used to compute the activations from the potential levels.
mirrored_conv_weights: If `True`, re-use weights for the second half of the kernel to create a
    symmetric convolution kernel.
conv_kernel_size: Size of the kernel for the 1-dim convolution along the potential-based neurons.
conv_padding_mode: Padding mode forwarded to [Conv1d][torch.nn.Conv1d], options are "circular",
    "reflect", or "zeros".
conv_out_channels: Number of filter for the 1-dim convolution along the potential-based neurons.
conv_pooling_norm: Norm type of the [torch.nn.LPPool1d][] pooling layer applied after the convolution.
    Unlike in typical scenarios, here the pooling is performed over the channel dimension. Thus, varying
    `conv_pooling_norm` only has an effect if `conv_out_channels > 1`.
tau_init: Initial value for the shared time constant of the potentials.
tau_learnable: Whether the time constant is a learnable parameter or fixed.
kappa_init: Initial value for the cubic decay, pass 0 to disable the cubic decay.
kappa_learnable: Whether the cubic decay is a learnable parameter or fixed.
potentials_init: Initial for the potentials, i.e., the network's hidden state.
init_param_kwargs: Additional keyword arguments for the policy parameter initialization.
device: Device to move this module to (after initialization).
dtype: Data type forwarded to the initializer of [Conv1d][torch.nn.Conv1d].
Source code in neuralfields/neural_fields.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def __init__(
    self,
    input_size: int,
    hidden_size: int,
    output_size: Optional[int] = None,
    input_embedding: Optional[nn.Module] = None,
    output_embedding: Optional[nn.Module] = None,
    activation_nonlin: Union[ActivationFunction, Sequence[ActivationFunction]] = torch.sigmoid,
    mirrored_conv_weights: bool = True,
    conv_kernel_size: Optional[int] = None,
    conv_padding_mode: str = "circular",
    conv_out_channels: int = 1,
    conv_pooling_norm: int = 1,
    tau_init: Union[float, int] = 10,
    tau_learnable: bool = True,
    kappa_init: Union[float, int] = 1e-5,
    kappa_learnable: bool = True,
    potentials_init: Optional[torch.Tensor] = None,
    init_param_kwargs: Optional[dict] = None,
    device: Union[str, torch.device] = "cpu",
    dtype: Optional[torch.dtype] = None,
):
    """
    Args:
        input_size: Number of input dimensions.
        hidden_size: Number of neurons with potential in the (single) hidden layer.
        output_size: Number of output dimensions. By default, the number of outputs is equal to the number of
            hidden neurons.
        input_embedding: Optional (custom) [Module][torch.nn.Module] to extract features from the inputs.
            This module must transform the inputs such that the dimensionality matches the number of
            neurons of the neural field, i.e., `hidden_size`. By default, a [linear layer][torch.nn.Linear]
            without biases is used.
        output_embedding: Optional (custom) [Module][torch.nn.Module] to compute the outputs from the activations.
            This module must map the activations of shape (`hidden_size`,) to the outputs of shape (`output_size`,)
            By default, a [linear layer][torch.nn.Linear] without biases is used.
        activation_nonlin: Nonlinearity used to compute the activations from the potential levels.
        mirrored_conv_weights: If `True`, re-use weights for the second half of the kernel to create a
            symmetric convolution kernel.
        conv_kernel_size: Size of the kernel for the 1-dim convolution along the potential-based neurons.
        conv_padding_mode: Padding mode forwarded to [Conv1d][torch.nn.Conv1d], options are "circular",
            "reflect", or "zeros".
        conv_out_channels: Number of filter for the 1-dim convolution along the potential-based neurons.
        conv_pooling_norm: Norm type of the [torch.nn.LPPool1d][] pooling layer applied after the convolution.
            Unlike in typical scenarios, here the pooling is performed over the channel dimension. Thus, varying
            `conv_pooling_norm` only has an effect if `conv_out_channels > 1`.
        tau_init: Initial value for the shared time constant of the potentials.
        tau_learnable: Whether the time constant is a learnable parameter or fixed.
        kappa_init: Initial value for the cubic decay, pass 0 to disable the cubic decay.
        kappa_learnable: Whether the cubic decay is a learnable parameter or fixed.
        potentials_init: Initial for the potentials, i.e., the network's hidden state.
        init_param_kwargs: Additional keyword arguments for the policy parameter initialization.
        device: Device to move this module to (after initialization).
        dtype: Data type forwarded to the initializer of [Conv1d][torch.nn.Conv1d].
    """
    if hidden_size < 2:
        raise ValueError("The humber of hidden neurons hidden_size must be at least 2!")
    if conv_kernel_size is None:
        conv_kernel_size = hidden_size
    if conv_padding_mode not in ["circular", "reflect", "zeros"]:
        raise ValueError("The conv_padding_mode must be either 'circular', 'reflect', or 'zeros'!")
    if not callable(activation_nonlin):
        raise ValueError("The activation function activation_nonlin must be a callable!")
    init_param_kwargs = init_param_kwargs if init_param_kwargs is not None else dict()

    # Set the multiprocessing start method to spawn, since PyTorch is using the GPU for convolutions if it can.
    if mp.get_start_method(allow_none=True) != "spawn":
        mp.set_start_method("spawn", force=True)

    # Create the common layers and parameters.
    super().__init__(
        input_size=input_size,
        hidden_size=hidden_size,
        output_size=output_size,
        activation_nonlin=activation_nonlin,
        tau_init=tau_init,
        tau_learnable=tau_learnable,
        kappa_init=kappa_init,
        kappa_learnable=kappa_learnable,
        potentials_init=potentials_init,
        input_embedding=input_embedding,
        output_embedding=output_embedding,
        device=device,
    )

    # Create the custom convolution layer that models the interconnection of neurons, i.e., their potentials.
    self.mirrored_conv_weights = mirrored_conv_weights
    conv1d_class = MirroredConv1d if self.mirrored_conv_weights else nn.Conv1d
    self.conv_layer = conv1d_class(
        in_channels=1,  # treat potentials as a time series of values (convolutions is over the "time" axis)
        out_channels=conv_out_channels,
        kernel_size=conv_kernel_size,
        padding_mode=conv_padding_mode,
        padding="same",  # to preserve the length od the output sequence
        bias=False,
        stride=1,
        dilation=1,
        groups=1,
        device=device,
        dtype=dtype,
    )
    init_param_(self.conv_layer, **init_param_kwargs)

    # Create a pooling layer that reduced all output channels to one.
    self.conv_pooling_layer = torch.nn.LPPool1d(
        conv_pooling_norm, kernel_size=conv_out_channels, stride=conv_out_channels
    )

    # Create the layer that converts the activations of the previous time step into potentials.
    self.potentials_to_activations = IndependentNonlinearitiesLayer(
        self._hidden_size, activation_nonlin, bias=True, weight=True
    )

    # Create the custom output embedding layer that combines the activations.
    self.output_embedding = nn.Linear(self._hidden_size, self.output_size, bias=False)

    # Move the complete model to the given device.
    self.to(device=device)

device: torch.device property

Get the device this model is located on. This assumes that all parts are located on the same device.

hidden_size: int property

Get the number of neurons in the neural field layer, i.e., the ones with the in-/exhibition dynamics.

kappa: Union[torch.Tensor, nn.Parameter] property

Get the cubic decay parameter \(\kappa\).

param_values: torch.Tensor property writable

Get the module's parameters as a 1-dimensional array. The values are copied, thus modifying the return value does not propagate back to the module parameters.

stimuli_external: torch.Tensor property

Get the neurons' external stimuli, resulting from the current inputs. This property is useful for recording during a simulation / rollout.

stimuli_internal: torch.Tensor property

Get the neurons' internal stimuli, resulting from the previous activations of the neurons. This property is useful for recording during a simulation / rollout.

tau: Union[torch.Tensor, nn.Parameter] property

Get the timescale parameter, called \(\tau\) in the original paper [Amari_77].

transform_to_img_space: Callable[[torch.Tensor], torch.Tensor] = torch.exp class-attribute instance-attribute

Function to map parameters to the image space of the original problem.

transform_to_opt_space: Callable[[torch.Tensor], torch.Tensor] = torch.log class-attribute instance-attribute

Function to map parameters to the optimization space.

forward(inputs, hidden=None)

Compute the external and internal stimuli, advance the potential dynamics for one time step, and return the model's output for several time steps in a row.

This method essentially calls forward_one_step several times in a row.

Parameters:

Name Type Description Default
inputs Tensor

Inputs of shape (batch_size, num_steps, dim_input) to evaluate the network on.

required
hidden Optional[Tensor]

Initial values of the hidden states, i.e., the potentials. By default, the network initialized the hidden state to be all zeros. However, via this argument one can set a specific initial value for the potentials. Depending on the shape of inputs, hidden is of shape (hidden_size,) if the input was not batched, else of shape (batch_size, hidden_size).

None

Returns:

Type Description
Tensor

The outputs, i.e., the (linearly combined) activations, and all intermediate potential values, both of

Tensor

shape (batch_size, num_steps, dim_output).

Source code in neuralfields/potential_based.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
def forward(self, inputs: torch.Tensor, hidden: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor, torch.Tensor]:
    """Compute the external and internal stimuli, advance the potential dynamics for one time step, and return
    the model's output for several time steps in a row.

    This method essentially calls [forward_one_step][neuralfields.PotentialBased.forward_one_step] several times
    in a row.

    Args:
        inputs: Inputs of shape `(batch_size, num_steps, dim_input)` to evaluate the network on.
        hidden: Initial values of the hidden states, i.e., the potentials. By default, the network initialized
            the hidden state to be all zeros. However, via this argument one can set a specific initial value
            for the potentials. Depending on the shape of `inputs`, `hidden` is of shape `(hidden_size,)` if
            the input was not batched, else of shape `(batch_size, hidden_size)`.

    Returns:
        The outputs, i.e., the (linearly combined) activations, and all intermediate potential values, both of
        shape `(batch_size, num_steps, dim_output)`.
    """
    # Bring the sequence of inputs into the shape (batch_size, num_steps, dim_input).
    batch_size = PotentialBased._infer_batch_size(inputs)
    inputs = inputs.view(batch_size, -1, self.input_size)  # moved to the desired device by forward_one_step() later

    # If given use the hidden tensor, i.e., the potentials of the last step, else initialize them.
    hidden = self.init_hidden(batch_size, hidden)  # moved to the desired device by forward_one_step() later

    # Iterate over the time dimension. Do this in parallel for all batched which are still along the 1st dimension.
    inputs = inputs.permute(1, 0, 2)  # move time to first dimension for easy iterating
    outputs_all = []
    hidden_all = []
    for inp in inputs:
        outputs, hidden_next = self.forward_one_step(inp, hidden)
        hidden = hidden_next.clone()
        outputs_all.append(outputs)
        hidden_all.append(hidden_next)

    # Return the outputs and hidden states, both stacked along the time dimension.
    return torch.stack(outputs_all, dim=1), torch.stack(hidden_all, dim=1)

init_hidden(batch_size=None, potentials_init=None)

Provide initial values for the hidden parameters. This usually is a zero tensor.

Parameters:

Name Type Description Default
batch_size Optional[int]

Number of batches, i.e., states to track in parallel.

None
potentials_init Optional[Tensor]

Initial values for the potentials to override the networks default values with.

None

Returns:

Type Description
Union[Tensor, Parameter]

Tensor of shape (hidden_size,) if hidden was not batched, else of shape (batch_size, hidden_size).

Source code in neuralfields/potential_based.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
def init_hidden(
    self, batch_size: Optional[int] = None, potentials_init: Optional[torch.Tensor] = None
) -> Union[torch.Tensor, torch.nn.Parameter]:
    """Provide initial values for the hidden parameters. This usually is a zero tensor.

    Args:
        batch_size: Number of batches, i.e., states to track in parallel.
        potentials_init: Initial values for the potentials to override the networks default values with.

    Returns:
        Tensor of shape `(hidden_size,)` if `hidden` was not batched, else of shape `(batch_size, hidden_size)`.
    """
    if potentials_init is None:
        if batch_size is None:
            return self._potentials_init.view(-1)
        return self._potentials_init.repeat(batch_size, 1).to(device=self.device)

    return potentials_init.to(device=self.device)

potentials_dot(potentials, stimuli)

Compute the derivative of the neurons' potentials w.r.t. time.

\(/tau /dot{u} = s + h - u + /kappa (h - u)^3, /quad /text{with} s = s_{int} + s_{ext} = W*o + /int{w(u, v) f(u) dv}\) with the potentials \(u\), the combined stimuli \(s\), the resting level \(h\), and the cubic decay \(\kappa\).

Parameters:

Name Type Description Default
potentials Tensor

Potential values at the current point in time, of shape (hidden_size,).

required
stimuli Tensor

Sum of external and internal stimuli at the current point in time, of shape (hidden_size,).

required

Returns:

Type Description
Tensor

Time derivative of the potentials \(\frac{dp}{dt}\), of shape (hidden_size,).

Source code in neuralfields/neural_fields.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def potentials_dot(self, potentials: torch.Tensor, stimuli: torch.Tensor) -> torch.Tensor:
    r"""Compute the derivative of the neurons' potentials w.r.t. time.

     $/tau /dot{u} = s + h - u + /kappa (h - u)^3,
    /quad /text{with} s = s_{int} + s_{ext} = W*o + /int{w(u, v) f(u) dv}$
    with the potentials $u$, the combined stimuli $s$, the resting level $h$, and the cubic decay $\kappa$.

    Args:
        potentials: Potential values at the current point in time, of shape `(hidden_size,)`.
        stimuli: Sum of external and internal stimuli at the current point in time, of shape `(hidden_size,)`.

    Returns:
        Time derivative of the potentials $\frac{dp}{dt}$, of shape `(hidden_size,)`.
    """
    rhs = stimuli + self.resting_level - potentials + self.kappa * torch.pow(self.resting_level - potentials, 3)
    return rhs / self.tau