layers.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. from __future__ import annotations
  2. from collections.abc import Callable
  3. import torch
  4. import torch.nn.functional as F
  5. from torch import Tensor, nn
  6. class TimeBatchNorm2d(nn.BatchNorm1d):
  7. """A batch normalization layer that normalizes over the last two dimensions of a
  8. sequence in PyTorch, mimicking Keras behavior.
  9. This class extends nn.BatchNorm1d to apply batch normalization across time and
  10. feature dimensions.
  11. Attributes:
  12. num_time_steps (int): Number of time steps in the input.
  13. num_channels (int): Number of channels in the input.
  14. """
  15. def __init__(self, normalized_shape: tuple[int, int]):
  16. """Initializes the TimeBatchNorm2d module.
  17. Args:
  18. normalized_shape (tuple[int, int]): A tuple (num_time_steps, num_channels)
  19. representing the shape of the time and feature dimensions to normalize.
  20. """
  21. num_time_steps, num_channels = normalized_shape
  22. super().__init__(num_channels * num_time_steps)
  23. self.num_time_steps = num_time_steps
  24. self.num_channels = num_channels
  25. def forward(self, x: Tensor) -> Tensor:
  26. """Applies the batch normalization over the last two dimensions of the input tensor.
  27. Args:
  28. x (Tensor): A 3D tensor with shape (N, S, C), where N is the batch size,
  29. S is the number of time steps, and C is the number of channels.
  30. Returns:
  31. Tensor: A 3D tensor with batch normalization applied over the last two dims.
  32. Raises:
  33. ValueError: If the input tensor is not 3D.
  34. """
  35. if x.ndim != 3:
  36. raise ValueError(f"Expected 3D input tensor, but got {x.ndim}D tensor instead.")
  37. # Reshaping input to combine time and feature dimensions for normalization
  38. x = x.reshape(x.shape[0], -1, 1)
  39. # Applying batch normalization
  40. x = super().forward(x)
  41. # Reshaping back to original dimensions (N, S, C)
  42. x = x.reshape(x.shape[0], self.num_time_steps, self.num_channels)
  43. return x
  44. class FeatureMixing(nn.Module):
  45. """A module for feature mixing with flexibility in normalization and activation.
  46. This module provides options for batch normalization before or after mixing features,
  47. uses dropout for regularization, and allows for different activation functions.
  48. Args:
  49. sequence_length: The length of the sequences to be transformed.
  50. input_channels: The number of input channels to the module.
  51. output_channels: The number of output channels from the module.
  52. ff_dim: The dimension of the feed-forward network internal to the module.
  53. activation_fn: The activation function used within the feed-forward network.
  54. dropout_rate: The dropout probability used for regularization.
  55. normalize_before: A boolean indicating whether to apply normalization before
  56. the rest of the operations.
  57. """
  58. def __init__(
  59. self,
  60. sequence_length: int,
  61. input_channels: int,
  62. output_channels: int,
  63. ff_dim: int,
  64. activation_fn: Callable[[torch.Tensor], torch.Tensor] = F.relu,
  65. dropout_rate: float = 0.1,
  66. normalize_before: bool = True,
  67. norm_type: type[nn.Module] = TimeBatchNorm2d,
  68. ):
  69. """Initializes the FeatureMixing module with the provided parameters."""
  70. super().__init__()
  71. self.norm_before = (
  72. norm_type((sequence_length, input_channels))
  73. if normalize_before
  74. else nn.Identity()
  75. )
  76. self.norm_after = (
  77. norm_type((sequence_length, output_channels))
  78. if not normalize_before
  79. else nn.Identity()
  80. )
  81. self.activation_fn = activation_fn
  82. self.dropout = nn.Dropout(dropout_rate)
  83. self.fc1 = nn.Linear(input_channels, ff_dim)
  84. self.fc2 = nn.Linear(ff_dim, output_channels)
  85. self.projection = (
  86. nn.Linear(input_channels, output_channels)
  87. if input_channels != output_channels
  88. else nn.Identity()
  89. )
  90. def forward(self, x: torch.Tensor) -> torch.Tensor:
  91. """Forward pass for the FeatureMixing module.
  92. Args:
  93. x: A 3D tensor with shape (N, C, L) where C is the channel dimension.
  94. Returns:
  95. The output tensor after feature mixing.
  96. """
  97. x_proj = self.projection(x)
  98. x = self.norm_before(x)
  99. x = self.fc1(x) # Apply the first linear transformation.
  100. x = self.activation_fn(x) # Apply the activation function.
  101. x = self.dropout(x) # Apply dropout for regularization.
  102. x = self.fc2(x) # Apply the second linear transformation.
  103. x = self.dropout(x) # Apply dropout again if needed.
  104. x = x_proj + x # Add the projection shortcut to the transformed features.
  105. return self.norm_after(x)
  106. class ConditionalFeatureMixing(nn.Module):
  107. """Conditional feature mixing module that incorporates static features.
  108. This module extends the feature mixing process by including static features. It uses
  109. a linear transformation to integrate static features into the dynamic feature space,
  110. then applies the feature mixing on the concatenated features.
  111. Args:
  112. input_channels: The number of input channels of the dynamic features.
  113. output_channels: The number of output channels after feature mixing.
  114. static_channels: The number of channels in the static feature input.
  115. ff_dim: The inner dimension of the feedforward network used in feature mixing.
  116. activation_fn: The activation function used in feature mixing.
  117. dropout_rate: The dropout probability used in the feature mixing operation.
  118. """
  119. def __init__(
  120. self,
  121. sequence_length: int,
  122. input_channels: int,
  123. output_channels: int,
  124. static_channels: int,
  125. ff_dim: int,
  126. activation_fn: Callable = F.relu,
  127. dropout_rate: float = 0.1,
  128. normalize_before: bool = False,
  129. norm_type: type[nn.Module] = nn.LayerNorm,
  130. ):
  131. super().__init__()
  132. self.fr_static = nn.Linear(static_channels, output_channels)
  133. self.fm = FeatureMixing(
  134. sequence_length,
  135. input_channels + output_channels,
  136. output_channels,
  137. ff_dim,
  138. activation_fn,
  139. dropout_rate,
  140. normalize_before=normalize_before,
  141. norm_type=norm_type,
  142. )
  143. def forward(
  144. self, x: torch.Tensor, x_static: torch.Tensor
  145. ) -> tuple[torch.Tensor, torch.Tensor]:
  146. """Applies conditional feature mixing using both dynamic and static inputs.
  147. Args:
  148. x: A tensor representing dynamic features, typically with shape
  149. [batch_size, time_steps, input_channels].
  150. x_static: A tensor representing static features, typically with shape
  151. [batch_size, static_channels].
  152. Returns:
  153. A tuple containing:
  154. - The output tensor after applying conditional feature mixing.
  155. - The transformed static features tensor for monitoring or further processing.
  156. """
  157. v = self.fr_static(x_static) # Transform static features to match output channels.
  158. v = v.unsqueeze(1).repeat(
  159. 1, x.shape[1], 1
  160. ) # Repeat static features across time steps.
  161. return (
  162. self.fm(
  163. torch.cat([x, v], dim=-1)
  164. ), # Apply feature mixing on concatenated features.
  165. v.detach(), # Return detached static feature for monitoring or further use.
  166. )
  167. class TimeMixing(nn.Module):
  168. """Applies a transformation over the time dimension of a sequence.
  169. This module applies a linear transformation followed by an activation function
  170. and dropout over the sequence length of the input feature tensor after converting
  171. feature maps to the time dimension and then back.
  172. Args:
  173. input_channels: The number of input channels to the module.
  174. sequence_length: The length of the sequences to be transformed.
  175. activation_fn: The activation function to be used after the linear transformation.
  176. dropout_rate: The dropout probability to be used after the activation function.
  177. """
  178. def __init__(
  179. self,
  180. sequence_length: int,
  181. input_channels: int,
  182. activation_fn: Callable = F.relu,
  183. dropout_rate: float = 0.1,
  184. norm_type: type[nn.Module] = TimeBatchNorm2d,
  185. ):
  186. """Initializes the TimeMixing module with the specified parameters."""
  187. super().__init__()
  188. self.norm = norm_type((sequence_length, input_channels))
  189. self.activation_fn = activation_fn
  190. self.dropout = nn.Dropout(dropout_rate)
  191. self.fc1 = nn.Linear(sequence_length, sequence_length)
  192. def forward(self, x: torch.Tensor) -> torch.Tensor:
  193. """Applies the time mixing operations on the input tensor.
  194. Args:
  195. x: A 3D tensor with shape (N, C, L), where C = channel dimension and
  196. L = sequence length.
  197. Returns:
  198. The normalized output tensor after time mixing transformations.
  199. """
  200. x_temp = feature_to_time(
  201. x
  202. ) # Convert feature maps to time dimension. Assumes definition elsewhere.
  203. x_temp = self.activation_fn(self.fc1(x_temp))
  204. x_temp = self.dropout(x_temp)
  205. x_res = time_to_feature(x_temp) # Convert back from time to feature maps.
  206. return self.norm(x + x_res) # Apply normalization and combine with original input.
  207. class MixerLayer(nn.Module):
  208. """A residual block that combines time and feature mixing for sequence data.
  209. This module sequentially applies time mixing and feature mixing, which are forms
  210. of data augmentation and feature transformation that can help in learning temporal
  211. dependencies and feature interactions respectively.
  212. Args:
  213. sequence_length: The length of the input sequences.
  214. input_channels: The number of input channels to the module.
  215. output_channels: The number of output channels from the module.
  216. ff_dim: The inner dimension of the feedforward network used in feature mixing.
  217. activation_fn: The activation function used in both time and feature mixing.
  218. dropout_rate: The dropout probability used in both mixing operations.
  219. """
  220. def __init__(
  221. self,
  222. sequence_length: int,
  223. input_channels: int,
  224. output_channels: int,
  225. ff_dim: int,
  226. activation_fn: Callable = F.relu,
  227. dropout_rate: float = 0.1,
  228. normalize_before: bool = False,
  229. norm_type: type[nn.Module] = nn.LayerNorm,
  230. ):
  231. """Initializes the MixLayer with time and feature mixing modules."""
  232. super().__init__()
  233. self.time_mixing = TimeMixing(
  234. sequence_length,
  235. input_channels,
  236. activation_fn,
  237. dropout_rate,
  238. norm_type=norm_type,
  239. )
  240. self.feature_mixing = FeatureMixing(
  241. sequence_length,
  242. input_channels,
  243. output_channels,
  244. ff_dim,
  245. activation_fn,
  246. dropout_rate,
  247. norm_type=norm_type,
  248. normalize_before=normalize_before,
  249. )
  250. def forward(self, x: torch.Tensor) -> torch.Tensor:
  251. """Forward pass for the MixLayer module.
  252. Args:
  253. x: A 3D tensor with shape (N, C, L) to be processed by the mixing layers.
  254. Returns:
  255. The output tensor after applying time and feature mixing operations.
  256. """
  257. x = self.time_mixing(x) # Apply time mixing first.
  258. # x = self.feature_mixing(x) # Then apply feature mixing.
  259. return x
  260. class ConditionalMixerLayer(nn.Module):
  261. """Conditional mix layer combining time and feature mixing with static context.
  262. This module combines time mixing and conditional feature mixing, where the latter
  263. is influenced by static features. This allows the module to learn representations
  264. that are influenced by both dynamic and static features.
  265. Args:
  266. sequence_length: The length of the input sequences.
  267. input_channels: The number of input channels of the dynamic features.
  268. output_channels: The number of output channels after feature mixing.
  269. static_channels: The number of channels in the static feature input.
  270. ff_dim: The inner dimension of the feedforward network used in feature mixing.
  271. activation_fn: The activation function used in both mixing operations.
  272. dropout_rate: The dropout probability used in both mixing operations.
  273. """
  274. def __init__(
  275. self,
  276. sequence_length: int,
  277. input_channels: int,
  278. output_channels: int,
  279. static_channels: int,
  280. ff_dim: int,
  281. activation_fn: Callable = F.relu,
  282. dropout_rate: float = 0.1,
  283. normalize_before: bool = False,
  284. norm_type: type[nn.Module] = nn.LayerNorm,
  285. ):
  286. super().__init__()
  287. self.time_mixing = TimeMixing(
  288. sequence_length,
  289. input_channels,
  290. activation_fn,
  291. dropout_rate,
  292. norm_type=norm_type,
  293. )
  294. self.feature_mixing = ConditionalFeatureMixing(
  295. sequence_length,
  296. input_channels,
  297. output_channels=output_channels,
  298. static_channels=static_channels,
  299. ff_dim=ff_dim,
  300. activation_fn=activation_fn,
  301. dropout_rate=dropout_rate,
  302. normalize_before=normalize_before,
  303. norm_type=norm_type,
  304. )
  305. def forward(self, x: torch.Tensor, x_static: torch.Tensor) -> torch.Tensor:
  306. """Forward pass for the conditional mix layer.
  307. Args:
  308. x: A tensor representing dynamic features, typically with shape
  309. [batch_size, time_steps, input_channels].
  310. x_static: A tensor representing static features, typically with shape
  311. [batch_size, static_channels].
  312. Returns:
  313. The output tensor after applying time and conditional feature mixing.
  314. """
  315. x = self.time_mixing(x) # Apply time mixing first.
  316. x, _ = self.feature_mixing(x, x_static) # Then apply conditional feature mixing.
  317. return x
  318. def time_to_feature(x: torch.Tensor) -> torch.Tensor:
  319. """Converts a time series tensor to a feature tensor."""
  320. return x.permute(0, 2, 1)
  321. feature_to_time = time_to_feature