iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0

先承認規則式的價值

在講它的問題之前先說清楚:規則式偵測不能不做。 它快、可解釋、可稽核、不需要 GPU、結果穩定可重現。對於格式明確的直接識別碼,它的精確度比模型高。

問題不在於用不用,在於只用它

一個能動的基礎版本

import re

PATTERNS = {
    "TW_ID": r"[A-Za-z][12]\d{8}",
    "TW_MOBILE": r"09\d{2}[-\s]?\d{3}[-\s]?\d{3}",
    "CREDIT_CARD": r"\b(?:\d[ -]?){13,19}\b",
    "EMAIL": r"[\w.+-]+@[\w-]+\.[\w.-]+",
    "TW_BAN": r"\b\d{8}\b",
}

def detect(text):
    findings = []
    for kind, pat in PATTERNS.items():
        for m in re.finditer(pat, text):
            findings.append({
                "type": kind,
                "value": m.group(),
                "start": m.start(),
                "end": m.end(),
            })
    return findings

拿去跑一般的測試資料,看起來完美。然後拿去跑真實文件,開始出事。

失效情境一:全形字元

A123456789

Unicode 的全形英數(U+FF10–U+FF19、U+FF21–U+FF3A)跟半形是不同碼位。[A-Za-z] 完全匹配不到。

從 PDF 複製、從 Word 貼上、中文輸入法未切換——這些都會產生全形數字,而且非常常見。

解法:偵測前先做正規化(NFKC)。

import unicodedata

def normalize(text: str) -> str:
    return unicodedata.normalize("NFKC", text)

但正規化會改變字串長度與位置索引。如果你要在原文上做遮罩,就必須維持一份正規化後位置 → 原文位置的對應表,否則遮罩會遮錯位置。這是實作上最容易踩的坑之一:

def normalize_with_map(text):
    out, index_map = [], []
    for i, ch in enumerate(text):
        norm = unicodedata.normalize("NFKC", ch)
        for c in norm:
            out.append(c)
            index_map.append(i)   # 每個正規化字元對回原文的位置
    return "".join(out), index_map

失效情境二:分隔符與空白

A123-456-789
A 123456789
A123456789     ← 全形半形混用

真實文件裡的個資不會乖乖連在一起。表格轉文字時會插入空白,PDF 抽取時會插入換行,使用者會自己加連字號。

解法:允許可選分隔符,但這會提高誤判。所以要搭配檢查碼驗證,並且對「有分隔符版本」給較低的信心分數。

失效情境三:跨行斷裂

PDF 抽取出來的文字常長這樣:

身分證字號:A12345
6789

換行插在號碼中間。任何單行 regex 都抓不到。

解法:在偵測前做行合併的前處理,但要小心不能把真正的段落也合併掉。實務上是用「行尾沒有標點 + 下一行開頭是數字」這類啟發式規則。

失效情境四:語意脈絡

他的證號是 A123456789

vs

案號 A123456789 已結案

第一個是個資,第二個是案件編號,格式一模一樣。純規則無法區分,只能靠上下文。

反過來也成立:

本行客戶編號:0000123456

這是個資(可連結到特定人),但沒有任何格式特徵。

失效情境五:完全沒有格式的個資

被告因長期洗腎,每週需就醫三次。

這是特種個資,規則式偵測連邊都沾不上。

把失效整理成一張表

情境 規則式表現 是否可用規則補救
標準格式直接識別碼 優秀
全形字元 失效 可(正規化)
分隔符/空白 部分失效 可(放寬 pattern)
跨行斷裂 失效 部分(前處理)
格式相同但非個資 誤判 難(需上下文)
無格式的識別碼 失效
敘述性特種個資 完全失效
人名 完全失效 否(字典法覆蓋率不足)

最後三列是規則式的天花板。這就是為什麼需要語意層——明天的主題。

一個實務建議:把規則式的角色定位為「高精確度層」

不要試圖用規則抓到所有東西,那會導致你不斷放寬 pattern,最後誤判高到使用者關掉它。

正確的定位是:規則式負責它擅長的那部分,並且把信心度標高;剩下的交給語意層。 兩層的結果做聯集,衝突時取較嚴格的處理。

def merge_findings(rule_hits, ner_hits):
    """位置重疊時,保留範圍較大者(較嚴格的遮罩)"""
    all_hits = sorted(rule_hits + ner_hits, key=lambda x: (x["start"], -x["end"]))
    merged = []
    for hit in all_hits:
        if merged and hit["start"] < merged[-1]["end"]:
            merged[-1]["end"] = max(merged[-1]["end"], hit["end"])
        else:
            merged.append(dict(hit))
    return merged

明天進語意式偵測,也就是我做「陽關」這個模型的原因。


關於作者

我是 Fngi,專注在 AI 資安、LLM 紅隊與 AI 治理框架落地。這個系列的每日更新,以及平常的 AI 攻防筆記、實驗過程與研討會現場,會同步發在 IG:

@aid3fend

有想討論的架構細節或不同意見,留言或私訊都歡迎。



上一篇
Day 4|在寫規則之前,先知道自己在找什麼
下一篇
Day 6|語意式偵測:補規則補不到的洞
系列文
《30 天為金融法務部門打造 LLM 個資防護閘:去識別化、可控還原與紅隊驗證》14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言