你正要去除的地方,偏偏是個零
到現在你已經能手算 高斯消去法:選第一個對角元素,也就是 主元,用它把那一行裡它下方的所有東西消成零,再移到下一行,重複下去。前幾篇把這個過程整理成乾淨的 A = L U 分解,你也學會了用前向與 後向代入 收尾一次求解。一切都很整齊——只要你每次伸手去拿的主元剛好都不是零。
但沒有什麼能阻止主元剛好是零。試著消去由列 (0, 1) 與 (1, 1) 構成的矩陣的第一行:最開頭的主元就是 0,而消去步驟卻要你去除以它。這個矩陣本身完全沒問題——它可逆、它的列彼此獨立、方程組 A x = b 有唯一解——可是演算法卻在第一步就一頭撞進除以零。崩潰是方法的,不是問題的。把那兩列交換,障礙立刻消失。
A = [ 0 1 ] pivot a_11 = 0 -> divide-by-zero on step 1
[ 1 1 ]
swap rows:
A'= [ 1 1 ] pivot a_11 = 1 -> elimination proceeds happily
[ 0 1 ]安靜的危險:不只是零主元,還有極小的主元
正好是零的情況反而簡單——你的程式會大聲崩潰,你也就知道要去修。陰險的情況是主元只是很小。除以一個極小的數會產生一個巨大的乘數,而那個乘數會把它碰到的每個元素都放大。在精確算術裡這毫無害處:那些大數之後會抵消回去。但你並不在精確算術裡。每個被放大的中間值都帶著它自己的捨入誤差,等到抵消真正到來時,真正的位數早已被淹沒——一場 災難性抵消 藏在一個看起來其他都正常的消去過程裡面。
這裡有個經典的二乘二例子把它揭穿。解一個矩陣列為 (1e-20, 1) 與 (1, 1)、右端為 (1, 2) 的方程組。精確答案接近 x = (1, 1)。如果你拿那個極小的 1e-20 當主元,乘數就是 1e20;第二式變成它減去 1e20 乘上第一式,而在雙精度下原本那些 '1' 全被四捨五入抹掉了。你會得到約 x = (0, 1)——一個自信滿滿的錯誤答案,連個錯誤訊息都沒有。把兩列交換,讓主元是 1 而不是 1e-20,用同樣的算術重做一遍,答案就對了。
部分選主元:那個有效又便宜的修法
解方簡單到幾乎令人尷尬。每一步,在你消去某一行之前,從對角線往下看這一行,找出絕對值最大的那個元素,把它所在的列交換上來放到主元的位置。然後照常消去。這就是 部分選主元,它一口氣做了兩件事:當下方存在非零元素時,它永遠不會挑到零主元;而且它保證選中的主元至少和它下方的每個元素一樣大,所以每個乘數的絕對值都不超過 1。乘數被限制住,就代表捨入誤差不再被無止境地放大。
- 在第 k 步,從第 k 列往下掃描第 k 行,找出元素絕對值最大的那一列 p。
- 把第 p 列與第 k 列交換,讓現有最大的元素現在坐在對角線上當主元。
- 計算乘數(主元下方的元素除以主元)——現在每一個的絕對值都不超過 1。
- 照常消去這一行,記錄這次交換,然後進到第 k+1 步。
記帳的工作微不足道:把列交換存進一個排列矩陣 P,分解就悄悄地從 A = L U 升級成 P A = L U。求解的其他部分都沒變——你先把 P 套到右端 b 上,再跑和先前一樣的前向與 後向代入。代價基本上是免費的:在一行裡找最大元素每步是 O(n) 的工作,被消去本身的 O(n^3) 給徹底蓋過。你幾乎不花什麼,卻買到了穩定性。
成長因子與這份保證的極限
部分選主元到底承諾了什麼?衡量危險最乾淨的方式是 成長因子:消去過程中曾出現的最大元素,除以原始矩陣裡的最大元素。如果什麼都沒長大,誤差就維持得很小;如果成長因子爆炸,你就又回到了小主元的災難,只是換了個偽裝。高斯消去法的後向誤差,在一些不大的常數倍意義下,正比於這個成長因子乘上單位捨入。把成長壓住,你就有一個 後向穩定 的求解器——它算出的答案,正是某個只與你的矩陣相差一個捨入級推動量的矩陣的精確解。
這裡有個誠實的但書,而且它很重要。部分選主元限制住了乘數,卻沒有在理論上限制住成長因子——存在一些精心構造的矩陣,其成長會達到 2^(n-1),一種會毀掉準確度的指數級暴增。那為什麼大家還是信任它?因為在超過半世紀的真實計算裡,那個最壞情況幾乎從不出現;在人們真正會去解的矩陣上,成長都停留在不大的小數附近。所以部分選主元是實務上穩定,而非嚴格最壞情況意義下可證明穩定。定理與經驗之間的這道縫隙,是這個領域著名的未解謎題之一,而一門誠實的課會把它點出來,而不是糊弄過去。
把它兜起來:選主元到底替你買到了什麼
退一步看清這堂課的形狀。選主元並沒有抽象地讓演算法更準確——它讓演算法穩定,而那是一個不同、也更謙遜的承諾。回想條件數那一階梯的主規則:前向誤差 <= 條件數乘以後向誤差。選主元只攻擊後向誤差那一半,也就是演算法掌管的那部分。它把後向誤差壓到接近單位捨入,好讓消去法掙得它後向穩定的徽章。它碰不到條件數,因為那屬於矩陣本身。
於是無法迴避的誠實是:一個選主元選得完美、後向穩定的求解,若矩陣病態,仍然會交給你一個糟糕的答案。如果 矩陣條件數 約為 1e8,那麼不論選主元多麼小心,你都該預期在約 16 位雙精度數字裡損失約 8 位——問題早在演算法開跑之前就把那份預算吃掉了。選主元保證的是:你沒有在那之上再添你自己本可避免的傷害;它不會、也不可能拯救一個本質上就敏感的問題。穩定性是一個演算法所能合理交付的極限,而選主元正是高斯消去法交付它的方式。
最後替你可能還揣著的一個顧慮打個包票:到處交換列,會不會搞壞「分解一次、求解多次」這個技巧?完全不會。排列矩陣 P 在分解時計算一次,並和 L、U 一起存起來,所以為了多個右端而重用因子所省下的一切,原封不動地保留了下來。手握 P A = L U,你就為下一篇做好了準備——在那裡,一類特別的矩陣,也就是對稱正定矩陣,結果完全不需要選主元,還回報你一個快上一倍的分解。