樸素貝葉斯法是基於貝葉斯定理與特徵條件獨立假設的分類方法,這與咱們生活中判斷一件事情的邏輯有點相似,樸素貝葉斯法的核心是參數的估計,在這以前,先來看一下如何用樸素貝葉斯法分類。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))]; ... } }
這部分概念很好懂,可是如何計數確實廢了一番腦筋,之後看看在優化吧,這個時間複雜度實在過高了。