[{"data":1,"prerenderedAt":537},["ShallowReactive",2],{"blog-\u002Fblog\u002Fstt-postedit-leverage":3},{"id":4,"title":5,"body":6,"date":525,"description":526,"extension":527,"meta":528,"navigation":529,"path":530,"seo":531,"stem":532,"tags":533,"__hash__":536},"blog\u002Fblog\u002Fstt-postedit-leverage.md","我測了兩天，證明「換更好的引擎」降了一個錯誤",{"type":7,"value":8,"toc":511},"minimark",[9,17,20,23,26,29,33,36,39,42,45,89,92,94,98,101,104,107,110,113,116,119,121,125,128,131,134,137,144,146,150,153,156,159,162,165,168,170,174,177,180,251,254,257,260,263,266,269,274,276,280,283,286,289,334,337,340,343,346,349,410,413,416,419,421,424,427,430,436,439,441,445,448,451,454,457,463,466,469,471,474,477,480,483,486,489,491,494,497,500],[10,11,12],"p",{},[13,14],"img",{"alt":15,"src":16},"一個人專心轉動一個巨大的旋鈕，旁邊有個不起眼的小開關才是真正接上電路的那個","\u002Fblog\u002Fimages\u002Fstt-postedit-leverage-hero.png",[10,18,19],{},"準備錄實驗素材那天，我打開對話視窗跟他說：等一下每段話講兩次，第一次按鍵盤右下角那個麥克風。",[10,21,22],{},"他回我：iPhone 有那個東西？",[10,24,25],{},"那一刻我的實驗死了。我設計了三組對照，其中一組叫「現況組」——而現況組從來不存在。我查了程式碼，確認過那個功能該怎麼運作；我沒查的是，這個人根本沒在用它。",[27,28],"hr",{},[30,31,32],"h2",{"id":32},"我原本要測什麼",[10,34,35],{},"我在幫自己的知識庫系統做語音輸入。走路的時候按住講一段話，鬆手，它應該變成當天的紀錄。",[10,37,38],{},"聽不聽得懂從來不是重點。真正卡住的是專有名詞——人名、專案代號、自己發明的縮寫——這些字在通用語音模型的訓練資料裡幾乎不存在，它只會挑一個發音接近的常見詞給你。我紀錄裡有個團隊代號，十次有九次被聽成一個完全無關的詞，每次都要人工回去改。",[10,40,41],{},"摩擦沒有被消除，只是被搬到後面去了。",[10,43,44],{},"所以我打算換一顆更好的引擎。手上有一台自架的推論機器，跑著比手機內建大得多的模型，端點打得通。剩下的就是證明它比較好。",[46,47,48,61],"table",{},[49,50,51],"thead",{},[52,53,54,58],"tr",{},[55,56,57],"th",{},"組",[55,59,60],{},"內容",[62,63,64,73,81],"tbody",{},[52,65,66,70],{},[67,68,69],"td",{},"A",[67,71,72],{},"手機內建聽寫（現況）",[52,74,75,78],{},[67,76,77],{},"B",[67,79,80],{},"自架引擎的原始輸出",[52,82,83,86],{},[67,84,85],{},"C",[67,87,88],{},"B 再加一層「帶知識庫上下文的語意後修」",[10,90,91],{},"有基準、有單一變數推進、有最終方案。這個設計是錯的，而且錯得很不明顯——我要繞很久才會發現。",[27,93],{},[30,95,97],{"id":96},"第一次翻車我查了程式碼沒查人","第一次翻車：我查了程式碼，沒查人",[10,99,100],{},"上面那個對話發生在正式開錄前十分鐘。",[10,102,103],{},"我對 A 組的認知是「使用者現在就是這樣輸入的」。這個認知從哪來？我 grep 了 bot 的原始碼，確認它怎麼處理進來的訊息——這一步做對了。",[10,105,106],{},"我沒做的是問一句「你平常是怎麼傳的」。",[10,108,109],{},"他從來沒用過鍵盤聽寫，一直是長壓輸入欄那個麥克風傳語音訊息。而 bot 的 handler 只認文字、照片、文件三種——語音訊息進來，沒有任何一段程式碼接得住。它們被靜默丟棄，而且不回任何訊息。",[10,111,112],{},"所以他過去每一次「用講的記一下」，都以為記進去了，實際上一句話都沒留下。這個洞比實驗本身嚴重，我當天先把它補起來：不轉錄，先存檔，至少不再遺失。",[10,114,115],{},"至於實驗，A 組不再是「現況」了。它變成一個從沒被試過的候選方案：如果手機內建聽寫夠準，最省事的解法是使用者改變操作習慣，一行程式碼都不用寫。",[10,117,118],{},"這個角色轉換，後來變成整件事的關鍵。",[27,120],{},[30,122,124],{"id":123},"第二次翻車我的系統污染了我自己的對照組","第二次翻車：我的系統污染了我自己的對照組",[10,126,127],{},"錄第一段的時候。",[10,129,130],{},"A 組的文字要進到知識庫裡才好比對，所以我讓他直接傳進去。傳進去之後我打開檔案一看——專有名詞已經被自動改對了，句子還被重新整理成條列。",[10,132,133],{},"原因很蠢，但值得記：我的知識庫本身就掛著一層路由，任何進來的文字都會被 LLM 順過一次，順便修正專有名詞、整理成結構。這是我自己設計的，每天都在用，好用到我完全忘了它存在。",[10,135,136],{},"等於 A 組在進場之前，就先被 C 組處理過一次了。原文不可復原，那一段只能重念。我把 A 組改走一條專用旁路——訊息開頭帶特定前綴就原文逐字落檔，禁止校正、禁止結構化。",[10,138,139,143],{},[140,141,142],"strong",{},"基礎設施會自己動手，它從來不是一根安靜的管子。"," 還有一層更麻煩的含意：這條路由平常就在自動校正我所有的紀錄，意味著知識庫裡既有的文字都不是原始輸入。以後拿它們當語料的分析，都得先記得這件事。",[27,145],{},[30,147,149],{"id":148},"第三次翻車差點把-kill-線設計掉","第三次翻車：差點把 kill 線設計掉",[10,151,152],{},"C 組的後修要由誰來做？最順手的當然是我自己——直到我想起朗讀腳本是我寫的，每一段的標準答案我都知道。我來做後修，那叫開卷考。",[10,154,155],{},"分數虛高還算小事。真正致命的是誤修數會恆為 0。",[10,157,158],{},"先解釋一下誤修。後修層是一個 LLM，它讀轉錄出來的文字，用知識庫的上下文判斷哪些詞被聽錯了。它會修對，也會把本來對的改成錯的——冷門正確詞被改成常見詞，這是這類方案最真實的風險。而且誤修比漏修危險：漏修的錯誤讀起來很怪，一眼看得出來；誤修的錯誤讀起來很通順，只是它是假的。",[10,160,161],{},"我的 kill 線有兩條，第二條就是「誤修數超過修對數的三分之一就收掉」。",[10,163,164],{},"如果由知道答案的人來做後修，這條線永遠不會被觸發。我等於在實驗開始前，先把自己最想量的那個風險刪掉了。",[10,166,167],{},"改由一個沒看過腳本的 agent 執行，只給它知識庫的專有名詞表——上線後它拿得到的東西——和轉錄的原始輸出。",[27,169],{},[30,171,173],{"id":172},"分數出來了然後兩條線同時成立","分數出來了，然後兩條線同時成立",[10,175,176],{},"15 段素材，每段 15 到 40 秒，在走路的時候錄的。刻意不在安靜房間念稿，那會同時高估所有組別，測不出差距。",[10,178,179],{},"評分不用 WER。中文的 WER 對這個問題沒有鑑別度——斷詞、同音字、贅字全部會污染分數。我改成數三種錯誤：專有名詞錯幾個、數字錯幾個、語意被破壞幾處。",[46,181,182,200],{},[49,183,184],{},[52,185,186,188,191,194,197],{},[55,187,57],{},[55,189,190],{},"專名",[55,192,193],{},"數字",[55,195,196],{},"語意",[55,198,199],{},"合計",[62,201,202,219,236],{},[52,203,204,207,210,213,216],{},[67,205,206],{},"A（手機內建）",[67,208,209],{},"31",[67,211,212],{},"3",[67,214,215],{},"7",[67,217,218],{},"41",[52,220,221,224,227,230,233],{},[67,222,223],{},"B（自架引擎）",[67,225,226],{},"24",[67,228,229],{},"1",[67,231,232],{},"4",[67,234,235],{},"29",[52,237,238,241,244,246,248],{},[67,239,240],{},"C（B + 後修）",[67,242,243],{},"0",[67,245,229],{},[67,247,229],{},[67,249,250],{},"2",[10,252,253],{},"C 組修對 27 處，誤修 0 處。",[10,255,256],{},"然後我盯著這張表看了很久，因為它同時觸發了兩件相反的事。",[10,258,259],{},"成功指標達成：我事前寫的成功線是「C 的專名錯誤 ≤ A 的一半，且誤修 = 0」。0 ≤ 15.5，誤修 0，過了。",[10,261,262],{},"kill 線也觸發了：另一條 kill 線是「B 的專名錯誤 ≥ A 的 70%」。A 是 31，70% 是 21.7，B 是 24。觸發。",[10,264,265],{},"兩條線在問不同的問題，所以它們可以同時成立。",[10,267,268],{},"kill 線問的是「換引擎有沒有用」——幾乎沒有。B 比 A 只降 23%，落在我開工前就寫死的噪音區間裡：兩組音檔不同源，只有兩倍以上的量級差距才算有效信號。成功線問的是「加後修有沒有用」——41 降到 2。",[10,270,271],{},[140,272,273],{},"價值來源是後修層，不是引擎。",[27,275],{},[30,277,279],{"id":278},"但我還是不能歸因因為第四格從來沒被測過","但我還是不能歸因，因為第四格從來沒被測過",[10,281,282],{},"上面那個結論其實還站不太住。",[10,284,285],{},"因為 C 相對 A 同時改了兩件事：換了引擎，又加了後修。C 大勝，我沒辦法把功勞分開。B 比 A 只好一點點是強烈的暗示——但只是暗示。",[10,287,288],{},"畫成 2×2 就很清楚了：",[46,290,291,303],{},[49,292,293],{},[52,294,295,297,300],{},[55,296],{},[55,298,299],{},"不加後修",[55,301,302],{},"加後修",[62,304,305,321],{},[52,306,307,312,315],{},[67,308,309],{},[140,310,311],{},"不換引擎",[67,313,314],{},"A ✅",[67,316,317,318],{},"❌ ",[140,319,320],{},"從沒測過",[52,322,323,328,331],{},[67,324,325],{},[140,326,327],{},"換引擎",[67,329,330],{},"B ✅",[67,332,333],{},"C ✅",[10,335,336],{},"我測了三格。缺的那格是「不換引擎、只加後修」——也就是最便宜的那一格。",[10,338,339],{},"遞增疊加的設計為什麼會騙人？因為相鄰兩臂之間確實只差一個變數：A 到 B 只換引擎，B 到 C 只加後修。它感覺起來像對照實驗。但整體看，它只是三個點在測一條線，永遠算不出「不換引擎的條件下，後修自己值多少」。",[10,341,342],{},"而如果後修自己就夠了，換引擎那一整條的工程成本全是浪費——外部服務依賴、音檔上傳管線，還有簡繁轉換（那顆引擎會偶發吐簡體字，15 段裡有兩段）。",[10,344,345],{},"補這一格幾乎免費：A 組的輸出還在，後修流程做 C 組時已經建好，套上去跑一次就好。",[10,347,348],{},"我跑了。",[46,350,351,364],{},[49,352,353],{},[52,354,355,357,359,361],{},[55,356],{},[55,358,299],{},[55,360,302],{},[55,362,363],{},"換引擎效果",[62,365,366,380,394],{},[52,367,368,373,375,377],{},[67,369,370],{},[140,371,372],{},"手機內建",[67,374,218],{},[67,376,212],{},[67,378,379],{},"−38",[52,381,382,387,389,391],{},[67,383,384],{},[140,385,386],{},"自架引擎",[67,388,235],{},[67,390,250],{},[67,392,393],{},"−27",[52,395,396,400,403,408],{},[67,397,398],{},[140,399,363],{},[67,401,402],{},"−12",[67,404,405],{},[140,406,407],{},"−1",[67,409],{},[10,411,412],{},"後修層降 27 到 38 個錯誤。換引擎在沒有後修時降 12 個，在有後修的條件下降 1 個。",[10,414,415],{},"一個。",[10,417,418],{},"我為了那個「1」，原本準備蓋一整條管線。",[27,420],{},[30,422,423],{"id":423},"為什麼後修層贏這麼多",[10,425,426],{},"因為它做的是純語音模型結構上做不到的事。",[10,428,429],{},"那個 agent 修對的四處裡，有這些：一個不存在的英文詞被還原成某專案的代號、一個發音相近的詞被改成「優化」、一串被聽成「R P 一八」的東西被還原成健身術語的「RPE 8」。",[10,431,432,433],{},"這些靠的全是語意推斷。它讀知識庫裡的上下文，判斷「在這個人的紀錄裡，這個位置該出現的是什麼」。",[140,434,435],{},"任何純語音引擎都做不到，因為它們沒有你的知識庫。",[10,437,438],{},"還有一個更窄的判準：如果你要換的那顆引擎沒有提供餵入領域詞彙的介面，那換誰都差不多。我測的那顆就是這樣，端點只吃音檔和語言參數，沒地方塞人名。",[27,440],{},[30,442,444],{"id":443},"最後一次翻車四格量的都是同一個指標","最後一次翻車：四格量的都是同一個指標",[10,446,447],{},"我根據 2×2 做了裁決：走最便宜的那格，手機內建聽寫加後修層。外部服務不用碰了，音檔管線省下來，連簡繁轉換這個坑都直接不存在。",[10,449,450],{},"然後拿去給使用者複核，被推翻了。",[10,452,453],{},"理由一句話就說完：手機內建聽寫這條路，前提是他要改變操作習慣。而整場實驗從頭到尾，A 組之所以存在，就是因為他從來沒用過那個功能。",[10,455,456],{},"語音訊息是按住、講、放開。鍵盤聽寫要開輸入框、點麥克風、盯著文字跑完、按送出——走路的時候還得看螢幕。我算的是一次性的工程成本，沒算每天都要付的摩擦成本。",[10,458,459,462],{},[140,460,461],{},"2×2 拆對了變數，但四格量的都是同一個指標：錯誤數。"," 當候選方案真正的差異在別的維度上，只比主指標就會選錯。",[10,464,465],{},"實驗的結論本身沒變——後修是槓桿，引擎不影響準度。改的是推論：既然引擎不影響準度，這一維就該由摩擦決定，而摩擦這維是自架引擎贏。零行為改變，音檔本來就在存，邊際成本只剩一個 HTTP call。",[10,467,468],{},"最後上線的是「語音訊息 → 自架引擎 → 後修層」。隔天用同樣那 15 段素材對生產環境做了一次盲評：誤修 0，三類錯誤合計 3。實驗量到的東西在生產上重現了。",[27,470],{},[30,472,473],{"id":473},"如果你也在調同一顆旋鈕",[10,475,476],{},"我從這兩天帶走三條，都是給下一次的我。",[10,478,479],{},"第一條寫成規則：任何「換掉 X，而且加上 Y」的假設，開工前先畫 2×2，確認四格都會被測到。真有一格不打算測，那就在筆記上明寫「不測哪格、因此哪個主效應無法歸因」——讓它是個判斷，不是疏忽。",[10,481,482],{},"四格全量同一個指標，等於沒量到候選方案真正的差異。這是我這次栽得最重的地方——沒被量到的那一維，通常就是最後推翻你的那一維。",[10,484,485],{},"還有一條沒那麼漂亮。查程式碼只告訴我系統能做什麼，沒告訴我人實際上在做什麼，我把前者做對了，後者整個漏掉。",[10,487,488],{},"有件事值得帶出這個故事之外：大部分「模型不夠聰明」的狀況，其實是模型不知道你的業務在講什麼。它不知道你的產品叫什麼、你的客戶怎麼稱呼那個流程、內部那個三個字的縮寫是什麼意思。換一顆更大的模型救不了這件事。你得把自己的上下文餵給它。",[27,490],{},[30,492,493],{"id":493},"如果你正在評估這件事",[10,495,496],{},"如果你手上有一個 AI 功能準確率卡住，而現在的討論是「要不要換更貴的模型」，那大概值得先停一下。是它聽不清楚，還是它不知道你在講什麼？這兩件事的解法完全不一樣，成本差一個量級。",[10,498,499],{},"我做技術諮詢，這類「先確認問題在哪」的對話通常十分鐘就有結論。沒有簡報，不賣套裝方案，如果你的狀況根本不用動 AI 我也會直說。",[10,501,502,503,510],{},"到 ",[504,505,509],"a",{"href":506,"rel":507},"https:\u002F\u002Fmattchang.dev",[508],"nofollow","mattchang.dev"," 加我的 LINE，直接描述你的狀況就好。那個帳號本身也是我自己蓋的——你跟它對話的體驗，就是你正在評估的那個東西的 live demo。",{"title":512,"searchDepth":513,"depth":513,"links":514},"",2,[515,516,517,518,519,520,521,522,523,524],{"id":32,"depth":513,"text":32},{"id":96,"depth":513,"text":97},{"id":123,"depth":513,"text":124},{"id":148,"depth":513,"text":149},{"id":172,"depth":513,"text":173},{"id":278,"depth":513,"text":279},{"id":423,"depth":513,"text":423},{"id":443,"depth":513,"text":444},{"id":473,"depth":513,"text":473},{"id":493,"depth":513,"text":493},"2026-08-05","AI 功能不夠準，第一個念頭通常是換模型。我為此設計了一場對照實驗，結果換引擎只降 1 個錯誤，真正把 41 降到 2 的是我原本當配角的那一層。這是實驗設計怎麼騙我、以及我怎麼發現的完整過程。","md",{},true,"\u002Fblog\u002Fstt-postedit-leverage",{"title":5,"description":526},"blog\u002Fstt-postedit-leverage",[534,535],"claude-code","experiment-design","zIpkLaGim0pcHQJvstOwSdcsdWMODNqfAUQnL3wBDtY",1785903592272]