相同 ISBN 的兩本書、相似度只有 0.67 — 加權平均稀釋 identity 訊號
觸發場景:Flutter 書籍管理 App 的書目比對——判斷 API 查回來的書跟館藏裡的書是不是同一本。測試給了兩本 ISBN 完全相同的書,相似度算出 0.67、低於 0.8 的匹配閾值:系統認為「不確定是同一本」 疑問來源:ISBN 相同幾乎就是同一本書的定義,演算法哪裡把這個確定性弄丟了? 整理目的:記下加權平均對異質訊號的稀釋機制、訊號分級的修法、以及 identity 比對前的正規化前置 本文邊界:素材是該專案 v0.6.1 的重構記錄;「identity 訊號 vs fuzzy 訊號」的分級思路通用於任何比對 / 評分系統
稀釋機制:平均假設訊號同質、但訊號不同質
原始的相似度是一條加權和:ISBN、標題、作者、出版社各占一個權重、各算一個相似度、加權平均出總分。這個結構對同質訊號成立——四個都是「有點像」的模糊證據時,加權平均是合理的融合。
但 ISBN 不是模糊證據。它是 identity 級訊號:兩本書 ISBN 相同、是同一本書的先驗機率接近 1——這是這個編號存在的目的。把它丟進平均池的後果:ISBN 完全匹配貢獻它權重上限的分數、然後被「標題寫法不同」「出版社欄位缺失」這些 fuzzy 訊號的低分往下拉——確定性被稀釋成 0.67。這個數字的含義很難堪:系統對「ISBN 相同的兩本書」的信心、只比「標題有點像的兩本書」高一點。
調閾值救不了它:降到 0.65 會讓真匹配過關、也讓一批「標題很像但不是同一本」的假陽性一起過關——問題不在線畫哪裡、在兩種確定性被壓進了同一個刻度。
修法:identity 訊號短路、fuzzy 訊號才進池
重構把計分拆成兩層、兩個函式各司其職:
_calculateIsbnPriorityScore——identity 層:ISBN 完全匹配(含等價形式)時直接給 0.9 以上的基礎分、不進加權池。確定的事實用確定的分數表達_calculateWeightedSimilarity——fuzzy 層:ISBN 不可比(缺失、不匹配)時才落到這裡,標題、作者、出版社的加權平均在同質訊號之間做它擅長的事
結構化之後、分數重新有了語意:0.9 以上讀作「identity 證據確認」、中間段讀作「fuzzy 證據的綜合強度」——下游要對兩種情況做不同處置(自動合併 vs 請使用者確認)時,分數本身就攜帶了依據。這個「強訊號短路、弱訊號加權」的形狀在別處反覆出現:規則引擎的 hard rule 先於 scoring、搜尋的 exact match 先於 ranking——凡是訊號有等級的地方,先分級再融合。
前置:identity 訊號自己要先正規化
第二個修正堵住 identity 層自己的漏接:同一本書的 ISBN 有兩種合法表示——ISBN-10 跟 ISBN-13(978 前綴加重算校驗碼)。等價檢查沒實作時(重構前它是一條 TODO 註解),一邊是 10 碼一邊是 13 碼的同一本書、在 identity 層比對失敗、跌進 fuzzy 層被當成「標題很像的兩本書」。
原則跟 Money 與 VO 的相等性相通:同一身分的多種表示、比對前先化到正規形式。identity 訊號的價值建立在「相等判斷可靠」上,表示層的多樣性沒被吸收掉、identity 層就會系統性漏接——而且漏得很安靜,每一筆都降級成 fuzzy 比對、只是分數低一點。
附帶的第三個修正也值得一行:原本自製的「簡化版 Jaro-Winkler」字串相似度被記錄為「過於簡化、無法準確計算」——fuzzy 層的訊號品質同樣要顧,自製知名演算法的簡化版、通常是拿演算法的名字背書一個不是它的東西。
判讀徵兆
- 「明明是同一個東西、比對分數卻不高」——查有沒有 identity 級欄位被丟進加權平均
- 評分公式裡確定性訊號(唯一編號、hash、精確鍵)跟模糊訊號(字串相似、日期接近)共用權重——先分級、讓確定訊號短路
- identity 欄位有多種合法表示(含連字號 / 不含、10 碼 / 13 碼、大小寫)而比對是裸字串相等——正規化缺席、identity 層在漏接
- 調閾值的討論反覆出現——通常是訊號結構的問題假扮成閾值問題
相關閱讀
- 正規形式的型別層做法:Money 三段遷移——同一身分的表示多樣性在 value object 層吸收
- identity 的建模:同一個品項、四個 model——「什麼算同一個」是 domain 定義、比對演算法只是它的執行者
- 概念地基:entity 與 value object 的判準——「相等性定義承載業務規則」段