Source code for sensai.torch.torch_models.seq.seq_models

import logging
from typing import Optional, List, Union

import pandas as pd
import torch

from ....torch import TorchVectorRegressionModel, Tensoriser, NNOptimiserParams, TorchModel
from ....torch.torch_models.seq.seq_modules import EncoderDecoderModule, EncoderFactory, DecoderFactory
from ....vectoriser import SequenceVectoriser

log = logging.getLogger(__name__)


[docs]class EncoderDecoderVectorRegressionModel(TorchVectorRegressionModel): """ A highly general encoder-decoder sequence model, which encodes a sequence of history items and uses the encoding to make predictions for one or more target sequence items. History and target sequences are converted to vectors via vectorisers. """ def __init__(self, cuda: bool, history_sequence_column_name: str, history_sequence_vectoriser: SequenceVectoriser, history_sequence_variable_length: bool, target_sequence_column_name: str, target_sequence_vectoriser: SequenceVectoriser, latent_dim: int, encoder_factory: EncoderFactory, decoder_factory: DecoderFactory, nn_optimiser_params: Optional[NNOptimiserParams] = None): """ :param cuda: whether to use a CUDA device :param history_sequence_column_name: the name of the data frame input column which contains the history sequences to be encoded. The column must contain a sequence of items that can be converted to vectors via the `history_sequence_vectorizer` :param history_sequence_vectoriser: a vectorizer which converts history sequence items to vectors :param history_sequence_variable_length: whether history sequences can be of variable length :param target_sequence_column_name: the column containing the target item sequence; Note that the column must contain sequences even if there is but a single target item for which predictions shall be made. In such cases, simply use a column that contains lists with a single item each. :param target_sequence_vectoriser: the vectoriser for the generation of feature vectors for the target items. :param latent_dim: the number of latent dimensions to be used by the encoder :param encoder_factory: a factory for the creation of the encoder, which takes sequence items from the history and encodes them into vectors of dimension `latent_dim` :param decoder_factory: a factory for the creation of the decoder component, which takes a latent vector produced by the encoder and (a sequence of) target features to make predictions :param nn_optimiser_params: the optimiser parameters """ super().__init__(self._create_model, nn_optimiser_params=nn_optimiser_params) self.history_sequence_variable_length = history_sequence_variable_length self.latent_dim = latent_dim self.cuda = cuda self.decoder_factory = decoder_factory self.encoder_factory = encoder_factory self.sequenceVectoriser = history_sequence_vectoriser self.history_sequence_dim_per_item: Optional[int] = None self.target_feature_dim: Optional[int] = None self.with_input_tensoriser(self.InputTensoriser(history_sequence_column_name, history_sequence_vectoriser, target_sequence_column_name, target_sequence_vectoriser)) def _create_model(self): return self.EncoderDecoderModel(self)
[docs] class InputTensoriser(Tensoriser): def __init__(self, history_sequence_column_name: str, history_sequence_vectoriser: SequenceVectoriser, target_sequence_column_name: str, target_sequence_vectoriser: SequenceVectoriser): self.history_sequence_column_name = history_sequence_column_name self.history_sequence_vectoriser = history_sequence_vectoriser self.target_sequence_column_name = target_sequence_column_name self.target_sequence_vectoriser = target_sequence_vectoriser
[docs] def fit(self, df: pd.DataFrame, model=None): model: "EncoderDecoderVectorRegressionModel" self.history_sequence_vectoriser.fit(df[self.history_sequence_column_name]) model.history_sequence_dim_per_item = self.history_sequence_vectoriser.get_vector_dim(df[self.history_sequence_column_name].iloc[0]) self.target_sequence_vectoriser.fit(df[self.target_sequence_column_name]) model.target_feature_dim = self.target_sequence_vectoriser.get_vector_dim(df[self.target_sequence_column_name].iloc[0])
def _tensorise(self, df: pd.DataFrame) -> Union[torch.Tensor, List[torch.Tensor]]: log.debug(f"Applying {self} to data frame of length {len(df)} ...") history = df[self.history_sequence_column_name] history_sequences, history_sequence_lengths = self.history_sequence_vectoriser.apply_multi_with_padding(history) targets = df[self.target_sequence_column_name] target_sequences, target_sequence_lengths = self.target_sequence_vectoriser.apply_multi_with_padding(targets) return [ torch.tensor(history_sequences).float(), torch.tensor(history_sequence_lengths), torch.tensor(target_sequences).float(), torch.tensor(target_sequence_lengths), ]
[docs] class EncoderDecoderModel(TorchModel): def __init__(self, parent: "EncoderDecoderVectorRegressionModel"): super().__init__(parent.cuda) self.parent = parent
[docs] def create_torch_module(self) -> torch.nn.Module: latent_dim = self.parent.latent_dim target_feature_dim = self.parent.target_feature_dim encoder = self.parent.encoder_factory.create_encoder(self.parent.history_sequence_dim_per_item, latent_dim) decoder = self.parent.decoder_factory.create_decoder(latent_dim, target_feature_dim) return EncoderDecoderModule(encoder, decoder, self.parent.history_sequence_variable_length)