统计学习方法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))];
...
    }
}

总结

这部分概念很好懂,可是如何计数确实废了一番脑筋,之后看看在优化吧,这个时间复杂度实在过高了。

相关文章
相关标签/搜索