朴素贝叶斯
p(xy)=p(x|y)p(y)=p(y|x)p(x)
so:p(x|y)=p(y|x)p(x)/p(y)
优点:在数据较少的情况下仍然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式比较敏感。
适用数据类型:标称型数据(有限)。
注释:“朴素”,这里指整个形式化过程只做最原始、最简单的假设。
本文中大量注释来源于Machelearning学习的Submission整理得到,方便自己和读者学习。
'''
Created on Oct 19, 2010
@author: Peter
'''
import numpy as np
#创建数据集
def loadDataSet():
"""
生成一个文本数据集和标签
参数:
无
返回:
postingList -- 文本列表
classVec -- 标签分类
"""
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): # 创建词汇列表
"""
建立词汇表,也就是所有文本的并集
参数:
dataSet -- 数据集
返回:
vocabSet -- 词汇表
"""
vocabSet = set([]) # 定义词汇集
for document in dataSet: # 遍历文档
vocabSet = vocabSet | set(document) # 将每个document合并到vocabSet,|用来联合两个集合
return list(vocabSet)
def setOfWords2Vec(vocabList, inputSet): # 把单词转换成向量
"""
把句子嵌入到词汇表中,也就是用表达一个句子
向量中为1,代表词汇表对应该处的词汇;反之,不对应该处词汇
参数:
vocabList -- 词汇表
inputSet -- 输入词汇集合
返回:
returnVec -- 返回句子对应的向量
"""
returnVec = [0]*len(vocabList) # 定义要返回的向量
for word in inputSet: # 遍历输出集中的单词
if word in vocabList: # 单词在词汇集中
returnVec[vocabList.index(word)] = 1 # 对应的位置设为1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
def trainNB0(trainMatrix, trainCategory):
"""
函数说明:朴素贝叶斯分类器训练函数
Parameters:
trainMatrix - 文档矩阵
trainCategory - 每篇文档类别标签所构成的向量
Returns:
p0Vect - 非侮辱类的条件概率数组
p1Vect - 侮辱类的条件概率数组
pAbusive - 文档属于侮辱类的概率
"""
numTrainDocs = len(trainMatrix) # 计算文档的数目
numWords = len(trainMatrix[0]) # 计算单词的数目
pAbusive = sum(trainCategory)/float(numTrainDocs) # 计算类别的概率,abusive为1,not abusive为0
p0Num = zeros(numWords) # 初始化计数器,p0是not abusive
p1Num = zeros(numWords) # 初始化计数器
p0Denom = 0.0 # 初始化分母
p1Denom = 0.0
for i in range(numTrainDocs):
if trainCategory[i] == 1: # 计算abusive对应的词汇的数目,trainMatrix为0-1值形成的向量
p1Num += trainMatrix[i] # p1Num存储的是每个词出现的次数
p1Denom += sum(trainMatrix[i]) # p1Denom存储的是词的总数目
else:
p0Num += trainMatrix[i] # 每个词在not abusive下出现的次数
p0Denom += sum(trainMatrix[i]) # not abusive下的总词数
p1Vect = p1Num/p1Denom # change to log() # 计算abusive下每个词出现的概率
p0Vect = p0Num/p0Denom # change to log() # 计算not abusive下每个词出现的概率
return p0Vect,p1Vect,pAbusive
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
"""
vec2Classify:待测数据
p0Vec:类别1,即侮辱性文档的列表
p1Vec:类别0,即正常性文档的列表
pClass1:类别1,侮辱性文档出现的概率
return:返回类别1 or 0
计算概率,比较概率,得出结果
由于所有概率均取了对数,所以乘法变加法,除法变减法
由于只需要比较相对大小,相同的p(W)也就不用除了
参数:
vec2Classify -- 要分类的文本向量
p0Vec -- p0向量
p1Vec -- p1向量
pClass1 -- 类别1的概率
返回:
判断结果 -- 0或1
"""
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) #element-wise mult
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
def bagOfWords2VecMN(vocabList, inputSet): # 把单词转换成向量,用词袋模型,计算词出现的次数
"""
文档的词袋模型
参数:
vocabList -- 词汇列表
inputSet -- 输入词集
返回:
returnVec -- 返回向量
"""
returnVec = [0]*len(vocabList) # 定义要返回的向量
for word in inputSet: # 遍历输出集中的单词
if word in vocabList: # 单词在词汇集中
returnVec[vocabList.index(word)] += 1 #对应的词出现次数 加1
return returnVec
def testingNB():
"""
函数说明:朴素贝叶斯分类函数
Parameters:
vec2Classify - 待分类的词条数组
p0Vec - 非侮辱类的条件概率数组
p1Vec - 侮辱类的条件概率数组
pClass1 - 文档属于侮辱类的概率
Returns:
类别标签 - 1或0
"""
#加载数据集
listOPosts, listClasses = loadDataSet()
#创建单词集合
myVocabList = createVocabList(listOPosts)
trainMat = []
#计算单词是否出现并创建矩阵
#仅看单词是否出现,出现就为1,出现多次也为1
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
#训练数据
p0V, p1V, pAb = trainNB0(np.array(trainMat), np.array(listClasses))#计算概率,计算不同类别下出现的概率
#测试数据
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
def textParse(bigString): # 文本解析 ### 输入是字符串,输出是单词列表
"""
输入很长的字符串,转换为向量
参数:
bigString -- 长字符串
返回:
去掉少于两个字符,转换为小写的字符串
"""
import re
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
def spamTest():
docList = [] # 定义docList文档列表
classList = [] # classList类别列表
fullText = [] # fullText所有文档词汇
for i in range(1,26):
# 定义并读取垃圾邮件文件的词汇分割列表
wordList = textParse(open('email/spam/%d.txt' % i).read()) # !!! 为了避免文件中非法字符的影响,尽量用下一行
#wordList = textParse(open('email/spam/%d.txt' % i, "rb").read().decode('GBK','ignore'))
docList.append(wordList) # 将词汇列表加到文档列表中
fullText.extend(wordList) # 将所有词汇列表汇总到fullText中
classList.append(1) # 文档类别为1,spam
#wordList = textParse(open('email/ham/%d.txt' % i).read()) # !!! 原书本中 bug 因为有可能文件中存在类似“�”非法字符。
# 提示:UnicodeDecodeError: 'gbk' codec can't decode byte 0xae in position 199: illegal multibyte sequence
wordList = textParse(open('email/ham/%d.txt' % i, "rb").read().decode('GBK','ignore') ) # 读取非垃圾邮件的文档
docList.append(wordList)
fullText.extend(wordList)
classList.append(0) # 类别为0,非垃圾邮件
vocabList = createVocabList(docList)
#trainingSet = range(50) # !!! 原书本中 python2.7 提示:TypeError: 'range' object doesn't support item deletion
#因为是python3中range不返回数组对象,而是返回range对象
trainingSet = list(range(50)) # 定义训练集的索引和测试集
testSet = []
for i in range(10): # 随机的选择10个作为测试集
randIndex = int(random.uniform(0,len(trainingSet))) # 随机索引
testSet.append(trainingSet[randIndex]) # 将随机选择的文档加入到测试集中
del(trainingSet[randIndex]) # 从训练集中删除随机选择的文档
trainMat = [] # 定义训练集的矩阵和类别
trainClasses = []
for docIndex in trainingSet: # 遍历训练集,求得先验概率和条件概率
trainMat.append(setOfWords2Vec(vocabList, docList[docIndex])) # 将词汇列表变为向量放到trainMat中
trainClasses.append(classList[randIndex]) # 训练集的类别标签
p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses)) # 计算先验概率,条件概率
errorCount = 0
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList,docList[docIndex]) # 将测试集词汇向量化
if classifyNB(array(wordVector), p0V, p1V,pSpam) != classList[docIndex]: # 对测试数据进行分类
errorCount += 1 # 分类不正确,错误计数加1
print("the error rate is:",float(errorCount)/len(testSet))
def calcMostFreq(vocabList, fullText):
import operator
freqDict = {}
for token in vocabList:
freqDict[token] = fullText.count(token)
sortedFreq = sorted(freqDict.items(), key=operator.itemgetter(1), reverse=True)
return sortedFreq[:30]
def localWords(feed1, feed0):
import feedparser
docList = []; classList = []; fullText = []
minLen = min(len(feed1['entries']), len(feed0['entries']))
for i in range(minLen):
wordList = textParse(feed1['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(1) #NY is class 1
wordList = textParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList)#create vocabulary
top30Words = calcMostFreq(vocabList, fullText) #remove top 30 words
for pairW in top30Words:
if pairW[0] in vocabList: vocabList.remove(pairW[0])
trainingSet = range(2*minLen); testSet = [] #create test set
for i in range(20):
randIndex = int(np.random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(list(trainingSet)[randIndex])
trainMat = []; trainClasses = []
for docIndex in trainingSet:#train the classifier (get probs) trainNB0
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = trainNB0(np.array(trainMat), np.array(trainClasses))
errorCount = 0
for docIndex in testSet: #classify the remaining items
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print('the error rate is: ', float(errorCount)/len(testSet))
return vocabList, p0V, p1V
def getTopWords(ny, sf):
import operator
vocabList, p0V, p1V = localWords(ny, sf)
topNY = []; topSF = []
for i in range(len(p0V)):
if p0V[i] > -6.0: topSF.append((vocabList[i], p0V[i]))
if p1V[i] > -6.0: topNY.append((vocabList[i], p1V[i]))
sortedSF = sorted(topSF, key=lambda pair: pair[1], reverse=True)
print("SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**")
for item in sortedSF:
print(item[0])
sortedNY = sorted(topNY, key=lambda pair: pair[1], reverse=True)
print("NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**")
for item in sortedNY:
print(item[0])