計算可重現性(computational reproducibility)
想像你讀到一篇論文,裡頭有個驚人的計算結果,你下載了作者的程式碼與資料,在你自己的機器上執行,竟得到一模一樣的數字。那種體驗——以及使其成為可能的紀律——就是計算可重現性。它的原則是:一個計算結果不應是某人筆電上一次性的幸運執行,而應是任何人都能從被記錄下來的材料中重新推導出來的東西。在一個每個數值答案都是近似的領域裡,能夠精確重建一個答案是怎麼被產生出來的,正是把它當成科學來認真看待的基石。
把兩個相關的詞分開會有幫助。「可重現性」(reproducibility)通常指:給定「相同」的程式碼、資料與參數,你得到相同的結果——這關乎來源出處(provenance)以及某一特定計算的逐位元(或足夠接近)的可重複性。「可複製性」(replicability)通常指更強的事:一個「獨立」的團隊,用他們自己的程式碼,達到相同的科學結論——這檢驗的是那個發現,而非只是那次執行。在實務上達成可重現性,意味著記錄每一種材料:確切的原始碼(在版本控制之下,帶著一個提交雜湊)、確切的資料與參數、軟體版本與相依套件(用容器或環境檔捕捉),以及隨機種子,好讓偽隨機抽樣能被精確地重播。把程式碼、結果與說明交織在一起的文學式筆記本(literate notebook),加上開放資料與開放程式碼,使整條鏈條都可被檢視。
誠實地說,這為何困難,值得講明。浮點運算是天真的逐位元可重現性的敵人:浮點加法不滿足結合律,所以把同樣的數字以不同順序相加——當平行執行緒以不同次序完成、或某個函式庫更新了它的分塊策略時就會發生——可能改變最後幾位數字。不同的編譯器、不同的 BLAS、不同的核心數,或融合乘加(FMA)指令,都可能讓結果略有偏移。所以「可重現到最後一個位元」是個嚴苛的目標,往往保留給回歸測試,而「可重現到一個聲明的容差之內」才是日常更誠實的標準。要避免的迷思:可重現性並不認證一個結果是「正確」的——一個可重現地錯誤的計算仍然是錯的——它認證的是這個結果就是被記錄下來的食譜所產生的東西,而這正是任何人要檢查、除錯或在其上建造的前提。
一個可重現的專案會附上:一個釘在提交 a1b2c3d 的 git 儲存庫、一個固定 NumPy 1.26 與特定 BLAS 的 Dockerfile 或 environment.yml、帶有校驗碼的輸入資料、一個列出每個參數的設定檔,以及那一行 numpy.random.seed(42) 讓蒙地卡羅抽樣得以重複。讀者執行一道指令,就得到論文裡的圖。換掉種子或 BLAS,最後幾位數字可能不同——這正是為何檢查的是一個容差,而非精確相等。
釘住版本的程式碼、固定的環境、記錄的參數,以及設定種子的亂數產生器,使一次執行可被重新推導。
可重現不代表正確:一個可重現地錯誤的結果仍然是錯的。而精確的逐位元可重複性往往無法達成,因為浮點加法不滿足結合律——不同的核心數或 BLAS 可能讓最後幾位數字偏移,所以一個容差才是誠實的目標。