想象一下有一些向量(可以是torch張量或numpy陣列),其中包含大量組件,每個組件都非常小(~ 1e-10)。
假設我們要計算這些向量之一(或其中兩個向量之間的點積)的范數。同樣使用float64資料型別,每個分量的精度將是 ~1e-10,而 2 個分量的乘積(在范數/點積計算期間)很容易達到 ~1e-20,導致很多舍入誤差,總結起來回傳錯誤的結果。
有沒有辦法處理這種情況?(例如,有沒有辦法為這些操作定義任意精度陣列,或者一些內置的運算子可以自動處理?)
uj5u.com熱心網友回復:
您在這里處理兩個不同的問題:
下溢/上溢
計算平方時,計算非常小的值的范數可能會下溢為零。大值可能會溢位到無窮大。這可以通過使用穩定的范數演算法來解決。處理此問題的一種簡單方法是臨時縮放值。例如,請參閱:
a = np.array((1e-30, 2e-30), dtype='f4')
np.linalg.norm(a) # result is 0 due to underflow in single precision
scale = 1. / np.max(np.abs(a))
np.linalg.norm(a * scale) / scale # result is 2.236e-30
現在這是一個兩遍演算法,因為您必須在確定縮放值之前迭代所有資料。如果這不符合您的喜好,可以使用單程演算法,但您可能不想在 Python 中實作它們。經典的是 Blue 的演算法:http : //degiorgi.math.hr/~singer/aaa_sem/Float_Norm/p15-blue.pdf
一種更簡單但效率低得多的方法是簡單地將呼叫鏈接到 hypot(使用穩定演算法)。你永遠不應該這樣做,而只是為了完成:
norm = 0.
for value in a:
norm = math.hypot(norm, value)
或者甚至是這樣的分層版本,以減少 numpy 呼叫的數量:
norm = a
while len(norm) > 1:
hlen = len(norm) >> 1
front, back = norm[:hlen], norm[hlen: 2 * hlen]
tail = norm[2 * hlen:] # only present with length is not even
norm = np.append(np.hypot(front, back), tail)
norm = norm[0]
您可以自由組合這些策略。例如,如果您的資料不是一次全部可用而是按塊(例如,因為資料集太大而您從磁盤讀取),您可以為每個塊選擇一個縮放值,然后將這些塊與一些鏈接在一起呼吁hypot。
舍入誤差
您累積舍入誤差,尤其是在累積不同量級的值時。如果您累積不同星座的值,您也可能會遇到災難性的取消。為了避免這些問題,您需要使用補償求和方案。Python 提供了一個非常好的math.fsum. 因此,如果您絕對需要最高精度,請使用以下方法:
math.sqrt(math.fsum(np.square(a * scale))) / scale
請注意,這對于簡單的范數來說是多余的,因為累積中沒有符號變化(因此沒有取消)并且平方會增加幅度上的所有差異,因此結果將始終由其最大分量主導,除非您正在處理真正可怕的資料集。numpy 沒有為這些問題提供內置的解決方案,這告訴你,樸素的演算法對于大多數現實世界的應用程式來說實際上已經足夠好了。在您真正遇到麻煩之前,沒有理由過度實施。
應用于點陣產品
我關注的是 l2 范數,因為這種情況通常被認為是危險的。當然,您可以將類似的策略應用于點積。
np.dot(a, b)
ascale = 1. / np.max(np.abs(a))
bscale = 1. / np.max(np.abs(b))
np.dot(a * ascale, b * bscale) / (ascale * bscale)
如果您使用混合精度,這將特別有用。例如,點積可以以單精度計算,但x / (ascale * bscale)可以以雙精度甚至擴展精度進行計算。
當然math.fsum仍然可用:dot = math.fsum(a * b)
獎金想法
整個縮放本身會引入一些舍入錯誤,因為沒有人保證 a/b 可以用浮點數精確表示。但是,您可以通過選擇一個精確為 2 的冪的縮放因子來避免這種情況。在 FP 中乘以 2 的冪總是精確的(假設您保持在可表示的范圍內)。你可以得到指數math.frexp
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/342665.html
下一篇:在id相同的陣列中添加值
