kevinwatt commited on
Commit
36d9085
·
verified ·
1 Parent(s): 66eb4a8

Translate model card and inference comments into English

Browse files

Preserves Traditional Chinese examples, model weights, vocabulary, inference configuration, license, and executable inference logic.

Files changed (1) hide show
  1. README.md +43 -33
README.md CHANGED
@@ -11,39 +11,42 @@ tags:
11
  - traditional-chinese
12
  ---
13
 
14
- # 靈犀注音(0c)D512 選字模型(PF512)
15
 
16
- 注音輸入法的神經選字排序模型。輸入法引擎先給出候選詞,本模型依已確認的前文(最多 64 字)與注音讀音
17
- 替每個候選算出 log P(候選|前文, 讀音),再和引擎分數合併決定是否改選。這個模型不是獨立的注音轉漢字模型,需要搭配候選來源使用。
 
 
18
 
19
- - 架構:自行訓練的 causal decoder,8 層、hidden 512、8 heads、FFN 768,從零預訓練,再做候選排序微調
20
- - 格式:ONNX(opset 18),CPU 推論
21
- - 字表:27857 字、416 個注音基底(`vocab.json`)
22
 
23
- ## 檔案
24
 
25
- | 檔案 | 說明 |
26
  |---|---|
27
- | `model.onnx` | 評分模型,SHA-256 `71af1f0e77b2752cc71cbc04bf19c8e8f2e765662246f766e65a2822bab4e66e` |
28
- | `vocab.json` | 特殊 token、字表與注音基底,SHA-256 `556dffaffbf4c05211a846dcee55ef735cea68c5754f9bffcc6f9588124172b3` |
29
- | `manifest.json` | 操作點、限制與檔案雜湊 |
30
 
31
- ## 安裝
32
 
33
- 需要 Python 3.9 以上,只用 CPU。
34
 
35
  ```sh
36
  pip install onnxruntime numpy huggingface_hub
37
  ```
38
 
39
- 第一次執行會從 Hugging Face 下載模型(約 130 MB),之後使用本機快取。
40
 
41
- ## 使用
42
 
43
- 下面是完整程式,存成 `rank.py` 就能執行。讀音每個音節一個字串(`ㄍㄨㄥ`、`ㄩㄢˊ`),候選的字數必須等於音節數。
 
44
 
45
- - 只有候選、沒有輸入法引擎:用 `rank()` 依模型分數排序,例如從同音詞裡挑最符合前文的一個。
46
- - 已經有輸入法引擎:用 `choose()` 把引擎分數和模型分數合併,只在模型夠有把握時改掉引擎首選。
47
 
48
  ```python
49
  import json
@@ -55,10 +58,10 @@ from huggingface_hub import snapshot_download
55
  path = snapshot_download("Funmula/0c-sequence-d512")
56
  manifest = json.load(open(f"{path}/manifest.json", encoding="utf-8"))
57
  vocab = json.load(open(f"{path}/vocab.json", encoding="utf-8"))
58
- CHARS = {c: i + 8 for i, c in enumerate(vocab["characters"])} # 0–7 是特殊 token,4 = UNK
59
  BASES = {b: i + 2 for i, b in enumerate(vocab["bases"])}
60
- TONES = {"ˊ": 2, "ˇ": 3, "ˋ": 4, "˙": 5} # 無聲調符號 = 一聲
61
- NEUTRAL = set(manifest.get("neutral_tolerant_last", [])) # 句尾助詞常被打成一聲
62
  session = ort.InferenceSession(f"{path}/model.onnx", providers=["CPUExecutionProvider"])
63
 
64
 
@@ -68,10 +71,11 @@ def syllable(s):
68
 
69
 
70
  def log_prob(context, reading, candidate):
71
- # [BOS] 前文 [SEP] 讀音×n [OUT] 候選前 n−1 字;回傳 log P(候選 | 前文, 讀音)。
 
72
  head = [1] + [CHARS.get(c, 4) for c in context[-64:]] + [2]
73
  sounds = [syllable(s) for s in reading]
74
- cand = [CHARS[c] for c in candidate] # 候選含字表外的字時 KeyError,由呼叫��回退
75
  n, out = len(cand), len(head) + len(sounds)
76
  ids = head + [0] * n + [3] + cand[:-1]
77
  bases = [0] * len(head) + [b for b, _ in sounds] + [0] * n
@@ -90,13 +94,14 @@ def score(context, reading, candidate):
90
  s = log_prob(context, reading, candidate)
91
  last = reading[-1]
92
  base = last[:-1] if last[-1] in TONES else last
93
- if base in NEUTRAL and last[-1] != "˙": # 最後一個音節另以輕聲評分,取較大者
94
  s = max(s, log_prob(context, reading[:-1] + [base + "˙"], candidate))
95
  return s
96
 
97
 
98
  def choose(context, reading, candidates, engine_scores, w=manifest["weight"], tau=manifest["margin"]):
99
- # candidates[0] 是輸入法引擎的首選,engine_scores 是引擎給的 log 分數;回傳要採用的候選索引。
 
100
  try:
101
  neural = [score(context, reading, c) for c in candidates]
102
  except KeyError:
@@ -107,7 +112,7 @@ def choose(context, reading, candidates, engine_scores, w=manifest["weight"], ta
107
 
108
 
109
  def rank(context, reading, candidates):
110
- # 沒有輸入法引擎時:只用模型分數排序同音候選,最可能的在前。
111
  return sorted(candidates, key=lambda c: score(context, reading, c), reverse=True)
112
 
113
 
@@ -115,15 +120,20 @@ print(rank("下週就是", ["ㄑㄧˊ", "ㄓㄨㄥ", "ㄎㄠˇ"], ["其中考",
115
  print(rank("", ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄊㄧㄢ", "ㄑㄧˋ", "ㄏㄣˇ", "ㄏㄠˇ"],
116
  ["金天天氣很好", "今天天氣很好", "今天天器很好"])) # ['今天天氣很好', ...]
117
 
118
- # 搭配輸入法引擎:candidates[0] 是引擎首選,模型分數夠高才改選
119
  candidates = ["公元", "公園"]
120
  print(candidates[choose("今天天氣很好我們去", ["ㄍㄨㄥ", "ㄩㄢˊ"], candidates, [-7.0, -7.5])]) # 公園
121
  ```
122
 
123
- ## 操作點與限制
124
 
125
- - 合併分數 = 引擎分數 + w × 模型分數,w = 2;只有最高分候選不是引擎首選、且分差大於 τ = 0 才改選
126
- - 音節數 2–32、候選數 ≤ 16;前文只用最後 64 字
127
- - 候選含字表外的字時整例不改選
128
- - 訓練時的候選來自靈犀注音的詞典(`dictionary_sha256 = 517e6c8bb5cbb145c0554edc3cc91c2b55fc753dd42e11194d83f6b0e119aea3`);換成其他引擎時,w 與 τ 需要依該引擎的分數尺度重新校準
129
- - 模型輸入也可以把多個候選的共同前綴合成一棵樹一次評分(`mask` 改為祖先遮罩、`target_node`/`target_ids` 多列),分數與逐一評分相同
 
 
 
 
 
 
11
  - traditional-chinese
12
  ---
13
 
14
+ # 0c D512 Zhuyin Candidate Reranker (PF512)
15
 
16
+ A neural candidate reranker for a Traditional Chinese Zhuyin (Bopomofo) input method.
17
+ Given confirmed preceding text (up to 64 characters), Zhuyin readings, and supplied candidates,
18
+ the model computes log P(candidate | context, reading). These scores can be combined with an input-method engine's
19
+ scores to decide whether to override its default candidate. A candidate source is required; the model does not generate candidates itself.
20
 
21
+ - Architecture: a dedicated causal Transformer decoder, 8 layers, hidden size 512, 8 attention heads, FFN size 768, pretrained from random initialization and then fine-tuned for candidate ranking
22
+ - Format: ONNX (opset 18), CPU inference
23
+ - Vocabulary: 27,857 characters and 416 Zhuyin bases (`vocab.json`)
24
 
25
+ ## Files
26
 
27
+ | File | Description |
28
  |---|---|
29
+ | `model.onnx` | Candidate scoring model; SHA-256 `71af1f0e77b2752cc71cbc04bf19c8e8f2e765662246f766e65a2822bab4e66e` |
30
+ | `vocab.json` | Special tokens, character vocabulary, and Zhuyin bases; SHA-256 `556dffaffbf4c05211a846dcee55ef735cea68c5754f9bffcc6f9588124172b3` |
31
+ | `manifest.json` | Operating points, input limits, and file hashes |
32
 
33
+ ## Installation
34
 
35
+ Requires Python 3.9 or later. The examples use CPU inference.
36
 
37
  ```sh
38
  pip install onnxruntime numpy huggingface_hub
39
  ```
40
 
41
+ The first run downloads the model from Hugging Face (approximately 130 MB). Subsequent runs use the local cache.
42
 
43
+ ## Usage
44
 
45
+ Save the complete example below as `rank.py` and run it. Supply one string per Zhuyin syllable
46
+ (for example, `ㄍㄨㄥ` and `ㄩㄢˊ`). Each candidate must have exactly one character per syllable.
47
 
48
+ - With supplied candidates only: use `rank()` to order homophonic candidates by model score and find the best match for the context.
49
+ - With an input-method engine: use `choose()` to combine engine and model scores, overriding the engine default only when the required score margin is met.
50
 
51
  ```python
52
  import json
 
58
  path = snapshot_download("Funmula/0c-sequence-d512")
59
  manifest = json.load(open(f"{path}/manifest.json", encoding="utf-8"))
60
  vocab = json.load(open(f"{path}/vocab.json", encoding="utf-8"))
61
+ CHARS = {c: i + 8 for i, c in enumerate(vocab["characters"])} # IDs 0–7 are special tokens; 4 = UNK
62
  BASES = {b: i + 2 for i, b in enumerate(vocab["bases"])}
63
+ TONES = {"ˊ": 2, "ˇ": 3, "ˋ": 4, "˙": 5} # No tone mark = first tone
64
+ NEUTRAL = set(manifest.get("neutral_tolerant_last", [])) # Final particles are often typed with first tone
65
  session = ort.InferenceSession(f"{path}/model.onnx", providers=["CPUExecutionProvider"])
66
 
67
 
 
71
 
72
 
73
  def log_prob(context, reading, candidate):
74
+ # [BOS] context [SEP] reading×n [OUT] first n−1 candidate characters.
75
+ # Return log P(candidate | context, reading).
76
  head = [1] + [CHARS.get(c, 4) for c in context[-64:]] + [2]
77
  sounds = [syllable(s) for s in reading]
78
+ cand = [CHARS[c] for c in candidate] # Unknown candidate characters raise KeyError for caller fallback
79
  n, out = len(cand), len(head) + len(sounds)
80
  ids = head + [0] * n + [3] + cand[:-1]
81
  bases = [0] * len(head) + [b for b, _ in sounds] + [0] * n
 
94
  s = log_prob(context, reading, candidate)
95
  last = reading[-1]
96
  base = last[:-1] if last[-1] in TONES else last
97
+ if base in NEUTRAL and last[-1] != "˙": # Also score the final syllable with neutral tone and keep the higher score
98
  s = max(s, log_prob(context, reading[:-1] + [base + "˙"], candidate))
99
  return s
100
 
101
 
102
  def choose(context, reading, candidates, engine_scores, w=manifest["weight"], tau=manifest["margin"]):
103
+ # candidates[0] is the engine default; engine_scores are engine log scores.
104
+ # Return the selected candidate index.
105
  try:
106
  neural = [score(context, reading, c) for c in candidates]
107
  except KeyError:
 
112
 
113
 
114
  def rank(context, reading, candidates):
115
+ # Rank supplied homophonic candidates by model score alone, highest first.
116
  return sorted(candidates, key=lambda c: score(context, reading, c), reverse=True)
117
 
118
 
 
120
  print(rank("", ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄊㄧㄢ", "ㄑㄧˋ", "ㄏㄣˇ", "ㄏㄠˇ"],
121
  ["金天天氣很好", "今天天氣很好", "今天天器很好"])) # ['今天天氣很好', ...]
122
 
123
+ # With an input-method engine, override candidates[0] only when the fused score warrants it.
124
  candidates = ["公元", "公園"]
125
  print(candidates[choose("今天天氣很好我們去", ["ㄍㄨㄥ", "ㄩㄢˊ"], candidates, [-7.0, -7.5])]) # 公園
126
  ```
127
 
128
+ ## Operating Points and Limitations
129
 
130
+ - Fused score = engine score + w × model score, with w = 2. Override only if another candidate has the highest score and exceeds the engine default by more than τ = 0.
131
+ - Supported operating range: 2–32 syllables, at most 16 candidates, and the last 64 context characters.
132
+ - If any candidate contains a character outside the vocabulary, `choose()` retains the engine default for the entire case.
133
+ - Training candidates came from the 0c dictionary (`dictionary_sha256 = 517e6c8bb5cbb145c0554edc3cc91c2b55fc753dd42e11194d83f6b0e119aea3`). Calibrate w and τ again when using a different engine's score scale.
134
+ - Shared candidate prefixes can also be scored together as a tree, using an ancestor attention mask and multiple `target_node`/`target_ids` rows. Scores match individual candidate scoring.
135
+
136
+ ## License and Release Scope
137
+
138
+ Model weights and the accompanying inference examples are released under the MIT license (see `LICENSE`).
139
+ This release covers this D512 checkpoint only. Training code and training materials are not included.