机器学习实战笔记(3)-朴素贝叶斯算法(代码实现)
书中第一个例子给的样本如下: 6句话,三句脏话,三句好话。
[['my', 'dog', 'has', 'flea', 'problems', 'help','please'] -- Good words
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park','stupid'] -- Shit words
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love','him'] -- Good words
['stop', 'posting', 'stupid', 'worthless', 'garbage'] --Shit words
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop','him'] -- Good words
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]-- Shit words
目前采用的算法是贝努力类型的朴素贝叶斯,就是对于文本分析,我们只考虑词是否出现,而不考虑出现的次数。
也就是所有的词都是一个权重!模型虽然简单,但是效果还是不错地。
首先是所有出现单词的列表:这里是只看出现不出现,不看频次。
['cute', 'love','help', 'garbage', 'quit', 'I', 'problems', 'is', 'park', 'stop', 'flea','dalmation', 'licks', 'food', 'not', 'him', 'buying', 'posting', 'has','worthless', 'ate', 'to', 'maybe', 'please', 'dog', 'how', 'stupid', 'so','take', 'mr', 'steak', 'my']
>>>
然后根据每个句子的单词,生成对应句子的向量,就是单词是否出现。
句子矩阵:
[0, 0, 1, 0, 0, 0,1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1] --Good words
[0, 0, 0, 0, 0, 0,0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0] -- Shitwords
[1, 1, 0, 0, 0, 1,0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1] --Good words
[0, 0, 0, 1, 0, 0,0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0] -- Shitwords
[0, 0, 0, 0, 0, 0,0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1] --Good words
[0, 0, 0, 0, 1, 0,0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0] -- Shitwords
P(Shitwords) = 3/6 = 50%
P(Goodwords) = 3/6 = 50%
如果我有句话My dog likes eating, 怎么算是脏话还是好话?
先过滤,不在样本里面的词被抛弃。
>>>bayes.setOfWords2Vec(myVocabList,['my','dog','likes','eating']
... )
the word: likes is not in my Vocabulary!
the word: eating is not in my Vocabulary!
[0, 0, 0, 0, 0, 0, 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, 1]
>>>
我们只需要计算:
p("my dog likes eating"/goodwords) =p(my/goodwords)*p(dog/goodwords)*p(my/goodwords)的结果。
下面就是用循环来计算的过程了:
我们对下面的矩阵做分析计算:
[0, 0, 1, 0, 0, 0,1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1] --Good words
[0, 0, 0, 0, 0, 0,0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0] -- Shitwords
[1, 1, 0, 0, 0, 1,0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1] --Good words
[0, 0, 0, 1, 0, 0,0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0] -- Shitwords
[0, 0, 0, 0, 0, 0,0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1] --Good words
[0, 0, 0, 0, 1, 0,0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0] -- Shitwords
listOPosts, listClasses = loadDataSet() : 数据载入
trainCategory = [0, 1, 0, 1, 0,1] :样本的分类
myVocabList =createVocabList(listOPosts)
trainMatrix = []
for postinDoc in listOPosts:
trainMatrix.append(setOfWords2Vec(myVocabList,postinDoc)) :生成前面所述的所有句子和单词出现否的向量表。
[0, 0, 1, 0, 0, 0,1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1] --Good words
[0, 0, 0, 0, 0, 0,0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0] -- Shitwords
[1, 1, 0, 0, 0, 1,0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1] --Good words
[0, 0, 0, 1, 0, 0,0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0] -- Shitwords
[0, 0, 0, 0, 0, 0,0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1] --Good words
[0, 0, 0, 0, 1, 0,0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0] -- Shitwords
numTrainDocs = len(trainMatrix)
print"numTrainDocs=",numTrainDocs
numWords = len(trainMatrix[0]) : 总的单词数,不考虑重复,只看是否出现。
print "words = ",numWords
pAbusive =sum(trainCategory)/float(numTrainDocs) :垃圾话的比例,其实就是数组求和,类型1都是垃圾话!
print "pAbusive =",pAbusive
# p0Num = ones(numWords); p1Num =ones(numWords)
p0Num = zeros(numWords); p1Num = zeros(numWords) :向量置零!
p1Num [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
print "p0Num ", p0Num
print "p1Num ", p1Num
p0Denom = 0.0; p1Denom = 0.0 #对于分类0和分类1,算该类总数的词量,先设置为0(注意这个地方后面会有变数)
for i in range(numTrainDocs): 这里巧妙的用了向量的加法,统计了本类中某个词累计出现的次数,很巧妙的用了数组加法!
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom +=sum(trainMatrix[i]): 算本类单词的总数
print "p1Num =", p1Num,"p1Denom =", p1Denom
else:
p0Num += trainMatrix[i]
p0Denom +=sum(trainMatrix[i])
print "p0Num =", p0Num,"p0Denom =", p0Denom
遍历后,我们看看什么结果:
p0Num = [ 1. 1. 1. 0. 0. 1. 1. 1. 0. 1. 1. 1. 1. 0. 0. 2. 0. 0.
1. 0. 1. 1. 0. 1. 1. 1. 0. 1. 0. 1. 1. 3.] 本类每个单词出现的次数。
p0Denom = 24.0: 本类总共的单词出现数量。
p1Num = [ 0. 0. 0. 1. 1. 0. 0. 0. 1. 1. 0. 0. 0. 1. 1. 1. 1. 1.
0. 2. 0. 1. 1. 0. 2. 0. 3. 0. 1. 0. 0. 0.]p1Denom = 19.0
算分类的概率吧:
p1Vect = p1Num/p1Denom #change to log()
p0Vect = p0Num/p0Denom #change to log()
(array([ 0.04166667, 0.04166667, 0.04166667, 0. , 0. ,
0.04166667, 0.04166667, 0.04166667, 0. , 0.04166667,
0.04166667, 0.04166667, 0.04166667, 0. , 0. ,
0.08333333, 0. , 0. , 0.04166667, 0. ,
0.04166667, 0.04166667, 0. , 0.04166667, 0.04166667,
0.04166667, 0. , 0.04166667, 0. , 0.04166667,
0.04166667, 0.125 ]),
array([ 0. , 0. , 0. , 0.05263158, 0.05263158,
0. , 0. , 0. , 0.05263158, 0.05263158,
0. , 0. , 0. , 0.05263158, 0.05263158,
0.05263158, 0.05263158, 0.05263158, 0. , 0.10526316,
0. , 0.05263158, 0.05263158, 0. , 0.10526316,
0. , 0.15789474, 0. , 0.05263158, 0. ,
0. , 0. ]),
对应['cute', 'love','help', 'garbage', 'quit', 'I', 'problems', 'is', 'park', 'stop', 'flea','dalmation', 'licks', 'food', 'not', 'him', 'buying', 'posting', 'has','worthless', 'ate', 'to', 'maybe', 'please', 'dog', 'how', 'stupid', 'so','take', 'mr', 'steak', 'my']可以看到每个词在对应的分类里面的概率:
概率最大的是 stupid= 0.15789474 在脏话里面!也合乎只觉得概率统计。同时在好话里面, 概率stupid= 0
这个算法还真是挺巧妙地啊。简单的向量运算,就可以分类了!
以上是最原始的朴素贝叶斯算法。这个算法对文本分析有2点缺陷:
1. 如果某个属性在某个分类没有出现过,就会导致条件概率为零。这个时候必须引入修正的方法。常见做法是假定每个句子所有的词汇都出现一次,
就是矩阵初始化就是1,然后统计的时候分子分母都变化了。当然这个时候样本空间也大了。这个就是拉普拉斯修正。我看到别的例子有不同的修正方法。
有的是分母加上单词总数,分子只是在没有出现的属性+1. 不过从统计结果看,应该分类效果差不离。
p0Num = [ 2. 2. 2. 1. 1. 2. 2. 2. 1. 2. 2. 2. 2. 1. 1. 3. 1. 1.
1. 2. 2. 1. 2. 2. 2. 1. 2. 1. 2. 2. 4.]
p0Denom = 26.0
p1Num = [ 1. 1. 1. 2. 2. 1. 1. 1. 2. 2. 1. 1. 1. 2. 2. 2. 2. 2.
3. 1. 2. 2. 1. 3. 1. 4. 1. 2. 1. 1. 1.]
p1Denom = 21.0
缺陷2: 对概率log计算,可以避免概率太低导致的下溢出。因为我们比较的是2个概率计算结果,所以都做log计算既可以避免计算溢出,又不影响比较大小。
对以上的缺陷修正后,有如下的计算演示:确实能分辨脏话和好话。
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1
['I', 'am', 'good', 'boy', 'life'] classifiedas: 0
['You', 'are', 'bad', 'boy', 'fool'] classifiedas: 0
以上就是基于贝努力朴素贝叶斯的简单分类方法,合适对不定长的文本做简单分类。因为这个方法不统计词频,所以分类精度受限。
词频才是甄别作者和标题最重要的属性。如果是定长的向量,就不用做拉普拉斯平滑了,因为每个属性都有数值。