带你彻彻底底搞懂朴素贝叶斯公式

本文参考了该博客的实例,但该博客中的朴素贝叶斯公式计算错误,评论中的也不对,所以,重新写一篇。

一. 朴素贝叶斯

      朴素贝叶斯中的朴素一词的来源就是假设各特征之间相互独立。这一假设使得朴素贝叶斯算法变得简单,但有时会牺牲一定的分类准确率

    首先给出贝叶斯公式:
    换成分类任务的表达式:
     我们最终求的p(类别|特征)即可!就相当于完成了我们的任务。
     则,朴素贝特斯公式为:
. 实例解析

首先,给出数据如下:


现在给我们的问题是,如果一对男女朋友,男生想女生求婚,男生的四个特点分别是不帅,性格不好,身高矮,不上进,请你判断一下女生是还是不嫁

这是典型的二分类问题,按照朴素贝叶斯的求解,转换为P(嫁|不帅、性格不好、矮、不上进)和P(不嫁|不帅、性格不好、矮、不上进)的概率,最终选择嫁与不嫁的答案。

这里我们根据贝特斯公式:

由此,我们将(嫁|不帅、性格不好、矮、不上进)转换成三个可求的P(嫁)、P(不帅、性格不好、矮、不上进|嫁)、P(不帅、性格不好、矮、不上进)。进一步分解可以得:
P(不帅、性格不好、矮、不上进)=P()P(不帅|嫁)P(性格不好|嫁)P(矮|嫁)P(不上进|嫁)+P(不)P(不帅|不嫁)P(性格不好|不嫁)P(矮|不嫁)P(不上进|不嫁)。

P(不帅、性格不好、矮、不上进|嫁)=P(不帅|嫁)P(性格不好|嫁)P(矮|嫁)P(不上进|嫁)

将上面的公式整理一下可得:


 P()=1/2、P(不帅|嫁)=1/2、P(性格不好|嫁)=1/6、P(矮|嫁)=1/6、P(不上进|嫁)=1/6。
 P(不嫁)=1/2、P(不帅|不嫁)=1/3、P(性格不好|不嫁)=1/2、P(矮|不嫁)=1、P(不上进|不嫁)=2/3
 但是由贝叶斯公式可得:对于目标求解为不同的类别,贝叶斯公式的分母总是相同的。所以,只求解分子即可:

于是,对于类别“嫁”的贝叶斯分子为:P(嫁)P(不帅|嫁)P(性格不好|嫁)P(矮|嫁)P(不上进|嫁)=1/2 * 1/2 * 1/6 * 1/6 * 1/6=1/864     
对于类别“不嫁”的贝叶斯分子为:P(不)P(不帅|不嫁)P(性格不好|不嫁)P(矮|不嫁)P(不上进|不嫁)=1/2 * 1/3 * 1/2 * 1* 2/3=1/18。
经代入贝叶斯公式可得:P(嫁|不帅、性格不好、矮、不上进)=(1/864) / (1/864+1/18)=1/49=2.04%
P(不嫁|不帅、性格不好、矮、不上进)=(1/18) / (1/864+1/18)=48/49=97.96%
P(不嫁|不帅、性格不好、矮、不上进) > P(嫁|不帅、性格不好、矮、不上进),则该女子选择不嫁!

. 朴素贝叶斯的优缺点

优点:
  (1) 算法逻辑简单,易于实现(算法思路很简单,只要使用贝叶斯公式转化即可!
(2)分类过程中时空开销小(假设特征相互独立,只会涉及到二维存储
缺点:
      朴素贝叶斯假设属性之间相互独立,这种假设在实际过程中往往是不成立的。在属性之间相关性越大,分类误差也就越大。

四. 朴素贝叶斯实战

    sklearn中有3种不同类型的朴素贝叶斯:

  • 高斯分布型:用于classification问题,假定属性/特征服从正态分布的。
  • 多项式型:用于离散值模型里。比如文本分类问题里面我们提到过,我们不光看词语是否在文本中出现,也得看出现次数。如果总词数为n,出现词数为m的话,有点像掷骰子n次出现m次这个词的场景。
  • 伯努利型:最后得到的特征只有0(没出现)和1(出现过)。
  4.1  我们使用iris数据集进行分类
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import cross_val_score
from sklearn import datasets
iris = datasets.load_iris()
gnb = GaussianNB()
scores=cross_val_score(gnb, iris.data, iris.target, cv=10)
print("Accuracy:%.3f"%scores.mean())
     输出: Accuracy:0.953
 

  4.2 Kaggle比赛之“旧金山犯罪分类预测”

       题目数据:第一种获取方式:Kaggle网站上;第二种获取方式:百度网盘

        题目背景:『水深火热』的大米国,在旧金山这个地方,一度犯罪率还挺高的,然后很多人都经历过大到暴力案件,小到东西被偷,车被划的事情。当地警方也是努力地去总结和想办法降低犯罪率,一个挑战是在给出犯罪的地点和时间的之后,要第一时间确定这可能是一个什么样的犯罪类型,以确定警力等等。后来干脆一不做二不休,直接把12年内旧金山城内的犯罪报告都丢带Kaggle上,说『大家折腾折腾吧,看看谁能帮忙第一时间预测一下犯罪类型』。犯罪报告里面包括日期描述星期几所属警区处理结果地址GPS定位等信息。当然,分类问题有很多分类器可以选择,我们既然刚讲过朴素贝叶斯,刚好就拿来练练手好了。

     (1) 首先我们来看一下数据

import pandas as pd  
import numpy as np  
from sklearn import preprocessing  
from sklearn.metrics import log_loss  
from sklearn.cross_validation import train_test_split
train = pd.read_csv('/Users/liuming/projects/Python/ML数据/Kaggle旧金山犯罪类型分类/train.csv', parse_dates = ['Dates'])  
test = pd.read_csv('/Users/liuming/projects/Python/ML数据/Kaggle旧金山犯罪类型分类/test.csv', parse_dates = ['Dates'])  
train  

 我们依次解释一下每一列的含义:

  • Date: 日期
  • Category: 犯罪类型,比如 Larceny/盗窃罪 等.
  • Descript: 对于犯罪更详细的描述
  • DayOfWeek: 星期几
  • PdDistrict: 所属警区
  • Resolution: 处理结果,比如说『逮捕』『逃了』
  • Address: 发生街区位置
  • X and Y: GPS坐标

        train.csv中的数据时间跨度为12年,包含了将近90w的记录。另外,这部分数据,大家从上图上也可以看出来,大部分都是『类别』型,比如犯罪类型,比如星期几。

    (2)特征预处理

       sklearn.preprocessing模块中的 LabelEncoder函数可以对类别做编号,我们用它对犯罪类型做编号;pandas中的get_dummies( )可以将变量进行二值化01向量,我们用它对”街区“、”星期几“、”时间点“进行因子化。

#对犯罪类别:Category; 用LabelEncoder进行编号  
leCrime = preprocessing.LabelEncoder()  
crime = leCrime.fit_transform(train.Category)   #39种犯罪类型  
#用get_dummies因子化星期几、街区、小时等特征  
days=pd.get_dummies(train.DayOfWeek)  
district = pd.get_dummies(train.PdDistrict)  
hour = train.Dates.dt.hour  
hour = pd.get_dummies(hour)  
#组合特征  
trainData = pd.concat([hour, days, district], axis = 1)  #将特征进行横向组合  
trainData['crime'] = crime   #追加'crime'列  
days = pd.get_dummies(test.DayOfWeek)  
district = pd.get_dummies(test.PdDistrict)  
hour = test.Dates.dt.hour  
hour = pd.get_dummies(hour)  
testData = pd.concat([hour, days, district], axis=1)  
trainData 

    特征预处理后,训练集feature,如下图所示:

   (3) 建模

from sklearn.naive_bayes import BernoulliNB
import time
features=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'BAYVIEW', 'CENTRAL', 'INGLESIDE', 'MISSION',  
 'NORTHERN', 'PARK', 'RICHMOND', 'SOUTHERN', 'TARAVAL', 'TENDERLOIN']  
X_train, X_test, y_train, y_test = train_test_split(trainData[features], trainData['crime'], train_size=0.6)  
NB = BernoulliNB()  
nbStart = time.time()  
NB.fit(X_train, y_train)  
nbCostTime = time.time() - nbStart  
#print(X_test.shape)  
propa = NB.predict_proba(X_test)   #X_test为263415*17; 那么该行就是将263415分到39种犯罪类型中,每个样本被分到每一种的概率  
print("朴素贝叶斯建模%.2f秒"%(nbCostTime))  
predicted = np.array(propa)  
logLoss=log_loss(y_test, predicted)  
print("朴素贝叶斯的log损失为:%.6f"%logLoss)  
输出:
朴素贝叶斯建模0.55秒
朴素贝叶斯的log损失为:2.582561

  • 128
    点赞
  • 573
    收藏
    觉得还不错? 一键收藏
  • 33
    评论
以下是朴素贝叶斯分类器的伪代码: 1. 训练阶段: (1)对于每个类别 i,计算先验概率P(Y=i)。 (2)对于每个特征 j,计算在每个类别 i 下该特征的条件概率P(Xj=x|Y=i)。 2. 测试阶段: (1)对于输入样本 x,计算该样本属于每个类别 i 的条件概率P(Y=i|X=x)。 (2)将样本分到概率最大的类别中。 下面是朴素贝叶斯分类器的 C++ 代码实现,其中假设特征和类别都是离散的: ``` #include <iostream> #include <vector> #include <map> #include <cmath> using namespace std; // 计算先验概率 map<int, double> cal_prior_prob(vector<int>& labels) { map<int, double> prior_prob; int total = labels.size(); for (int i = 0; i < total; i++) { int label = labels[i]; if (prior_prob.find(label) == prior_prob.end()) { prior_prob[label] = 0.0; } prior_prob[label] += 1.0; } for (auto iter = prior_prob.begin(); iter != prior_prob.end(); iter++) { iter->second /= total; } return prior_prob; } // 计算条件概率 map<int, map<int, double>> cal_cond_prob(vector<vector<int>>& features, vector<int>& labels) { map<int, map<int, double>> cond_prob; int total = labels.size(); int feature_num = features[0].size(); for (int i = 0; i < total; i++) { int label = labels[i]; if (cond_prob.find(label) == cond_prob.end()) { cond_prob[label] = map<int, double>(); } for (int j = 0; j < feature_num; j++) { int feature = features[i][j]; if (cond_prob[label].find(j) == cond_prob[label].end()) { cond_prob[label][j] = map<int, double>(); } if (cond_prob[label][j].find(feature) == cond_prob[label][j].end()) { cond_prob[label][j][feature] = 0.0; } cond_prob[label][j][feature] += 1.0; } } for (auto iter1 = cond_prob.begin(); iter1 != cond_prob.end(); iter1++) { int label = iter1->first; for (auto iter2 = iter1->second.begin(); iter2 != iter1->second.end(); iter2++) { int feature = iter2->first; int feature_total = iter2->second.size(); for (auto iter3 = iter2->second.begin(); iter3 != iter2->second.end(); iter3++) { iter3->second /= feature_total; } } } return cond_prob; } // 预测 int predict(vector<int>& feature, map<int, double>& prior_prob, map<int, map<int, double>>& cond_prob) { int label = -1; double max_prob = -1.0; for (auto iter = prior_prob.begin(); iter != prior_prob.end(); iter++) { int cur_label = iter->first; double cur_prob = log(iter->second); for (int i = 0; i < feature.size(); i++) { cur_prob += log(cond_prob[cur_label][i][feature[i]]); } if (cur_prob > max_prob) { max_prob = cur_prob; label = cur_label; } } return label; } // 测试 void test(vector<vector<int>>& features, vector<int>& labels, map<int, double>& prior_prob, map<int, map<int, double>>& cond_prob) { int total = labels.size(); int correct = 0; for (int i = 0; i < total; i++) { int predict_label = predict(features[i], prior_prob, cond_prob); if (predict_label == labels[i]) { correct++; } } cout << "Accuracy: " << (double)correct / total << endl; } int main() { // 读取数据,features存放特征,labels存放类别 vector<vector<int>> features; vector<int> labels; // 训练 map<int, double> prior_prob = cal_prior_prob(labels); map<int, map<int, double>> cond_prob = cal_cond_prob(features, labels); // 测试 test(features, labels, prior_prob, cond_prob); return 0; } ``` 注意:这里的代码只是朴素贝叶斯分类器的一种实现方式,可能还有其他实现方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值