朴素贝叶斯 - 过滤垃圾邮件
准备数据:切分文本
之前我们介绍了如何创建一个词向量,并且基于词向量进行朴素贝叶斯分类。但是,先前的词向量是预先给出的,我们应该如何从文本文档中构建属于自己的词向量呢?
对于一个文本字符串,我们科研使用内置函数string.split()进行切分:
mySent = "This book is the best book on Python or M.L. I have ever laid eyes upon."
print(mySent.split())
>>>
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M.L.', 'I', 'have', 'ever', 'laid', 'eyes', 'upon.']
我们看到标点符号也被当成了词的一部分(“M.L.”)。
所以改进切分方法,尝试通过正则表达式来切分,将分隔符定为除字母和数字以外的任意字符。
import re
regEx = re.compile("\\W+")
mySent = "This book is the best book on Python or M.L. I have ever laid eyes upon."
listOfTokens = regEx.split(mySent)
listOfTokens = [tok for tok in listOfTokens if len(tok)>0] #筛选出单词长度不为0的
listOfTokens = [tok.lower() for tok in listOfTokens] # 将所有单词全部转换成小写
print(listOfTokens)
>>>
['this', 'book', 'is', 'the', 'best', 'book', 'on', 'python', 'or', 'm', 'l', 'i', 'have', 'ever', 'laid', 'eyes', 'upon']
测试算法:使用朴素贝叶斯进行交叉验证
def textParse(bigString): # 用于接受一个字符串并解析成列表
import re
listOfTakens = re.split(r'\W+', bigString)
return [tok.lower() for tok in listOfTakens if len(tok) > 2] # 转换成小写并去掉小于两个字符的
def spamTest():
docList = [] ; classList = [] ; fullList = []
for i in range(1, 26):
wordList = textParse(open(r'E:\work\ML\机器学习实战\machinelearninginaction\Ch04\email\spam\%d.txt' % i).read())
docList.append(wordList) # 添加的是列表
fullList.extend(wordList) # 将元素添加到列表
classList.append(1) # 对相应的spam贴上标签1
wordList = textParse(open(r'E:\work\ML\机器学习实战\machinelearninginaction\Ch04\email\ham\%d.txt' % i).read())
docList.append(wordList)
fullList.extend(wordList)
classList.append(0) # 相应的ham贴上标签0
vocabList = createVocabList(docList) # 将docList的所有元素转换成无重复列表
#trainingSet = range(50); testSet = []
trainingSet = list(range(50)); testSet = []
for i in range(10): # 随机选取10封邮件作为测试集
randIndex = int(random.uniform(0, len(trainingSet))) # 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(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
print("the error rate is: " + str(float(errorCount)/len(testSet)))
我们可以看到,第一个函数rextParse()将大字符串解析成了字符串列表,并且去掉了长度小于2的元素。第二个函数spamText()导入了两个文件夹下的文本文件,并且贴上标签。最重要的是,该函数随机选出了一部分数据作为训练集,而剩余的部分作为测试集。假设只完成了一次划分,那么可能会存在随机误差,高估或者低估模型的准确性,所以通过多次划分数据,最后求出平均错误率,用平均错误率作为衡量分类准确性的指标。这个过程叫做交叉验证。
bayes.spamTest()
>>>
the error rate is: 0.1
最终结果是10%的错误率。
我们通过朴素贝叶斯的学习,发现该算法主要是通过计算查询文本在词汇表中出现的单词的频率,没有考虑到单词的位置。而我们的NLL基因家族的分析,有一个关键信息是L出现的位置,所以我认为该算法并不适用于LRR蛋白家族的鉴定。
由于不可抗因素导致plantFamily公布时期待定,我也趁着这个机会规划了一下自己的硕士生涯。细想之下,三年硕士已经过去了一大半,跑过了不少数据,从基础的RNA-seq、BS-seq到GWAS分析再到Nanopore测序技术,期间接受的各种零零散散探索性分析更是不少,仿佛是个数据产出机器,自己的时间也就像是衔尾蛇般如环无端,大量的时间投入到了克莱因瓶中,却毫无成果,升学更是希望渺茫。所以不如还是花点时间做些让自己真正快乐的事情,希望能够把机器学习学扎实,提前做好面向就业的准备。