一、朴素贝叶斯
朴素贝叶斯法(Naive Bayes)是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集,首先基于特征条件独立假设学习输入/输出的联合概率分布;然后基于此模型,对给定的输入 x ,利用贝叶斯定理求出后验概率最大的输出 y 。
1、朴素贝叶斯
输入空间:
输出空间:y={C1,C2,…,CK}。
训练集:T={(x1,y1),(x2,y2),…,(xN,yN)}。
对于每个实例,其P(X,Y)独立同分布。在进行分类之前,需要先将计算先验概率和条件概率然后据此计算出后验概率。
1)先验概率分布:
P(Y=Ck),k=1,2,..,K。
先验概率的极大似然估计:
2)条件概率分布:
设第j个特征可能取值的集合为:{aj1,aj2,..,asj}
则极大似然估计:
说明:每个实例有n个特征,分别为x1,x2,..,xn,每个特征分别有s1,s2,…,sn种取值,即特征xi有si种取值。则计算该条件概率分布的时间复杂度为:O(s1*s2*…*sn *K)。时间复杂度非常的高。
3)对新的实例进行分类:
为了计算将新的实例进行分类,我们需要计算该实例属于每类的后验概率,最终将此实例分给后验概率最大的类。
后验概率为:
在此需要用到条件独立的假设,即在分类确定的情况下,x的各特征相互独立。因为用到了此假设故而在贝叶斯前面加了朴素二字。于是有:
所以有:
由于对同一个实例,P(X=x)的概率相通同,故而只需考虑分子部分即可。
二、朴素贝叶斯算法分析
优点:
(1)朴素贝叶斯模型发源于古典数学理论,有稳定的分类效率。
(2)对小规模的数据表现很好,能个处理多分类任务,适合增量式训练,尤其是数据量超出内存时,我们可以一批批的去增量训练。
(3)对缺失数据不太敏感,算法也比较简单,常用于文本分类。
缺点:
(1)理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型给定输出类别的情况下,假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。
(2)需要知道先验概率,且先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型的原因导致预测效果不佳。
(3)由于我们是通过先验和数据来决定后验的概率从而决定分类,所以分类决策存在一定的错误率。
(4)对输入数据的表达形式很敏感。
三、贝叶斯过滤器的使用过程
现在我们收到一封新邮件,我们假定它是 正常邮件 和 垃圾邮件的概率各是50%,即:
P(正常)= P(垃圾)=50%
然后,对这封新邮件的内容进行解析,发现其中含有“发票”这个词,那么这封邮件属于垃圾邮件的概率提高到多少?其实就是计算一个条件概率,在有“发票”词语的条件下,邮件是垃圾邮件的概率:P(垃圾|发票)。直接计算肯定是无法计算了,这时要用到贝叶斯定理:
垃圾发票发票垃圾垃圾发票P(垃圾|发票)=P(发票|垃圾)⋅P(垃圾)P(发票)
根据全概率公式:
P(发票)=P(发票|垃圾)⋅P(垃圾)+P(发票|正常)⋅P(正常)
所以: 垃圾发票发票垃圾垃圾发票垃圾垃圾发票正常正常P(垃圾|发票)=P(发票|垃圾)⋅P(垃圾)P(发票|垃圾)⋅P(垃圾)+P(发票|正常)⋅P(正常)
其中,P(发票|垃圾) 表示所有垃圾邮件中出现“发票”的概率,我们假设100封垃圾邮件中有5封包含“发票”这个词,那么这个概率是5%。P(发票|正常) 表示所有正常邮件中出现“发票”的概率,我们假设1000封正常邮件中有1封包含“发票”这个词,那么这个概率是0.1%。于是:
P(垃圾|发票)=(5%×50%) / (5%×50% + 0.1%×50%)=98%
因此,这封新邮件是垃圾邮件的概率是98%。从贝叶斯思维的角度,这个“发票”推断能力很强,直接将垃圾邮件50%的概率提升到98%了。那么,我们是否就此能给出结论:这是封垃圾邮件?
四、代码实现:
import os
import re
import string
import math
DATA_DIR = 'enron'
target_names = ['ham', 'spam']
def get_data(DATA_DIR):
subfolders = ['enron%d' % i for i in range(1, 7)]
data = []
target = []
for subfolder in subfolders:
# spam
spam_files = os.listdir(os.path.join(DATA_DIR, subfolder, 'spam'))
for spam_file in spam_files:
with open(os.path.join(DATA_DIR, subfolder, 'spam', spam_file), encoding="latin-1") as f:
data.append(f.read())
target.append(1)
# ham
ham_files = os.listdir(os.path.join(DATA_DIR, subfolder, 'ham'))
for ham_file in ham_files:
with open(os.path.join(DATA_DIR, subfolder, 'ham', ham_file), encoding="latin-1") as f:
data.append(f.read())
target.append(0)
return data, target
X, y = get_data(DATA_DIR)
class SpamDetector_1(object):
def clean(self, s):
translator = str.maketrans("", "", string.punctuation)
return s.translate(translator)
def tokenize(self, text):
text = self.clean(text).lower()
return re.split("\W+", text)
def get_word_counts(self, words):
word_counts = {}
for word in words:
word_counts[word] = word_counts.get(word, 0.0) + 1.0
return word_counts
class SpamDetector_2(SpamDetector_1):
def fit(self, X, Y):
self.num_messages = {}
self.log_class_priors = {}
self.word_counts = {}
self.vocab = set()
self.num_messages['spam'] = sum(1 for label in Y if label == 1)
self.num_messages['ham'] = sum(1 for label in Y if label == 0)
self.log_class_priors['spam'] = math.log(
self.num_messages['spam'] / (self.num_messages['spam'] + self.num_messages['ham']))
self.log_class_priors['ham'] = math.log(
self.num_messages['ham'] / (self.num_messages['spam'] + self.num_messages['ham']))
self.word_counts['spam'] = {}
self.word_counts['ham'] = {}
for x, y in zip(X, Y):
c = 'spam' if y == 1 else 'ham'
counts = self.get_word_counts(self.tokenize(x))
for word, count in counts.items():
if word not in self.vocab:
self.vocab.add(word)
if word not in self.word_counts[c]:
self.word_counts[c][word] = 0.0
self.word_counts[c][word] += count
MNB = SpamDetector_2()
MNB.fit(X[100:], y[100:])
class SpamDetector(SpamDetector_2):
def predict(self, X):
result = []
flag_1 = 0
for x in X:
counts = self.get_word_counts(self.tokenize(x))
spam_score = 0
ham_score = 0
flag_2 = 0
for word, _ in counts.items():
if word not in self.vocab:
continue
else:
if word in self.word_counts['spam'].keys() and word in self.word_counts['ham'].keys():
log_w_given_spam = math.log(
(self.word_counts['spam'][word] + 1) / (
sum(self.word_counts['spam'].values()) + len(self.vocab)))
log_w_given_ham = math.log(
(self.word_counts['ham'][word] + 1) / (sum(self.word_counts['ham'].values()) + len(
self.vocab)))
if word in self.word_counts['spam'].keys() and word not in self.word_counts['ham'].keys():
log_w_given_spam = math.log(
(self.word_counts['spam'][word] + 1) / (
sum(self.word_counts['spam'].values()) + len(self.vocab)))
log_w_given_ham = math.log(1 / (sum(self.word_counts['ham'].values()) + len(
self.vocab)))
if word not in self.word_counts['spam'].keys() and word in self.word_counts['ham'].keys():
log_w_given_spam = math.log(1 / (sum(self.word_counts['spam'].values()) + len(self.vocab)))
log_w_given_ham = math.log(
(self.word_counts['ham'][word] + 1) / (sum(self.word_counts['ham'].values()) + len(
self.vocab)))
spam_score += log_w_given_spam
ham_score += log_w_given_ham
flag_2 += 1
spam_score += self.log_class_priors['spam']
ham_score += self.log_class_priors['ham']
if spam_score > ham_score:
result.append(1)
else:
result.append(0)
flag_1 += 1
return result
MNB = SpamDetector()
MNB.fit(X[100:], y[100:])
pred = MNB.predict(X[:100])
true = y[:100]
accuracy = 0
for i in range(100):
if pred[i] == true[i]:
accuracy += 1
print(accuracy)
运行结果图