Skip to content

Khôi phục dấu (tiếng Việt)

Khôi phục dấu thanh và biến điệu nguyên âm trên văn bản tiếng Việt được viết không dấu: Toi yeu Viet NamTôi yêu Việt Nam. Đây là bước tiền xử lý phổ biến nhất trên văn bản tiếng Việt nhiễu — kết quả OCR, gõ từ bàn phím nước ngoài, viết tắt mạng xã hội, chuỗi Telex chưa gõ.

TL;DR — gợi ý của chúng tôi

bash
pip install "nom-vn[diacritic-hf]"   # transformers<5 + torch + sentencepiece
python
from nom.text.diacritic_models import HFDiacriticModel
restorer = HFDiacriticModel()  # mặc định Toshiiiii1, lazy-load lần gọi đầu
restorer("Toi yeu Viet Nam")    # 'Tôi yêu Việt Nam'

# Theo lô (nhanh hơn 7.6× trên 3080)
restorer.predict_batch(sentences, batch_size=16)

Mô hình mặc định là Toshiiiii1/Vietnamese_diacritics_restoration_5th (Apache 2.0, T5 200 M, safetensors) — SOTA công khai trên ma trận 4 register. Với corpora thiên về tiếng Việt formal / pháp lý / hội thoại, mô hình của chúng tôi nrl-ai/vn-diacritic-vit5-base hơn +1.29 pp trên formal / +0.18 pp trên hội thoại với cùng kích thước và giấy phép.

Bức tranh công khai — đo ngày 2026-05-02

Mô hìnhGiấy phépFormatbusiness 55literary 800conv 300formal 72Kết luận
Toshiiiii1/Vietnamese_diacritics_restoration_5thApache 2.0safetensors97.81 %89.40 %93.94 %98.14 %⭐ SOTA công khai, mặc định hiện tại
nrl-ai/vn-diacritic-vit5-base (chúng tôi)Apache 2.0safetensors94.98 %90.24 %94.12 %99.43 %cân bằng register tốt nhất; chọn cho pháp lý / hội thoại
qthuan2604/ViT5_Restore_Diacritics_VietnameseMITbin90.59 %yếu hơn của chúng tôi; bỏ qua
qthuan2604/BARTPho_Syllable_Restore_Diacritics_VietnameseMITsafetensors83.92 %yếu nhất trong số đã audit; bỏ qua
yammdd/vietnamese-diacritic-restoration-v2MITtf_model.h5chưa đochỉ TF; chi phí chuyển đổi cao, để sau
Bảng quy tắc (nom.text.fix_diacritics)Apache 2.0none41.06 %dự phòng zero-deps
LLM cục bộ (gemma4:e4b Q4 qua Ollama)Apache 2.0gguf93,18 %77,78 %87,91 %92,71 %Tốt nhất trong nhóm LLM cục bộ, chỉ sau ViT5 fine-tune 5-12 pp; ~0,9 s/câu trên RTX 3090. Cần ≥10 GB VRAM.
LLM cục bộ (qwen3:8b Q4 qua Ollama)Apache 2.0ggufChưa đo61,40 %75,29 %88,43 %Đứng giữa gemma3:4b và gemma4:e4b; ~0,6 s/câu trên RTX 3090. Cần ~5 GB VRAM.
LLM cục bộ (gemma3:4b Q4 qua Ollama)Apache 2.0gguf89,06 %62,05 %79,70 %81,26 %Mạnh ở văn bản hiện đại, sụt 27 pp ở văn học; ~0,9 s/câu trên RTX 3090
LLM cục bộ (phi4-mini Q4 qua Ollama)MITggufChưa đoChưa đo10,48 %8,24 %Hỏng trên tiếng Việt — dưới sàn quy tắc 41 %, kém cả qwen3:1.7b; bỏ qua.
LLM cục bộ (qwen3:1.7b Q4 qua Ollama)Apache 2.0gguf16,60 %Dưới sàn quy tắc 41 %; bỏ qua, dùng gemma4:e4b nếu cần LLM cục bộ
LLM đám mây (gpt-4o-mini)Độc quyền95,37 %81,84 %89,51 %95,79 %0,15 USD vào / 0,60 USD ra mỗi 1M token; điểm cân bằng chi phí–chất lượng của OpenAI; thắng gpt-5.4-mini ở văn học (+3,16 pp)
LLM đám mây (gpt-5.4-mini)Độc quyền94,85 %78,68 %92,06 %96,12 %0,75 USD vào / 4,50 USD ra mỗi 1M token (gấp 5 lần gpt-4o-mini); hơn 0,3-2,6 pp ở văn bản hành chính/hội thoại nhưng kém ở văn học
LLM đám mây (gpt-5.4-nano)Độc quyền92,92 %73,18 %85,15 %93,82 %0,20 USD vào / 1,25 USD ra mỗi 1M token; kém gpt-4o-mini ở cả 3 loại văn bản dù mới hơn; bỏ qua, dùng gpt-4o-mini
LLM đám mây (claude-haiku-4-5)Độc quyền95,88 %81,68 %90,49 %96,19 %1,00 USD vào / 5,00 USD ra mỗi 1M token; chất lượng nhỉnh hơn gpt-4o-mini ở văn bản trang trọng và hội thoại nhưng đắt hơn ~6 lần; gần như hoà ở văn học

Khoảng cách 8.7 pp giữa các register trên Toshiiiii1 xác nhận mô hình này over-fit về tiếng Việt formal/business hiện đại. Bản fine-tune vit5-base của chúng tôi đánh đổi 4 pp business để được +1.4 pp formal và ngang điểm literary — lựa chọn đúng cho corpora pháp lý / chat / OCR.

JSON baseline: benchmarks/results/baseline_diacritic_*.json.

Pipeline của chúng tôi

nom.text.fix_diacritics là một seam dạng Protocol: bất kỳ callable nào ánh xạ str -> str đều cắm vào được.

python
from nom.text import fix_diacritics
from nom.text.diacritic_models import HFDiacriticModel

# Mặc định Toshiiiii1
fix_diacritics("Hop dong nay duoc lap", model=HFDiacriticModel())

# Bản fine-tune cân bằng register của chúng tôi
fix_diacritics(
    "Hop dong nay duoc lap",
    model=HFDiacriticModel(model_id="nrl-ai/vn-diacritic-vit5-base"),
)

# Hoặc qua Ollama LLM
from nom.llm import Ollama
fix_diacritics("Hop dong nay duoc lap", llm=Ollama(model="gemma3:4b"))

# Hoặc dự phòng quy tắc zero-deps (chỉ best-effort)
fix_diacritics("Hop dong nay duoc lap")

HFDiacriticModel cung cấp predict() (1 câu) và predict_batch() (suy luận batched có pad, throughput 7.60×, đo trên 3080 16 GB Mobile, 120/120 chất lượng tương đương đường gọi đơn).

Mô hình đã huấn luyện — nrl-ai/*

Mô hình HFGiấy phépBaseTham sốDiskLatency (3080)Khi nào chọn
nrl-ai/vn-diacritic-vit5-baseApache-2.0ViT5-base (MIT)220 M900 MB100-272 ms/câubase tier — chất lượng cân bằng register tốt nhất
nrl-ai/vn-diacritic-smallApache-2.0BARTpho-syllable (MIT)115 M530 MB38-94 ms/câufast tier — nhanh 2.2×, chi phí ~3-4 pp chất lượng trung bình

Cả hai cùng huấn luyện trên một corpus 500K mixed Wiki+news trong 5 epoch cosine LR (mô hình nhỏ huấn luyện trên corpus lớn tổng quát hoá tốt hơn; cắt dữ liệu huấn luyện cho fast tier là cạm bẫy phổ biến mà chúng tôi chủ ý tránh).

Δ so với Toshiiiii1 (SOTA công khai chúng tôi đối chiếu):

RegisterToshiiiii1base (chúng tôi)Δ vs Toshismall (chúng tôi)Δ vs Toshi
formal_udhr98.14 %99.43 %+1.29 pp91.51 %-6.63 pp
business_5597.81 %94.98 %-2.83 pp94.44 %-3.37 pp
conversational_30093.94 %94.12 %+0.18 pp90.68 %-3.26 pp
literary_udvtb89.40 %90.24 %+0.84 pp86.33 %-3.07 pp

Bản base thắng 3/4 register so với Toshiiiii1 và hoà ở ngưỡng cổng chính; cổng nghiêm ngặt cho business (≥ 96 %) vẫn fail 1.02 pp, nên chưa bản nào nhận tên canonical nrl-ai/vn-diacritic-restoration — cả hai đều xuất xưởng dưới tên mô tả arch. Chọn base tier khi chất lượng quan trọng nhất; chọn small tier khi latency hoặc VRAM là ràng buộc và mức rớt ~3-4 pp trung bình chấp nhận được.

Tier kế hoạch tiếp theo:

TierBaseTrạng thái
nrl-ai/vn-diacritic-nanodistilled (10-30 M)tương lai — distillation từ teacher base, đích <50 ms inference CPU

Bộ dữ liệu — nrl-ai/*

Cả hai đã verify render được + load được qua datasets.load_dataset.

Bộ dữ liệu HFGiấy phépLà gì
nrl-ai/vn-diacritic-evalCC-BY-SA-4.0 (chặt nhất trong các thành phần)Lưới đánh giá 4 register: business_55 (CC0), formal_72 (PD UDHR), conversational_300 (CC-BY 2.0 Tatoeba), literary_800 (CC-BY-SA 4.0 UD-VTB). Tổng 1.227 cặp câu.
nrl-ai/vn-diacritic-trainCC-BY-SA-4.0 (per-config: wiki_500k=CC-BY-SA, news_150k=CC-BY-4.0)500K cặp Wikipedia + 150K cặp tin tức VN đã sửa NFC. Đã chống rò rỉ với vn-diacritic-eval. NFC-normalize tại lúc ghi.
python
from datasets import load_dataset

# Đánh giá bất kỳ mô hình nào trên cùng lưới 4 register
ds = load_dataset("nrl-ai/vn-diacritic-eval", "business_55", split="train")

# Tự huấn luyện — cùng dữ liệu chúng tôi dùng cho nrl-ai/vn-diacritic-vit5-base
wiki = load_dataset("nrl-ai/vn-diacritic-train", "wiki_500k", split="train")
news = load_dataset("nrl-ai/vn-diacritic-train", "news_150k", split="train")

Kết quả — đã đo

Mọi con số đều tái lập được trên một bản clone sạch qua các script bench dưới benchmarks/accuracy/training/diacritic/eval_checkpoint.py. Đo trên RTX 3080 16 GB Mobile / RTX 3090, có NFC + chuẩn hoá dấu câu ở cả hai phía, warmup 3 lần, num_beams=1.

RegisterSố câuToshiiiii1 (ms/câu)nrl-ai/vit5-base (ms/câu)
formal_udhr72245272
business_5555119147
conversational_30030091101
literary_udvtb800137156

Latency bị decoder ViT5 220 M chi phối; cả hai mô hình cùng họ arch. Để được 7.6× throughput trên cả hai, dùng predict_batch.

Bench thực tế ngoài-phân-phối (OOD, đo ngày 2026-05-01 sau v0.2.29)

benchmarks/data/spell_correction_eval_real/ là tập 150 câu hand-curate mà nhiễu lấy từ nguồn lỗi VN thực tế (forum / mobile / Telex thật / OCR engine / pháp lý / tin tức) — KHÔNG phải nom.text.noise. Cùng eval áp dụng cho cả khôi phục dấu và sửa chính tả vì sửa chính tả là siêu tập của khôi phục dấu.

Cả hai tier khôi phục dấu của chúng tôi vs Toshiiiii1 trên OOD:

Slicevit5-base v0.2.29vit5-base v0.2.28small v0.2.28Toshiiiii1
forum_2543.5449.3146.2860.11
mobile_2576.9979.6681.5196.95
telex_real_2514.3714.899.3318.54
ocr_2594.8394.5393.2994.22
legal_real_2593.0288.0589.1593.80
news_real_2596.0595.8090.3594.07
Tổng hợp (n=150)71.15 [66-76]71.50 [66-77]70.27 [65-76]77.40 [73-82]

Phát hiện chính sau v0.2.29 retrain (Wiki+news+legal corpus):

  1. Văn bản formal/legal cải thiện rõ. legal_real_25: 88.05 → 93.02 (+4.97 pp), news +0.25, OCR +0.30. Đây là chính cái mục tiêu của việc thêm 100K cặp legal vào corpus: phủ thêm vocab pháp lý.
  2. Văn bản informal regress. forum_25: 49.31 → 43.54 (-5.77 pp), mobile -2.67. Lý do: mô hình diacritic-only chỉ thấy cặp (stripped, clean), nên thêm corpus legal đẩy phân phối nghiêng về formal — informal vì thế hơi tệ đi.
  3. Tổng hợp -0.35 pp (71.50 → 71.15) vì regression informal lớn hơn improvement formal trong tỷ lệ slice. Nhưng đây là trade-off đúng cho use case thực tế.

Hệ quả thực tế (cập nhật sau v0.2.29):

  • Văn bản formal đã strip-dấu (legal docs, news, OCR text-only): dùng vn-diacritic-vit5-base v0.2.29. Tốt hơn v0.2.28 trên các slice này.
  • Nhiễu thực tế hỗn hợp (OCR + người gõ tay + social): dùng vn-spell-correction-base (siêu tập). Aggregate 79.62 % vs diacritic-only 71.15 % — chênh lệch +8.47 pp, vượt cả Toshiiiii1.
  • Toshiiiii1 vẫn dẫn đầu trên informal (forum / mobile / telex slices) cho mục đích diacritic-only thuần. Sự lựa chọn giữa Toshiiiii1 và vn-diacritic-vit5-base là register-dependent.

JSON nguồn: diacritic-vit5-base / diacritic-small / Toshiiiii1.

JSON baseline:

  • benchmarks/results/baseline_diacritic_toshiiiii_4register.json (Toshiiiii1)
  • benchmarks/results/baseline_diacritic_toshiiiii_t5.json, ..._tatoeba300.json, ..._udhr72.json, ..._udvtb_test.json (per-register)
  • training/diacritic/results/vit5-base-500k-cosine-full_summary.json (fine-tune của chúng tôi)
  • training/diacritic/results/vit5-base-500k-cosine-full_eval_local.json (re-eval local, ±0.12 pp tái lập được)
  • benchmarks/results/baseline_diacritic_qthuan_*.json (các ứng viên đã audit)

Tái lập

bash
# 1. Build các slice eval (tất định, không cần mạng)
python benchmarks/data/tatoeba_vi/build_diacritic_eval.py
python benchmarks/data/udhr_vi/build_diacritic_eval.py

# 2. Chạy eval 4 register cho bất kỳ mô hình HF nào
python training/diacritic/eval_checkpoint.py \
    --checkpoint Toshiiiii1/Vietnamese_diacritics_restoration_5th \
    --output-json benchmarks/results/baseline_diacritic_toshiiiii_4register.json

python training/diacritic/eval_checkpoint.py \
    --checkpoint nrl-ai/vn-diacritic-vit5-base \
    --output-json benchmarks/results/baseline_diacritic_vit5_base_4register.json

Huấn luyện

Pipeline huấn luyện đầy đủ ở training/diacritic/:

  • prep_data.py — Wikipedia stream → các cặp (input, target) đã lọc (NFC, chống rò eval).
  • prep_data_news.py — tương tự cho tmnam20/Vietnamese-News-dedup (CC-BY-4.0, đã sửa NFC).
  • train.py — HF Seq2SeqTrainer cosine LR, early stopping tuỳ chọn, eval 4 register hậu huấn luyện.
  • eval_checkpoint.py — re-eval độc lập từ một checkpoint dir hoặc HF repo id.
  • publish_hf.py — publish HF Hub có gate-check + tự sinh model card.
  • post_train.sh — rsync từ host GPU → re-eval local (lệch >0.5 pp là fail) → publish chạy thử.

Lịch sử thí nghiệm (đến nay 5 lượt) ở training/diacritic/README.md.

Cạm bẫy đặc thù tiếng Việt gặp trong quá trình này

  • NFC vs NFD. tmnam20/Vietnamese-News-dedup ship ~79 % văn bản decompose NFD. Một lần huấn luyện mixed-source trước đó đã train trên đó; mô hình emit ký tự kết hợp decompose mà eval byte-compare NFC bỏ sót → regression thảm khốc -15.45 pp trên register business. Hiện đã NFC-normalize tại 3 tầng (prep, prep-news, train preprocess).
  • Early stopping trên eval nhỏ nhiễu. --early-stopping-patience 3 với eval 200 mẫu fire ở epoch 0.96 của v3 — mô hình chưa kịp hội tụ. Hiện mặc định --eval-samples 1000 và khuyến nghị --early-stopping-patience 0 cho các run huấn luyện full-budget.
  • Chuẩn hoá dấu câu để khớp byte câu nguyên. UD-VTB ship câu có khoảng trắng quanh từng dấu (quy ước treebank); đầu ra seq2seq hiện đại có dấu dính liền. Bench script nay normalize_punct() cả hai phía trước so sánh — đã bắt được lỗi "0/800 sentence-exact" giả ở v0.2.17.
  • Đa nghĩa danh từ riêng. HungHùng / Hưng / Hứng (các tên thật khác nhau). Mô hình chọn theo tần số huấn luyện; không phải lúc nào cũng đúng cho input. Cần tài liệu hoá NER+lookup riêng cho các use case đòi hỏi danh từ riêng đúng.

Tham khảo