簡體   English   中英

C中精度浮點運算的問題

[英]Problem with Precision floating point operation in C

對於我的課程項目之一,我開始在C中實現“朴素貝葉斯分類器”。我的項目是使用大量訓練數據實現文檔分類器應用程序(尤其是垃圾郵件)。

由於C數據類型的限制,現在我在實現算法時遇到了問題。

(我在這里使用的算法, http://en.wikipedia.org/wiki/Bayesian_spam_filtering

問題陳述:該算法涉及獲取文檔中的每個單詞並計算它是垃圾詞的概率。 如果p1,p2 p3 .... pn是字-1,2,3 ... n的概率。 使用以下方法計算doc是否為垃圾郵件的概率

替代文字

這里,概率值可以非常容易地在0.01左右。 因此,即使我使用數據類型“double”,我的計算也會進行折騰。 為了證實這一點,我寫了一個給出的示例代碼。

#define PROBABILITY_OF_UNLIKELY_SPAM_WORD     (0.01)
#define PROBABILITY_OF_MOSTLY_SPAM_WORD     (0.99)

int main()
{
    int index;
    long double numerator = 1.0;
    long double denom1 = 1.0, denom2 = 1.0;
    long double doc_spam_prob;

    /* Simulating FEW unlikely spam words  */
    for(index = 0; index < 162; index++)
    {
        numerator = numerator*(long double)PROBABILITY_OF_UNLIKELY_SPAM_WORD;
        denom2    = denom2*(long double)PROBABILITY_OF_UNLIKELY_SPAM_WORD;
        denom1    = denom1*(long double)(1 - PROBABILITY_OF_UNLIKELY_SPAM_WORD);
    }
    /* Simulating lot of mostly definite spam words  */
    for (index = 0; index < 1000; index++)
    {
        numerator = numerator*(long double)PROBABILITY_OF_MOSTLY_SPAM_WORD;
        denom2    = denom2*(long double)PROBABILITY_OF_MOSTLY_SPAM_WORD;
        denom1    = denom1*(long double)(1- PROBABILITY_OF_MOSTLY_SPAM_WORD);
    }
    doc_spam_prob= (numerator/(denom1+denom2));
    return 0;
}

我嘗試了Float,double甚至是long double數據類型,但仍然存在同樣的問題。

因此,在我正在分析的100K字文件中,如果只有162個單詞具有1%的垃圾郵件概率而剩余的99838個是明顯的垃圾郵件單詞,那么我的應用程序仍然會因為精度錯誤而將其稱為非垃圾郵件doc(因為分子容易進行)到零)!!!

這是我第一次遇到這樣的問題。 那么究竟應該如何解決這個問題呢?

這通常發生在機器學習中。 AFAIK,關於精度的損失你無能為力。 因此,為了繞過這一點,我們使用log函數並將除法和乘法轉換為減法和加法。

所以我決定做數學,

原始等式是:

問題

我稍微修改一下:

在此輸入圖像描述

記錄兩側的日志:

在此輸入圖像描述

讓,

在此輸入圖像描述

代,

在此輸入圖像描述

因此,計算組合概率的替代公式:

在此輸入圖像描述

如果您需要我對此進行擴展,請發表評論。

這是一個訣竅:

for the sake of readability, let S := p_1 * ... * p_n and H := (1-p_1) * ... * (1-p_n), 
then we have:

  p = S / (S + H)
  p = 1 / ((S + H) / S)
  p = 1 / (1 + H / S)

let`s expand again:

  p = 1 / (1 +  ((1-p_1) * ... * (1-p_n)) / (p_1 * ... * p_n))
  p = 1 / (1 + (1-p_1)/p_1 * ... * (1-p_n)/p_n)

所以基本上,你將獲得相當大的數的乘積(之間0 ,並且對於p_i = 0.0199 )。 這個想法是,不要將大量的小數字彼此相乘,以獲得0 ,但是要得到兩個小數的商。 例如,如果n = 1000000 and p_i = 0.5 for all in = 1000000 and p_i = 0.5 for all i ,則上述方法將給出0/(0+0) ,即NaN ,而建議的方法將給出1/(1+1*...1) ,這是0.5

你可以得到更好的結果,當所有的p_i都被排序並且你以相反的順序將它們配對時(讓我們假設p_1 < ... < p_n ),那么下面的公式將獲得更好的精度:

  p = 1 / (1 + (1-p_1)/p_n * ... * (1-p_n)/p_1)

這樣你就可以將大分子(小p_i )與大分母(大p_(n+1-i) )和小分子與小分母分開。

編輯: MSalter在他的回答中提出了一個有用的進一步優化。 使用它,公式如下:

  p = 1 / (1 + (1-p_1)/p_n * (1-p_2)/p_(n-1) * ... * (1-p_(n-1))/p_2 * (1-p_n)/p_1)

您的問題是由於您收集太多條款而不考慮其大小而引起的。 一種解決方案是采用對數。 另一個是對您的個人條款進行排序。 首先,讓我們將等式重寫為1/p = 1 + ∏((1-p_i)/p_i) 現在你的問題是某些術語很小,而其他術語很大。 如果你連續有太多的小術語,你會下流,而且有太多大術語你會溢出中間結果。

所以,不要連續放入太多相同的訂單。 對術語(1-p_i)/p_i排序。 結果,第一個是最小的,最后一個是最大的。 現在,如果你馬上將它們相乘,你仍然會有下溢。 但計算順序無關緊要。 在臨時集合中使用兩個迭代器。 一個(1-p_0)/p_0開始(即(1-p_0)/p_0 ),另一個在結尾(即(1-p_n)/p_n ),你的中間結果從1.0開始。 現在,當您的中間結果> = 1.0時,您從前面獲取一個術語,當您的中間結果<1.0時,您從后面獲取結果。

結果是,當您使用術語時,中間結果將在1.0附近振盪。 當你用完小或大的時候,它只會上升或下降。 但那沒關系。 那時,你已經消耗了兩端的極值,因此中間結果將慢慢接近最終結果。

當然有溢出的可能性。 如果輸入完全不可能是垃圾郵件(p = 1E-1000),則1/p將溢出,因為∏((1-p_i)/p_i)溢出。 但由於這些術語已經排序,我們知道只有∏((1-p_i)/p_i)溢出時,中間結果才會溢出。 因此,如果中間結果溢出,則不會出現后續的精度損失。

嘗試計算逆1 / p。 這給你一個形式1 + 1 /(1-p1)*(1-p2)的等式......

如果你然后計算每個概率的出現 - 看起來你有少量值重復 - 你可以使用pow()函數 - pow(1-p,occurences_of_p)* pow(1-q, occurrences_ofs) - 並避免每次乘法的單獨舍入。

您可以使用概率百分比或promiles:

doc_spam_prob= (numerator*100/(denom1+denom2));

要么

doc_spam_prob= (numerator*1000/(denom1+denom2));

或使用其他系數

我的數學能力不強,所以我無法對可能消除或減少問題的公式進行簡化評論。 但是,我熟悉long double類型的精度限制,並且知道C的幾個任意和擴展的精度數學庫。檢查:

http://www.nongnu.org/hpalib/http://www.tc.umn.edu/~ringx004/mapm-main.html

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM