朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法。
朴素贝叶斯分类器假设样本每个特征与其他特征都不相关。
一. 基于离散变量
下面以一个简单的数据集为例,阐述基于NB的回归/预测模型:
上述三篇文本的词列表如下:
首先可以将上述两篇训练文本(train1和train2),以及一篇测试文本(test1)转为如下向量格式:
分析:
基于NB的回归模型如何在已知train1和train2的标准答案(即公众感到“joy”的概率)分别为0.6和0.7的前提下,预测test1对应的公众感到“joy”的概率值。
为了便于模型的推导,我们将文本train1记为d1,train2记为d2,test1记为d3,情感joy记为j,词sheva记为s,词goal记为g。我们要估计的是p(d3, j),即文本test1和情感joy的联合概率值(也就是说,100名用户看到test1,会有多大的可能性将它关联到joy)。由于d3包含s和g两个词,因此要估计的p(d3, j)近似等于p(s, g, j),即词sheva、goal和情感joy的联合概率值。基于NB的回归模型利用所有的训练文本及其标准答案来估计这个值:
p(s, g, j) = p(d1, s, g, j) + p(d2, s, g, j) 概率的加法法则,
其中,p(d1, s, g, j) = p(d1, j, s, g) = p(d1, j)p(s | d1, j)p(g | d1, j, s) 概率的乘法法则,同上
上式中,p(d1, j) = 0.6;p(s | d1, j) = 0.33;p(g | d1, j, s) = 0,这里假设给定每篇文本和情感的前提下,词与词之间是独立的,也就是说p(g | d1, j, s) = p(g | d1, j) = 0。
所以,p(d1, s, g, j) = 0.6*0.33*0 = 0。
同理,p(d2, s, g, j) = 0.7*0.25*0.25 = 0.044。
所以,p(s, g, j) = 0 + 0.044 = 0.044。即,基于NB的回归模型会将test1对应的公众感到“joy”的概率值预测为0.044。
存在的问题:
(1)一个词概率为0就会得到全0结果
解决方法:拉普拉斯平滑,将乘式中所有的0用一个很小的常数代替
(2)乘积过小
下溢出解决方法:对乘积取自然对数,将相乘变为相加,计算完以后 加上一个数作补偿再做指数变换。
(这个方法更大的好处是,可以将计算过程转为非常简单的矩阵乘法)
预测结果非常小解决方法:将所有结果进行归一化, 算出所有情感值之和,取每一个值除以总和的商。
二. 基于连续变量
需要将连续变量离散化:
(1)使用相应的离散区间替换连续属性值。但是这种方法不方便控制离散区间划分的粒度。
(2)假设变量服从高斯分布,使用训练数据来估计分布的参数。
附上属性连续型代码,数据集格式为:第一行为58个属性名称,最后一个是分享值(0或1),接下来n行是训练数据集,再继续来m行是预测数据集,其share值为空。需要求出预测数据集的share预测值。
<span style="font-size:14px;">#include <bits/stdc++.h>
#define train_n 27751
#define test_n 11893
#define feature_n 58
#define pi acos(-1)
#define inf 1000000
using namespace std;
double MAX[60],MIN[60];
struct Continuous{ //连续型属性
double mean[2],var[2]; //m/v[i]是share=i的均值和方差
}f[60];
typedef vector<double> feature;
vector <feature> text; //所有文本
vector <int> shares; //测试文本的share值
double p[2]; // share=0/1概率
int text_n = test_n+train_n; //所有文本个数
void input()
{
string line;
double data;
int n;
ifstream fin("Datac_all.csv");
getline(fin, line);
for(int i=0;i<text_n;i++){
getline(fin, line);
istringstream ss(line);
feature tmp;
while (!ss.eof()){
ss >> data; //忽略逗号
ss.ignore(line.size(), ',');
tmp.push_back(data);
}
text.push_back(tmp);
if(i<train_n){
shares.push_back(data);
if(data>0) n++;//计算share=1的个数
}
}
fin.close();
p[1] = 1.0*n / train_n;
p[0] = 1.0*(train_n-n) / train_n;
cout << "The read is done\n";
}
void train_work() //计算均值(mean)和方差(var)
{
for (int i=0; i<feature_n; i++) //对每个属性
{
for (int j=0; j<train_n; j++){
int k = shares[j];
f[i].mean[k] += text[j][i];
}
f[i].mean[1] /= train_n; //share=1的均值
f[i].mean[0] /= train_n; //share=0的均值
for (int j=0; j<train_n; j++){
int k = shares[j];
f[i].var[k] += (text[j][i]-f[i].mean[k])*(text[j][i]-f[i].mean[k]);
}
f[i].var[1] /= train_n; //share=1的方差
f[i].var[0] /= train_n; //share=0的方差
}
cout << "The train is done\n";
}
double gauss_probability(int i, int j, int share) //计算概率
{
double x = text[i][j];
double m = f[j].mean[share];
double v = f[j].var[share];
return (exp(-(x-m)*(x-m)/(2*v)) / (sqrt(2*pi*v))); //高斯分布
}
void predict() //进行预测
{
ofstream out("NB.txt");
for (int i=train_n; i<text_n; i++){
double share = p[1], not_share = p[0];
for (int j=0; j<feature_n; j++){
share *= gauss_probability(i, j, 1);
not_share *= gauss_probability(i, j, 0);
}
out << (share>not_share) << endl;
}
cout << "The predict is done\n";
}
int main(){
input();
train_work();
predict();
return 0;
}
</span>