class_docstr: Time series encoder. This module can learn features for any `order_by` temporal column, both with and without accompanying target data.
  
  The backbone of this encoder is either a recurrent neural network or a transformer; both structured in an encoder-decoder fashion.
class_docstr_tok: ["Time","series","encoder",".","This","module","can","learn","features","for","any","`","order_by","`","temporal","column",",","both","with","and","without","accompanying","target","data",".","The","backbone","of","this","encoder","is","either","a","recurrent","neural","network","or","a","transformer",";","both","structured","in","an","encoder-decoder","fashion","."]
docstr: :param priming_data: a list of (self._n_dims)-dimensional time series [[dim1_data], ...]
  :param dependency_data: raw data from other columns
  :param ts_analysis: dictionary with time analysis info (e.g. normalizers for each target group)
  :param feedback_hoop_function: method to use if you want to get feedback on the training process
  :param batch_size
docstr_tok: [":","param","priming_data",":","a","list","of","(","self._n_dims",")","-dimensional","time","series","[","[","dim1_data","]",",","...","]",":","param","dependency_data",":","raw","data","from","other","columns",":","param","ts_analysis",":","dictionary","with","time","analysis","info","(","e.g",".","normalizers","for","each","target","group",")",":","param","feedback_hoop_function",":","method","to","use","if","you","want","to","get","feedback","on","the","training","process",":","param","batch_size"]
code: def prepare(
      self,
      train_priming_data: pd.Series,
      dev_priming_data: pd.Series,
      dependency_data={},
      ts_analysis=None,
      feedback_hoop_function=log.info,
      batch_size=256,
  ):
      """
      :param priming_data: a list of (self._n_dims)-dimensional time series [[dim1_data], ...]
      :param dependency_data: raw data from other columns
      :param ts_analysis: dictionary with time analysis info (e.g. normalizers for each target group)
      :param feedback_hoop_function: method to use if you want to get feedback on the training process
      :param batch_size
      """
      priming_data = pd.concat([train_priming_data, dev_priming_data])
      priming_data = list(priming_data.values)
  
      if self.is_prepared:
          raise Exception(
              'You can only call "prepare" once for a given encoder.'
          )
      else:
          self.setup_nn(ts_analysis, dependency_data)
  
      started = time.time()
  
      # Convert to array and determine max length
      priming_data, lengths_data = self._prepare_raw_data(priming_data)
      self._max_ts_length = int(lengths_data.max())
  
      if self._normalizer:
          self._normalizer.prepare(priming_data)
          priming_data = self._normalizer.encode(priming_data).to(
              self.device
          )
          if len(priming_data.shape) < 3:
              priming_data = priming_data.unsqueeze(-1)
      else:
          priming_data = (
              torch.stack([d for d in priming_data])
              .unsqueeze(-1)
              .to(self.device)
          )
  
      # merge all normalized data into a training batch
      normalized_tensors = []
      for dep_name, dep_data in dependency_data.items():
          if dep_name in self.grouped_by:
              continue
          if dep_data["original_type"] in (dtype.integer, dtype.float):
              dep_data["group_info"] = {
                  group: dependency_data[group]["data"]
                  for group in self.grouped_by
              }
              data = torch.zeros(
                  (
                      len(priming_data),
                      lengths_data.max().int().item(),
                      1,
                  )
              )
              all_idxs = set(range(len(data)))
              grouped = dep_data["data"].groupby(by=self.grouped_by)
              for group_name, normalizer in self.dep_norms[
                  dep_name
              ].items():
                  if group_name != "__default":
                      subset = grouped.get_group(normalizer.combination)
                      normalized = normalizer.encode(subset).unsqueeze(
                          -1
                      )
                      data[subset.index, :, :] = normalized
                      all_idxs -= set(subset.index)
              if (
                  len(all_idxs) > 0
                  and "__default" in self.dep_norms[dep_name].keys()
              ):
                  default_norm = self.dep_norms[dep_name]["__default"]
                  subset = [
                      dep_data["data"][idx] for idx in list(all_idxs)
                  ]
                  data[list(all_idxs), :, :] = torch.Tensor(
                      default_norm.encode(subset)
                  ).unsqueeze(-1)
  
          else:
              # categorical has only one normalizer at all times
              normalizer = self.dep_norms[dep_name]["__default"]
              data = normalizer.encode(dep_data["data"].values)
              if len(data.shape) < 3:
                  data = data.unsqueeze(-1)  # add feature dimension
          data[torch.isnan(data)] = 0.0
          normalized_tensors.append(data)
  
      if normalized_tensors:
          normalized_data = torch.cat(normalized_tensors, dim=-1).to(
              self.device
          )
          priming_data = torch.cat(
              [priming_data, normalized_data], dim=-1
          )
  
      self._encoder.train()
      running_losses = np.full(self._epochs_running_avg, np.nan)
      bad_epochs = 0
  
      for epoch in range(self._epochs):
          average_loss = 0
  
          for batch_idx in range(0, len(priming_data), batch_size):
              # setup loss and optimizer
              self._optimizer.zero_grad()
              loss = 0
  
              # shape: (batch_size, timesteps, n_dims)
              batch = self._get_batch(
                  priming_data,
                  batch_idx,
                  min(batch_idx + batch_size, len(priming_data)),
              )
  
              # encode and decode through time
              with LightwoodAutocast():
                  if self.encoder_class == TransformerEncoder:
                      # pack batch length info tensor
                      len_batch = self._get_batch(
                          lengths_data,
                          batch_idx,
                          min(
                              batch_idx + batch_size, len(priming_data)
                          ),
                      )
                      batch = batch, len_batch
  
                      (
                          next_tensor,
                          hidden_state,
                          dec_loss,
                      ) = self._encoder.bptt(
                          batch, self._enc_criterion, self.device
                      )
                      loss += dec_loss
  
                  else:
                      (
                          next_tensor,
                          hidden_state,
                          enc_loss,
                      ) = self._encoder.bptt(
                          batch, self._enc_criterion, self.device
                      )
                      loss += enc_loss
  
                      (
                          next_tensor,
                          hidden_state,
                          dec_loss,
                      ) = self._decoder.decode(
                          batch,
                          next_tensor,
                          self._dec_criterion,
                          self.device,
                          hidden_state=hidden_state,
                      )
                      loss += dec_loss
  
              loss.backward()
  
              self._optimizer.step()
              average_loss += loss.item()
  
          average_loss = average_loss / len(priming_data)
          batch_idx += batch_size
  
          if (
              epoch > self._epochs_running_avg
              and average_loss > np.average(running_losses)
          ):
              bad_epochs += 1
  
          # update running loss
          running_losses[:-1] = running_losses[1:]
          running_losses[-1] = average_loss
  
          if feedback_hoop_function is not None:
              feedback_hoop_function(
                  "time series encoder epoch [{epoch_n}/{total}] average_loss = {average_loss}".format(
                      epoch_n=epoch + 1,
                      total=self._epochs,
                      average_loss=average_loss,
                  )
              )
  
          if bad_epochs > self._stop_on_n_bad_epochs:
              break
          elif (time.time() - started) > self.stop_after:
              break
  
      self.is_prepared = True
  
imports: import time
  from math import gcd
  from typing import List
  from copy import deepcopy
  import numpy
  import pandas
  import torch
  import torch.nn
  from torch import optim
  from type_infer.dtype import dtype
  from lightwood.helpers.log import log
  from lightwood.encoder.base import BaseEncoder
  from lightwood.helpers.device import get_device_from_name
  from lightwood.helpers.torch import LightwoodAutocast
  from lightwood.encoder.datetime import DatetimeNormalizerEncoder
  from lightwood.encoder.time_series.helpers.rnn_helpers import EncoderRNNNumerical, DecoderRNNNumerical
  from lightwood.encoder.helpers import MinMaxNormalizer, CatNormalizer
  from lightwood.encoder.time_series.helpers.transformer_helpers import TransformerEncoder, get_chunk, len_to_mask
label_desc: Use image node_id 6 for calling the TimeSeriesEncoder obj's underlying member method code with example usage: obj.prepare(train_priming_data, dev_priming_data, dependency_data, ts_analysis, feedback_hoop_function, batch_size) without return types