from typing import Callable, List, Optional import torch import torch.nn as nn class FeatureEmbedder(nn.Module): def __init__(self, cardinalities: List[int], embedding_dims: List[int],) -> None: super().__init__() self.__num_features = len(cardinalities) def create_embedding(c: int, d: int) -> nn.Embedding: embedding = nn.Embedding(c, d) return embedding self.__embedders = nn.ModuleList( [create_embedding(c, d) for c, d in zip(cardinalities, embedding_dims)] ) def forward(self, features: torch.Tensor) -> torch.Tensor: if self.__num_features > 1: # we slice the last dimension, giving an array of length # self.__num_features with shape (N,T) or (N) cat_feature_slices = torch.chunk(features, self.__num_features, dim=-1) else: cat_feature_slices = [features] return torch.cat( [ embed(cat_feature_slice.squeeze(-1)) for embed, cat_feature_slice in zip( self.__embedders, cat_feature_slices ) ], dim=-1, ) class FeatureAssembler(nn.Module): def __init__( self, T: int, embed_static: Optional[FeatureEmbedder] = None, embed_dynamic: Optional[FeatureEmbedder] = None, ) -> None: super().__init__() self.T = T self.embeddings = nn.ModuleDict( {"embed_static": embed_static, "embed_dynamic": embed_dynamic} ) def forward( self, feat_static_cat: torch.Tensor, feat_static_real: torch.Tensor, feat_dynamic_cat: torch.Tensor, feat_dynamic_real: torch.Tensor, ) -> torch.Tensor: processed_features = [ self.process_static_cat(feat_static_cat), self.process_static_real(feat_static_real), self.process_dynamic_cat(feat_dynamic_cat), self.process_dynamic_real(feat_dynamic_real), ] return torch.cat(processed_features, dim=-1) def process_static_cat(self, feature: torch.Tensor) -> torch.Tensor: if self.embeddings["embed_static"] is not None: feature = self.embeddings["embed_static"](feature) return feature.unsqueeze(1).expand(-1, self.T, -1).float() def process_dynamic_cat(self, feature: torch.Tensor) -> torch.Tensor: if self.embeddings["embed_dynamic"] is None: return feature.float() else: return self.embeddings["embed_dynamic"](feature) def process_static_real(self, feature: torch.Tensor) -> torch.Tensor: return feature.unsqueeze(1).expand(-1, self.T, -1) def process_dynamic_real(self, feature: torch.Tensor) -> torch.Tensor: return feature