Twinity-1
中國大陸中文 → 臺灣正體中文的確定性轉換器。8.86M 參數,CPU 單執行緒 11,300 字/秒。
Twinity-1 不生成文字。字典編譯成 conversion lattice,界定「哪些位置可以改、可以改成什麼」; 模型只在每個歧義位點裁決「這個語境該不該改」;Viterbi 選出全域一致的編輯集合, 最後對原文做最小 splice——編輯範圍以外的每一個位元組原樣保留。
因此它結構上不可能改寫語句、增刪內容、或破壞外語片段與程式碼。
import twlat
twlat.convert("这个程序有bug,请在服务器上重新部署。")
# '這個程式有 bug,請在伺服器上重新部署。'
為什麼不用查表或 LLM
| 查表(OpenCC) | LLM 改寫 | Twinity-1 | |
|---|---|---|---|
| 語境判斷 | ✗ 無 | ✓ | ✓ |
| 確定性 | ✓ | ✗ | ✓ 100% |
| 只改該改的 | ✓ | ✗ 會潤飾/增刪 | ✓ 最小 splice |
| 外語/程式碼保留 | ✓ | ✗ | ✓ 1,248/1,248 |
| 成本 | $0 | API | $0 |
| 延遲(p95) | <1 ms | 秒級 | 23 ms |
「程序」在軟體語境是程式、在法律語境就是程序;「里」在「那里」該轉裡、 在「公里」不能動。查表沒有語境;LLM 有語境但不確定且會多改。
評測
TWBench-Neutral(1,496 題/8,438 個歧義位點,gold = 臺灣正體語料原文, 不由任何系統產生;gold 自身誤差經分層盲審量測並逐條修正):
| 系統 | site accuracy | 維持 | 改動 |
|---|---|---|---|
| OpenCC | 0.9176 | 0.9411 | 0.8604 |
| zhtw-mcp(規則層) | 0.9263 | 0.9510 | 0.8661 |
| 前代 V1(6.06M) | 0.9456 | 0.9694 | 0.8877 |
| 前代 V2(3.32M) | 0.9445 | 0.9565 | 0.9153 |
| Twinity-1(8.86M) | 0.9712 | 0.9941 | 0.9153 |
對前代顯著勝出(+2.7pp/+2.6pp,paired bootstrap p < 0.001)。 與 frontier LLM 基線在 320 題子集上無顯著差異(0.9782 vs 0.9709,p = 0.34)—— 本模型不主張比 LLM 更準,而是在同等準確度下提供確定性、零成本與 23 ms 延遲。
盲測排序(103 題、14 位獨立評審、匿名、正反序雙輪):Twinity-1 平均名次 1.296, 優於 V2(1.612)與 V1(2.199)。
怎麼訓練的
Confusion-set cloze 自監督,在 6.5 億字真實臺灣正體語料上:
每個 lattice 位點收合成單一 [MASK],模型預測臺灣書寫者實際用了哪個形式。
這把無標註語料變成上億個監督事件,且消除了「相信輸入表面形式」的捷徑。
雙 pass 架構:masked pass 提供無洩漏的語境證據,clean pass(陸式汙染文本) 提供表面證據與文件級領域向量。候選一律由與文本共用的字元 embedding 動態編碼,沒有 per-candidate 查表參數——這是字典熱更新的前提。
架構:d=256,8 層(6 層 dilated TCN dilation 1..128 + 2 層 local attention), 預訓練 120k 步 + finetune 8k 步,RTX 4080 約 2.8 小時。
操作點
模型與字典相同,只差「要多少證據才動手」(τ 是對數勝算比門檻):
| preset | 語意 | site acc |
|---|---|---|
accuracy |
20:1 勝算才改,benchmark 最佳 | 0.9712 |
balanced |
2.7:1 即改,預設 | 0.9668 |
taiwanize |
額外壓制陸式專用詞(視頻/博客/實時) | 0.9640 |
aggressive |
只靠字典硬過濾把關 | 0.9631 |
conv = twlat.Converter(preset="taiwanize")
r = conv.explain("这个视频的信息量很大")
for d in r.decisions:
print(d) # Decision('視頻'→'影片' @[2,4) cross_strait u=2.75)
熱更新
新增字典條目只需重新編譯 data/dict/lattice_lexicon.json,模型權重不動。
實測:訓練時完全沒見過的 50 條規則,zero-shot change accuracy 0.71
(前代架構同一量測為 0.12)。
檔案
| 檔案 | 用途 |
|---|---|
twinity-1.pt |
模型權重(8.86M 參數) |
data/dict/lattice_lexicon.json |
編譯後的字典(1,780 confusion group/3,977 詞形)——熱更新入口 |
data/dict/char_vocab_v3.json |
字元表(4,096),OOV 走 hash bucket |
data/dict/rules/ |
簡繁字表、臺標變體表、必轉字表 |
data/model/tau.json |
部署操作點的決策門檻 |
twlat/ |
推論程式碼(純 Python,相依:torch、numpy、regex、pyahocorasick、opencc) |
使用
pip install torch numpy regex pyahocorasick opencc-python-reimplemented
from huggingface_hub import snapshot_download
import os, sys
path = snapshot_download("JacobLinCool/Twinity-1")
os.environ["TWLAT_HOME"] = f"{path}/data"
sys.path.insert(0, path)
import twlat
conv = twlat.Converter(ckpt=f"{path}/twinity-1.pt", device="cpu")
print(conv.convert("这个程序有bug,请在服务器上部署"))
CPU 單執行緒最快(torch.set_num_threads(1)):11,300 字/秒、p95 23 ms。
執行緒開多反而慢 3–24 倍(小矩陣的執行緒同步成本壓倒運算)。
已知限制
- 陸式專用詞的漏轉:
服務器→伺服器、搜索→搜尋等在訓練語料中缺少 change 方向訊號(網爬語料原生大量出現且被標為保留),模型高信心保留。 約佔殘餘 change 錯誤的 25%。修正路徑明確但需重訓。 - INT8 量化未達標(−1.33pp),交付 FP32。
- 台/臺 由語境決定而非固定政策——多數語境正確,但官方機關名語域仍有殘餘錯誤。 若你的場景要求一律「臺」,請在後處理強制。
- 訓練語料含 CC-100 網爬文本,可能帶有其偏誤。
- 輸入超過 512 字會以滑動視窗處理(stride 384),跨窗的一致性未特別最佳化。
授權與致謝
模型權重 MIT。字典衍生自 zhtw-mcp(MIT) 與 OpenCC(Apache-2.0)——本專案的核心主張之一 正是「這些字典裡的語意資訊被嚴重低估」。訓練語料:維基百科 zh-tw(CC BY-SA 4.0)、 臺灣立法院法律研究資料(OGDL-1.0)、CC-100 繁體。