6.17. 求解器與隨機數

當所研究的函式是由 Python 程式碼而非取樣緩衝區所定義時,另一套工具才是正確的選擇:這個函式的根在哪裡?它的最小值是多少?它在給定區間上的積分為何?scipy.integratescipy.optimize 子模組涵蓋了這類工作。每個演算法都會回呼使用者提供的 Python 函式,因此每次迭代的成本高於緩衝區歸約;其便利之處在於不必自行撰寫求解器。

scipy.special 子模組涵蓋了統計上的特殊函式(誤差函式、gamma 函式),這些函式會在計算機率分布的累積分布函式(cumulative distribution function,CDF,即樣本不大於某給定值的機率)或機率密度函式(probability density function,PDF,即某給定值處的相對可能性)時出現。numpy.random 則涵蓋了用於抖動、模擬與合成測試資料的偽隨機產生器。

6.17.1. 可呼叫物件的數值積分

當被積函式是 Python 函式而非取樣緩衝區時,scipy.integrate 提供四種數值積分演算法:

  • quad(),自適應 Gauss-Kronrod。是平滑被積函式的正確預設值。回傳 (value, error)

  • romberg(),經典的 Romberg / Newton-Cotes。回傳單一浮點數。上游已棄用;此處為相容性而納入。

  • simpson(),自適應 Simpson 法則。回傳單一浮點數。

  • tanhsinh(),雙指數數值積分。當被積函式在端點有奇異性或積分上限為無限時使用。回傳 (value, error)

以雙指數法則(tanhsinh)計算高斯積分:

from math import exp
from math import pi
from math import sqrt
from ulab import numpy as np
from ulab import scipy as sp

f = lambda x: exp(-x * x)
value, err = sp.integrate.tanhsinh(f, -np.inf, np.inf)
print("approx:", value, "   exact:", sqrt(pi))

輸出:

approx: 1.7724538...   exact: 1.7724538...

6.17.2. 求根與最小化

scipy.optimize 涵蓋三個經典的單變數求解器。每次迭代都會回呼使用者提供的 Python 函式,因此相較於純 Python 求解器的加速幅度有限(大約 2 倍);其便利之處在於不必自行撰寫求解器。

  • bisect(),藉由對區間取半,在 [a, b] 上尋找 f 的根。f(a)f(b) 必須異號:

    def f(x):
        return x * x - 1
    
    sp.optimize.bisect(f, 0, 4)        # ~1.0
    
  • newton(),使用割線法 / Newton-Raphson 迭代求根:

    def f(x):
        return x * x * x - 2.0
    
    sp.optimize.newton(f, 3., tol=0.001, rtol=0.01)
    # ~1.260
    
  • fmin(),使用下坡單純形(Nelder-Mead)法尋找局部最小值:

    def f(x):
        return (x - 1) ** 2 - 1
    
    sp.optimize.fmin(f, 3.0)           # ~1.0
    

對於大多數相機端的最佳化問題,單變數的範圍已經足夠,例如感測器的校正常數、使對比量測最大化的增益,或直方圖雙峰性最為明顯處的閾值。對於多變數問題,正確的做法通常是將問題重新表述為一個小型的線性代數求解,而非動用通用的非線性最佳化器。

6.17.3. 特殊函式

scipy.special 提供少數幾個統計與機率函式,它們的行為類似於通用函式,亦即接受純量、可迭代物件或 ndarray,並回傳浮點 ndarray:

x = np.linspace(0, 4, num=8)

sp.special.erf(x)         # error function
sp.special.erfc(x)        # complementary error function
sp.special.gamma(x + 1)   # gamma function
sp.special.gammaln(x + 1) # log-gamma function

誤差函式及其補函式會出現在高斯分布的 CDF 中,是在量測到的 z 分數與機率之間進行換算、或計算常態分布尾端積分時的首選應用。gamma 與 log-gamma 函式則出現在 beta / 卡方 / 學生 t 的計算中;gammaln 是在大引數情形下數值穩定的形式,此時 gamma 本身會溢位。

6.17.4. 隨機數

numpy.random 提供一個 Generator 類別,可從常見分布中抽取樣本。此產生器具有狀態:每次呼叫都會推進其內部狀態,因此連續呼叫會回傳彼此獨立的樣本:

from ulab import numpy as np

rng = np.random.Generator(seed=42)

rng.random(size=5)             # 5 uniform [0.0, 1.0) samples
rng.uniform(low=-1.0, high=1.0, size=10)
rng.normal(loc=0.0, scale=1.0, size=(2, 4))

輸出 dtype 永遠是 floatsize= 接受整數(一維輸出)或元組(n 維輸出);省略時則回傳單一 Python 浮點數。

此產生器適用於模擬、抖動、合成測試資料,以及任何不需要密碼學強度的應用。它適合用於金鑰或權杖;那些情形請透過 os 使用系統的隨機來源。

6.17.5. 建置時的可用性

每個子模組是否確實存在,取決於相機是如何建置的。scipy.optimizescipy.special 並非在每台相機上都啟用;呼叫相機未納入的函式會引發 AttributeErrordir(sp)dir(sp.optimize)dir(np.random) 等可回報目標相機上有哪些可用項目。