glossary-classifier
English summary. A Chinese sentence-pair classifier that scores glossary candidates
extracted from Chinese web novels. Given (term, context) it returns the probability that the
term deserves its own glossary entry (a proper name or a story-specific term such as a sect,
technique, realm or artifact) rather than being a common word or a broken fragment of a longer
term. Fine-tuned from hfl/chinese-roberta-wwm-ext,
shipped as ONNX fp32 for CPU inference. Built for
novel2epub (a Chinese web-novel to EPUB translation tool), where it powers the "AI score" column on the
glossary-candidate review page. ROC-AUC 0.985 on an 800-item gold set from 3 held-out novels.
Bộ phân loại cặp câu tiếng Trung, chấm điểm ứng viên glossary do bộ trích thuật ngữ lấy ra từ
truyện mạng Trung Quốc. Với đầu vào (thuật ngữ, ngữ cảnh), model trả về xác suất thuật ngữ đáng
có mục glossary riêng:
| Nhãn | Nghĩa | Ví dụ |
|---|---|---|
giữ (keep, chỉ số 1) |
Tên riêng hoặc thuật ngữ riêng của truyện: nhân vật, địa danh, tông môn, công pháp, cảnh giới, pháp bảo, danh hiệu… | 青云宗, 萧炎, 筑基 |
| bỏ (chỉ số 0) | Từ thường (common) hoặc mảnh cắt dở của một thuật ngữ dài hơn (fragment) |
道理, 占据, 北方, 云宗 |
Model chỉ dùng để sắp xếp và lọc cho người duyệt, không tự quyết định Nhận/Bỏ.
Nội dung repo
| Đường dẫn | Mô tả |
|---|---|
model.onnx |
Model ONNX fp32 (~409 MB), đầu ra logits [batch, 2] |
tokenizer.json |
Tokenizer (thư viện tokenizers), WordPiece của chinese-roberta-wwm-ext |
meta.json |
Thông số suy luận (max_len, keep_index) và chỉ số đánh giá |
checkpoint/ |
Bản Hugging Face Transformers (model.safetensors, config.json, tokenizer, report.json) để train tiếp hoặc xuất lại ONNX |
Cách dùng
Đầu vào / đầu ra
- Câu 1: thuật ngữ, ví dụ
青云宗. - Câu 2: một đoạn văn quanh một lần xuất hiện của thuật ngữ trong truyện, khoảng 40 ký tự (dữ liệu train: trung bình 39, từ 22 đến 46). Ngắn hơn vẫn chạy được, nhưng ngữ cảnh rất ngắn hoặc trống làm model kém chính xác.
- Mã hoá dạng cặp câu (
[CLS] term [SEP] context [SEP]),token_type_ids= 1 cho câu 2, tối đa 128 token, chỉ cắt phần ngữ cảnh (only_second). - Đầu ra
logits2 lớp. Lấy softmax rồi đọc cộtkeep_index(= 1).
Python (onnxruntime + tokenizers, không cần torch)
pip install onnxruntime tokenizers numpy huggingface_hub
hf download yukihuy99yuki/glossary-classifier --include "model.onnx" "tokenizer.json" "meta.json" --local-dir glossary-classifier
import json
import numpy as np
import onnxruntime as ort
from tokenizers import Tokenizer
d = "glossary-classifier"
meta = json.load(open(f"{d}/meta.json", encoding="utf-8"))
tok = Tokenizer.from_file(f"{d}/tokenizer.json")
tok.enable_truncation(max_length=meta["max_len"], strategy="only_second")
tok.enable_padding()
sess = ort.InferenceSession(f"{d}/model.onnx", providers=["CPUExecutionProvider"])
names = {i.name for i in sess.get_inputs()}
def score(pairs):
"""pairs: [(term, context), ...] -> xác suất 'giữ' cho từng cặp."""
enc = tok.encode_batch(pairs)
feed = {
"input_ids": np.array([e.ids for e in enc], dtype=np.int64),
"attention_mask": np.array([e.attention_mask for e in enc], dtype=np.int64),
"token_type_ids": np.array([e.type_ids for e in enc], dtype=np.int64),
}
logits = sess.run(["logits"], {k: v for k, v in feed.items() if k in names})[0]
p = np.exp(logits - logits.max(axis=1, keepdims=True))
return (p / p.sum(axis=1, keepdims=True))[:, meta["keep_index"]]
print(score([
("青云宗", "他拜入青云宗,成为外门弟子。"),
("道理", "你说的很有道理。"),
])) # điểm cao cho 青云宗, thấp cho 道理
Nên chấm theo lô (32–64 cặp). Trên CPU, chỉ dùng 4 luồng, tốc độ khoảng 25 cặp/giây và cần khoảng 400 MB RAM.
Transformers (PyTorch, từ checkpoint/)
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
repo = "yukihuy99yuki/glossary-classifier"
tok = AutoTokenizer.from_pretrained(repo, subfolder="checkpoint")
model = AutoModelForSequenceClassification.from_pretrained(repo, subfolder="checkpoint").eval()
terms = ["青云宗", "道理"]
contexts = ["他拜入青云宗,成为外门弟子。", "你说的很有道理。"]
batch = tok(terms, contexts, truncation="only_second", max_length=128, padding=True, return_tensors="pt")
with torch.no_grad():
print(torch.softmax(model(**batch).logits, dim=-1)[:, 1])
Ngôn ngữ khác
model.onnx + tokenizer.json dùng được với mọi runtime ONNX: onnxruntime-node / onnxruntime-web
(tokenizer qua @huggingface/transformers), C# Microsoft.ML.OnnxRuntime (+ Microsoft.ML.Tokenizers),
Java, Rust (ort + tokenizers). Cần giữ đúng mã hoá cặp câu, token_type_ids và max_len như trên,
nếu không điểm sẽ lệch.
Trong novel2epub
Đặt 3 file gốc vào models/glossary-classifier/ (hoặc trỏ env N2E_GLOSSARY_CLASSIFIER), cài
onnxruntime tokenizers, khởi động app rồi bấm Chấm Điểm AI trên trang Thuật ngữ.
Chọn ngưỡng
Trên tập vàng (800 mục, 183 mục "giữ"):
| Ngưỡng | Precision (giữ) | Recall (giữ) | Tỉ lệ mục bị đánh dấu giữ |
|---|---|---|---|
| 0.50 | 0.737 | 0.978 | 30.4% |
| 0.70 | 0.765 | 0.978 | 29.2% |
| 0.80 | 0.775 | 0.978 | 28.9% |
| 0.90 | 0.805 | 0.967 | 27.5% |
| 0.95 | 0.838 | 0.962 | 26.3% |
- Muốn không bỏ sót thuật ngữ thật: dùng 0.5 (recall 98%) rồi người duyệt loại phần dư.
- Muốn ít nhầm hơn: dùng 0.9–0.95, chấp nhận sót khoảng 3–4%.
- Điểm < 0.1 gần như chắc chắn là từ thường hoặc mảnh cắt, có thể bỏ hàng loạt sau khi xem lướt.
Đánh giá
Tập vàng: 800 ứng viên lấy từ 3 truyện không có trong dữ liệu train, gắn nhãn bằng Codex (OpenAI), tỉ lệ giữ/bỏ khoảng 23% / 77%.
| Model | ROC-AUC | Accuracy | F1 (giữ) |
|---|---|---|---|
| Điểm tin cậy của bộ trích thuật ngữ (mốc) | 0.580 | – | – |
| Xếp theo tần suất xuất hiện (mốc) | 0.61 | – | – |
| Fine-tune chỉ với nhãn suy từ glossary | 0.973 | 0.877 | 0.786 |
| Fine-tune chỉ với nhãn Codex | 0.984 | 0.934 | 0.865 |
| Bản này: nhãn gộp | 0.985 | 0.915 | 0.840 |
Ma trận nhầm lẫn ở ngưỡng 0.5: TP 179 · FP 64 · FN 4 · TN 553.
Độ chính xác theo nhóm (nguồn nhãn suy từ glossary → nhãn Codex):
| Nhóm | Số mục | Accuracy |
|---|---|---|
| Có trong glossary → giữ | 169 | 0.98 |
| Có trong glossary → từ thường | 72 | 0.46 |
| Mảnh cắt → mảnh cắt | 243 | 0.98 |
| Mảnh cắt → từ thường | 69 | 0.81 |
| Ngoài glossary → từ thường | 149 | 0.99 |
| Ngoài glossary → mảnh cắt | 70 | 1.00 |
Nhóm yếu nhất là từ thường đã bị người dịch đưa vào glossary (vd. từ chung nhưng được dịch cố định): model thường vẫn cho điểm cao, vì phần lớn nhãn train coi "có trong glossary" là "giữ".
Bản ONNX fp32 khớp model PyTorch gốc (lệch xác suất tối đa 0.002). Các bản lượng tử int8 đã thử đều lệch nhiều (AUC 0.975 và 0.44), nên không phát hành.
Dữ liệu train
- Nguồn: 17 truyện mạng tiếng Trung (chủ yếu tiên hiệp, huyền huyễn, đô thị dị năng), tối đa 300 chương mỗi truyện. Ứng viên lấy bằng bộ trích thuật ngữ của novel2epub (n-gram + thống kê, tần suất ≥ 3).
- Nhãn yếu (13.438 mục): suy từ glossary người dịch đã duyệt của từng truyện. Mục có trong glossary là "giữ"; mảnh cắt của mục glossary và từ lặp nhiều (≥ 8 lần) không vào glossary là "bỏ".
- Nhãn Codex (~7.000 mục): Codex CLI (OpenAI) gắn nhãn
keep/common/fragmentkèm độ tin cậy, dựa trên thuật ngữ và ngữ cảnh. Một phần chọn theo active learning (mục model đang phân vân, điểm gần 0.5). - Gộp: mỗi mục lấy nhãn theo thứ tự ưu tiên người duyệt > Codex > nhãn suy từ glossary. Tổng 13.918 mục.
- Chia train/test theo truyện: 3 truyện test không xuất hiện trong train.
Dữ liệu train không được phát hành, vì nó chứa trích đoạn từ các truyện có bản quyền.
Huấn luyện
| Tham số | Giá trị |
|---|---|
| Model gốc | hfl/chinese-roberta-wwm-ext (102M tham số) |
| Đầu | AutoModelForSequenceClassification, 2 lớp, cross-entropy |
| Epoch | 3 |
| Learning rate | 3e-5, AdamW (weight decay 0.01), linear warmup 6% |
| Batch | 32, max 128 token, fp16 |
| Seed | 13 |
| Phần cứng | RTX 3050 4 GB, ~12 phút |
Xuất ONNX bằng torch.onnx.export (opset mặc định), giữ fp32.
Hạn chế
- Chỉ train trên truyện mạng tiếng Trung giản thể, thiên về tiên hiệp/huyền huyễn. Với thể loại khác (ngôn tình, lịch sử, khoa học viễn tưởng) hay văn bản phồn thể thì nên kiểm tra lại trước khi dùng.
- Điểm phụ thuộc vào ngữ cảnh: cùng một từ có thể được điểm khác nhau ở các đoạn khác nhau.
- Nhãn chủ yếu do LLM gắn, nên có thể mang thiên lệch của LLM về ranh giới giữa "thuật ngữ riêng" và "từ chung".
- Không nhận diện được thực thể chưa từng được bộ trích đưa ra làm ứng viên. Đây là bộ xếp hạng ứng viên, không phải bộ NER.
Giấy phép và ghi công
- Trọng số: Apache-2.0, kế thừa từ
hfl/chinese-roberta-wwm-ext. - Model gốc: Cui et al., Pre-Training with Whole Word Masking for Chinese BERT (2019/2021).
@article{cui-etal-2021-pretrain,
title = {Pre-Training with Whole Word Masking for Chinese BERT},
author = {Cui, Yiming and Che, Wanxiang and Liu, Ting and Qin, Bing and Yang, Ziqing},
journal = {IEEE/ACM Transactions on Audio, Speech, and Language Processing},
year = {2021},
volume = {29},
pages = {3504--3514}
}
Model tree for yukihuy99yuki/glossary-classifier
Base model
hfl/chinese-roberta-wwm-extEvaluation results
- roc_auc on Codex-labelled gold set (800 candidates, 3 held-out novels)self-reported0.985
- f1 on Codex-labelled gold set (800 candidates, 3 held-out novels)self-reported0.840
- precision on Codex-labelled gold set (800 candidates, 3 held-out novels)self-reported0.737
- recall on Codex-labelled gold set (800 candidates, 3 held-out novels)self-reported0.978
- accuracy on Codex-labelled gold set (800 candidates, 3 held-out novels)self-reported0.915