Download models/tasks/trigger.py from sysy9292/kf-deberta-base-kg-extractor: direct link, hf CLI and curl.
- Browser
- Download file 5.51 kB
-
https://huggingface.co/sysy9292/kf-deberta-base-kg-extractor/resolve/main/models/tasks/trigger.py
- Command line
-
hf download hf://sysy9292/kf-deberta-base-kg-extractor/models/tasks/trigger.py
-
curl -L -o trigger.py https://huggingface.co/sysy9292/kf-deberta-base-kg-extractor/resolve/main/models/tasks/trigger.py
5.51 kB
| """Trigger boundary 학습의 class-balance와 negative-sampling 계약. | |
| 이 모듈은 Trigger neural Head를 소유하지 않는다. 동일 logits/target에 적용할 학습 목적만 | |
| 교체하며, production 기본값은 full-source all-negative BCE와 ``pos_weight=4``다. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| import torch | |
| from torch import nn | |
| from torch.nn import functional as F | |
| from ..contracts import TriggerTrainingConfig | |
| class TriggerBoundaryLossStrategy(nn.Module): | |
| """동일 boundary logits에 weighted BCE와 deterministic negative sampling을 적용한다.""" | |
| def __init__(self, config: TriggerTrainingConfig | None = None) -> None: | |
| super().__init__() | |
| self.config = config or TriggerTrainingConfig() | |
| def forward( | |
| self, | |
| start_logits: torch.Tensor, | |
| end_logits: torch.Tensor, | |
| start_target: torch.Tensor, | |
| end_target: torch.Tensor, | |
| token_mask: torch.BoolTensor, | |
| *, | |
| sample_step: int = 0, | |
| ) -> tuple[torch.Tensor, dict[str, torch.Tensor]]: | |
| if ( | |
| start_logits.shape != end_logits.shape | |
| or start_target.shape != start_logits.shape | |
| or end_target.shape != end_logits.shape | |
| or token_mask.shape != start_logits.shape[:-1] | |
| ): | |
| raise ValueError("Trigger boundary loss tensor shapes differ") | |
| if sample_step < 0: | |
| raise ValueError("Trigger sample_step must be non-negative") | |
| valid = token_mask.unsqueeze(-1).expand_as(start_logits) | |
| start_loss, start_stats = self._one_boundary( | |
| start_logits, | |
| start_target, | |
| valid, | |
| sample_step=sample_step, | |
| stream=0, | |
| ) | |
| end_loss, end_stats = self._one_boundary( | |
| end_logits, | |
| end_target, | |
| valid, | |
| sample_step=sample_step, | |
| stream=1, | |
| ) | |
| diagnostics = { | |
| "trigger/start_loss": start_loss, | |
| "trigger/end_loss": end_loss, | |
| "trigger/effective_pos_weight": start_logits.new_tensor( | |
| self.config.pos_weight | |
| ), | |
| } | |
| diagnostics.update( | |
| {f"trigger/start_{name}": value for name, value in start_stats.items()} | |
| ) | |
| diagnostics.update( | |
| {f"trigger/end_{name}": value for name, value in end_stats.items()} | |
| ) | |
| diagnostics["trigger/positive_boundary_count"] = ( | |
| start_stats["positive_count"] + end_stats["positive_count"] | |
| ) | |
| diagnostics["trigger/negative_boundary_count"] = ( | |
| start_stats["negative_count"] + end_stats["negative_count"] | |
| ) | |
| diagnostics["trigger/sampled_negative_count"] = ( | |
| start_stats["sampled_negative_count"] | |
| + end_stats["sampled_negative_count"] | |
| ) | |
| return start_loss + end_loss, diagnostics | |
| def _one_boundary( | |
| self, | |
| logits: torch.Tensor, | |
| target: torch.Tensor, | |
| valid: torch.BoolTensor, | |
| *, | |
| sample_step: int, | |
| stream: int, | |
| ) -> tuple[torch.Tensor, dict[str, torch.Tensor]]: | |
| positive = valid & target.gt(0.5) | |
| negative = valid & ~positive | |
| selected_negative = self._sample_negative( | |
| negative, | |
| positive_count=int(positive.sum().item()), | |
| sample_step=sample_step, | |
| stream=stream, | |
| ) | |
| selected = positive | selected_negative | |
| if not torch.any(selected): | |
| loss = logits.sum() * 0.0 | |
| else: | |
| arguments: dict[str, object] = {} | |
| if self.config.pos_weight != 1.0: | |
| arguments["pos_weight"] = logits.new_tensor(self.config.pos_weight) | |
| loss = F.binary_cross_entropy_with_logits( | |
| logits[selected], | |
| target[selected].to(logits.dtype), | |
| **arguments, | |
| ) | |
| return loss, { | |
| "positive_count": logits.new_tensor(float(positive.sum().item())), | |
| "negative_count": logits.new_tensor(float(negative.sum().item())), | |
| "sampled_negative_count": logits.new_tensor( | |
| float(selected_negative.sum().item()) | |
| ), | |
| } | |
| def _sample_negative( | |
| self, | |
| negative: torch.BoolTensor, | |
| *, | |
| positive_count: int, | |
| sample_step: int, | |
| stream: int, | |
| ) -> torch.BoolTensor: | |
| ratio = self.config.negative_to_positive_ratio | |
| if ratio is None: | |
| return negative | |
| negative_flat = torch.nonzero( | |
| negative.detach().to(device="cpu").reshape(-1), as_tuple=False | |
| ).flatten() | |
| # Zero-positive batches still contribute a small, deterministic negative signal. | |
| requested = math.ceil(ratio * max(positive_count, 1)) | |
| count = min(int(negative_flat.numel()), requested) | |
| selected_cpu = torch.zeros(negative.numel(), dtype=torch.bool) | |
| if count: | |
| generator = torch.Generator(device="cpu") | |
| mixed_seed = ( | |
| self.config.sampling_seed | |
| + 1_000_003 * sample_step | |
| + 97_409 * stream | |
| ) % (2**63 - 1) | |
| generator.manual_seed(mixed_seed) | |
| order = torch.randperm( | |
| negative_flat.numel(), generator=generator | |
| )[:count] | |
| selected_cpu[negative_flat[order]] = True | |
| return selected_cpu.reshape(negative.shape).to(device=negative.device) | |