Source code for cursus.processing.categorical.categorical_label_processor
# processor/categorical_label_processor.py
from typing import List
from ..processors import Processor
[docs]
class CategoricalLabelProcessor(Processor):
"""Map category strings to integer labels.
.. warning::
With ``update_on_new=True`` (the default), :meth:`process` MUTATES the
category->label mapping on first sight of a new category. This is **not safe
across DataLoader workers**: under ``num_workers > 0`` the processor is forked
per worker, so each worker assigns its own (divergent) ids to the same category
and labels become inconsistent. For multi-worker / distributed use, pass a
complete ``initial_categories`` list and set ``update_on_new=False`` so the
mapping is fixed and read-only during processing.
"""
def __init__(
self,
initial_categories: List[str] = None,
update_on_new: bool = True,
unknown_label: int = -1,
):
"""
Args:
initial_categories (List[str], optional): Initial list of categories.
update_on_new (bool): If True, add new categories to the mapping as they are
encountered. NOTE: mutates state during process() — single-process / fit-only.
For multi-worker use, supply initial_categories and set this False.
unknown_label (int): Label to assign if update_on_new is False and a new category is encountered.
"""
super().__init__()
self.processor_name = "categorical_label_processor"
if initial_categories is None:
self.category_to_label = {}
self.next_label = 0
else:
self.category_to_label = {
cat: idx for idx, cat in enumerate(initial_categories)
}
self.next_label = len(initial_categories)
self.update_on_new = update_on_new
self.unknown_label = unknown_label
[docs]
def process(self, input_text: str) -> int:
# Transform category string into a numeric label.
if input_text in self.category_to_label:
return self.category_to_label[input_text]
else:
if self.update_on_new:
self.category_to_label[input_text] = self.next_label
self.next_label += 1
return self.category_to_label[input_text]
else:
return self.unknown_label