sinlib 0.2.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sinlib-0.2.2 → sinlib-0.3.0}/.gitignore +1 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/.readthedocs.yaml +1 -1
- {sinlib-0.2.2 → sinlib-0.3.0}/PKG-INFO +50 -2
- {sinlib-0.2.2 → sinlib-0.3.0}/README.md +46 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/pyproject.toml +4 -1
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/__init__.py +10 -2
- sinlib-0.3.0/src/sinlib/charbert/__init__.py +15 -0
- sinlib-0.3.0/src/sinlib/charbert/alignment.py +154 -0
- sinlib-0.3.0/src/sinlib/charbert/backend.py +362 -0
- sinlib-0.3.0/src/sinlib/charbert/char_encoder.py +92 -0
- sinlib-0.3.0/src/sinlib/charbert/char_tokenization.py +123 -0
- sinlib-0.3.0/src/sinlib/charbert/config.py +35 -0
- sinlib-0.3.0/src/sinlib/charbert/embeddings.py +92 -0
- sinlib-0.3.0/src/sinlib/charbert/encoder.py +151 -0
- sinlib-0.3.0/src/sinlib/charbert/hi_module.py +107 -0
- sinlib-0.3.0/src/sinlib/charbert/modeling.py +81 -0
- sinlib-0.3.0/src/sinlib/charbert/seq2seq_decoder.py +482 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/spellcheck.py +279 -53
- sinlib-0.3.0/tests/test_charbert_backend.py +247 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/tests/test_spellcheck.py +15 -12
- {sinlib-0.2.2 → sinlib-0.3.0}/website/astro.config.mjs +2 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/examples/typo-correction.md +1 -9
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/examples/visualization.md +4 -1
- {sinlib-0.2.2 → sinlib-0.3.0}/.github/workflows/python-publish.yml +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/.github/workflows/test.yml +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/LICENSE +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/requirements-dev.txt +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/requirements.txt +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/encoding.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/romanize.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/subword.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/tokenizer.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/transliterate.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/__init__.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/chars.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/dataset_utils.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/model_utils.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/models/__init__.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/models/akshara_ngram.json +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/models/akshara_vocab.json +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/models/bigru_detector.pt +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/models/transliterator-checkpoint.pth +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/models/transliterator_model.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/preprocessing.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/src/sinlib/utils/visualize.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/tests/test_combined_detector.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/tests/test_phase3_enhancements.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/tests/test_romanizer.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/tests/test_tokenizer.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/tests/test_tokenizer_batch.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/tests/test_tokenizer_bos.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/tests/test_tokenizer_special_tokens.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/tests/test_transliterator.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/tests/test_visualize.py +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/.gitignore +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/.vscode/extensions.json +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/.vscode/launch.json +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/README.md +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/package-lock.json +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/package.json +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/public/favicon.svg +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/assets/houston.webp +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/assets/logo-dark.png +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/assets/logo-light.png +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/api/encoding.mdx +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/api/preprocessing.mdx +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/api/romanizer.mdx +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/api/spellcheck.mdx +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/api/tokenizer.mdx +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/api/visualization.mdx +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/examples/tokenization.md +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/guides/spellcheck.mdx +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/guides/tokenization.mdx +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content/docs/index.mdx +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/content.config.ts +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/src/styles/custom.css +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/website/tsconfig.json +0 -0
- {sinlib-0.2.2 → sinlib-0.3.0}/welcome.png +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: sinlib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Sinhala NLP Toolkit
|
|
5
5
|
Project-URL: Code, https://github.com/Ransaka/sinlib
|
|
6
6
|
Project-URL: Docs, https://sinlib.readthedocs.io
|
|
@@ -39,6 +39,8 @@ Requires-Dist: matplotlib>=3.9.4
|
|
|
39
39
|
Requires-Dist: numpy>=1.24.0
|
|
40
40
|
Requires-Dist: tqdm>=4.64.1
|
|
41
41
|
Requires-Dist: transformers>=4.31.0
|
|
42
|
+
Provides-Extra: charbert
|
|
43
|
+
Requires-Dist: torch>=2.0; extra == 'charbert'
|
|
42
44
|
Provides-Extra: dev
|
|
43
45
|
Requires-Dist: pytest; extra == 'dev'
|
|
44
46
|
Description-Content-Type: text/markdown
|
|
@@ -105,6 +107,52 @@ detector.suggest_correction("අඩිරාජ")
|
|
|
105
107
|
# ['අධිරාජ']
|
|
106
108
|
```
|
|
107
109
|
|
|
110
|
+
### Neural Typo Correction (optional, CharBERT)
|
|
111
|
+
|
|
112
|
+
TypoDetector can optionally delegate hard cases to a **Sinhala-CharBERT** neural
|
|
113
|
+
corrector — a dual-channel (subword + phonological akshara) seq2seq model. This
|
|
114
|
+
catches noise classes the statistical dictionary pipeline cannot fix: Singlish
|
|
115
|
+
transliteration, dialectal morphology (`යන්ඩ` → `යන්න`), ZWJ-damaged ligatures
|
|
116
|
+
(`ක්රීඩාව` → `ක්රීඩාව`), split/fused words, and Unicode decomposition errors.
|
|
117
|
+
|
|
118
|
+
Install the optional dependency and pick a backend mode:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from sinlib import TypoDetector
|
|
122
|
+
|
|
123
|
+
# "denoise" - bounded word-level neural fix when dictionary suggestions fail
|
|
124
|
+
# "seq2seq" - open-vocabulary sentence-level fix when structural noise is detected
|
|
125
|
+
# "hybrid" - both, in cascade (recommended)
|
|
126
|
+
detector = TypoDetector(neural_backend="hybrid")
|
|
127
|
+
|
|
128
|
+
detector("මම ගෙදර යන්ඩ ඕනේ")
|
|
129
|
+
# 'මම ගෙදර යන්න ඕනේ'
|
|
130
|
+
|
|
131
|
+
detector("මම gedara යන්න ඕනේ")
|
|
132
|
+
# 'මම ගෙදර යන්න ඕනේ'
|
|
133
|
+
|
|
134
|
+
detector("ක්රීඩාව")
|
|
135
|
+
# 'ක්රීඩාව'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
| Kwarg | Default | Description |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `neural_backend` | `None` | `None`, `"denoise"`, `"seq2seq"`, or `"hybrid"` |
|
|
141
|
+
| `backend_model` | `Ransaka/sinhala-charbert-seq2seq` | HF Hub repo id or local checkpoint dir (`pytorch_model.bin` + `char_vocab.json`) |
|
|
142
|
+
| `backend_device` | auto (cuda > mps > cpu) | Torch device |
|
|
143
|
+
| `backend_revision` | `None` | Pin a Hub revision |
|
|
144
|
+
| `backend_num_beams` | `4` | Beam width for generation |
|
|
145
|
+
|
|
146
|
+
Notes:
|
|
147
|
+
|
|
148
|
+
- **Default behavior is unchanged** — without `neural_backend` the detector is
|
|
149
|
+
purely statistical and requires no torch.
|
|
150
|
+
- Neural corrections are gated: clean in-dictionary sentences never hit the
|
|
151
|
+
model, and a candidate is accepted only if it scores no worse than the input
|
|
152
|
+
(hallucination guard).
|
|
153
|
+
- If the checkpoint cannot be downloaded, the detector degrades gracefully to
|
|
154
|
+
statistical-only correction with a warning.
|
|
155
|
+
|
|
108
156
|
### Preprocessing
|
|
109
157
|
|
|
110
158
|
```python
|
|
@@ -60,6 +60,52 @@ detector.suggest_correction("අඩිරාජ")
|
|
|
60
60
|
# ['අධිරාජ']
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
### Neural Typo Correction (optional, CharBERT)
|
|
64
|
+
|
|
65
|
+
TypoDetector can optionally delegate hard cases to a **Sinhala-CharBERT** neural
|
|
66
|
+
corrector — a dual-channel (subword + phonological akshara) seq2seq model. This
|
|
67
|
+
catches noise classes the statistical dictionary pipeline cannot fix: Singlish
|
|
68
|
+
transliteration, dialectal morphology (`යන්ඩ` → `යන්න`), ZWJ-damaged ligatures
|
|
69
|
+
(`ක්රීඩාව` → `ක්රීඩාව`), split/fused words, and Unicode decomposition errors.
|
|
70
|
+
|
|
71
|
+
Install the optional dependency and pick a backend mode:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from sinlib import TypoDetector
|
|
75
|
+
|
|
76
|
+
# "denoise" - bounded word-level neural fix when dictionary suggestions fail
|
|
77
|
+
# "seq2seq" - open-vocabulary sentence-level fix when structural noise is detected
|
|
78
|
+
# "hybrid" - both, in cascade (recommended)
|
|
79
|
+
detector = TypoDetector(neural_backend="hybrid")
|
|
80
|
+
|
|
81
|
+
detector("මම ගෙදර යන්ඩ ඕනේ")
|
|
82
|
+
# 'මම ගෙදර යන්න ඕනේ'
|
|
83
|
+
|
|
84
|
+
detector("මම gedara යන්න ඕනේ")
|
|
85
|
+
# 'මම ගෙදර යන්න ඕනේ'
|
|
86
|
+
|
|
87
|
+
detector("ක්රීඩාව")
|
|
88
|
+
# 'ක්රීඩාව'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| Kwarg | Default | Description |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `neural_backend` | `None` | `None`, `"denoise"`, `"seq2seq"`, or `"hybrid"` |
|
|
94
|
+
| `backend_model` | `Ransaka/sinhala-charbert-seq2seq` | HF Hub repo id or local checkpoint dir (`pytorch_model.bin` + `char_vocab.json`) |
|
|
95
|
+
| `backend_device` | auto (cuda > mps > cpu) | Torch device |
|
|
96
|
+
| `backend_revision` | `None` | Pin a Hub revision |
|
|
97
|
+
| `backend_num_beams` | `4` | Beam width for generation |
|
|
98
|
+
|
|
99
|
+
Notes:
|
|
100
|
+
|
|
101
|
+
- **Default behavior is unchanged** — without `neural_backend` the detector is
|
|
102
|
+
purely statistical and requires no torch.
|
|
103
|
+
- Neural corrections are gated: clean in-dictionary sentences never hit the
|
|
104
|
+
model, and a candidate is accepted only if it scores no worse than the input
|
|
105
|
+
(hallucination guard).
|
|
106
|
+
- If the checkpoint cannot be downloaded, the detector degrades gracefully to
|
|
107
|
+
statistical-only correction with a warning.
|
|
108
|
+
|
|
63
109
|
### Preprocessing
|
|
64
110
|
|
|
65
111
|
```python
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sinlib"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Sinhala NLP Toolkit"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Ransaka", email = "ransaka.ravihara@gmail.com" }
|
|
@@ -28,6 +28,9 @@ classifiers = [
|
|
|
28
28
|
dev = [
|
|
29
29
|
"pytest",
|
|
30
30
|
]
|
|
31
|
+
charbert = [
|
|
32
|
+
"torch >= 2.0",
|
|
33
|
+
]
|
|
31
34
|
|
|
32
35
|
[project.urls]
|
|
33
36
|
Code = "https://github.com/Ransaka/sinlib"
|
|
@@ -17,7 +17,13 @@ BatchEncoding
|
|
|
17
17
|
|
|
18
18
|
TypoDetector
|
|
19
19
|
N-gram–based spell checker for Sinhala. Use
|
|
20
|
-
``TypoDetector.from_pretrained()`` or instantiate directly.
|
|
20
|
+
``TypoDetector.from_pretrained()`` or instantiate directly. Optionally
|
|
21
|
+
accepts ``neural_backend="seq2seq" | "denoise" | "hybrid"`` to delegate
|
|
22
|
+
hard cases to a CharBERT neural corrector.
|
|
23
|
+
|
|
24
|
+
CharBERTBackend
|
|
25
|
+
Loader/corrector for published Sinhala-CharBERT seq2seq checkpoints
|
|
26
|
+
(HF Hub or local directory). See ``sinlib.charbert``.
|
|
21
27
|
|
|
22
28
|
preprocessing
|
|
23
29
|
Utility module exposing ``remove_english_characters``,
|
|
@@ -42,6 +48,7 @@ from sinlib.subword import SubwordTokenizer
|
|
|
42
48
|
from sinlib.spellcheck import TypoDetector
|
|
43
49
|
from sinlib.romanize import Romanizer
|
|
44
50
|
from sinlib.transliterate import Transliterator # noqa: F401
|
|
51
|
+
from sinlib.charbert import CharBERTBackend
|
|
45
52
|
from sinlib.utils import preprocessing, setup_matplotlib
|
|
46
53
|
|
|
47
54
|
__all__: List[str] = [
|
|
@@ -49,9 +56,10 @@ __all__: List[str] = [
|
|
|
49
56
|
"Tokenizer",
|
|
50
57
|
"SubwordTokenizer",
|
|
51
58
|
"TypoDetector",
|
|
59
|
+
"CharBERTBackend",
|
|
52
60
|
"Romanizer",
|
|
53
61
|
"preprocessing",
|
|
54
62
|
"setup_matplotlib",
|
|
55
63
|
]
|
|
56
64
|
|
|
57
|
-
__version__ = "0.
|
|
65
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Optional Sinhala-CharBERT neural backend for sinlib's TypoDetector.
|
|
3
|
+
|
|
4
|
+
Provides inference-only model code and a high-level ``CharBERTBackend`` that
|
|
5
|
+
loads published CharBERT seq2seq checkpoints (HF Hub or local directory).
|
|
6
|
+
``torch`` is imported lazily; the base sinlib install does not require it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from sinlib.charbert.backend import CharBERTBackend
|
|
10
|
+
from sinlib.charbert.config import SinhalaCharBERTConfig
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"CharBERTBackend",
|
|
14
|
+
"SinhalaCharBERTConfig",
|
|
15
|
+
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sequence Alignment Engine for mapping subword tokens to phonological character unit boundaries.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
7
|
+
from transformers import AutoTokenizer, PreTrainedTokenizerFast, PreTrainedTokenizer
|
|
8
|
+
|
|
9
|
+
from sinlib.utils.preprocessing import normalize_sinhala, process_text
|
|
10
|
+
from sinlib.charbert.char_tokenization import SinhalaCharTokenizer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class AlignedSequence:
|
|
15
|
+
"""Represents a dual-channel aligned text sequence."""
|
|
16
|
+
text: str
|
|
17
|
+
tokens: List[str]
|
|
18
|
+
input_ids: List[int]
|
|
19
|
+
attention_mask: List[int]
|
|
20
|
+
char_units: List[str]
|
|
21
|
+
char_input_ids: List[int]
|
|
22
|
+
char_attention_mask: List[int]
|
|
23
|
+
start_char_idx: List[int]
|
|
24
|
+
end_char_idx: List[int]
|
|
25
|
+
subword_offsets: List[Tuple[int, int]] = field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SequenceAlignmentEngine:
|
|
29
|
+
"""
|
|
30
|
+
Computes exact bidirectional boundary mappings between Subword Tokens (m)
|
|
31
|
+
and Phonological Units / Aksharas (N).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
subword_tokenizer: Union[PreTrainedTokenizerFast, PreTrainedTokenizer, Any],
|
|
37
|
+
char_tokenizer: SinhalaCharTokenizer,
|
|
38
|
+
max_subword_length: int = 256,
|
|
39
|
+
max_char_length: int = 512,
|
|
40
|
+
):
|
|
41
|
+
self.subword_tokenizer = subword_tokenizer
|
|
42
|
+
self.char_tokenizer = char_tokenizer
|
|
43
|
+
self.max_subword_length = max_subword_length
|
|
44
|
+
self.max_char_length = max_char_length
|
|
45
|
+
|
|
46
|
+
def _get_phonological_spans(self, text: str, units: List[str]) -> List[Tuple[int, int]]:
|
|
47
|
+
"""Calculates character string offsets [start_char, end_char) for each phonological unit in raw text."""
|
|
48
|
+
spans: List[Tuple[int, int]] = []
|
|
49
|
+
curr_idx = 0
|
|
50
|
+
for u in units:
|
|
51
|
+
# Locate unit starting from current offset
|
|
52
|
+
found_idx = text.find(u, curr_idx)
|
|
53
|
+
if found_idx == -1:
|
|
54
|
+
# Fallback if normalization shifted index
|
|
55
|
+
found_idx = curr_idx
|
|
56
|
+
end_idx = found_idx + len(u)
|
|
57
|
+
spans.append((found_idx, end_idx))
|
|
58
|
+
curr_idx = end_idx
|
|
59
|
+
return spans
|
|
60
|
+
|
|
61
|
+
def align(self, text: str) -> AlignedSequence:
|
|
62
|
+
"""
|
|
63
|
+
Aligns a single sentence across subword and character channels.
|
|
64
|
+
Returns subword IDs, character IDs, and boundary coordinate arrays start_char_idx and end_char_idx.
|
|
65
|
+
"""
|
|
66
|
+
norm_text = normalize_sinhala(text.strip())
|
|
67
|
+
if not norm_text:
|
|
68
|
+
norm_text = " "
|
|
69
|
+
|
|
70
|
+
# 1. Subword Tokenization with offset mapping
|
|
71
|
+
subword_encoding = self.subword_tokenizer(
|
|
72
|
+
norm_text,
|
|
73
|
+
truncation=True,
|
|
74
|
+
max_length=self.max_subword_length,
|
|
75
|
+
return_offsets_mapping=True,
|
|
76
|
+
add_special_tokens=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
subword_ids: List[int] = subword_encoding["input_ids"]
|
|
80
|
+
subword_attn: List[int] = subword_encoding.get("attention_mask", [1] * len(subword_ids))
|
|
81
|
+
subword_tokens: List[str] = self.subword_tokenizer.convert_ids_to_tokens(subword_ids)
|
|
82
|
+
subword_offsets: List[Tuple[int, int]] = subword_encoding["offset_mapping"]
|
|
83
|
+
|
|
84
|
+
# 2. Phonological Unit Tokenization
|
|
85
|
+
raw_units = process_text(norm_text)
|
|
86
|
+
unit_spans = self._get_phonological_spans(norm_text, raw_units)
|
|
87
|
+
|
|
88
|
+
# Prepend <bos> and append <eos> to phonological units
|
|
89
|
+
char_units = [SinhalaCharTokenizer.BOS_TOKEN] + raw_units + [SinhalaCharTokenizer.EOS_TOKEN]
|
|
90
|
+
char_ids = (
|
|
91
|
+
[self.char_tokenizer.bos_token_id]
|
|
92
|
+
+ [self.char_tokenizer.vocab_map.get(u, self.char_tokenizer.unk_token_id) for u in raw_units]
|
|
93
|
+
+ [self.char_tokenizer.eos_token_id]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Truncate character sequence if needed
|
|
97
|
+
if len(char_ids) > self.max_char_length:
|
|
98
|
+
char_ids = char_ids[: self.max_char_length - 1] + [self.char_tokenizer.eos_token_id]
|
|
99
|
+
char_units = char_units[: self.max_char_length - 1] + [SinhalaCharTokenizer.EOS_TOKEN]
|
|
100
|
+
unit_spans = unit_spans[: len(char_units) - 2]
|
|
101
|
+
|
|
102
|
+
char_attn = [1] * len(char_ids)
|
|
103
|
+
bos_idx = 0
|
|
104
|
+
eos_idx = len(char_ids) - 1
|
|
105
|
+
|
|
106
|
+
# 3. Align Subword Tokens to Phonological Units
|
|
107
|
+
start_char_idx: List[int] = []
|
|
108
|
+
end_char_idx: List[int] = []
|
|
109
|
+
|
|
110
|
+
for i, (tok_start, tok_end) in enumerate(subword_offsets):
|
|
111
|
+
# Special tokens [CLS], [SEP], [PAD]
|
|
112
|
+
if tok_start == 0 and tok_end == 0:
|
|
113
|
+
if i == 0: # First token is [CLS] / [BOS]
|
|
114
|
+
start_char_idx.append(bos_idx)
|
|
115
|
+
end_char_idx.append(bos_idx)
|
|
116
|
+
else: # Last or padding token is [SEP] / [EOS]
|
|
117
|
+
start_char_idx.append(eos_idx)
|
|
118
|
+
end_char_idx.append(eos_idx)
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
# Find matching phonological units by offset overlap
|
|
122
|
+
matching_unit_indices = []
|
|
123
|
+
for u_idx, (u_start, u_end) in enumerate(unit_spans):
|
|
124
|
+
# Check for overlap: max(tok_start, u_start) < min(tok_end, u_end)
|
|
125
|
+
if max(tok_start, u_start) < min(tok_end, u_end):
|
|
126
|
+
# Offset +1 because index 0 is <bos>
|
|
127
|
+
matching_unit_indices.append(u_idx + 1)
|
|
128
|
+
|
|
129
|
+
if matching_unit_indices:
|
|
130
|
+
start_char_idx.append(matching_unit_indices[0])
|
|
131
|
+
end_char_idx.append(matching_unit_indices[-1])
|
|
132
|
+
else:
|
|
133
|
+
# Nearest fallback to prevent out-of-bounds indexing
|
|
134
|
+
closest_idx = min(
|
|
135
|
+
range(len(unit_spans)),
|
|
136
|
+
key=lambda idx: abs(unit_spans[idx][0] - tok_start),
|
|
137
|
+
default=0,
|
|
138
|
+
)
|
|
139
|
+
adjusted_idx = min(closest_idx + 1, eos_idx - 1)
|
|
140
|
+
start_char_idx.append(adjusted_idx)
|
|
141
|
+
end_char_idx.append(adjusted_idx)
|
|
142
|
+
|
|
143
|
+
return AlignedSequence(
|
|
144
|
+
text=norm_text,
|
|
145
|
+
tokens=subword_tokens,
|
|
146
|
+
input_ids=subword_ids,
|
|
147
|
+
attention_mask=subword_attn,
|
|
148
|
+
char_units=char_units,
|
|
149
|
+
char_input_ids=char_ids,
|
|
150
|
+
char_attention_mask=char_attn,
|
|
151
|
+
start_char_idx=start_char_idx,
|
|
152
|
+
end_char_idx=end_char_idx,
|
|
153
|
+
subword_offsets=subword_offsets,
|
|
154
|
+
)
|