Source code for nlp_shap.masking.policies
"""Absence policies for rendering masked conversation snapshots."""
from __future__ import annotations
from dataclasses import dataclass
from ..domain.coalition import CoalitionMask
from ..domain.conversation import ConversationSnapshot
from ..domain.players import PlayerSet
from .tokens import rebuild_snapshot, tokenize_snapshot
[docs]
@dataclass(frozen=True, slots=True)
class DeletePolicy:
"""Remove absent tokens from the rendered conversation."""
@property
def name(self) -> str:
"""Return the registered absence-policy identifier."""
return "delete"
[docs]
def apply(
self,
snapshot: ConversationSnapshot,
players: PlayerSet,
mask: CoalitionMask,
) -> ConversationSnapshot:
"""Drop absent tokens while preserving turn and message structure."""
mask.validate_against(players)
spans = tokenize_snapshot(snapshot)
if len(spans) != players.num_players:
msg = "token layout does not match the supplied player set"
raise ValueError(msg)
rendered = tuple(
span.text
for span, present in zip(spans, mask.present, strict=True)
if present
)
kept_spans = tuple(
span for span, present in zip(spans, mask.present, strict=True) if present
)
if not rendered:
msg = "delete policy cannot remove every token"
raise ValueError(msg)
return rebuild_snapshot(snapshot, kept_spans, rendered)
[docs]
@dataclass(frozen=True, slots=True)
class PadPolicy:
"""Replace absent tokens with a fixed mask placeholder."""
placeholder: str = "[MASK]"
"""Placeholder text inserted for absent tokens."""
@property
def name(self) -> str:
"""Return the registered absence-policy identifier."""
return "pad"
[docs]
def apply(
self,
snapshot: ConversationSnapshot,
players: PlayerSet,
mask: CoalitionMask,
) -> ConversationSnapshot:
"""Substitute absent tokens with :attr:`placeholder`."""
mask.validate_against(players)
spans = tokenize_snapshot(snapshot)
if len(spans) != players.num_players:
msg = "token layout does not match the supplied player set"
raise ValueError(msg)
rendered = tuple(
span.text if present else self.placeholder
for span, present in zip(spans, mask.present, strict=True)
)
return rebuild_snapshot(snapshot, spans, rendered)
[docs]
@dataclass(frozen=True, slots=True)
class NeutralPolicy:
"""Replace absent tokens with neutral fillers that preserve token width."""
fill_char: str = "_"
"""Single character repeated to match each absent token length."""
@property
def name(self) -> str:
"""Return the registered absence-policy identifier."""
return "neutral"
[docs]
def apply(
self,
snapshot: ConversationSnapshot,
players: PlayerSet,
mask: CoalitionMask,
) -> ConversationSnapshot:
"""Substitute absent tokens with width-matched neutral fillers."""
mask.validate_against(players)
if len(self.fill_char) != 1:
msg = "fill_char must be exactly one character"
raise ValueError(msg)
spans = tokenize_snapshot(snapshot)
if len(spans) != players.num_players:
msg = "token layout does not match the supplied player set"
raise ValueError(msg)
rendered = tuple(
span.text if present else self.fill_char * len(span.text)
for span, present in zip(spans, mask.present, strict=True)
)
return rebuild_snapshot(snapshot, spans, rendered)