经过一周的读书、视频以及网站教程,终于弄清楚了朴素贝叶斯算法的原理,这里粘贴机器学习实战上相关的代码以记录我的学习成果。附代码及数据集。
手写数据集用于测试算法:
import numpy
'''Python进行文本分类'''
'''将所有文档出现过的单词找出来构成词汇表,然后每个文档生成文档向量来表示文档中具体出现了哪些词,以此将文档向量化'''
def loadDataSet():
"""创建一个样本,共包含6个文档"""
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'],\
['mr','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
构建词汇表:
def createVocabList(dataSet):
"""利用集合的不重复特性提取出样本文档里所有不同的单词,返回词汇列表"""
vocabSet = set() # 创建一个空集,而集合里面的元素是列表类型
for document in dataSet: # dataset是一个列表,而列表里面的元素也是列表类型
# print(document) # 此处可看出元素document是列表类型
vocabSet = vocabSet | set(document) # 列表document转换为集合,且合并两个集合所有元素
return list(vocabSet) # 最终返回一个包含所有词汇但不重复的列表
根据词汇表,将数据集文档转换为文档向量(数字化):
# 基于词集模型
def setOfWords2Vec(vocabList,inputSet):
"""判断词汇列表中的单词是否在输入文档中出现,返回文档向量"""
returnVec1 = [0]*len(vocabList) # 创建一个与词汇列表等长的0向量
# returnVec2 = [0]*len(vocabList) # 创建一个与词汇列表等长的0向量
''' 书上的代码 '''
for word in inputSet: # 遍历文档的单词,判断在词汇表哪一个位置出现,向量对应位置置1
if word in vocabList:
returnVec1[vocabList.index(word)] = 1
else: print("the word :%s is not in my Vocabulary!" % word) # 文档中单词未被词汇表收录
''' ----------------错误理解,,,--------------------------------------------- '''
''' for word in vocabList: # 遍历列表的词汇,判断在文档中是否出现
if word in inputSet:
returnVec2[vocabList.index(word)] = 1
else: print("the word :%s is not in my document!" % word)'''
return returnVec1
# 基于词袋模型
def bagOfWords2VecMN(vocabList,inputSet):
"""判断单词在文档是否出现,且累计出现次数,返回文档向量"""
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
训练分类器,这里的算法原理是贝叶斯公式:
'''分类器训练函数'''
def trainNB0(trainMatrix,trainCategory):
"""输入矩阵是文档向量组成的矩阵以及类别标签"""
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0]) # 文档向量长度(词汇表长度)
pAbusive = sum(trainCategory)/float(numTrainDocs) # 有侮辱性词汇出现的文档所占文档总数的比例 P(C) 任意文档属于侮辱性文档的概率
'''拉普拉斯平滑,避免概率值为0'''
p0Num = numpy.ones(numWords); p1Num = numpy.ones(numWords) # 创建指定大小的数组,数组元素为0,数组类型为ndarray,多维数组
p0Denom = 2.0;p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1: # 如果文档i是侮辱性言论(包含侮辱性词汇)(由loadDataSet中的类别标签决定)
p1Num += trainMatrix[i] # 把侮辱性文档向量叠加,最终得出词汇表每个单词在侮辱性文档中出现次数 Ni
p1Denom += sum(trainMatrix[i]) # 因为文档中每个单词必然会出现在词汇表中,文档向量求和即为文档单词数,此处计算所有侮辱性文档的总单词数 N
else: # 同理,计算每个单词在非侮辱性文档中出现次数以及文档总单词数
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
'''求对数,避免下溢出'''
p1Vect = numpy.log(p1Num/p1Denom) # P(Fi/C) = Ni/N 计算每个单词再侮辱性文档中出现的概率
p0Vect = numpy.log(p0Num/p0Denom) # 计算每个单词再非侮辱性文档中出现的概率
return p0Vect,p1Vect,pAbusive
分母相同,只需要比较分子的大小:
'''分类函数'''
'''算法:通过比较贝叶斯公式分子的大小即可 '''
def classifyNB(vec2Clasify,p0Vec,p1Vec,pClass1):
"""输入为要分类的文档、训练概率数组、类别概率"""
p1 = sum(vec2Clasify*p1Vec) + numpy.log(pClass1) # 文档vec2Clasify是侮辱性文档的概率,因为上面求得是对数,所以这里求积转为求和
p0 = sum(vec2Clasify*p0Vec) + numpy.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
def testingNB():
"""测试以上bayes函数"""
listOPosts,listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
trainMat=[]
for postingDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList,postingDoc))
p0V,p1V,pAb = trainNB0(numpy.array(trainMat),numpy.array(listClasses))
testEntry = ['love','my','dalmation','abcd'] # abcd没有收录到词汇表的情况
thisDoc = numpy.array(setOfWords2Vec(myVocabList,testEntry))
print(testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
testEntry = ['stupid','garbage']
thisDoc = numpy.array(setOfWords2Vec(myVocabList,testEntry))
print(testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
垃圾邮件分类测试:
'''垃圾邮件分类测试函数'''
def textParse(bigString):
"""文本解析器"""
import re
regEx = re.compile('\\W+') # 编译正则表达式,生成一个正则表达式(pattern)对象 \W :匹配任何非单词字符 + :??
listOfTokens = regEx.split(bigString) # re.split() 将字符串分割后返回列表
# listOfTokens = re.split(r'\W+',bigString) # r'\W'等价于 \\W
return [tok.lower() for tok in listOfTokens if len(tok) > 2] # 不要少于2位的字符串
def spamTest():
"""测试过程自动"""
docList=[] # 文档列表,存放所有文档,元素为文档
classList=[] # 类别标签,1表示垃圾邮件,0表示非垃圾邮件
fullText=[] # 单词列表,存放所有文档(以单词形式)
'''导入文本并解析'''
for i in range(1,26): # 本例测试文档 垃圾邮件共25个
wordList = textParse(open('email/spam/%d.txt' % i).read())
docList.append(wordList) # 在列表末添加新元素
fullText.extend(wordList) # 将新列表扩充入原列表
classList.append(1) # 类别标签,先读入垃圾邮件,前25位置1
wordList = textParse(open('email/ham/%d.txt' % i).read()) # 非垃圾邮件25个
docList.append(wordList) # 以列表形式保存所有文档
fullText.extend(wordList)
classList.append(0) # 类别标签,后读非垃圾邮件,后25位置0
vocabList = createVocabList(docList) # 利用所有测试文档构建词汇表
trainingSet = list(range(50));testSet=[] # del range返回range对象,不返回数组对象,故不支持迭代删除
'''随机抽取10个文档,作为分类测试集,其余40个文档作为训练集'''
for i in range(10):
randIndex = int(numpy.random.uniform(0,len(trainingSet))) # 生成随机数
testSet.append(trainingSet[randIndex]) # 随机抽取10个文档
del(trainingSet[randIndex]) # 一次随机抽出一个文档,删除目录索引(列表trainingSet的元素),让已经抽过的文档不再重复抽
trainMat=[];trainClasses=[]
'''用训练集训练分类器'''
for docIndex in trainingSet:
trainMat.append(setOfWords2Vec(vocabList,docList[docIndex])) # 构建所有训练文档向量
trainClasses.append(classList[docIndex])
p0V,p1V,pSpam = trainNB0(numpy.array(trainMat),numpy.array(trainClasses)) # 分类器训练,得到概率
errorCount = 0
'''对测试集分类并验证'''
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList,docList[docIndex]) # 文档向量
if classifyNB(numpy.array(wordVector),p0V,p1V,pSpam)!=classList[docIndex]: # 将分类结果与类别标签对比,累计错误数
errorCount +=1
print('classification error: ',docList[docIndex]) # 显示出错误文档
print('the error rate is: ',float(errorCount)/len(testSet))
只需要Python3环境下执行spamTest()函数就会自动随机构建分类器并测试,我得到的错误率最大为0.2,一般为0或0.1。
借鉴的视频链接:https://www.bilibili.com/video/av57027507
Python教程网址:https://www.runoob.com/python3
邮件分类测试数据集:https://pan.baidu.com/s/1y7p2ZPqfdlDGWRicLD5Pnw