机器学习实战-【朴素贝叶斯】
文章目录
0.总结
'''
对于本例中的二分类
@ vocabset 统计所有文档中出现的词条列表
@ returnVec 输入文档转化为词条向量
@ pAbusive 所有文档中属于类别 (c1)1 所占的概率
@ p1Vect 利用NumPy数组计算 p(wi|c1)
@ p0Vect 利用NumPy数组计算p(wi|c0)
对待分类 vec2Classify 进行比较 p1 和 p2 进行分类
@ vec2Classify
p1 = np.sum(vec2Classify*p1Vec)+np.log(pClass1)
p0 = np.sum(vec2Classify*p0Vec)+np.log(1.0-pClass1)
'''
1.难点说明
1. python中的集合
set是一个无序且不重复的元素集合
利用集合求 交集,并集、、、
2. list中index()方法
index() 函数用于从列表中找出某个值第一个匹配项的索引位置
>>> list_1 = [x for x in range(10,1,-1)]
>>> list_1
[10, 9, 8, 7, 6, 5, 4, 3, 2]
>>> list_1.index(10)
0
3. 训练算法中(trainNB0)
有个词条向量——统计所有文档中出现不重复词的列表(createVocabList())
-
计算概率 p ( c i ) p(c_i) p(ci)——类别 i 的文档数 / 总的文档数
# 所有文档中属于类别 1 所占的概率 pAbusive = np.sum(trainCategory) / float(numTrainDocs)
-
p ( w 0 , w 1 , w 2 . . w n / c i ) p(w_0,w_1,w_2..w_n/c_i) p(w0,w1,w2..wn/ci) - p1Vect
#(统计类别为 i 的词条向量中各个词条出现的此次 / 类别为 i 中所有词条的总数) p1Vect=p1Num/p1Denom # i=1
-
需要计算多个概率的乘积
-
p ( w 0 / 1 ) p ( w 1 / 1 ) p ( w 2 / 1 ) p(w_0/1)p(w_1/1)p(w_2/1) p(w0/1)p(w1/1)p(w2/1)——如果其中一个概率值为0,后面的也为0
-
降低这种影响,采用拉普拉斯平滑,在分子上添加a(一般为1),分母上添加ka(k表示类别总数),即在这里将所有词的出现数初始化为1,并将分母初始化为2*1=2
p0Num=ones(numWords);p1Num=ones(numWords) p0Denom=2.0;p1Denom=2.0
-
-
解决下溢出问题
p ( w 0 / 1 ) p ( w 1 / 1 ) p ( w 2 / 1 ) p(w_0/1)p(w_1/1)p(w_2/1) p(w0/1)p(w1/1)p(w2/1)——很多很小的数相乘时,最后相乘的结果四舍五入为0
解决——对乘积取对数
p0Vect=np.log(p0Num/p0Denom);p1Vect=np.log(p1Num/p1Denom)
4.词集模型和词袋模型
词集模型(set-of-words)——每个词的出现与否作为一个特征
词袋模型(bag-of-words)——在词袋中每个单词可以出现多次
def setOfWords2Vec(vocabSet, inputSet):
'''
将输入文档转化为词条向量
'''
# 新建一个长度为vocabset的列表,并且各维度元素初始化为0
returnVec = [0]*len(vocabSet)
# 遍历文档中的每一个词条
for word in inputSet:
if word in vocabSet:
returnVec[vocabSet.index(word)] = 1 # 通过列表获取当前word的索引(下标)
else: print('the word: %s is not in my vocabulary! '%'word')
return returnVec
# 朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList,inputSet):
#词袋向量
returnVec=[0]*len(vocabList)
for word in inputSet:
if word in vocabList:
# 区别于词集模型—每遇到一个单词,会增加词向量中对应的值
returnVec[vocabList.index(word)]+=1
return returnVec
5.准备数据(切分文本)
-
string.split()
def textParse(bigString): ''' 处理文本 1.以除单词和数字之外的任意符号串,对字符串进行分割 2.将大写变为小写,并只保留长度大于2的单词 ''' listOfTokens = re.split(r'\W+', bigString) # 分割后有空格 return [tok.lower() for tok in listOfTokens if len(tok)>2]
-
占位符
open('email/spam/%d.txt' % i).read() %s字符串占位符。%ns表示字符串的截取,%4s表示截取4位字符串。 %d数字占位符。 %f浮点数、小数占位符。(默认会保留6位小数) 保留两位小数可以用%.2f 保留两位小数可以用%.3f
2.使用朴素贝叶斯进行文本分类
2.1伪代码
计算每个类别中的文档数目
对每篇训练文档:
对每个类别:
如果词条出来再文档中 —> 增加该词条的计数值
增加所词条的计数值
对每个类别:
对每个词条
将该词条的数目除以总词条数目得到条件概率
返回每一个类别的条件概率
2.2训练算法
import numpy as np
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'],
['my','licks','ate','my','steak','how', 'to','stop','him'],
['quit','buying','worthless','dog','food','stupid']]
#由人工标注的每篇文档的类标签
classVec=[0,1,0,1,0,1]
return postingList,classVec
def createVocabList(dataSet):
'''
统计所有文档中出现的词条列表
'''
# 新建一个存放词条的集合
vocabset = set([])
for document in dataSet:
vocabset = vocabset | set(document) # 去并集
return list(vocabset) # 将集合转化为列表
def setOfWords2Vec(vocabSet, inputSet):
'''
将输入文档转化为词条向量
'''
# 新建一个长度为vocabset的列表,并且各维度元素初始化为0
returnVec = [0]*len(vocabSet)
# 遍历文档中的每一个词条
for word in inputSet:
if word in vocabSet:
returnVec[vocabSet.index(word)] = 1 # 通过列表获取当前word的索引(下标)
else: print('the word: %s is not in my vocabulary! '%'word')
return returnVec
def trainNB0(trainMatrix, trainCategory):
'''
训练算法
@ trainMatrix 文档矩阵(将文档转化为词列表的矩阵)
@ trainCategory 类别标签
'''
numTrainDocs = len(trainMatrix) #获取文档中 文档的数目
numWords = len(trainMatrix[0]) # 获取词条向量的长度(list(vocabset)的长度)
# 所有文档中属于类别 1 所占的概率
pAbusive = np.sum(trainCategory) / float(numTrainDocs)
# 创建一个长度为词条向量等长的列表
p0Num = np.ones(numWords);p1Num=np.ones(numWords)
p0Denom=2.0;p1Denom=2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
#统计所有类别为1的词条向量中各个词条出现的次数
p1Num += trainMatrix[i]
#统计类别为1的词条向量中出现的所有词条的总数
p1Denom += sum(trainMatrix[i])
else:
p0Num+=trainMatrix[i]
p0Denom+=sum(trainMatrix[i])
#利用NumPy数组计算p(wi|c1)
p1Vect=np.log(p1Num/p1Denom)
#利用NumPy数组计算p(wi|c0)
p0Vect=np.log(p0Num/p0Denom)
return p0Vect,p1Vect,pAbusive
if __name__=='__main__':
listOPosts, listClass = loadDataSet()
myVocalList = createVocabList(listOPosts)
trainMat = []
for postiDoc in listOPosts:
trainMat.append(setofWords2Vec(myVocalList, postiDoc))
p0v, p1v, pAb = trainNB0(trainMat, listClass)
print(p0v)
print(setofWords2Vec(myVocalList, listOPosts[0]))
print(myVocalList)
['posting', 'worthless', 'my', 'garbage', 'has', 'food', 'park', 'how', 'licks', 'problems', 'take', 'him', 'buying', 'dog', 'help', 'love', 'flea', 'stop', 'so', 'not', 'steak', 'is', 'ate', 'I', 'dalmation', 'please', 'maybe', 'to', 'stupid', 'cute', 'quit']
2.3测试算法(bayes_1.py)
import numpy as np
import bayes
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
'''
朴素贝叶斯分类器
@ vec2Classify 待分类的词条向量
@ p0Vec 类别1所有文档中各个词条出现的频数p(wi|c1)
@ pClass1 类别为1的文档占文档总数比例
'''
#根据朴素贝叶斯分类函数分别计算待分类文档属于类1和类0的概率
p1 = np.sum(vec2Classify*p1Vec)+np.log(pClass1)
p0 = np.sum(vec2Classify*p0Vec)+np.log(1.0-pClass1)
if p1>p0:
return 1
else:
return 0
def testingNB():
'''
分类测试函数
'''
listOPosts,listClasses = bayes.loadDataSet() # 获取数据集 和 标签
# 创建词条列表
myVocabList = bayes.createVocabList(listOPosts)
''' 将文档根据 词条列表 转化为 向量'''
trainMat=[]
for postinDoc in listOPosts:
trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))
# 得到概率值
p0V,p1V,pAb=bayes.trainNB0(np.array(trainMat),np.array(listClasses))
''' 测试
1. 测试文档
2. 转化为 向量
3. 利用分类函数
'''
testEntry=['love','my','dalmation']
thisDoc=np.array(bayes.setOfWords2Vec(myVocabList,testEntry))
print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
if __name__=='__main__':
testingNB()
['love', 'my', 'dalmation'] classified as: 0
3.示例:使用朴素贝叶斯过滤垃圾邮件(bayes_2.py)
import re
import bayes
import bayes_1
import numpy as np
def textParse(bigString):
'''
处理文本
1.以除单词和数字之外的任意符号串,对字符串进行分割
2.将大写变为小写,并只保留长度大于2的单词
'''
listOfTokens = re.split(r'\W+', bigString) # 分割后有空格
return [tok.lower() for tok in listOfTokens if len(tok)>2]
def spamTest():
docList = [];classList = [];fullTest = []
for i in range(1,26):
# 对标签为1的文件进行处理
wordList=textParse(open('./email/spam/%d.txt' % i).read()) # %d 数字占位符
docList.append(wordList)
fullTest.extend(wordList) #将字符串的元素添加到fullTest
classList.append(1)
# 类别为0的文件进行处理
wordList=textParse(open('./email/ham/%d.txt' % i).read())
docList.append(wordList)
fullTest.extend(wordList)
classList.append(0)
vocabList = bayes.createVocabList(docList) #创建字符串列表
#构建一个大小为50的整数列表和一个空列表
trainingSet=list(range(50));testSet=[]
'''随机选取1~50中的10个数,作为索引,构建测试集'''
for i in range(10):
randIndex = int(np.random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
#从整数列表中删除选出的数,防止下次再次选出
#同时将剩下的作为训练集
del(trainingSet[randIndex])
'''对训练集'''
trainMat=[];trainClasses=[]
for docIndex in trainingSet:
trainMat.append(bayes.setOfWords2Vec(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = bayes.trainNB0(np.array(trainMat), np.array(trainClasses))
'''测试集'''
errorCount = 0
for docIndex in testSet:
wordVector = bayes.setOfWords2Vec(vocabList, docList[docIndex])
if bayes_1.classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print('错误率为:{}'.format(float(errorCount/len(testSet))))
if __name__=='__main__':
spamTest()
错误率为:0.2