Source code for torch_ecg.models.transformers.transformer

""" """

import warnings
from typing import Any, Optional, Sequence, Union

import torch
import torch.nn as nn
from einops.layers.torch import Rearrange

from ...utils.misc import get_kwargs
from ...utils.utils_nn import SizeMixin
from ..registry import ATTN_LAYERS

__all__ = [
    "Transformer",
]


[docs] @ATTN_LAYERS.register(name="transformer") @ATTN_LAYERS.register(name="transformer_encoder") @ATTN_LAYERS.register() class Transformer(nn.Module, SizeMixin): """Transformer feature extractor. Parameters ---------- input_size : int Number of input channels. hidden_size : int Number of hidden units in the encoding layer. num_heads : int, default 8 Number of attention heads. num_layers : int, default 1 Number of encoding layers. dropout : float, default 0.1 Dropout probability batch_first : bool, default True If True, the input is of shape ``(batch_size, seq_len, input_size)``, otherwise, the input is of shape ``(seq_len, batch_size, input_size)``. kwargs : dict Other hyper-parameters of the Module, including activation, kernel_initializer, etc. """ __name__ = "Transformer" def __init__( self, input_size: int, hidden_size: int, num_heads: int = 8, num_layers: int = 1, dropout: float = 0.1, batch_first: bool = True, **kwargs: Any, ) -> None: super().__init__() self.__input_size = input_size self.__hidden_size = hidden_size self.__num_layers = num_layers self.__num_heads = num_heads self.__batch_first = batch_first if self.__input_size % self.__num_heads != 0: input_size = self.__input_size self.__input_size = self.__input_size // self.__num_heads * self.__num_heads warnings.warn( f"`input_size` {input_size} is not divisible by `num_heads` {self.__num_heads}, " f"adjusted to {self.__input_size}", RuntimeWarning, ) if self.__batch_first: self.project = nn.Linear(input_size, self.__input_size) else: self.project = nn.Sequential( Rearrange("seq_len batch_size input_size -> batch_size seq_len input_size"), nn.Linear(input_size, self.__input_size), Rearrange("batch_size seq_len input_size -> seq_len batch_size input_size"), ) else: self.project = nn.Identity() if "batch_first" in get_kwargs(nn.TransformerEncoderLayer): encoder_layer = nn.TransformerEncoderLayer( d_model=self.__input_size, nhead=self.__num_heads, dim_feedforward=self.__hidden_size, dropout=dropout, batch_first=self.__batch_first, activation=kwargs.get("activation", "relu"), ) else: encoder_layer = nn.TransformerEncoderLayer( d_model=self.__input_size, nhead=self.__num_heads, dim_feedforward=self.__hidden_size, dropout=dropout, activation=kwargs.get("activation", "relu"), ) if self.__batch_first: warnings.warn( "`batch_first` only supports torch >= 1.9, defaults to False", RuntimeWarning, ) self.__batch_first = False self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=self.__num_layers)
[docs] def forward(self, x: torch.Tensor) -> torch.Tensor: """Forward pass of the transformer feature extractor. Parameters ---------- x : torch.Tensor Input tensor, of shape ``(seq_len, batch_size, input_size)`` if `batch_first` is False, else ``(batch_size, seq_len, input_size)``. Returns ------- torch.Tensor Output tensor, of shape ``(seq_len, batch_size, output_size)`` if `batch_first` is False, else ``(batch_size, seq_len, output_size)``. """ x = self.project(x) return self.encoder(x)
[docs] def compute_output_shape( self, seq_len: Optional[int] = None, batch_size: Optional[int] = None ) -> Sequence[Union[int, None]]: """Compute the output shape of the transformer feature extractor. Parameters ---------- seq_len : int, optional Length of the input tensor. batch_size : int, optional Batch size of the input tensor. Returns ------- output_shape : sequence Output shape of the transformer feature extractor. """ if self.__batch_first: return (batch_size, seq_len, self.__input_size) else: return (seq_len, batch_size, self.__input_size)
@property def batch_first(self) -> bool: return self.__batch_first