統計學習方法c++實現之三 樸素貝葉斯法

樸素貝葉斯法

前言

樸素貝葉斯法是基於貝葉斯定理與特徵條件獨立假設的分類方法,這與咱們生活中判斷一件事情的邏輯有點相似,樸素貝葉斯法的核心是參數的估計,在這以前,先來看一下如何用樸素貝葉斯法分類。git

代碼地址https://github.com/bBobxx/statistical-learning,歡迎提問。github

基本方法

樸素貝葉斯法必須知足特徵條件獨立假設,分類時,對給定的輸入\(x\),經過學習到的模型計算後驗機率分佈\(P(Y=c_i|X=x)\),將後驗機率最大的類做爲輸出,後驗機率的計算由貝葉斯定理:數據結構

\[P(Y=c_k|X=x) = \frac{P(X=x|Y=c_k)P(Y=c_k)}{\sum_{k}P(X=x|Y=c_k)P(Y=c_k)}\]學習

再根據特徵條件獨立假設,優化

\[P(Y=c_k|X=x) = \frac{P(Y=c_k)\prod_{j}{P(X=x^{(j)}|Y=c_k)}}{\sum_{k}P(Y=c_k)\prod_{j}P(X=x^{(j)}|Y=c_k)}\]spa

因爲分母都同樣,因此咱們只比較分子就能夠肯定類別。code

參數估計

對於上面的公式來講,咱們須要知道兩個機率,即:blog

先驗機率:\(P(Y=c_k)=\frac{\sum^{N}_{i=1}I(y_i=c_k)}{N}\)get

通俗來講就是數個數而後除以總數。string

還有一個條件機率:\(P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum^{N}_{i=1}I(x_{i}^{(j)}=a_{jl},y_i=c_k)}{\sum^{N}_{i=1}I(y_i=c_k)}\)

不要被公式唬住了,看含義就容易懂,其實仍是數個數,只不過如今要同時知足x y的限制,即第i個樣本的第j維特徵\(x_{i}^{(j)}\)\(a_{jl}\)這個值,而且所屬類別爲\(c_k\)的機率。

上面的就是經典的最大似然估計,就是數個數嘛,於此相對的還有貝葉斯學派的參數估計貝葉斯估計,這裏直接給出公式:

先驗機率:\(P(Y=c_k)=\frac{\sum^{N}_{i=1}I(y_i=c_k)+\lambda}{N+K\lambda}\)

條件機率:\(P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum^{N}_{i=1}I(x_{i}^{(j)}=a_{jl},y_i=c_k)+\lambda}{\sum^{N}_{i=1}I(y_i=c_k)+S_j\lambda}\)

一般取\(\lambda=1\),此時叫作拉普拉斯平滑。\(K\)是Y取值的個數,\(S_j\)是某個特徵的可能的取值個數。

代碼結構

在train裏面分別調用了兩種參數估計的方法。

實現細節

對於貝葉斯法,終點在於參數估計,這裏其實就是計數(我的見解,但願獲得指導,想破腦殼也沒想起來如何不用遍歷的方法計算機率)。

首先,我選用了map結構來存儲條件機率和先驗機率:

vector<map<pair<string,string>, double>> condProb;
    map<string, double> priProb;

map是基於紅黑樹的一種數據結構,因此查找很快,這樣計算後驗機率的時候就能很快查找到相應的機率。

其餘的應該都好理解,無非是循環計數,不過在貝葉斯估計這裏我耍了個花招,就是將公式拆成兩部分:\(P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum^{N}_{i=1}I(x_{i}^{(j)}=a_{jl},y_i=c_k)}{\sum^{N}_{i=1}I(y_i=c_k)+S_j\lambda}+\frac{\lambda}{\sum^{N}_{i=1}I(y_i=c_k)+S_j\lambda}\)

void NavieBayes::bayesEstim(const double& lmbda = 1.0){
    for(const auto& gt: trainDataGT){
        priProb[std::to_string(gt)] += 1.0;
    }
    for(unsigned long i=0;i<indim;++i){
        for(unsigned long j=0;j<trainDataF.size();++j)
        {
            auto cond = std::make_pair(std::to_string(trainDataF[j][i]), std::to_string(trainDataGT[j]));

            condProb[i][cond] += 1.0/(priProb[std::to_string(trainDataGT[j])]+lmbda*xVal[i].size());
        }//先跟最大似然估計同樣,因爲採用了連加,若是把lambda那部分也包含,須要計算一個每一個特徵取某個值時的個數
        //因而將公式拆成兩個分母相同的式子相加的形式,這部分計算分子中除去lambda那部分
    }
    for(unsigned long i=0;i<indim;++i){
        for(auto& d:condProb[i]){
            d.second += lmbda/(priProb[d.first.second]+lmbda*xVal[i].size());
        }
    }//這裏計算另外一部分

    for(auto& iter:priProb)
        iter.second = (iter.second+lmbda)/(double(trainDataF.size()+yVal.size()));
}

至於預測就是取\(P(Y=c_k)\prod_{j}{P(X=x^{(j)}|Y=c_k)}\)最大的那一部分

void NavieBayes::predict() {
...
        for(const auto& y: yVal){
            auto pr = priProb[std::to_string(y)];
            for(unsigned long i=0;i<indim;++i)
                pr *= condProb[i][std::make_pair(std::to_string(testDataF[j][i]), std::to_string(y))];
...
    }
}

總結

這部分概念很好懂,可是如何計數確實廢了一番腦筋,之後看看在優化吧,這個時間複雜度實在過高了。

相關文章
相關標籤/搜索