基于概率论的分类方法:朴素贝叶斯
经典举例:已知学校总人数N,男生占3/5,都穿长裤,女生占2/5,一半穿长裤,一般穿裙子。
(1).穿长裤的人数:N*3/5 + N*2/5*1/2
(2).当你看到一个人穿长裤,不知性别时,问该人是女生的概率?
P(女生|长裤) = 女生穿长裤的人数/穿长裤总人数 = P(女)*P(长裤|女)/P(长裤)
经典贝叶斯公式:
下面通过对文本进行分类来理解贝叶斯模型:
1、准备数据
其中列表中的每一项来自斑点犬爱好者留言板,这些留言文本被切分成一系列的词条集合,标点符号从文本中去掉,留言的文本被分为两类,分为侮辱性和非侮辱性。
def loadDataSet():
dataSet = [['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']]
classLabel = [0,1,0,1,0,1] #1代表侮辱性,0代表正常言论
return dataSet,classLabel
2、创建一个在所有文档中出现的不重复的词表
def vocabularyTable(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)
3、构建词条向量
对于每一个文档,即每一条评论,生成一个文档向量。首先创建一个和词表长度一样长的向量,并将其元素都置为0,接着遍历文档中的所有单词,如果在词表中出现,则将文档向量中对应值设为1。
#vocabSet为词表,document为文档即评论
def doc2vec(vocabSet,document):
docVec = [0]*len(vocabSet)
for word in document:
if (word in vocabSet):
docVec[vocabSet.index(word)] = 1
return docVec
4.训练贝叶斯分类器模型
前面介绍了如何将一组单词转换为一组数字,接下来看看如何使用这些数字计算概率。现在已经知道一个词是否出现在一篇文档中,也知道该文档所属的类别。利用经典贝叶斯公式:
其中表示一个向量 ,即它由多个词组成,表示w属于i类的概率。
具体如何计算呢:首先通过类别i(侮辱性或非侮辱性留言)中文档数除以总的文档数来计算概率,接下来计算,这里就要用到朴素贝叶斯假设。如果将w展开为一个个独立特征,那么就可以将上述概率写成。这里假设所有词都相互独立,该假设也称作条件独立假设,它意味着可以用
**...来计算上述概率,这就极大简化了计算的过程。
#trainVec为文档矩阵,由每条文档的词条向量构成,classLabel为分类标签
import numpy as np
def trainBayes(trainVec,classLabel):
numData = len(trainVec)
numWords = len(trainVec[0])
pAbusive = sum(classLabel)/float(numData)
p0num = np.zeros(numWords); p1num = np.zeros(numWords)
p0sum = 0.0; p1sum = 0.0
for i in range(numData):
if(classLabel[i]==1):
p1num += trainVec[i]
p1sum += sum(trainVec[i])
else:
p0num += trainVec[i]
p0sum += sum(trainVec[i])
p1Vect = p1num/p1sum
p0Vect = p0num/p0sum
return pAbusive,p1Vect,p0Vect
trainVec = []
for document in dataSet:
trainVec.append(doc2vec(vocabSet,document))
pAbusive,p1Vect,p0Vect = trainBayes(trainVec,classLabel)
pAbusive
>>0.5
p1Vect
>>array([0.10526316, 0. , 0.05263158, 0. , 0.05263158,
0. , 0.05263158, 0.05263158, 0. , 0. ,
0.05263158, 0.10526316, 0.05263158, 0. , 0.05263158,
0. , 0. , 0.05263158, 0. , 0.15789474,
0.05263158, 0. , 0.05263158, 0. , 0.05263158,
0.05263158, 0. , 0. , 0. , 0. ,
0. , 0. ])
p0Vect
>>array([0. , 0.04166667, 0.08333333, 0.04166667, 0.04166667,
0.04166667, 0. , 0. , 0.04166667, 0.04166667,
0. , 0.04166667, 0. , 0.125 , 0.04166667,
0.04166667, 0.04166667, 0. , 0.04166667, 0. ,
0. , 0.04166667, 0. , 0.04166667, 0. ,
0. , 0.04166667, 0.04166667, 0.04166667, 0.04166667,
0.04166667, 0.04166667])
#我们可看到倒数第三个概率分别是0与0.04166667,词汇表的倒数第三个单词是cute,其在类别0中出现1次,在类别1中未出现,说明该计算是正确的
#我们找到所有概率中的最大值,该值出现在p1Vect的第20个位置,大小为0.15789474,词汇表的第20个位置单词是stupid,这意味着stupid是最能表征类别1(侮辱性文档类)的单词
5.根据现实情况修改分类器
利用贝叶斯分类器进行文档分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算***...。如果其中一个概率值为 0,那么最后的乘积也为0。为降低这种影响,可以将所有词的出现初始化为1,并将分母初始化为2。还有一个问题是由于太多小数相乘可能造成下溢出,这种解决办法是取自然对数。修改代码如下:
import numpy as np
import math
def trainBayes(trainVec,classLabel):
numData = len(trainVec)
numWords = len(trainVec[0])
pAbusive = sum(classLabel)/float(numData)
p0num = np.ones(numWords); p1num = np.ones(numWords)
p0sum = 2; p1sum = 2
for i in range(numData):
if(classLabel[i]==1):
p1num += trainVec[i]
p1sum += sum(trainVec[i])
else:
p0num += trainVec[i]
p0sum += sum(trainVec[i])
for i in range(numWords):
p1Vect[i] = math.log(p1num[i]/p1sum)
p0Vect[i] = math.log(p0num[i]/p0sum)
return pAbusive,p1Vect,p0Vect
6.朴素贝叶斯分类函数
def classify(docVec,pClass1,p1Vect,p0Vect):
p1 = sum(docVec * p1Vect)+math.log(pClass1)
p0 = sum(docVec *p0Vect) +math.log(1-pClass1)
if(p1>p0):
return 1
else:
return 0
#进行测试
test = ['love','my','dalmation']
testVec = doc2vec(vocabSet,test)
pClass1,p1Vect,p0Vect = trainBayes(trainVec,classLabel)
classify(testVec,pClass1,p1Vect,p0Vect)
>>0
test = ['stupid','garbage']
testVec = doc2vec(vocabSet,test)
pClass1,p1Vect,p0Vect = trainBayes(trainVec,classLabel)
classify(testVec,pClass1,p1Vect,p0Vect)
>>1
#对训练集进行测试
test = dataSet[0]
testVec = doc2vec(vocabSet,test)
pClass1,p1Vect,p0Vect = trainBayes(trainVec,classLabel)
classify(testVec,pClass1,p1Vect,p0Vect)
>>0
7.词袋模型
上面那个例子非常简单,但是它展示了朴素贝叶斯的工作原理。接下来,我们会对代码稍加修改,使分类器工作得更好。
目前为止,我们将每个词的出现与否作为一个特征,这可以被描述为词集模型(set-of-words model)。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法称为词袋模型(bag-of-words model)。在词袋中,每个文档即每个评论中每个单词可以出现多次,而在词集中每个词只能出现一次,为适应词袋模型,需要对doc2vec稍加修改。与词集模型的代码一模一样,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为1。
def bagOfWordsDoc2Vec(vocabSet,document):
docVec = [0]*len(vocabSet)
for word in document:
if (word in vocabSet):
docVec[vocabSet.index(word)] += 1
return docVec