docstr: Log interpretation metrics to tensorboard.
docstr_tok: ["Log","interpretation","metrics","to","tensorboard","."]
code: def log_interpretation(self, outputs):
      """
      Log interpretation metrics to tensorboard.
      """
      # extract interpretations
      interpretation = {
          # use padded_stack because decoder length histogram can be of different length
          name: padded_stack(
              [x["interpretation"][name].detach() for x in outputs],
              side="right",
              value=0,
          ).sum(0)
          for name in outputs[0]["interpretation"].keys()
      }
      # normalize attention with length histogram squared to account for: 1. zeros in attention and
      # 2. higher attention due to less values
      attention_occurances = (
          interpretation["encoder_length_histogram"][1:]
          .flip(0)
          .float()
          .cumsum(0)
      )
      attention_occurances = (
          attention_occurances / attention_occurances.max()
      )
      attention_occurances = torch.cat(
          [
              attention_occurances,
              torch.ones(
                  interpretation["attention"].size(0)
                  - attention_occurances.size(0),
                  dtype=attention_occurances.dtype,
                  device=attention_occurances.device,
              ),
          ],
          dim=0,
      )
      interpretation["attention"] = interpretation[
          "attention"
      ] / attention_occurances.pow(2).clamp(1.0)
      interpretation["attention"] = (
          interpretation["attention"]
          / interpretation["attention"].sum()
      )
  
      figs = self.plot_interpretation(
          interpretation
      )  # make interpretation figures
      label = self.current_stage
      # log to tensorboard
      for name, fig in figs.items():
          self.logger.experiment.add_figure(
              f"{label.capitalize()} {name} importance",
              fig,
              global_step=self.global_step,
          )
  
      # log lengths of encoder/decoder
      for type in ["encoder", "decoder"]:
          fig, ax = plt.subplots()
          lengths = (
              padded_stack(
                  [
                      out["interpretation"][f"{type}_length_histogram"]
                      for out in outputs
                  ]
              )
              .sum(0)
              .detach()
              .cpu()
          )
          if type == "decoder":
              start = 1
          else:
              start = 0
          ax.plot(torch.arange(start, start + len(lengths)), lengths)
          ax.set_xlabel(f"{type.capitalize()} length")
          ax.set_ylabel("Number of samples")
          ax.set_title(
              f"{type.capitalize()} length distribution in {label} epoch"
          )
  
          self.logger.experiment.add_figure(
              f"{label.capitalize()} {type} length distribution",
              fig,
              global_step=self.global_step,
          )
  
imports: from copy import copy
  from typing import Dict, List, Tuple, Union
  from matplotlib import pyplot
  import numpy
  import torch
  from torch import nn
  from torchmetrics import Metric
  from pytorch_forecasting.data import TimeSeriesDataSet
  from pytorch_forecasting.data.encoders import NaNLabelEncoder
  from pytorch_forecasting.metrics import MAE, MAPE, MASE, RMSE, SMAPE, MultiHorizonMetric, MultiLoss, QuantileLoss
  from pytorch_forecasting.models.base_model import BaseModelWithCovariates
  from pytorch_forecasting.models.nn import LSTM, MultiEmbedding
  from pytorch_forecasting.models.temporal_fusion_transformer.sub_modules import AddNorm, GateAddNorm, GatedLinearUnit, GatedResidualNetwork, InterpretableMultiHeadAttention, VariableSelectionNetwork
  from pytorch_forecasting.utils import create_mask, detach, integer_histogram, masked_op, padded_stack, to_list
label_desc: Use image node_id 13 for calling the TemporalFusionTransformer obj's underlying member method code with example usage: obj.log_interpretation(outputs) without return types