朴素贝叶斯(naive bayes)
目录
- 朴素贝叶斯算法原理
- 文本分类练习
一、朴素贝叶斯——算法原理
优点:在数据较少的情况下仍然有效,可以处理多分类问题
缺点:对于输入数据的准备方式较为敏感
适用数据类型:标称型数据
首先,我们来讲一下贝叶斯决策理论的核心思想
场景:假设有一个数据集,它由两类数据组成,数据分类如下:
p1(x,y)表示数据点(x,y)属于红色一类的概率
p2(x,y)表示数据点(x,y)属于蓝色一类的概率
那么对于一个新数据点(x,y),可以用下面规则来判断其类别:
如果p1(x,y) > p2(x,y),则(x,y)为红色一类。
如果p1(x,y) < p2(x,y), 则(x,y)为蓝色一类。
所以,其核心思想就是选择具有最高概率的决策。
抛出问题,那么p1和p2怎么计算呀?
那就得说说条件概率啦~
条件概率公式:
P
(
B
∣
A
)
=
P
(
A
B
)
P
(
A
)
P(B | A) = \frac{P(AB)}{P(A)}
P(B∣A)=P(A)P(AB)
定义:设事件A和事件B,且
P
(
A
)
P(A)
P(A)大于0,在已知事件A发生的条件下,事件B发生的概率,叫做条件概率,记
P
(
B
∣
A
)
P(B | A)
P(B∣A)
小学生数学题:
罐子里有7块石头,3个灰色,4个黑色,你抓一块是黑色的概率是多少?抓一块是灰色的概率呢?
答: 4 7 \frac{4}{7} 74 和 3 7 \frac{3}{7} 73
往下看:
于是有:
P(灰色 | B桶)= P(灰色且B桶)/ P(B桶)
1 7 \frac{1}{7} 71/ 3 7 \frac{3}{7} 73 = 1 3 \frac{1}{3} 31
如果已知 P(c | x),求 P(x | c)?
这个公式就不要死记硬背了,推一下就出来了~
P ( c ∣ x ) = P ( c x ) P ( x ) P(c | x) = \frac{P(cx)}{P(x)} P(c∣x)=P(x)P(cx)
P ( x ∣ c ) = P ( c x ) P ( c ) P(x | c) = \frac{P(cx)}{P(c)} P(x∣c)=P(c)P(cx)
P ( c x ) P ( x ) ∗ P ( x ) P ( c ) \frac{P(cx)}{P(x)} * \frac{P(x)}{P(c)} P(x)P(cx)∗P(c)P(x) = P ( c ∣ x ) ∗ P ( x ) P ( c ) P(c | x) * \frac{P(x)}{P(c)} P(c∣x)∗P(c)P(x) = P ( x ∣ c ) P(x | c) P(x∣c)
二、文本分类练习
场景:
在线社区有很多留言,留言中包括:侮辱性与非侮辱性两种,我们需要过滤侮辱性留言。
实验思路:
- 首先,将文本转化为数字向量
- 其次,基于这些向量来计算条件概率,并构建分类器
- 最后,介绍一些利用python实现朴素贝叶斯过程需要考虑的问题
1、从文本中构建词向量
#创建实验样本
def loadDataSet():
postingList=[['my', 'dog', '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
#创建一个列表,包含文档中出现的所有词,且不重复
#(不会出现重复的单词)
def createVocabList(dataSet):
vocabSet = set([]) # 创建一个空集合
for document in dataSet:
vocabSet = vocabSet | set(document) # 创建两个集合的并集
# print(vocabSet)
return list(vocabSet)
#inputSet在vocabList中是否出现
#出现为1,反之为0
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList) # 和词汇表等长的0向量
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
listOPosts, listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
print(myVocabList)
print(listOPosts[0])
myVec = setOfWords2Vec(myVocabList, listOPosts[0])
print(myVec)
>>>
['I', 'dog', 'cute', 'help', 'garbage', 'please', 'stop', 'dalmation', 'worthless', 'take', 'maybe', 'mr', 'problems', 'stupid', 'love', 'licks', 'not', 'food', 'my', 'is', 'posting', 'ate', 'flea', 'steak', 'how', 'quit', 'so', 'him', 'park', 'has', 'to', 'buying']
['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']
[0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0]
2、从词向量计算概率
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 文档数目,这里是6篇
numWords = len(trainMatrix[0]) # 唯一词表的长度
print(sum(trainCategory)) # 求和,[0, 1, 0, 1, 0, 1],和是3
pAbusive = sum(trainCategory)/float(numTrainDocs) # 6篇里有3篇是侮辱性的,概率为0.5
p0Num = zeros(numWords) # 全为0的矩阵
p1Num = zeros(numWords) # 全为0的矩阵
p0Denom = 0.0
p1Denom = 0.0 # 初始化工作
for i in range(numTrainDocs): # 对每一篇
if trainCategory[i] == 1: # 该篇如果是侮辱性的
p1Num += trainMatrix[i] # 因为是二分类所以与0矩阵求和
p1Denom += sum(trainMatrix[i]) # 矩阵内各元素求和
else: # 非侮辱性
p0Num += trainMatrix[i] # 因为是二分类所以与0矩阵求和
p0Denom += sum(trainMatrix[i]) # 矩阵内各元素求和
p1Vect = p1Num/p1Denom # 对矩阵各元素做除法
p0Vect = p0Num/p0Denom
return p0Vect, p1Vect, pAbusive
listOPosts, listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, listClasses)
print(p0V) # 给定文档类别条件下(非侮辱),词汇表中单词出现概率
print(p1V) # 给定文档类别条件下(侮辱),词汇表中单词出现概率
print(pAb) # 文档属于侮辱类的概率
要计算多个概率的乘积以获得文档属于某个类别的概率,即计算 p(w0|1) * p(w1|1) * p(w2|1)
如果其中一个概率值为 0,那么最后的乘积也为 0。为降低这种影响,可以将所有词的出现数初始化为 1,并将分母初始化为 2 (取1 或 2 的目的主要是为了保证分子和分母不为0,大家可以根据业务需求进行更改)
另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。当计算乘积 p(w0|ci) * p(w1|ci) * p(w2|ci)… p(wn|ci) 时,由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。(用 Python 尝试相乘许多很小的数,最后四舍五入后会得到 0)。
一种解决办法是对乘积取自然对数。在代数中有 ln(a * b) = ln(a) + ln(b), 于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。
代码修改:
p0Num = zeros(numWords) # 全为0的矩阵
p1Num = zeros(numWords) # 全为0的矩阵
p0Denom = 0.0
p1Denom = 0.0
p1Vect = p1Num/p1Denom # 对矩阵各元素做除法
p0Vect = p0Num/p0Denom
#改为:
p0Num = ones(numWords) # 全为1的矩阵
p1Num = ones(numWords) # 全为1的矩阵
p0Denom = 2.0
p1Denom = 2.0
p1Vect = log(p1Num/p1Denom) # 对矩阵各元素做除法
p0Vect = log(p0Num/p0Denom)
构造分类器:
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
print(sum(vec2Classify * p1Vec))
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
# log()是因为我们之前得出的概率取了对数,所以这里变成了相加
# 由于是取对数,所以乘积变为直接求和即可,注意这里list和list是无法相乘,vec2Classify需要为array格式
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
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(thisDoc)
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
这个blog写的也不错:
https://blog.csdn.net/c369624808/article/details/78906630
词袋模型:
之前,我们只记录单词有没有出现,但是出现的次数并没考虑进去
而词袋模型则是每遇到一个单词时,它会增加词向量中的对应值,而不是将对应的数值设为1。
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec