我们都知道在概率论中有个大名鼎鼎的公式——贝叶斯公式(Bayes),主要是存在一个先验概率的思想,公式如下的:
基于这个公式,我们就可以用来进行分类,对公式不熟悉翻一下书就回忆起来了吧。很有名的一个例子就是用Bayes来判断一封邮件是不是垃圾邮件,这个分类是基于单词出现概率的,也就是说,分类器已经有一个词库,并且统计好了各种词出现的概率和是垃圾邮件的先验概率。
我们对上面的公式稍作修改,记C i i 为第i个类别, w⃗ w → 表示一个向量,它由多个条件组成(我自己演算时的一种记录方式,不一定严谨),也就是垃圾邮件的条件下各单词的出现概率,于是公式表示如下:
OK,到了这里,我们会发现 p(Ci) p ( C i ) 是个定值,而 p(w⃗ |Ci),p(w⃗ ) p ( w → | C i ) , p ( w → ) 就有点不好求了。的确,这就是我们为什么叫朴素贝叶斯的这个“朴素”的含义了,我们假设各变量之间相互独立,相互独立嘛。。。这下就好办了,直接将各变量对应的概率值相乘就好了。于是乎;
我们要作的工作大致分为以下几步:
- 构建一个词库
- 统计出各词分别在是垃圾邮件和不是垃圾邮件条件下的概率
说明: p(Ci),∏nj=0p(wj) p ( C i ) , ∏ j = 0 n p ( w j ) ,是个定值,无需求出确切值,比较分子大小即可。
可以开始写代码了。
数据集就6个sample,是某些人在宠物社区留言板的评论。有些喷子,爱好说脏话;有些人比较文明,和本人一样。
# 词表到向量的转化函数
def loadDatatSet():
# 评论列表
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'],
['my', '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
```
然后,根据这些评论生成一个词库(估计20几个词吧),同时定义一个函数,功能是将一句话转变为一个对应和词库维度相等的向量
```python
def createVocabList(dataset):
vocabSet = set()
for item in dataset:
# 集合求并集
vocabSet = vocabSet | set(item)
vocabList = list(vocabSet)
# 返回结果排序
return sorted(vocabList, key=operator.itemgetter(0))
def setOfWords2vec(vocabList, input):
resultVec = np.zeros((len(vocabList),))
for word in input:
# 如果词库中有这个词,就对应index位置置为1。
if word in vocabList:
resultVec[vocabList.index(word)] = 1
# 词库中不存在该次暂时忽略
else:
print(word, "is not in the vocabulary list")
return resultVec
class="se-preview-section-delimiter">div>
然后根据朴素贝叶斯求概率。说明一下,pAbusiveWords
和pGoodWords
就是在求 p(wj|Ci) p ( w j | C i ) 。防止出现因为词库中没统计到某个单词的出现频率而导致概率为0的情况(实际上不可能为0),用到了拉普拉斯平滑处理(Laplace Smoothing)。防止概率太小,用了log函数对最终结果处理,对数函数并不会影响单调性。
def trainNaiveBayes(trainMatrix, trainLabels):
# 朴素贝叶斯
m = len(trainMatrix) # 训练数据总数
n = len(trainMatrix[0]) # 词库词汇总数
# 下面是做统计工作
pAbusive = sum(trainLabels) / float(m) # 1代表是脏话,利用了列表加法
pGood = 1 - pAbusive
# 初始化时利用了拉普拉斯平滑处理,分子加1,分母加单词总数
abusiveWordsNum = np.ones((n,)) # 脏话条件下,各词汇出现的次数。 初始化为1,放置出现很多单词出现次数为0
abusiveWordsTotal = n # 脏话里面所有词总数。 初始化为n
goodWordsNum = np.ones((n,)) # 好话条件下,各词汇出现的次数
goodWordsTotal = n # 好话里面所有词总数。 初始化为n
for i in range(m):
if trainLabels[i] == 1:
abusiveWordsNum += trainMatrix[i]
abusiveWordsTotal += sum(trainMatrix[i])
elif trainLabels[i] == 0:
goodWordsNum += trainMatrix[i]
goodWordsTotal += sum(trainMatrix[i])
pAbusiveWords = np.log(abusiveWordsNum / abusiveWordsTotal) # 脏话条件下,各词汇出现频率,对数处理,防止下溢
pGoodWords = np.log(goodWordsNum / goodWordsTotal) # 好话条件下,各词汇出现频率
return pAbusive, pGood, pAbusiveWords, pGoodWords
class="se-preview-section-delimiter">div>
然后就是测试了,代码思路也很简单清晰。
def test():
# 训练的过程
postingList, classVec = loadDatatSet()
vocabList = createVocabList(postingList)
print("所有的词汇库为", vocabList)
trainMatrix = []
for item in postingList:
trainMatrix.append(setOfWords2vec(vocabList, item))
pAbusive, pGood, pAbusiveWords, pGoodWords = trainNaiveBayes(trainMatrix, classVec)
print(pAbusiveWords)
print(pGoodWords)
# 预测的过程
testPostingList = [
['love', 'my', 'dalmation'],
['stupid', 'garbage']
]
testMatrix = []
for index, item in enumerate(testPostingList):
testMatrix.append(setOfWords2vec(vocabList, testPostingList[index]))
for i in range(len(testMatrix)):
if classify(testMatrix[i], pAbusive, pGood, pAbusiveWords, pGoodWords) == 1:
print(testPostingList[i], "是一句脏话")
else:
print(testPostingList[i], "是一句好话")
class="se-preview-section-delimiter">div>
2. 真正处理文本文件
说明:提取词库的时候,我们采取的办法是字符大于2的都保留在词库中,用一个简单的正则处理一下即可。
def textParse(bigStr):
listOfTokens = re.split(r'\\w*', bigStr)
# 返回字长大于等于2的所有单词
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
def spamTest():
emailList = []
classLabels = []
fullWords = []
# 读取垃圾邮件
for i in range(1,26):
wordList = textParse(open("email/spam/%d.txt" % i,encoding='gbk').read())
fullWords.extend(wordList)
classLabels.append(1)
emailList.append(wordList)
# 读取非垃圾邮件
for i in range(1,26):
wordList = textParse(open("email/ham/%d.txt" % i,encoding='gbk').read())
fullWords.extend(wordList)
classLabels.append(0)
emailList.append(wordList)
# 构建词库(Token库)
vocabulary = createVocabList(emailList)
print(vocabulary)
trainingSetIndexList = list(range(50))
testSetIndexList = []
# 构建训练集和测试集,随机抽10个作为测试集
for i in range(10):
randIndex = int(random.uniform(0, len(trainingSetIndexList)))
testSetIndexList.append(randIndex)
del(trainingSetIndexList[randIndex])
trainMatrix = []
trainClassLabels = []
for index in trainingSetIndexList:
trainMatrix.append(setOfWords2vec(vocabulary, emailList[index]))
trainClassLabels.append(classLabels[index])
# 训练集训练
pSpam, pNotSpam, pSpamWords, pNotSpamWords = trainNaiveBayes(trainMatrix, trainClassLabels)
# 测试数据
errorCount=0
errorEmails=[]
for index in testSetIndexList:
wordVect=setOfWords2vec(vocabulary,emailList[index])
predit=classify(wordVect, pSpam, pNotSpam, pSpamWords, pNotSpamWords)
if predit!=classLabels[index]:
errorCount +=1
errorEmails.append({"email":emailList[index],"errorClass":predit})
return float(errorCount)/len(testSetIndexList),errorEmails
if __name__ == '__main__':
#test()
for
errorRate, errorEmails=spamTest()
print("测试集分类错误率为:",errorRate*100,"%")
print("错误分类的邮件和对应的分类为:")
for i in range(len(errorEmails)):
print(errorEmails[i]["email"])
print(errorEmails[i]["errorClass"])
运行结果:
第 1 次测试集分类错误率为: 20.0 %
第 2 次测试集分类错误率为: 10.0 %
第 3 次测试集分类错误率为: 10.0 %
第 4 次测试集分类错误率为: 0.0 %
第 5 次测试集分类错误率为: 30.0 %
第 6 次测试集分类错误率为: 10.0 %
第 7 次测试集分类错误率为: 10.0 %
第 8 次测试集分类错误率为: 0.0 %
第 9 次测试集分类错误率为: 10.0 %
第 10 次测试集分类错误率为: 20.0 %
因为在选择测试集的时候是随机选取的,每次的错误结果可以能略有差别,我们跑10次,总体上来说,结果还是挺不错的(毕竟所有数据也只有50个邮件,邮件次数也比较少),谁叫机器都是数据喂出来的呢。
溜了溜了 :)