联合概率表示两个事件共同发生的概率。A与B的联合概率表示为P(A∩B)或者P(A,B)。
条件概率(又称后验概率):事件A在另外一个事件B已经发生条件下的发生概率。条件概率表示为P(A|B),读作“在B条件下A的概率”。
贝叶斯定理便是基于下述贝叶斯公式:
P(A|B)=P(B|A)P(A)/P(B)
上述公式的推导其实非常简单,就是从条件概率推出。
根据条件概率的定义,在事件B发生的条件下事件A发生的概率是
P(A|B)=P(A∩B)/P(B)
同样地,在事件A发生的条件下事件B发生的概率
P(B|A)=P(A∩B)/P(A)
整理与合并上述两个方程式,便可以得到:
P(A|B)P(B)=P(A∩B)=P(B|A)P(A)
接着,上式两边同除以P(B),若P(B)是非零的,我们便可以得到贝叶斯定理的公式表达式:
P(A|B)=P(B|A)*P(A)/P(B)
假定A事件表示得病,那么P(A)为0.001。这就是"先验概率",即没有做试验之前,我们预计的发病率。
再假定B事件表示阳性,那么要计算的就是P(A|B)。这就是"后验概率",即做了试验以后,对发病率的估计。
根据条件概率公式,
用全概率公式改写分母,
例子一:
在确实感染HIV的人群中,血液检查诊断阳性的概率P(检验阳性|患病)=81%,在未患病的人群中,错误诊断为阳性的概率P(检验阳性|未患病)=26%。根据全国范围的流行病学调查,HIV的感染率为5%[P(患病)=5%,P(未患病=95%)]。
所以,根据贝叶斯定理,在血液检查诊断阳性的人群中,确实感染HIV的概率:
这就是贝叶斯定理在诊断试验中的应用场景。因为小王是否感染HIV这个结论不能仅仅基于血液检查结果(P(患病|检验阳性)≠ P(检验阳性|患病)),而应该结合疾病的发病率(先验概率),获得一个综合诊断。
例子二:
村子里有三个小偷,事件B={村子失窃},已知小偷们的偷窃成功率依次是,除夕夜去偷的概率依次是
全概率公式:
求:村庄除夕夜失窃的概率
贝叶斯公式:
求:在村子失窃的条件下,偷窃者是某个小偷的概率
例子二:
有一个训练集包含100个人,特征1是皮肤颜色(黑、黄)、特征2是个人资产情况(富、穷),标记是地区(非洲、亚洲)。在训练集中有60个非洲人(黑穷*47, 黑富*1, 黄穷*11, 黄富*1),有40个亚洲人(黑穷*1, 黄穷*32, 黄富*7)。请分别训练一个朴素贝叶斯模型。(作者这里不存在任何人种歧视的观点)
先计算先验概率:
再计算每一个特征的条件概率:
到此,已经完成了朴素贝叶斯模型所有参数的计算,模型学习完毕。
假设新来了一个人,他的特征是【[黑,穷],地区=?】,请用朴素贝叶斯模型预测一下他的地区。
根据计算结果,模型会将这个人的地区预测为非洲。
二、Python实现基于朴素贝叶斯的垃圾邮件分类
https://blog.csdn.net/shijing_0214/article/details/51200965
听说朴素贝叶斯在垃圾邮件分类的应用中效果很好,在400封邮件(正常邮件与垃圾邮件各一半)的测试集中测试结果为分类准确率95.15%,在仅仅统计词频计算概率的情况下,分类结果还是相当不错的。
实现代码及数据集下载 https://github.com/shijing888/BayesSpam
3、实现步骤
具体实现的源码已经给出,这里简单说下思路,就是一个分词并记录词频的过程:
(1)对训练集用结巴分词,并用停用表进行简单过滤,然后使用正则表达式过滤掉邮件中的非中文字符;
(2)分别保存正常邮件与垃圾邮件中出现的词有多少邮件出现该词,得到两个词典。例如词”疯狂”在8000封正常邮件中出现了20次,在8000封垃圾邮件中出现了200次;
(3)对测试集中的每一封邮件做同样的处理,并计算得到P(s|w) 最高的100个词,在计算过程中,若该词只出现在垃圾邮件的词典中,
(4)对得到的每封邮件中重要的100个词 计算概率,若概率>>阈值α(一般设为0.9)α(一般设为0.9),则判为垃圾邮件,否则判为正常邮件。
1、朴素贝叶斯实现垃圾邮件分类的步骤
(1)收集数据:提供文本文件。
(2)准备数据:将文本文件解析成词条向量。
(3)分析数据:检查词条确保解析的正确性。
(4)训练算法:计算不同的独立特征的条件概率。
(5)测试算法:计算错误率。
(6)使用算法:构建一个完整的程序对一组文档进行分类。
介绍垃圾邮件分类器的设计与实现,分为一下几个步骤:
- 选择分类:
根据不同的结构风险,设定阈值,决定将文档归于哪一类。
在垃圾邮件分类的问题中,可以看到,如果将一封垃圾邮件错误的判定为非垃圾邮件,则没有什么损失,用户顶多看到邮箱里有一封垃圾邮件而已;相反,若把一封非垃圾邮件判定为垃圾邮件而直接扔进了垃圾箱,则用户遭受的损失将会很大,因为可能错过了很重要的信息。故而误判的风险是不一样的。
此处,假设垃圾邮件的类别为“+”,非垃圾邮件的类别为“-”。
我们首先分别计算 P(+ | document)和P(- | document).如果属于+类的概率较大,即更有可能属于垃圾邮件。这时,我们应该设定一个阈值 T,如果 P(+|document)> T * P(- | document)时,我们才将这封邮件当成垃圾邮件(如T = 3.0)。则在这种情况下,正常邮件被误判为垃圾邮件的概率将大大下降,整个算法的结构化风险将会更小。
- 特征提取:
将训练样本的正文切分为特征,如果是英文,直接按照空格切分,每个词可以作为一个特征;如果是中文,则需要借助分词器,如jieba分词器等。进行简单过滤,使用正则表达式过滤掉邮件中的非中文字符(特殊字符);
切分后,将词和所属类别建立一个字典存储。字典的结构是:
{word1:{class1:count1, class2:count2}, word2:{class1:count1, class2:count2}…}
- 改进特征提取的方法:
1)标题中的单词单独作为特征,添加标志,如前面加“title_”。
2)摘要中的单词单独作为特征,添加标志,如前面加“summary_”。
3)统计大写单词的数目,多于一定比例,则新增一个标志作为特征。
4)相邻单词组合为词组作为特征。
- 训练数据:
依次读取每条训练数据,按照上述方法将句子切分为单词,以切分好的单词作为特征,维护字典。
- 计算概率:
这也是贝叶斯分类器的核心内容,计算特征F在类别cat中出现的概率:
P(f | cat) = P(f, cat) / P(cat)
即为:词与该类别共同出现的文章数 / 该类别的文章总数
- 将经过训练的分类器持久化:
即:使用数据库保存和维护字典,而非在程序开始时再读取训练数据进行特征提取和训练。这样可以一次训练,多次使用。此处可以用自己喜欢的任何数据库,如SQLITE和Mysql等。
- 计算整篇文档的概率:
P(document | class1) = P(word1 | class1) * P(word2 | class2) * P(word3 | class3) ….
这个假设是基于朴素贝叶斯中个特征条件独立的假设,具体的关于朴素贝叶斯的只是可以参见《统计学习方法》相关章节。
下一步根据贝叶斯公式:
P(class1 | document) = P(document | class1) * P(class1) / P(document)
#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
Created on Oct 30, 2017
朴素贝叶斯
'''
from numpy import *
def loadDataSet():
postingList = [['my', 'like', 'has', 'flea', 'problems', 'help', 'please'],
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him']]
# ,
# ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']] # 每一行代表一篇文档
classVec = [0, 1, 0, 1, 0]#, 1] # 1代表侮辱性文字,0代表正常言论
return postingList, classVec # 返回数据集和类别标签
'''
使用set构造函数去除数据集中每篇文档的重复词语
'''
def createVocabList(dataSet):
vocabSet = set([]) # 创建一个空的集合
for document in dataSet: # 读取每一篇文档
vocabSet = vocabSet | set(document) # 两个集合的并操作
return list(vocabSet) # 返回不包含重复词语的列表,该列表包含所有词,并且以Ascll排序好
'''
在词汇表中,把一篇文档中出现的单词标记为1,其余标记为0
@param vocabList:词汇表
@param inputSet:一篇文档
'''
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) # 创建一个长度为len(vocabList)的零向量
for word in inputSet:
if word in vocabList: # 如果该词汇出现在词汇表中,则找到该词汇在词汇表中的位置,标记为1
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word) # 提示后,忽略该不存在的词语
return returnVec # 返回一个和词汇表等长的0, 1向量
'''
朴素贝叶斯分类器训练函数
@param trainMatrix:文档矩阵,每一行为一个和词汇表等长的0, 1向量,行数代表文档个数
@param trainCategory:每篇文档的类别标签向量
'''
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 文档数目
numWords = len(trainMatrix[0]) # 计算词汇表长度
pAbusive = sum(trainCategory) / float(numTrainDocs) # 计算属于侮辱性文档(class=1)的概率
p0Num = ones(numWords);
p1Num = ones(numWords) # 分子,所有词出现数初始化为1,防止在计算P(w0|1)P(w1|1)P(w2|1)某个概率为0乘积就为0的情况发生
p0Denom = 2.0;
p1Denom = 2.0 # 分母,初始化次数为2,代表每一类文档至少有两个词,总词数
for i in range(numTrainDocs):
if trainCategory[i] == 1: # 如果该篇文档是侮辱性文档
p1Num += trainMatrix[i] # 每篇文档每个单词的次数累加
p1Denom += sum(trainMatrix[i]) # 每篇文档总单词数累加
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num / p1Denom) # P(wi|c1),词汇表中属于侮辱性词汇的每个单词在总单词中出现的概率,
p0Vect = log(p0Num / p0Denom) # P(wi|c0),词汇表中属于非侮辱性词汇的每个单词的概率,用log防止多个小概率连乘后乘积变为0的情况
print(p1Num,'\n', p1Denom,'\n',p1Vect)
print(p0Num,'\n', p0Denom,'\n',p0Vect)
return p0Vect, p1Vect, pAbusive # 返回和词汇表等长的非侮辱性词汇的概率向量、和词汇表等长的侮辱性词汇的概率向量、侮辱性文档的概率
'''
print(p1Num,'\n', p1Denom,'\n',p1Vect)
打印结果:
[1. 1. 1. 1. 2. 1. 1. 1. 2. 1. 2. 2. 1. 1. 1. 2. 1. 2. 2. 1. 1. 2. 2. 1.1. 1. 1. 2. 3. 2.]
15.0
[-2.7080502 -2.7080502 -2.7080502 -2.7080502 -2.01490302 -2.7080502-2.7080502 -2.7080502 -2.01490302 -2.7080502 -2.01490302 -2.01490302
-2.7080502 -2.7080502 -2.7080502 -2.01490302 -2.7080502 -2.01490302 -2.01490302 -2.7080502 -2.7080502 -2.01490302 -2.01490302 -2.7080502
-2.7080502 -2.7080502 -2.7080502 -2.01490302 -1.60943791 -2.01490302]
'''
'''
朴素贝叶斯分类函数(对于一个待分类的文本,计算所有词汇概率之和属于哪一个类的概率最大,就确定为该类)
@param vec2Classify:待分类的和词汇表等长的0, 1向量
@param p0Vec:词汇表等长的非侮辱性词汇的概率向量
@param p1Vec:词汇表等长的侮辱性词汇的概率向量
@param pClass1:侮辱性词汇的文档概率
则通过该函数计算得 p1 < p0 , vec2Classify 更可能划分为非侮辱性文档
'''
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1) # 对应元素相乘,然后相加,得到P(w|c1),因为前面是log,故后面也是 +log
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) # 对应元素相乘,然后相加,得到P(w|c0),因为前面是log,故后面也是 +log
if p1 > p0:
return 1
else:
return 0
'''
使用朴素贝叶斯过滤网站的恶意留言
'''
def testingNB():
listOPosts, listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(array(trainMat), array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage', '33']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testingNB() # ['love', 'my', 'dalmation'] classified as: 0 ['stupid', 'garbage'] classified as: 1
'''
修改 setOfWords2Vec() 函数,变为词袋模型,每篇文档中的词汇可以出现多次
'''
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec # 返回一个和词汇表等长的向量,数值代表该词汇出现在该文档中的次数
'''
利用python的正则表达式模块re切分句子
@param bigString:文本字符串
'''
def textParse(bigString): # input is big string, #output is word list
import re
# listOfTokens = re.split(r'\W*', bigString) # 分隔符是除单词、数字外的任意字符串 (\W* = 分隔符不是单词、数字,\w* = 分隔符是单词、数字)
listOfTokens = re.split(r'\W+', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2] # 去除长度小于3的字符串(包括空字符串,如末尾有句号的就会产生空字符串)
'''
使用朴素贝叶斯过滤垃圾邮件
'''
def spamTest():
# docList 是文档集合(相当于前面的dataSet),fullText 是所有文档中的词汇,classList 是分类类别
docList = [];
fullText = [];
classList = []
# 导入并解析文本文件
for i in range(1, 26): # [1,26)
# print(open('email/spam/%d.txt' % i).read())
wordList = textParse(open('email/spam/%d.txt' % i).read()) # spam(垃圾)类邮件
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList = textParse(open('email/ham/%d.txt' % i).read()) # ham类邮件
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList) # 创建词汇表
trainingSet = list(range(50)) # 训练集,是一个整数列表,范围是[0, 50)。用list转化是因为python3中range会返回range对象,而不是列表
testSet = [] # 创建的一个大小为10的测试集
for i in range(10): # 从50个训练文档中随机选择10个文档作为测试集
randIndex = int(random.uniform(0, len(trainingSet))) # random.uniform 在[0,50) 内随机生成一个实数,然后将其转化为整数
testSet.append(trainingSet[randIndex])
del (trainingSet[randIndex]) # 支持list对象删除元素,而不支持range对象删除,故将range改成list
trainMat = [];
trainClasses = []
# 利用剩下的40篇文档训练分类器
for docIndex in trainingSet:
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
# 对10篇文档进行分类测试,统计出错概率
errorCount = 0
for docIndex in testSet:
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
# print(vocabList)
print("classList[docIndex]", classList[docIndex], "判断结果:", classifyNB(array(wordVector), p0V, p1V, pSpam))
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print("docIndex:", docIndex, "第几个文件::", docIndex / 2, "classification error", docList[docIndex])
print('\nthe error rate of spam email is: ', float(errorCount) / len(testSet))
print('\nthe error rate of spam email is: ', '{:.2f}%'.format(float(errorCount) / len(testSet) * 100))
# return vocabList,fullText
spamTest() # 测试10篇文档的出错率
'''
得到高频出现的前30个词汇,因为高频出现的30个词汇可能是无意义的助词等辅助性词汇,之后要把它们删除
@param vocabList:词汇表
@param fullText:所有文档中的词汇
'''
def calcMostFreq(vocabList, fullText):
import operator
freqDict = {}
for token in vocabList:
freqDict[token] = fullText.count(token) # 统计词汇表中的每一个单词在所有文档中出现的次数
sortedFreq = sorted(freqDict.items(), key=operator.itemgetter(1), reverse=True)
return sortedFreq[:30] # 返回一个包含频率最高的30个词汇的字典,key=词汇,value=该词汇的次数
'''
使用朴素贝叶斯从个人广告中获取区域倾向
@param feed1:属于第一类的RSS文件
@param feed0:属于第二类的RSS文件
'''
def localWords(feed1, feed0):
import feedparser
docList = [];
classList = [];
fullText = []
minLen = min(len(feed1['entries']), len(feed0['entries'])) # 取两个RSS源(xml文件数量)中的最小值
for i in range(minLen):
wordList = textParse(feed1['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(1) # NY是类别1
wordList = textParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList)
# 移除频率最高的30个词汇,可以删除以下3行代码,观察错误率和输出的频率最高的词汇
top30Words = calcMostFreq(vocabList, fullText)
for pairW in top30Words:
if pairW[0] in vocabList: vocabList.remove(pairW[0]) # pairW[0] 是词汇,pairW[1] 是词汇出现次数
trainingSet = list(range(2 * minLen));
testSet = []
# 随机选取20个作为测试集
for i in range(20):
randIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del (trainingSet[randIndex])
trainMat = [];
trainClasses = []
for docIndex in trainingSet:
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print('\nthe error rate of RSS is: ', float(errorCount) / len(testSet))
return vocabList, p0V, p1V
if __name__ == '__main__':
testingNB() # ['love', 'my', 'dalmation'] classified as: 0 ['stupid', 'garbage'] classified as: 1
spamTest() # 测试10篇文档的出错率
三、TF-IDF及其算法
1、TF-IDF算法介绍
TF-IDF(term frequency–inverse document frequency,词频-逆向文件频率)是一种用于信息检索(information retrieval)与文本挖掘(text mining)的常用加权技术。
TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
TF-IDF的主要思想是:如果某个单词在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
(1)TF是词频(Term Frequency)
词频(TF)表示词条(关键字)在文本中出现的频率。
这个数字通常会被归一化(一般是词频除以文章总词数), 以防止它偏向长的文件。
(2) IDF是逆向文件频率(Inverse Document Frequency)
逆向文件频率 (IDF) :某一特定词语的IDF,可以由总文件数目除以包含该词语的文件的数目,再将得到的商取对数得到。
如果包含词条t的文档越少, IDF越大,则说明词条具有很好的类别区分能力。
(3)TF-IDF实际上是:TF * IDF
某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语。
注: TF-IDF算法非常容易理解,并且很容易实现,但是其简单结构并没有考虑词语的语义信息,无法处理一词多义与一义多词的情况。
2、TF-IDF应用
(1)搜索引擎;(2)关键词提取;(3)文本相似性;(4)文本摘要
3、Python3实现TF-IDF算法
# -*- coding: utf-8 -*-
from collections import defaultdict
import math
import operator
"""
函数说明:创建数据样本
Returns:
dataset - 实验样本切分的词条
classVec - 类别标签向量
"""
def loadDataSet():
dataset = [['my', 'my', 'dog', 'has', 'flea', 'problems', 'help', 'stupid'], # 切分的词条8
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],# 8
['my', 'dalmation', 'is', 'so', 'cute', 'problems', 'love', 'him'], # 8
['i','stop', 'posting', 'stupid', 'worthless', 'garbage', 'study'], # 7
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop'], # 8
['quit', 'buying', 'worthless', 'dog', 'food', 'sun', 'sun']] # 7
classVec = [0, 1, 0, 1, 0, 1] # 类别标签向量,1代表好,0代表不好
return dataset, classVec
"""
函数说明:特征选择TF-IDF算法
Parameters: list_words:词列表
Returns: dict_feature_select:特征选择词字典
"""
def feature_select(list_words):
# 总词频统计
doc_frequency = defaultdict(int)
for word_list in list_words:
for i in word_list:
doc_frequency[i] += 1
print('doc_frequency: ' ,len(doc_frequency) ,doc_frequency)
# 计算每个词的TF值
word_tf = {} # 存储没个词的tf值
for i in doc_frequency:
word_tf[i] = doc_frequency[i] / sum(doc_frequency.values())
print('每个单词在总数出现的频率word_tf : ',len(word_tf),word_tf)
# 计算每个词的IDF值
doc_num = len(list_words) # 6
word_idf = {} # 存储每个词的idf值
word_doc = defaultdict(int) # 存储包含该词的文档数
for i in doc_frequency:
for j in list_words:
if i in j:
word_doc[i] += 1
for i in doc_frequency:
word_idf[i] = math.log(doc_num / (word_doc[i] + 1))
print('每个单词在总数出现的频率 word_idf :',len(word_idf),word_idf)
# 计算每个词的TF*IDF的值
word_tf_idf = {}
for i in doc_frequency:
word_tf_idf[i] = word_tf[i] * word_idf[i]
print('word_tf_idf=word_tf[i]* word_idf[i]',len(word_tf_idf),word_tf_idf)
# 对字典按值由大到小排序
dict_feature_select = sorted(word_tf_idf.items(), key=operator.itemgetter(1), reverse=True)
print('对字典按值由大到小排序dict_feature' ,dict_feature_select)
return dict_feature_select
if __name__ == '__main__':
data_list, label_list = loadDataSet() # 加载数据
print(data_list,'\n',label_list)
features = feature_select(data_list) # 所有词的TF-IDF值
print(features)
print(len(features))