一、思路框架
1.收集数据
2.准备数据
3.分析数据
4.训练算法
5.测试算法
6.使用算法
二、具体实施
1.准备数据阶段:因为《机器学习实战》这本书提供的有源数据,因此省去了数据收集和准备的阶段,直接分析数据。这里分享一下《机器学习实战》里面的源数据链接:
https://pan.baidu.com/s/1B7PCunfHF8J4gmbu22ljPQ
提取码:3vpk
2.分析数据:用open(filename).read()读取并打开文本,发现其中一段的文本如下图所示,可以发现文中除了字母数字还有一些空格,非字符串符合需要去除。因此需要使用正则化公式进行处理。
正则化公式可以设定为【\w.*?】,其中,【\w]匹配字母、数字或者下划线;【.】匹配除换行符(\n,\r)以外的任何单个字符;【*】匹配前面的子表达式零次或者多次;【?】匹配前面的子表达式一次或者零次。
经正则化处理后的结果如图所示,发现里面还有空格也被读取了,需要处理掉;并且一些首字母是大写的也要变成小写的。使用三目表达式,可以节省代码空间,使代码更加简洁。
[tok.lower() for tok in listOfTokens if len(tok) > 2]
3.算法:直接放python代码(完整版)
import re
import random
from math import log
import numpy as np
# 创建文本词汇查询表
def createVocabList(dataSet):
# 创建一个空集
vocabSet = set([])
# 将新词集合添加到创建的集合中
for document in dataSet:
# 操作符 | 用于求两个集合的并集
vocabSet = vocabSet | set(document)
# 返回一个包含所有文档中出现的不重复词的列表
return list(vocabSet)
# 将文本词汇转换成矩阵向量
def setOfWords2Vec(vocabList, inputSet):
# 创建一个所含元素都为0的向量
returnVec = [0] * len(vocabList)
# 遍历文档中词汇
for word in inputSet:
# 如果文档中的单词在词汇表中,则相应向量位置置1
if word in vocabList:
returnVec[vocabList.index(word)] = 1
# 否则输出打印信息
else:
print("%s 不在词袋中" % word)
# 向量的每一个元素为1或0,表示词汇表中的单词在文档中是否出现
return returnVec
# 训练函数
def trainNB0(trainMatrix, trainCategory):
# 获得训练集中文档个数
numTrainDocs = len(trainMatrix)
# 获得训练集中单词个数
numWords = len(trainMatrix[0])
# 计算文档属于侮辱性文档的概率
pAbusive = sum(trainCategory) / float(numTrainDocs)
# 初始化概率的分子变量
p0Num = np.ones(numWords)
p1Num = np.ones(numWords)
# 初始化概率的分母变量
p0Denom = 2.0
p1Denom = 2.0
# 遍历训练集trainMatrix中所有文档
for i in range(numTrainDocs):
# 如果侮辱性词汇出现,则侮辱词汇计数加一,且文档的总词数加一
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
# 如果非侮辱性词汇出现,则非侮辱词汇计数加一,且文档的总词数加一
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
# 对每个元素做除法求概率
p1Vect = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p0Denom)
# 返回两个类别概率向量和一个概率
return p0Vect, p1Vect, pAbusive
# 分类函数
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass):
p1 = sum(vec2Classify * p1Vec) + log(pClass)
p0 = sum(vec2Classify * p0Vec) + log(1 - pClass)
if p1 > p0:
return 1
else:
return 0
# 文本处理
def textParse(bigString):
listOfTokens = re.split(r'\W.*?', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
# 测试函数
def spamTest():
docList = [];classList = []; fullText = []
# 导入并解析文件
for i in range(1, 26):
wordList = textParse(open("E:/python/Github/python/Python/machinelearninginaction/Ch04/email/spam/%d.txt" % i, 'r',encoding='gb18030',errors='ignore').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList = textParse(open("E:/python/Github/python/Python/machinelearninginaction/Ch04/email/ham/%d.txt" % i, 'r',encoding='gb18030',errors='ignore').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList)
# 构建训练集
trainingSet = list(range(50)); testSet = []
for i in range(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]))
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = trainNB0(np.array(trainMat), np.array(trainClasses))
errorCount = 0
print(testSet)
# 对测试集分类
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
if classifyNB(np.array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount += 1
print("the error rate is:",float(errorCount)/len(testSet))
# 运行
if __name__ == '__main__':
spamTest()
三、总结
代码跑完,对于为什么使用朴素贝叶斯进行文本分类,还是存在着一些疑惑。具体代码的思路是:第一、先获得两个表,一个是训练集文本的词汇汇总表,一个是训练集分类的列表(如本例是分两类:含侮辱性词汇为1,不含侮辱性的为0)。第二、将训练集文本和测试集文本转换成向量矩阵便于操作。第三、根据分类列表分别计算训练集的文本向量中含侮辱性词汇和不含侮辱性词汇出现的概率,还有含有侮辱性词汇的文本出现的概率。第四、就是将测试集的文本向量分别与两组概率相乘再加数相应类别出现的概率,比较计算结果,结果较大的类别就是该测试文本所属的类别。
那么问题来了,为什么根据判断词汇出现的概率,而不是根据词汇的内容就可以判断词汇所属文本的类别了呢?
书中有这么一段解释我觉得很好理解,就是假设一个特征或者一个单词出现的可能性与它和其他单词相邻与否没有关系。也就是说bacon(熏肉)出现在unhealthy(不健康的)后面和出现delicious(美味的)的后面的概率是相同的。但是实际上这种假设并不正确,bacon更常出现在delicious附近,而很少出现在unhealthy附近。那么这样就可以通过计算bacon出现的概率来判断文本中是含有delicious的概率高还是含有unhealthy的概率高。
同理,我们可以据此来过滤垃圾邮件。