sinlib 0.3.0__tar.gz → 0.3.2__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.
Files changed (78) hide show
  1. {sinlib-0.3.0 → sinlib-0.3.2}/PKG-INFO +3 -4
  2. {sinlib-0.3.0 → sinlib-0.3.2}/README.md +2 -3
  3. {sinlib-0.3.0 → sinlib-0.3.2}/pyproject.toml +1 -1
  4. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/__init__.py +1 -1
  5. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/encoder.py +19 -0
  6. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/seq2seq_decoder.py +5 -1
  7. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/spellcheck.py +32 -17
  8. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_charbert_backend.py +4 -3
  9. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/api/spellcheck.mdx +46 -0
  10. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/guides/spellcheck.mdx +55 -2
  11. {sinlib-0.3.0 → sinlib-0.3.2}/.github/workflows/python-publish.yml +0 -0
  12. {sinlib-0.3.0 → sinlib-0.3.2}/.github/workflows/test.yml +0 -0
  13. {sinlib-0.3.0 → sinlib-0.3.2}/.gitignore +0 -0
  14. {sinlib-0.3.0 → sinlib-0.3.2}/.readthedocs.yaml +0 -0
  15. {sinlib-0.3.0 → sinlib-0.3.2}/LICENSE +0 -0
  16. {sinlib-0.3.0 → sinlib-0.3.2}/requirements-dev.txt +0 -0
  17. {sinlib-0.3.0 → sinlib-0.3.2}/requirements.txt +0 -0
  18. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/__init__.py +0 -0
  19. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/alignment.py +0 -0
  20. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/backend.py +0 -0
  21. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/char_encoder.py +0 -0
  22. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/char_tokenization.py +0 -0
  23. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/config.py +0 -0
  24. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/embeddings.py +0 -0
  25. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/hi_module.py +0 -0
  26. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/charbert/modeling.py +0 -0
  27. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/encoding.py +0 -0
  28. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/romanize.py +0 -0
  29. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/subword.py +0 -0
  30. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/tokenizer.py +0 -0
  31. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/transliterate.py +0 -0
  32. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/__init__.py +0 -0
  33. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/chars.py +0 -0
  34. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/dataset_utils.py +0 -0
  35. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/model_utils.py +0 -0
  36. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/models/__init__.py +0 -0
  37. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/models/akshara_ngram.json +0 -0
  38. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/models/akshara_vocab.json +0 -0
  39. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/models/bigru_detector.pt +0 -0
  40. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/models/transliterator-checkpoint.pth +0 -0
  41. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/models/transliterator_model.py +0 -0
  42. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/preprocessing.py +0 -0
  43. {sinlib-0.3.0 → sinlib-0.3.2}/src/sinlib/utils/visualize.py +0 -0
  44. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_combined_detector.py +0 -0
  45. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_phase3_enhancements.py +0 -0
  46. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_romanizer.py +0 -0
  47. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_spellcheck.py +0 -0
  48. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_tokenizer.py +0 -0
  49. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_tokenizer_batch.py +0 -0
  50. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_tokenizer_bos.py +0 -0
  51. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_tokenizer_special_tokens.py +0 -0
  52. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_transliterator.py +0 -0
  53. {sinlib-0.3.0 → sinlib-0.3.2}/tests/test_visualize.py +0 -0
  54. {sinlib-0.3.0 → sinlib-0.3.2}/website/.gitignore +0 -0
  55. {sinlib-0.3.0 → sinlib-0.3.2}/website/.vscode/extensions.json +0 -0
  56. {sinlib-0.3.0 → sinlib-0.3.2}/website/.vscode/launch.json +0 -0
  57. {sinlib-0.3.0 → sinlib-0.3.2}/website/README.md +0 -0
  58. {sinlib-0.3.0 → sinlib-0.3.2}/website/astro.config.mjs +0 -0
  59. {sinlib-0.3.0 → sinlib-0.3.2}/website/package-lock.json +0 -0
  60. {sinlib-0.3.0 → sinlib-0.3.2}/website/package.json +0 -0
  61. {sinlib-0.3.0 → sinlib-0.3.2}/website/public/favicon.svg +0 -0
  62. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/assets/houston.webp +0 -0
  63. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/assets/logo-dark.png +0 -0
  64. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/assets/logo-light.png +0 -0
  65. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/api/encoding.mdx +0 -0
  66. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/api/preprocessing.mdx +0 -0
  67. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/api/romanizer.mdx +0 -0
  68. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/api/tokenizer.mdx +0 -0
  69. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/api/visualization.mdx +0 -0
  70. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/examples/tokenization.md +0 -0
  71. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/examples/typo-correction.md +0 -0
  72. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/examples/visualization.md +0 -0
  73. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/guides/tokenization.mdx +0 -0
  74. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content/docs/index.mdx +0 -0
  75. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/content.config.ts +0 -0
  76. {sinlib-0.3.0 → sinlib-0.3.2}/website/src/styles/custom.css +0 -0
  77. {sinlib-0.3.0 → sinlib-0.3.2}/website/tsconfig.json +0 -0
  78. {sinlib-0.3.0 → sinlib-0.3.2}/welcome.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sinlib
3
- Version: 0.3.0
3
+ Version: 0.3.2
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
@@ -147,9 +147,8 @@ Notes:
147
147
 
148
148
  - **Default behavior is unchanged** — without `neural_backend` the detector is
149
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).
150
+ - A neural candidate is accepted only if it scores no worse than the input
151
+ (hallucination guard), so already-clean text is never degraded.
153
152
  - If the checkpoint cannot be downloaded, the detector degrades gracefully to
154
153
  statistical-only correction with a warning.
155
154
 
@@ -100,9 +100,8 @@ Notes:
100
100
 
101
101
  - **Default behavior is unchanged** — without `neural_backend` the detector is
102
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).
103
+ - A neural candidate is accepted only if it scores no worse than the input
104
+ (hallucination guard), so already-clean text is never degraded.
106
105
  - If the checkpoint cannot be downloaded, the detector degrades gracefully to
107
106
  statistical-only correction with a warning.
108
107
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sinlib"
3
- version = "0.3.0"
3
+ version = "0.3.2"
4
4
  description = "Sinhala NLP Toolkit"
5
5
  authors = [
6
6
  { name = "Ransaka", email = "ransaka.ravihara@gmail.com" }
@@ -62,4 +62,4 @@ __all__: List[str] = [
62
62
  "setup_matplotlib",
63
63
  ]
64
64
 
65
- __version__ = "0.3.0"
65
+ __version__ = "0.3.2"
@@ -120,6 +120,25 @@ class SinhalaCharBERTEncoder(nn.Module):
120
120
  all_char_hidden_states: List[torch.Tensor] = []
121
121
  all_attentions: List[torch.Tensor] = []
122
122
 
123
+ # Normalize the attention mask to the 4-D extended additive format
124
+ # (batch, 1, 1, seq_len) expected by transformers' attention
125
+ # implementations. Newer versions of transformers (>= 4.56) index the
126
+ # mask as attention_mask[:, :, :, :key_len] and crash with
127
+ # "too many indices for tensor of dimension 2" if a 2-D padding mask
128
+ # is passed straight through.
129
+ if attention_mask is not None and attention_mask.dim() != 4:
130
+ if attention_mask.dim() == 2:
131
+ mask = attention_mask[:, None, None, :]
132
+ elif attention_mask.dim() == 3:
133
+ mask = attention_mask[:, None, :, :]
134
+ else:
135
+ raise ValueError(
136
+ f"Unsupported attention_mask dims: {attention_mask.dim()}"
137
+ )
138
+ # Convert 1/0 padding mask to an additive mask (0 keep, -inf drop)
139
+ dtype = token_hidden_states.dtype
140
+ attention_mask = (1.0 - mask.to(dtype)) * torch.finfo(dtype).min
141
+
123
142
  curr_token = token_hidden_states
124
143
  curr_char = char_hidden_states
125
144
 
@@ -78,7 +78,11 @@ class SinhalaCharBERTDecoderLayer(nn.Module):
78
78
  key=hidden_states,
79
79
  value=hidden_states,
80
80
  attn_mask=causal_mask,
81
- need_weights=False,
81
+ # need_weights=True forces the math code path of
82
+ # nn.MultiheadAttention; the fused fast path has shown
83
+ # device-dependent failures with 2-D float causal masks
84
+ # on some CUDA builds.
85
+ need_weights=True,
82
86
  )
83
87
  hidden_states = self.self_attn_layer_norm(residual + self.dropout(attn_out))
84
88
 
@@ -4,6 +4,7 @@ import math
4
4
  import warnings
5
5
  import json
6
6
  import re
7
+ import traceback
7
8
  from pathlib import Path
8
9
  from difflib import get_close_matches, SequenceMatcher
9
10
  from typing import Dict, List, Optional, Set, Tuple
@@ -663,22 +664,19 @@ class TypoDetector:
663
664
 
664
665
  def _try_neural_sentence(self, text: str) -> Optional[str]:
665
666
  """
666
- Runs the CharBERT seq2seq neural pass over a sentence when structural
667
- noise or unfixable suspicious words are detected. Returns the neural
668
- correction if it passes the acceptance guard, otherwise None.
667
+ Runs the CharBERT seq2seq neural pass over a sentence and returns the
668
+ neural correction if it passes the acceptance guard, otherwise None.
669
+
670
+ The model runs on every non-empty sentence: real-word typos
671
+ (e.g. sibilant confusion) and word split/fusion artifacts consist of
672
+ perfectly valid dictionary words and cannot be detected by any cheap
673
+ statistical heuristic. The acceptance guard — the neural output must
674
+ score no worse than the input — is what protects already-clean text
675
+ from spurious "corrections".
669
676
  """
670
677
  if not text or not text.strip() or not self.has_charbert:
671
678
  return None
672
679
 
673
- # Trigger gate: structural noise or any out-of-dictionary word
674
- words = [self._extract_punctuation(t)[1] for t in text.split()]
675
- trigger = CharBERTBackend.has_structural_noise(text) or any(
676
- w and (w not in self._dictionary and normalize_sinhala(w) not in self._dictionary)
677
- for w in words
678
- )
679
- if not trigger:
680
- return None
681
-
682
680
  if text in self._neural_sentence_cache:
683
681
  return self._neural_sentence_cache[text]
684
682
 
@@ -687,8 +685,13 @@ class TypoDetector:
687
685
  candidate = self._charbert.correct_sentence(
688
686
  text, num_beams=self._backend_num_beams
689
687
  ).strip()
690
- except Exception as exc:
691
- warnings.warn(f"CharBERT neural correction failed: {exc}", RuntimeWarning)
688
+ except Exception:
689
+ # Include the traceback: identical warnings are deduplicated by the
690
+ # warnings module, so a short message would hide repeated failures.
691
+ warnings.warn(
692
+ "CharBERT neural correction failed:\n" + traceback.format_exc(),
693
+ RuntimeWarning,
694
+ )
692
695
 
693
696
  result: Optional[str] = None
694
697
  if candidate and candidate != text:
@@ -934,8 +937,19 @@ class TypoDetector:
934
937
  using Phonological Trie search, BiGRU Seq2Seq, and Stupid Backoff LM.
935
938
  """
936
939
  self._ensure_loaded()
940
+
941
+ # Neural-first pass (seq2seq / hybrid modes): when structural noise or
942
+ # out-of-dictionary words are present, run the CharBERT model on the
943
+ # RAW input BEFORE the statistical word loop, so that word-level
944
+ # replacements cannot corrupt the sentence the model conditions on.
945
+ neural_done: Optional[str] = None
946
+ if self.neural_backend in ("seq2seq", "hybrid"):
947
+ neural_done = self._try_neural_sentence(text)
948
+
949
+ work_text = neural_done if neural_done is not None else text
950
+
937
951
  corrected: List[str] = []
938
- raw_tokens = text.split() if isinstance(text, str) else [str(text)]
952
+ raw_tokens = work_text.split() if isinstance(work_text, str) else [str(work_text)]
939
953
  parsed_tokens = [self._extract_punctuation(t) for t in raw_tokens]
940
954
  words = [p[1] for p in parsed_tokens]
941
955
  n_words = len(words)
@@ -979,8 +993,9 @@ class TypoDetector:
979
993
 
980
994
  result_text = " ".join(corrected)
981
995
 
982
- # Open-vocabulary neural sentence pass (seq2seq / hybrid modes)
983
- if self.neural_backend in ("seq2seq", "hybrid"):
996
+ # Fallback neural pass (seq2seq / hybrid modes): only when the
997
+ # neural-first pass did not already handle this sentence.
998
+ if self.neural_backend in ("seq2seq", "hybrid") and neural_done is None:
984
999
  neural_sentence = self._try_neural_sentence(result_text)
985
1000
  if neural_sentence is not None:
986
1001
  result_text = neural_sentence
@@ -142,11 +142,12 @@ def test_neural_pass_applies_correction(neural_detector):
142
142
  assert result == "මම ගෙදර යන්න ඕනේ"
143
143
 
144
144
 
145
- def test_neural_pass_skipped_on_clean_in_dictionary_sentence(neural_detector):
146
- # All words in dictionary and no structural noise -> no neural call
145
+ def test_clean_in_dictionary_sentence_unchanged(neural_detector):
146
+ # Clean sentence: the model is consulted, but its (unchanged) output is
147
+ # rejected by the acceptance guard, so the text is returned as-is.
147
148
  result = neural_detector("ගෙදර පොත")
148
149
  backend = FakeCharBERTBackend.instances[0]
149
- assert len(backend.sentence_calls) == 0
150
+ assert len(backend.sentence_calls) == 1
150
151
  assert result == "ගෙදර පොත"
151
152
 
152
153
 
@@ -47,6 +47,31 @@ Main spell checking and correction class. Detects typos using Akshara Trigram sc
47
47
  <td><span class="param-type">bool</span><span class="param-badge badge-optional">Optional</span></td>
48
48
  <td>If True, defers downloading/loading weights until first call.</td>
49
49
  </tr>
50
+ <tr>
51
+ <td><span class="param-name">neural_backend</span></td>
52
+ <td><span class="param-type">str | None</span><span class="param-badge badge-optional">Optional</span></td>
53
+ <td>Optional CharBERT neural correction mode: <code>None</code>, <code>&quot;denoise&quot;</code>, <code>&quot;seq2seq&quot;</code>, or <code>&quot;hybrid&quot;</code>. Default <code>None</code> (statistical only).</td>
54
+ </tr>
55
+ <tr>
56
+ <td><span class="param-name">backend_model</span></td>
57
+ <td><span class="param-type">str</span><span class="param-badge badge-optional">Optional</span></td>
58
+ <td>HF Hub repo id or local checkpoint directory for the CharBERT model. Default <code>&quot;Ransaka/sinhala-charbert-seq2seq&quot;</code>.</td>
59
+ </tr>
60
+ <tr>
61
+ <td><span class="param-name">backend_device</span></td>
62
+ <td><span class="param-type">str | None</span><span class="param-badge badge-optional">Optional</span></td>
63
+ <td>Torch device (cuda &gt; mps &gt; cpu by default).</td>
64
+ </tr>
65
+ <tr>
66
+ <td><span class="param-name">backend_revision</span></td>
67
+ <td><span class="param-type">str | None</span><span class="param-badge badge-optional">Optional</span></td>
68
+ <td>Pin a specific HF Hub revision of the CharBERT checkpoint.</td>
69
+ </tr>
70
+ <tr>
71
+ <td><span class="param-name">backend_num_beams</span></td>
72
+ <td><span class="param-type">int</span><span class="param-badge badge-optional">Optional</span></td>
73
+ <td>Beam width for seq2seq generation. Default <code>4</code>.</td>
74
+ </tr>
50
75
  </tbody>
51
76
  </table>
52
77
 
@@ -187,5 +212,26 @@ prob = detector.word_ngram_probability("සිංහල")
187
212
  # ~3.2e-05 (higher is more common)
188
213
  ```
189
214
 
215
+ #### Neural Correction (CharBERT, optional)
216
+ ```python
217
+ # pip install "sinlib[charbert]"
218
+ detector = TypoDetector(neural_backend="hybrid")
219
+
220
+ detector("මම ගෙදර යන්ඩ ඕනේ")
221
+ # 'මම ගෙදර යන්න ඕනේ' (dialectal morphology)
222
+
223
+ detector("මම gedara යන්න ඕනේ")
224
+ # 'මම ගෙදර යන්න ඕනේ' (Singlish code-mixing)
225
+
226
+ detector("ක්රීඩාව")
227
+ # 'ක්‍රීඩාව' (ZWJ-damaged ligature)
228
+
229
+ # Point at a local checkpoint instead of the Hub
230
+ detector = TypoDetector(
231
+ neural_backend="seq2seq",
232
+ backend_model="/path/to/checkpoint",
233
+ )
234
+ ```
235
+
190
236
  </div>
191
237
  </div>
@@ -101,9 +101,62 @@ words = detector.get_dictionary() # set[str]
101
101
  probs = detector.get_ngram_probs() # dict
102
102
  ```
103
103
 
104
+ ## Neural typo correction (optional, CharBERT)
105
+
106
+ Version 0.3.0 adds an optional neural backend backed by **Sinhala-CharBERT** —
107
+ a dual-channel (subword + phonological akshara) seq2seq model. It handles noise
108
+ classes the statistical pipeline cannot fix: Singlish transliteration,
109
+ dialectal morphology (`යන්ඩ` → `යන්න`), ZWJ-damaged ligatures
110
+ (`ක්රීඩාව` → `ක්‍රීඩාව`), split/fused words, and Unicode decomposition errors.
111
+
112
+ Install the optional dependency and pick a backend mode:
113
+
114
+ ```python
115
+ from sinlib import TypoDetector
116
+
117
+ # "denoise" - bounded word-level neural fix when dictionary suggestions fail
118
+ # "seq2seq" - open-vocabulary sentence-level fix when structural noise is detected
119
+ # "hybrid" - both, in cascade (recommended)
120
+ detector = TypoDetector(neural_backend="hybrid")
121
+
122
+ detector("මම ගෙදර යන්ඩ ඕනේ")
123
+ # 'මම ගෙදර යන්න ඕනේ'
124
+
125
+ detector("මම gedara යන්න ඕනේ")
126
+ # 'මම ගෙදර යන්න ඕනේ'
127
+
128
+ detector("ක්රීඩාව")
129
+ # 'ක්‍රීඩාව'
130
+ ```
131
+
132
+ ### Constructor keywords
133
+
134
+ | Keyword | Default | Description |
135
+ |---|---|---|
136
+ | `neural_backend` | `None` | `None`, `"denoise"`, `"seq2seq"`, or `"hybrid"` |
137
+ | `backend_model` | `Ransaka/sinhala-charbert-seq2seq` | HF Hub repo id or a local checkpoint directory containing `pytorch_model.bin` + `char_vocab.json` |
138
+ | `backend_device` | auto (cuda > mps > cpu) | Torch device for the model |
139
+ | `backend_revision` | `None` | Pin a specific HF Hub revision |
140
+ | `backend_num_beams` | `4` | Beam width for seq2seq generation |
141
+
142
+ ### How the gates work
143
+
144
+ - Without `neural_backend`, behaviour is unchanged and purely statistical — no
145
+ torch required.
146
+ - In `"seq2seq"` / `"hybrid"` modes, the model runs on every sentence: real-word
147
+ typos (e.g. sibilant confusion) and word split/fusion artifacts consist of
148
+ valid dictionary words, so no cheap heuristic can pre-detect them. On GPU the
149
+ beam search pass takes well under a second per sentence.
150
+ - A neural candidate is accepted only if it is no noisier than the input
151
+ (hallucination guard), so clean text is never "corrected" into garbage.
152
+ - If the checkpoint cannot be downloaded, the detector degrades gracefully to
153
+ statistical-only correction with a warning.
154
+
104
155
  ## Limitations
105
156
 
106
- - Corrections are made word-by-word; the model has no sentence context.
157
+ - Statistical correction is word-by-word; the model has no sentence context
158
+ (the optional neural backend does operate on full sentences).
107
159
  - Only the top suggestion replaces a typo — alternatives available via `suggest_correction()`.
108
160
  - The dictionary covers general Sinhala vocabulary; domain-specific or neologistic words may be flagged.
109
- - Romanised Sinhala (Singlish) is not supported.
161
+ - Romanised Sinhala (Singlish) and structural noise are only handled by the
162
+ optional CharBERT backend described above.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes