赞
踩
内容里的公式和代码皆手打,码字不易求个赞(●´ω`●)。
朴素贝叶斯作为十分经典的一款基于贝叶斯定理的统计分类模型,应用场景十分广泛,在垃圾邮件检测,情感分类等场景都有着不错的表现,并且由于其原理简单易懂,实现简单,很适合作为入门初学的一款统计模型,并且能有效运用在数据量大,维度高的场景下。
其核心思想就是利用贝叶斯公式,以及假设数据集中的每个特征都是独立的,这样可以把概率计算公式大大简化.
两个先导知识,贝叶斯公式与条件独立。
基本的概率学公式,其变形 P(AB) = P(A|B)P(B) = P(B|A)P(A),这一个形式是我们等下要 用到的。
我们一般说所说的两个随机变量独立,即满足P(AB) = P(A)P(B),即认为随机变量A与B 是独立的。而条件独立的定义则如下:
这样则称为在C条件下随机变量A与B独立。注意P(A,B),P(AB),P(A+B)都是同一个意思 都是A且B.
朴素贝叶斯所给出的分类方法如下,以二分类任务为例,
其中,x(下标a,上面1)代表样本在第一个特征上取值为a。
通俗一点说,对于一个新的样本X,假设他有两个特征, 最终的分类也有两个类别,买和 不买,用表格表示如下:
现在对于这一个样本,我们分别计算
P1 = P(年龄=大于30 , 性别=男,购买=是);
P2 = P(年龄=大于30 , 性别=男, 购买=否)
若P1>P2,那么就把该样本分类到"购买=是"这一类中,反之亦然。
知道分类准则后,看起来就很简单了,但实际上当你真的进行计算之后,你就会发现问 题了。
例如我们要计算P1=P(x1,x2,x3,x4,x5,w1), 我们就得利用贝叶斯公式变形为:
P1 = P(x1,x2,x3,x4,x5 | w1)P(w1)
P(w1)这一项比较好计算,直接从已有的数据集里面可以近似的计算出来。比如你有 100条数据,里面有67条属于w1,那么p(w1)直接近似计算为 67/100 = 0.67就可以了.
困难的是计算P(x1,x2,x3,x4,x5 | w1)这一项,我们只能按照贝叶斯决策里面同样的方 式去计算这一项,计算所有特征上的联合分布率,这在你数据量大,特征很多的情况 下, 往往是不现实的(不了解贝叶斯决策的可以看看我另一篇, 贝叶斯决策)。
在现在的情况下,我们就可以引入了条件独立的假设,从而达到简化计算公式的效果。
在假设样本的各个特征的取值仅依赖于类别标签,而特征之间是相互独立的(即假设条件 独立),我们可以把计算P1的公式简化为如下公式:
P1 = P(x1 | w1)P(x2 | w1)P(x3 | w1)P(x4 | w1)p(x5 | w1)p(w1)
这样计算起来就简单很多了,以前面的是否购买为例,如果我们要计算
P1 = P(年龄=大于30 , 性别=男,购买=是),
我们可以直接计算:
P1 = P(年龄=大于30 | 购买=是)P(性别=男 | 购买=是)P(购买=是)
而其中,如果我们要计算P(年龄=大于30 | 购买=是), 即是 用 已有数据中 购买=是且年龄 大于30的用户数 / 购买=是的用户数 ,来近似的得到。
将我们上面说的计算方法用公式系统的表达出来,就是如下:
按照书上给的标准公式即左边这种,y表示第几个样本,I(·)是一个指示函数,里面条件 满足取1,不满足取0。为了方便理解,在右边用本次的例子给出了解释。
按照我们上面说的计算方法你会发现一些问题,比如说如果新的样本是(年龄=小于30), 让你对他分类,然后你在计算P(年龄=小于30 | 购买 = 否)的时候,发现已有数据里面就没有 年龄小于30又购买=否的数据,那怎么办,这样 计算出来的概率值就是0了,明显不合理。 所以做出的第一种改进就是在上面两个公式的分子处都加个1作为平滑项,以防止分子为0. 改进后公式如下:
但是这第一种改进又会带来一些问题,以第一个公式为例。若当前已有数据就两条,全部属于类别w1,那么在计算P(w1)时你就会发现,2 +1 / 2 ,最终概率值居然会大于1了。为了解决这一问题,那么就应该在分母处额外加一个值,避免这种情况的出现,而这个值最好能有着自适应的特性,不用我们每一次都去指定分母应该加多少才能避免上述情况。所以综合考量下来,决定在第一个公式的分母处添加一个总的类别数, 在第二个公式的分母处添加一个该特征的可能取值数.
改进后公式如下:
这便是我们最终的计算公式。上述两种改进所添加的额外项就是拉普拉斯平滑项。
手敲了以下虚拟数据,共三个特征,“是否购买”作为最终的类别标签
以第一个样本为例,虽然我们已经知道了他的真实标签是1.但是我们仍然可以按照朴素贝叶斯的方法,过一遍流程,你就当我们在对一个特征与第一个样本完全一样的不知道类别标签的新样本进行预测.
所以我们当前的目标就是计算两个概率值:
P1 = P(年龄=大于30,性别=男,收入=高,是否购买=1)
P2 = P(年龄=大于30,性别=男,收入=高,是否购买=0)
计算这两个值并比较大小,取概率值较大的那一个进行分类.
首先,按照之前的简化公式,我们先计算每个类别标签的概率:
P(是否购买=1) = (4+1)/(6+2)=0.825 ; P(是否购买=0) = (2+1)/(6+2)=0.175
再计算每个特征下的条件概率:
P(年龄=大于30 | 是够购买=1) = (4+1)/(4+2)=0.833
P(年龄=大于30 | 是够购买=0) = (0+1)/(2+2)=0.25
P(性别=男 | 是否购买=1) = (2+1)/(4+2) = 0.5
P(性别=男 | 是否购买=0) = (1+1)/(2+2) = 0.5
P(收入=高 | 是否购买=1) = (2+1)/(4+2) = 0.5
P(收入=高 | 是否购买=0) = (1+1)/(2+2) = 0.5
于是,最终的P1 = P(年龄=大于30 | 是够购买=1) P(性别=男 | 是否购买=1)P(收入=高 | 是否购买=1)P(是否购买=1)=0.833*0.5*0.5*0.825=0.172
P2 = P(年龄=大于30 | 是够购买=0) P(性别=男 | 是否购买=0)P(收入=高 | 是否购买=0)P(是否购买=0)=0.25*0.5*0.5*0.175=0.011
P1 > P2,最终我们把该样本的类别标签分到 "购买=1" 中
我自己写的代码,设计的算法不是很好,对于每一个样本都会去循环遍历已有数据集,重新计算条件概率,效率太低了。按照我的设想,更合理的方法应该是先计算出每一种条件概率并存放好,这样对新样本进行预测的时候直接查找对应的条件概率就可以了。但是我懒哈哈哈直接套用了3个if(切忌不可学我,直接用在实际场景中效率太低了),感兴趣的小伙伴可以自己搜一搜别的大神怎么实现的。
这里放出来主要是觉得我写得很简单,感兴趣的可以读一遍更深刻的理解一下计算流程。
- import pandas as pd
- import numpy as np
- from sklearn.metrics import roc_auc_score
-
-
- df = pd.DataFrame({'年龄':['大于30','小于30','大于30','小于30','大于30','大于30'],
- '性别':['男','女','男','男','女','女'],
- '收入':['高','高','低','低','高','低'],
- '是否购买':[1,0,1,0,1,1]})
- label = '是否购买'
- def Naive_Bayes(data_set:pd.DataFrame, predict:pd.DataFrame, label:str):
- '''
- 实现朴素贝叶斯,predict中的label列会被替换为预测的结果
- '''
- assert set(predict.columns).issubset(data_set.columns), 'predict中有不属于数据集的列'
-
- features = list(predict.columns)
- # 计算带有拉普拉斯平滑的先验概率
- prior_probability = (data_set[label].value_counts()+1) / (len(data_set) + len(data_set[label].unique()))
- amount_cate = len(prior_probability)
- # 存放每个样本在每个labebl类别下的概率
- result = prior_probability.to_frame().T
- result = result.drop(result.index)
- for num in range(predict.shape[0]): # 遍历每一个样本,计算
- sample = predict.iloc[num,:].loc[lambda x: x.index.isin(features)]
- prob = prior_probability.values.copy() # 存该样本在每一个label类别下的概率
- for label_index, label_value in enumerate(prior_probability.index): # 遍历每一个label类别
- now = data_set.loc[data_set[label] == label_value].copy()
- length = now.shape[0] + amount_cate
- for feature in features:# 遍历每一个feature
- feature_count = now.loc[now[feature] == sample[feature]].shape[0]
- ratio = (feature_count + 1) / length
- prob[label_index] *= ratio
- result.loc[result.shape[0]] = prob
- predict.loc[num, label] = prior_probability.index[np.argmax(prob)]
- return result,predict
-
- result,predict = Naive_Bayes(df, df.drop(label, axis=1), label)
- print('AUC:', roc_auc_score(df[label], predict[label]))
最终auc是1,分类全部正确,啊意料之中啊毕竟才六条数据,感兴趣的小伙伴可以生成大一点的数据集测试下性能及准确度。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。