900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 《机器学习实战》程序清单4-2 朴素贝叶斯分类器训练函数

《机器学习实战》程序清单4-2 朴素贝叶斯分类器训练函数

时间:2023-04-10 12:10:37

相关推荐

《机器学习实战》程序清单4-2 朴素贝叶斯分类器训练函数

此文旨在把trainNB0这个函数详细讲清楚。

下面所做的工作都是为了求下面这个贝叶斯概率,也叫条件概率:

为了计算方便,书中的操作实际上是把这个式子转化为了下式:

概率P(ci)就是通过类别i(侮辱性留言或非侮辱性留言)中文档数除以总的文档数来得到的,也就是最后得到的计算结果0.5。

这里有一个重要的转化,因为w是一个词条向量,它可以展开为[w0, w1, w2,.......wn]。因为我们此例用到的是朴素贝叶斯假设,所以所有词条都互相独立,

此假设也称为条件独立性假设。那么就意味着我们可以做这样的变换:

p(w|ci) == p(w0,w1,w2,......w2|ci) == p(w0|ci)p(w1|ci)p(w2|ci).......p(wn|ci)

然后这部分就可以转化为p(w0,w1,w2,......w2|ci)p(ci)/p(w),进一步转化为:p(w0|ci)p(w1|ci)p(w2|ci).......p(wn|ci)/p(w)

这个转化,是本例能够成立的一个必要条件。

deftrainNB0(trainMatrix,trainCategory):numTrainDocs=len(trainMatrix)numWords=len(trainMatrix[0])pAbusive=sum(trainCategory)/float(numTrainDocs)#(以下两行)初始化概率p0Num=zeros(numWords);p1Num=zeros(numWords)p0Denom=0.0;p1Denom=0.0foriinrange(numTrainDocs):iftrainCategory[i]==1:#(以下两行)向量相加p1Num+=trainMatrix[i]p1Denom+=sum(trainMatrix[i])else:p0Num+=trainMatrix[i]p0Denom+=sum(trainMatrix[i])p1Vect=p1Num/p1Denom#changetolog()#对每个元素做除法p0Vect=p0Num/p0Denom#changetolog()returnp0Vect,p1Vect,pAbusive

下面把这个函数逐步分解:

1.参数

此函数的参数有两个,一个是trainMatrix,另一个是trainCategory,这两个参数是一步一步的数据处理产生的结果,本节的目的是说明这两个参数值的产生过程。详细如下:

1.1第一步 创建实验样本

可能是为了简化操作,突出重点,作者在这里手工创建了数据集,手工设置了类别,在实际的应用场景中,应当是自动判断自动生成的。

listOPosts,listClasses=bayes.loadDataSet()

这一句产生了listOPosts和listClasses

详细内容分别是:

listOPosts:

[['my','dog','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']

]

listClasses:

[0,1,0,1,0,1]

其中的listOPosts即list Of Posts,文档列表,就是帖子列表、邮件列表等等。你可以认为列表中的一元素就是一个帖子或者回复,

在此例中一共6个文档、帖子、回复(以后统称文档)。

分别是:

['my','dog','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']

可以看到,2、4、6句标红部分,存在侮辱性词条,第1、3、5个句子,不存在侮辱性词条,所以,对应的类别标签设置为

listClasses = [0,1,0,1,0,1]

1.2第二步 创建包含所有不重复词条的集合(词汇表)

这一步是为了产生一个大而全的集合,这个集合包括了所有文档(即第一步产生的6个文档)中的词条,但每个词条都不重复。

#创建一个所有文档中的不重复单词列表def createVocabList(dataSet):vocabSet = set([]) #创建一个空集n = 0for document in dataSet:vocabSet = vocabSet | set(document) #创建两个集合的并集n += 1# print('vocabSet:',n,vocabSet)# print('文档集合的总长度:',len(vocabSet))a = list(vocabSet) a.sort()

return a

Python中的集合(set)具有消除重复元素的功能。

书中代码没有排序。为了看得更清楚,我加上了排序。

上述代码中的

vocabSet=vocabSet|set(document)

并集操作,相当于 += 操作

此函数的参数dataSet,即是上一步产生的listOPosts

调用方式:

myVocablList = createVocabList(listOfPosts)

运行结果是:

['I', 'ate', 'buying', 'cute', 'dalmation', 'dog', 'flea', 'food', 'garbage', 'has', 'help', 'him', 'how', 'is', 'licks', 'love',

'maybe', 'mr', 'my', 'not', 'park', 'please', 'posting', 'problems', 'quit', 'so', 'steak', 'stop', 'stupid', 'take', 'to', 'worthless']

1.3第三步 文档向量

获得词汇表后,便可以使用函数setOfWords2Vec(),该函数的输入参数为词汇表及某个文档,输出的是文档向量,向量的每一元素为1或0,分别表示词汇表中的单词在输入文档中是否出现。

def setOfWords2Vec(vocabList, inputSet):returnVec = [0] * len(vocabList)

for word in inputSet:if word in vocabList:# print("word:",word)returnVec[vocabList.index(word)] = 1else:print("the word:%s is not in my Vocabulary!" % word)return returnVec # 返回一个list

vocabList即上一步产生的词汇表,inputSet可以是任意一篇文档,此处为了简化操作,在6篇文档中选取。

调用方式:

listOfPosts,listClasses = loadDataSet()print(listOfPosts)myVocablList = createVocabList(listOfPosts)print(myVocablList)l = listOfPosts[0]l.append("中华人民共和国")l.append("kk")print("listOfPosts:", listOfPosts[0])b= setOfWords2Vec(myVocablList, listOfPosts[0])print(b)

我们的输入是:

listOfPosts[0],它的值是:

['my', 'dog', 'dog', 'has', 'flea', 'problems', 'help', 'please']

从索引为0的元素开始循环,如果这个元素存在于词汇表中,则把要返回的类别向量returnVec中对应位置的值设为1。

此处第1个值是my,它存在于词汇表中,位置是18,所以把returnVec中的对应位置的值设置为1

得到:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

第2个值是dog,它存在于词汇表中,位置是5,把returnVec中的对应位置的值设置为1

得到:[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

以此类推,直到最后得到:

[0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]

至此,我们得到了一篇文档listOfPosts[0]的词向量

用同样的方式,我们还可以得到listOfPosts[1]、listOfPosts[2]、listOfPosts[3]、listOfPosts[4]、listOfPosts[5]文档的词向量,分别是:

[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0]

[1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]

[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1]

[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0]

[0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1]

1.4第四步

至此,我们可以说明trainNB0(trainMatrix, trainCategory)中的参数是什么了。

trainMatrix就是由各个文档转化成的词向量构成的矩阵,而trainCategory就是这几个文档的类别,也就是这几个文档是不是含有侮辱性词条。

trainMatrix的值为:

[

[0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],

[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0],

[1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],

[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1],

[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0],

[0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1]

]

trainCategory的值为:

[0, 1, 0, 1, 0, 1]

2.过程

numTrainDocs=len(trainMatrix)

这句是取得词向量矩阵的长度,也就是说文档的数量,此例中是6个。

numWords=len(trainMatrix[0])

取得词向量矩阵中第一条记录的长度,也就是词条(即特征)的数量,此例应当是32个。

pAbusive=sum(trainCategory)/float(numTrainDocs)

p表示概率,abusive的意思是辱骂的、滥用的,pAbusive表示辱骂文档的概率。这个值即是第一节的公式中所需要的P(Ci),是通过类别i(侮辱性留言或非侮辱性留言)中文档数除以总的文档数来计算的。

sum(trainCategory) ==> sum([0, 1, 0, 1, 0, 1]) ==> 3

此处用==>符号表示“推出”、“等于”

numTrainDocs==6

所以

pAbusive=sum(trainCategory)/float(numTrainDocs)==>

pAbusive == 3/6 ==>

pAbusive == 0.5

也就是说,6篇文档,其中有3篇含有侮辱性词条,概率是0.5,即P(C1)==0.5。

需要求的3个值,已经求出了一个,还需要P(w|Ci)和P(w)两个值。

p0Num=zeros(numWords)

p1Num=zeros(numWords)

上面这两句是要初始化一个概率,是什么概率?

p0Denom=0.0;p1Denom=0.0

上式中的Denom是分母的意思,把分母项置为0,这是要干什么?

foriinrange(numTrainDocs):iftrainCategory[i]==1:#❷(以下两行)向量相加p1Num+=trainMatrix[i]p1Denom+=sum(trainMatrix[i])else:p0Num+=trainMatrix[i]p0Denom+=sum(trainMatrix[i])

按照文档个数,从0到5循环。

如果文档类别是侮辱性(trainCategory[i]==1),则把侮辱性文档的词向量相叠加,否则把非侮辱性文档的词向量相叠加。这样说有点拗口,看看下面的实际执行过程:

由前面的计算结果可知,trainCategory的值是[0, 1, 0, 1, 0, 1],放在这里看着方便。

i==0时:

trainCategory[0]是0,

所以p0Num+=trainMatrix[0],

而trainMatrix[0]是[0, 0, 0, 0, 0, 1[5], 1[6], 0, 0, 1[9], 1[10], 0, 0, 0, 0, 0, 0, 0, 1[18], 0, 0, 1[21], 0, 1[22], 0, 0, 0, 0, 0, 0, 0, 0]

为了方便比较,我在列表中增加了中括号括起来的索引值

同时,p0Denom += sum(trainMatrix[0]),trainMatrix[0]中有7个1,所以此时p0Denom的值是7

i==1时:

trainCategory[1]是1,

所以p1Num+=trainMatrix[1],

而trainMatrix[1]是[0, 0, 0, 0, 0, 1[5], 0, 0, 0, 0, 0, 1[11], 0, 0, 0, 0, 1[16], 0, 0, 1[19], 1[20], 0, 0, 0, 0, 0, 0, 0, 1[28], 1[29], 1[30], 0]

同时,p1Denom += sum(trainMatrix[1]),trainMatrix[1]中有8个1,所以此时p1Denom的值是8

i==2时:

trainCategory[2]是0,所以p0Num+=trainMatrix[2]

而trainMatrix[2]是[1[0], 0, 0, 1[3], 1[4], 0, 0, 0, 0, 0, 0, 1[5], 0, 1[7], 0, 1[9], 0, 0, 1[12], 0, 0, 0, 0, 0, 0, 1[19], 0, 0, 0, 0, 0, 0]

叠加之后,p0Num的值为:[ 1. 0. 0. 1. 1. 1. 1. 0. 0. 1. 1. 1. 0. 1. 0. 1. 0. 0.2. 0. 0. 1. 0. 1. 0. 1. 0. 0. 0. 0. 0. 0.]

可以看到,是列表中的每个位置对应的值相加。

同时,p0Denom += sum(trainMatrix[2]),trainMatrix[2]中有8个1,所以此时p0Denom的值是7+8=15

以此类推,最后的结果是:

p0Num == [ 1. 1. 0. 1. 1. 1. 1. 0. 0. 1. 1. 2. 1. 1. 1. 1. 0. 1.3. 0. 0. 1. 0. 1. 0. 1. 1. 1. 0. 0. 1. 0.]

p1Num == [ 0. 0. 1. 0. 0. 2. 0. 1. 1. 0. 0. 1. 0. 0. 0. 0. 1. 0.0. 1. 1. 0. 1. 0. 1. 0. 0. 1. 3. 1. 1. 2.]

p0Denom==24

p1Denom==19

插播一句,发现了一个翻译错误:英文版第70页,原文是Thenumeratoris a NumPy array with the same number of elements as you have words in your vocabulary.中文版第61页,译文是“上述程序中的分母变量是一个元素个数等于词汇表大小的NumPy数组。”

应改为:“上述程序中的分子变量是一个元素个数等于词汇表大小的NumPy数组。”

运行结果如下:

对于这个结果,我曾经对作者的说明感到困惑不解。下面列出我经过逐步了解后的解释:

首先,我们发现文档属于侮辱类的概率pAb为0.5,该值是正确的。

接下来,看一看在给定文档类别条件下词汇表中单词的出现概率,看看是否正确。

词汇表中的第一个词是cute,其在类别0中出现1次,而在类别1中从未出现。对应的条件概率分别为0.041 666 67与0.0。该计算是正确的。

我们找找所有概率中的最大值,该值出现在P(1)数组第26个下标位置,大小为0.15789474。在myVocabList的第26个下标位置上可以查到该单词是stupid。

这意味着stupid是最能表征类别1(侮辱性文档类)的单词。

第一句说,“我们发现文档属于侮辱类的概率pAb为0.5,该值是正确的。”,0.5这个数值的来源是清楚的,但此处作者做了一个诊断,说该值是正确的,是什么意思?一直没太明白。

可能一:有3个非侮辱,3个侮辱,所以概率是0.5,正确的。

可能二:经过计算,和我们肉眼可见的3/6符合,所以结果是正确的。如果是这样,那这是一句废话,本来就是按照这个算法计算的,何必要强调一下。

还有其它可能吗?待定,也许将来更加深入以后会知道。

第二句,接下来,看一看在给定文档类别条件下词汇表中单词的出现概率,看看是否正确。这里指的是P(w|ci)

第三句,最大值是0.15789474,对应的词条是stupid,它在类别为1的类别中出现了3次,所以它是最能表往类别1的词条。此处存疑。

如果把3个stupid分别改成stupid、fuck、shit,那么它就会和其它只出现1一次的词条一样,值变为0.05263158。

这个时候,谁是更能突出表征类别1的词条?

这一节只是把词条的出现概率计算完毕,没有完成整个算式。

4.5.3 测试算法:根据现实情况修改分类器

前面所提到的概率公式转化结果p(w0|ci)p(w1|ci)p(w2|ci).......p(wn|ci)/p(w)的意义是,列表内部的概率相乘,得到的积除以p(w)。

如果有概率为0,那么乘积就是0,凭经验也可以知道这是不合理的,对于这个问题,书中给出了一个方法,将所有词的出现数初始化为1,并将分母初始化为2。

书中只给出了方法,并没有解释为什么这么做。后经查询,这种方法叫拉普拉斯平滑。来源:/knownx/p/7860174.html

背景:为什么要做平滑处理?

零概率问题,就是在计算实例的概率时,如果某个量x,在观察样本库(训练集)中没有出现过,会导致整个实例的概率结果是0。在文本分类的问题中,当一个词语没有在训练样本中出现,该词语调概率为0,使用连乘计算文本出现概率时也为0。这是不合理的,不能因为一个事件没有观察到就武断的认为该事件的概率是0。

拉普拉斯的理论支撑

为了解决零概率的问题,法国数学家拉普拉斯最早提出用加1的方法估计没有出现过的现象的概率,所以加法平滑也叫做拉普拉斯平滑。

假定训练样本很大时,每个分量x的计数加1造成的估计概率变化可以忽略不计,但可以方便有效的避免零概率问题。

它的背后的原理就是当数量特别庞大时,个体就没有那么重要。99%和100%在概率上来讲也没什么区别。

p(w0|ci)p(w1|ci)p(w2|ci).......p(wn|ci)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。