class_docstr: Multi-head classifier with separate heads for each task.
  
  Typically used in task-incremental benchmarks where task labels are
  available and provided to the model.
  
  .. note::
      Each output head may have a different shape, and the number of
      classes can be determined automatically.
  
      However, since pytorch doest not support jagged tensors, when you
      compute a minibatch's output you must ensure that each sample
      has the same output size, otherwise the model will fail to
      concatenate the samples together.
  
      These can be easily ensured in two possible ways:
  
      - each minibatch contains a single task, which is the case in most
          common benchmarks in Avalanche. Some exceptions to this setting
          are multi-task replay or cumulative strategies.
      - each head has the same size, which can be enforced by setting a
          large enough `initial_out_features`.
class_docstr_tok: ["Multi-head","classifier","with","separate","heads","for","each","task",".","Typically","used","in","task-incremental","benchmarks","where","task","labels","are","available","and","provided","to","the","model",".","..","note",":",":","Each","output","head","may","have","a","different","shape",",","and","the","number","of","classes","can","be","determined","automatically",".","However",",","since","pytorch","doest","not","support","jagged","tensors",",","when","you","compute","a","minibatch","'s","output","you","must","ensure","that","each","sample","has","the","same","output","size",",","otherwise","the","model","will","fail","to","concatenate","the","samples","together",".","These","can","be","easily","ensured","in","two","possible","ways",":","-","each","minibatch","contains","a","single","task",",","which","is","the","case","in","most","common","benchmarks","in","Avalanche",".","Some","exceptions","to","this","setting","are","multi-task","replay","or","cumulative","strategies",".","-","each","head","has","the","same","size",",","which","can","be","enforced","by","setting","a","large","enough","`","initial_out_features","`","."]
docstr: If `dataset` contains new tasks, a new head is initialized.
  
  :param experience: data from the current experience.
  :return:
docstr_tok: ["If","`","dataset","`","contains","new","tasks",",","a","new","head","is","initialized",".",":","param","experience",":","data","from","the","current","experience",".",":","return",":"]
code: def adaptation(self, experience: CLExperience):
      """If `dataset` contains new tasks, a new head is initialized.
  
      :param experience: data from the current experience.
      :return:
      """
      super().adaptation(experience)
      device = self._adaptation_device
      curr_classes = experience.classes_in_this_experience
      task_labels = experience.task_labels
      if isinstance(task_labels, ConstantSequence):
          # task label is unique. Don't check duplicates.
          task_labels = [task_labels[0]]
  
      for tid in set(task_labels):
          tid = str(tid)
          # head adaptation
          if tid not in self.classifiers:  # create new head
              new_head = IncrementalClassifier(
                  self.in_features,
                  self.starting_out_features,
                  masking=False,
              ).to(device)
              self.classifiers[tid] = new_head
  
              au_init = torch.zeros(
                  self.starting_out_features,
                  dtype=torch.int8,
                  device=device,
              )
              self.register_buffer(f"active_units_T{tid}", au_init)
  
          self.classifiers[tid].adaptation(experience)
  
          # update active_units mask for the current task
          if self.masking:
              # TODO: code below assumes a single task for each experience
              # it should be easy to generalize but it may be slower.
              if len(task_labels) > 1:
                  raise NotImplementedError(
                      "Multi-Head unit masking is not supported when "
                      "experiences have multiple task labels. Set "
                      "masking=False in your "
                      "MultiHeadClassifier to disable masking."
                  )
  
              au_name = f"active_units_T{tid}"
              curr_head = self.classifiers[tid]
              old_nunits = self._buffers[au_name].shape[0]
  
              new_nclasses = max(
                  curr_head.classifier.out_features,
                  max(curr_classes) + 1,
              )
              if old_nunits != new_nclasses:  # expand active_units mask
                  old_act_units = self._buffers[au_name]
                  self._buffers[au_name] = torch.zeros(
                      new_nclasses, dtype=torch.int8, device=device
                  )
                  self._buffers[au_name][
                      : old_act_units.shape[0]
                  ] = old_act_units
              # update with new active classes
              if self.training:
                  self._buffers[au_name][curr_classes] = 1
  
imports: import torch
  from torch.nn import Module
  from typing import Optional
  from avalanche.benchmarks.utils.flat_data import ConstantSequence
  from avalanche.benchmarks.scenarios import CLExperience
label_desc: Use image node_id 4 for calling the MultiHeadClassifier obj's underlying member method code with example usage: obj.adaptation(experience) without return types