| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- from __future__ import annotations
- from collections.abc import Callable
- import torch
- import torch.nn.functional as F
- from torch import Tensor, nn
- class TimeBatchNorm2d(nn.BatchNorm1d):
- """A batch normalization layer that normalizes over the last two dimensions of a
- sequence in PyTorch, mimicking Keras behavior.
- This class extends nn.BatchNorm1d to apply batch normalization across time and
- feature dimensions.
- Attributes:
- num_time_steps (int): Number of time steps in the input.
- num_channels (int): Number of channels in the input.
- """
- def __init__(self, normalized_shape: tuple[int, int]):
- """Initializes the TimeBatchNorm2d module.
- Args:
- normalized_shape (tuple[int, int]): A tuple (num_time_steps, num_channels)
- representing the shape of the time and feature dimensions to normalize.
- """
- num_time_steps, num_channels = normalized_shape
- super().__init__(num_channels * num_time_steps)
- self.num_time_steps = num_time_steps
- self.num_channels = num_channels
- def forward(self, x: Tensor) -> Tensor:
- """Applies the batch normalization over the last two dimensions of the input tensor.
- Args:
- x (Tensor): A 3D tensor with shape (N, S, C), where N is the batch size,
- S is the number of time steps, and C is the number of channels.
- Returns:
- Tensor: A 3D tensor with batch normalization applied over the last two dims.
- Raises:
- ValueError: If the input tensor is not 3D.
- """
- if x.ndim != 3:
- raise ValueError(f"Expected 3D input tensor, but got {x.ndim}D tensor instead.")
- # Reshaping input to combine time and feature dimensions for normalization
- x = x.reshape(x.shape[0], -1, 1)
- # Applying batch normalization
- x = super().forward(x)
- # Reshaping back to original dimensions (N, S, C)
- x = x.reshape(x.shape[0], self.num_time_steps, self.num_channels)
- return x
- class FeatureMixing(nn.Module):
- """A module for feature mixing with flexibility in normalization and activation.
- This module provides options for batch normalization before or after mixing features,
- uses dropout for regularization, and allows for different activation functions.
- Args:
- sequence_length: The length of the sequences to be transformed.
- input_channels: The number of input channels to the module.
- output_channels: The number of output channels from the module.
- ff_dim: The dimension of the feed-forward network internal to the module.
- activation_fn: The activation function used within the feed-forward network.
- dropout_rate: The dropout probability used for regularization.
- normalize_before: A boolean indicating whether to apply normalization before
- the rest of the operations.
- """
- def __init__(
- self,
- sequence_length: int,
- input_channels: int,
- output_channels: int,
- ff_dim: int,
- activation_fn: Callable[[torch.Tensor], torch.Tensor] = F.relu,
- dropout_rate: float = 0.1,
- normalize_before: bool = True,
- norm_type: type[nn.Module] = TimeBatchNorm2d,
- ):
- """Initializes the FeatureMixing module with the provided parameters."""
- super().__init__()
- self.norm_before = (
- norm_type((sequence_length, input_channels))
- if normalize_before
- else nn.Identity()
- )
- self.norm_after = (
- norm_type((sequence_length, output_channels))
- if not normalize_before
- else nn.Identity()
- )
- self.activation_fn = activation_fn
- self.dropout = nn.Dropout(dropout_rate)
- self.fc1 = nn.Linear(input_channels, ff_dim)
- self.fc2 = nn.Linear(ff_dim, output_channels)
- self.projection = (
- nn.Linear(input_channels, output_channels)
- if input_channels != output_channels
- else nn.Identity()
- )
- def forward(self, x: torch.Tensor) -> torch.Tensor:
- """Forward pass for the FeatureMixing module.
- Args:
- x: A 3D tensor with shape (N, C, L) where C is the channel dimension.
- Returns:
- The output tensor after feature mixing.
- """
- x_proj = self.projection(x)
- x = self.norm_before(x)
- x = self.fc1(x) # Apply the first linear transformation.
- x = self.activation_fn(x) # Apply the activation function.
- x = self.dropout(x) # Apply dropout for regularization.
- x = self.fc2(x) # Apply the second linear transformation.
- x = self.dropout(x) # Apply dropout again if needed.
- x = x_proj + x # Add the projection shortcut to the transformed features.
- return self.norm_after(x)
- class ConditionalFeatureMixing(nn.Module):
- """Conditional feature mixing module that incorporates static features.
- This module extends the feature mixing process by including static features. It uses
- a linear transformation to integrate static features into the dynamic feature space,
- then applies the feature mixing on the concatenated features.
- Args:
- input_channels: The number of input channels of the dynamic features.
- output_channels: The number of output channels after feature mixing.
- static_channels: The number of channels in the static feature input.
- ff_dim: The inner dimension of the feedforward network used in feature mixing.
- activation_fn: The activation function used in feature mixing.
- dropout_rate: The dropout probability used in the feature mixing operation.
- """
- def __init__(
- self,
- sequence_length: int,
- input_channels: int,
- output_channels: int,
- static_channels: int,
- ff_dim: int,
- activation_fn: Callable = F.relu,
- dropout_rate: float = 0.1,
- normalize_before: bool = False,
- norm_type: type[nn.Module] = nn.LayerNorm,
- ):
- super().__init__()
- self.fr_static = nn.Linear(static_channels, output_channels)
- self.fm = FeatureMixing(
- sequence_length,
- input_channels + output_channels,
- output_channels,
- ff_dim,
- activation_fn,
- dropout_rate,
- normalize_before=normalize_before,
- norm_type=norm_type,
- )
- def forward(
- self, x: torch.Tensor, x_static: torch.Tensor
- ) -> tuple[torch.Tensor, torch.Tensor]:
- """Applies conditional feature mixing using both dynamic and static inputs.
- Args:
- x: A tensor representing dynamic features, typically with shape
- [batch_size, time_steps, input_channels].
- x_static: A tensor representing static features, typically with shape
- [batch_size, static_channels].
- Returns:
- A tuple containing:
- - The output tensor after applying conditional feature mixing.
- - The transformed static features tensor for monitoring or further processing.
- """
- v = self.fr_static(x_static) # Transform static features to match output channels.
- v = v.unsqueeze(1).repeat(
- 1, x.shape[1], 1
- ) # Repeat static features across time steps.
- return (
- self.fm(
- torch.cat([x, v], dim=-1)
- ), # Apply feature mixing on concatenated features.
- v.detach(), # Return detached static feature for monitoring or further use.
- )
- class TimeMixing(nn.Module):
- """Applies a transformation over the time dimension of a sequence.
- This module applies a linear transformation followed by an activation function
- and dropout over the sequence length of the input feature tensor after converting
- feature maps to the time dimension and then back.
- Args:
- input_channels: The number of input channels to the module.
- sequence_length: The length of the sequences to be transformed.
- activation_fn: The activation function to be used after the linear transformation.
- dropout_rate: The dropout probability to be used after the activation function.
- """
- def __init__(
- self,
- sequence_length: int,
- input_channels: int,
- activation_fn: Callable = F.relu,
- dropout_rate: float = 0.1,
- norm_type: type[nn.Module] = TimeBatchNorm2d,
- ):
- """Initializes the TimeMixing module with the specified parameters."""
- super().__init__()
- self.norm = norm_type((sequence_length, input_channels))
- self.activation_fn = activation_fn
- self.dropout = nn.Dropout(dropout_rate)
- self.fc1 = nn.Linear(sequence_length, sequence_length)
- def forward(self, x: torch.Tensor) -> torch.Tensor:
- """Applies the time mixing operations on the input tensor.
- Args:
- x: A 3D tensor with shape (N, C, L), where C = channel dimension and
- L = sequence length.
- Returns:
- The normalized output tensor after time mixing transformations.
- """
- x_temp = feature_to_time(
- x
- ) # Convert feature maps to time dimension. Assumes definition elsewhere.
- x_temp = self.activation_fn(self.fc1(x_temp))
- x_temp = self.dropout(x_temp)
- x_res = time_to_feature(x_temp) # Convert back from time to feature maps.
- return self.norm(x + x_res) # Apply normalization and combine with original input.
- class MixerLayer(nn.Module):
- """A residual block that combines time and feature mixing for sequence data.
- This module sequentially applies time mixing and feature mixing, which are forms
- of data augmentation and feature transformation that can help in learning temporal
- dependencies and feature interactions respectively.
- Args:
- sequence_length: The length of the input sequences.
- input_channels: The number of input channels to the module.
- output_channels: The number of output channels from the module.
- ff_dim: The inner dimension of the feedforward network used in feature mixing.
- activation_fn: The activation function used in both time and feature mixing.
- dropout_rate: The dropout probability used in both mixing operations.
- """
- def __init__(
- self,
- sequence_length: int,
- input_channels: int,
- output_channels: int,
- ff_dim: int,
- activation_fn: Callable = F.relu,
- dropout_rate: float = 0.1,
- normalize_before: bool = False,
- norm_type: type[nn.Module] = nn.LayerNorm,
- ):
- """Initializes the MixLayer with time and feature mixing modules."""
- super().__init__()
- self.time_mixing = TimeMixing(
- sequence_length,
- input_channels,
- activation_fn,
- dropout_rate,
- norm_type=norm_type,
- )
- self.feature_mixing = FeatureMixing(
- sequence_length,
- input_channels,
- output_channels,
- ff_dim,
- activation_fn,
- dropout_rate,
- norm_type=norm_type,
- normalize_before=normalize_before,
- )
- def forward(self, x: torch.Tensor) -> torch.Tensor:
- """Forward pass for the MixLayer module.
- Args:
- x: A 3D tensor with shape (N, C, L) to be processed by the mixing layers.
- Returns:
- The output tensor after applying time and feature mixing operations.
- """
- x = self.time_mixing(x) # Apply time mixing first.
- # x = self.feature_mixing(x) # Then apply feature mixing.
- return x
- class ConditionalMixerLayer(nn.Module):
- """Conditional mix layer combining time and feature mixing with static context.
- This module combines time mixing and conditional feature mixing, where the latter
- is influenced by static features. This allows the module to learn representations
- that are influenced by both dynamic and static features.
- Args:
- sequence_length: The length of the input sequences.
- input_channels: The number of input channels of the dynamic features.
- output_channels: The number of output channels after feature mixing.
- static_channels: The number of channels in the static feature input.
- ff_dim: The inner dimension of the feedforward network used in feature mixing.
- activation_fn: The activation function used in both mixing operations.
- dropout_rate: The dropout probability used in both mixing operations.
- """
- def __init__(
- self,
- sequence_length: int,
- input_channels: int,
- output_channels: int,
- static_channels: int,
- ff_dim: int,
- activation_fn: Callable = F.relu,
- dropout_rate: float = 0.1,
- normalize_before: bool = False,
- norm_type: type[nn.Module] = nn.LayerNorm,
- ):
- super().__init__()
- self.time_mixing = TimeMixing(
- sequence_length,
- input_channels,
- activation_fn,
- dropout_rate,
- norm_type=norm_type,
- )
- self.feature_mixing = ConditionalFeatureMixing(
- sequence_length,
- input_channels,
- output_channels=output_channels,
- static_channels=static_channels,
- ff_dim=ff_dim,
- activation_fn=activation_fn,
- dropout_rate=dropout_rate,
- normalize_before=normalize_before,
- norm_type=norm_type,
- )
- def forward(self, x: torch.Tensor, x_static: torch.Tensor) -> torch.Tensor:
- """Forward pass for the conditional mix layer.
- Args:
- x: A tensor representing dynamic features, typically with shape
- [batch_size, time_steps, input_channels].
- x_static: A tensor representing static features, typically with shape
- [batch_size, static_channels].
- Returns:
- The output tensor after applying time and conditional feature mixing.
- """
- x = self.time_mixing(x) # Apply time mixing first.
- x, _ = self.feature_mixing(x, x_static) # Then apply conditional feature mixing.
- return x
- def time_to_feature(x: torch.Tensor) -> torch.Tensor:
- """Converts a time series tensor to a feature tensor."""
- return x.permute(0, 2, 1)
- feature_to_time = time_to_feature
|