朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法,这与咱们生活中判断一件事情的逻辑有点相似,朴素贝叶斯法的核心是参数的估计,在这以前,先来看一下如何用朴素贝叶斯法分类。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))]; ... } }
这部分概念很好懂,可是如何计数确实废了一番脑筋,之后看看在优化吧,这个时间复杂度实在过高了。