关于算法原理可参照:朴素贝叶斯算法原理_简单示例描述
使用 Python3.7 编译,修正了一些语法和格式问题。
训练集为手动输入的小型训练集:
X(特征:词条组成的集合) | Y(Label:是否侮辱性) |
|
0 |
|
1 |
|
0 |
|
1 |
|
0 |
|
1 |
整个程序设计的函数比较多,这里分开来讲解。
首先导入 Numpy(本程序只需要 Numpy):
from numpy import *
创建训练集,并将文本数据进行向量化处理:
#加载训练集
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']]
#标识 6 个数据的 label
# 0 代表正常言论,1 代表侮辱性言论
classVec = [0,1,0,1,0,1]
return postingList,classVec
#汇总词条列表的所有元素并去重
#输入变量 dataSet 为词条列表(训练集特征)的集合
def createVocabList(dataSet):
#创建一个空的 set(Python 的一个数据结构:无序不重复的元素集)
vocabSet = set([])
for document in dataSet:
#遍历 dataSet 的元素,合并到 vocabSet(重复元素自动过滤)
vocabSet = vocabSet | set(document)
#转换为列表形式,返回
return list(vocabSet)
#记录 inputSet 中的元素出现在 vocabList 中的情况
#输入变量 vocabList 为经过去重处理的训练集中所有的单词组成的列表
#输入变量 inputSet 为待统计(其中词汇出现在 vocabList 中情况)的列表
def setOfWords2Vec(vocabList, inputSet):
#创建一个和 vocabList 等长的向量,所有元素置 0
returnVec = [0]*len(vocabList)
#遍历 inputSet,如果出现 vocabList 中包含的单词,则将 returnVec 中对应位置的元素置 1
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else: print("the word: %s is not in my Vocabulary!" % word)
return returnVec
第一个函数生成训练集,第二个函数对训练集处理,生成包含训练集所有单词(不重复)的列表,共 32 个单词:
['problems', 'how', 'dog', 'ate', 'help', 'so', 'him', 'worthless', 'I', 'steak', 'stupid', 'not', 'park', 'my', 'garbage', 'has', 'please', 'take', 'dalmation', 'to', 'posting', 'cute', 'quit', 'flea', 'is', 'mr', 'food', 'licks', 'stop', 'maybe', 'love', 'buying']
Process finished with exit code 0
利用第三个函数处理 6 个训练集数据,可以将训练集向量化(后续对于测试数据也会做同样的处理,生成向量化数据):
[[0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1],
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0]]
Process finished with exit code 0
根据算法公式:
两者分母相同,故只需考虑分子,P(c1)、P(c2)可以直接计算得出,P(w | c1)、P(w | c2) 还需要知道测试数据的向量化表示,若测试数据的单词有 n 个(包含在测试集所有的单词中,且不重复计数),则向量 W 可表示为(w1, w2, ……, wn)。
计算 P(c1)、P(c2) ,并为 P(w | c1)、P(w | c2) 计算中间变量(这部分在 STEP3 接着解释):
#公式:P(ci | w) = P(w | ci) * P(ci) / P(w) ,其中 c1 = 0 、c2 = 1
#根据算法原理,P(c1 | w) 和 P(c2 | w) 的分母 P(w) 相同,不需要计算
#故我们只需要求得 P(w | c1)、P(w | c2)、P(c1)、P(c2) 即可,其中 P(c1) + P(c2) = 1
#输入变量 trainMatrix 为 x1, x2,……, x6 通过 setOfWords2Vec 生成的向量列表
#输入变量 trainCategory 为 label 列表
def trainNB0(trainMatrix,trainCategory):
#向量列表的样本数(6)
numTrainDocs = len(trainMatrix)
#向量列表的每个向量的长度(32)
numWords = len(trainMatrix[0])
#计算 P(c2) ,即 label 为 1 的概率(侮辱性言论)
pAbusive = sum(trainCategory)/float(numTrainDocs)
#初始化所有单词在 p0Num(正常言论)和 p1Num(侮辱性言论)列表的计数为 1
#(根据算法原理,使用贝叶斯估计而不是极大似然估计,所以不初始化为 0 )
p0Num = ones(numWords); p1Num = ones(numWords)
#初始化 p0Denom(正常言论) 和 p1Denom(侮辱性言论)的总数为 2.0
#使用浮点数格式是为了后续直接使用除法
#(不设置为 0.0 以避免可能的分母为 0 的情况)
p0Denom = 2.0; p1Denom = 2.0
#遍历 6 个样本
for i in range(numTrainDocs):
#如果这个样本 label 为 1(侮辱性言论)
if trainCategory[i] == 1:
#更新 p1Num 的向量表
p1Num += trainMatrix[i]
#统计 p1Num 的向量表的总数
p1Denom += sum(trainMatrix[i])
# 如果这个样本 label 为 0(正常言论)
else:
#更新 p0Num 的向量表
p0Num += trainMatrix[i]
#统计 p0Num 的向量表的总数
p0Denom += sum(trainMatrix[i])
#以列表形式记录 P(w1 | c2)、P(w2 | c2)、……、P(wn | c2) 计算所需的中间变量
#(使用对数避免下溢出)
p1Vect = log(p1Num/p1Denom)
#以列表形式记录 P(w1 | c1)、P(w2 | c1)、……、P(wn | c1) 计算所需的中间变量
#(使用对数避免下溢出)
p0Vect = log(p0Num/p0Denom)
#返回 P(w | c1)、P(w | c2) 和 P(c2) 的值
return p0Vect,p1Vect,pAbusive
这里运用到一些数据处理的技巧,对 p0Vect 和 p1Vect 进行了取对数运算,得到的 p0Vect、p1Vect 和 pAbusive 分别为:
[-2.56494936 -3.25809654 -2.56494936 -2.15948425 -2.56494936 -2.56494936
-2.56494936 -3.25809654 -2.56494936 -1.87180218 -3.25809654 -2.56494936
-2.56494936 -2.56494936 -3.25809654 -2.56494936 -3.25809654 -3.25809654
-2.56494936 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654
-3.25809654 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
-2.56494936 -2.56494936]
[-3.04452244 -2.35137526 -3.04452244 -2.35137526 -3.04452244 -3.04452244
-3.04452244 -2.35137526 -3.04452244 -3.04452244 -2.35137526 -3.04452244
-3.04452244 -3.04452244 -1.94591015 -3.04452244 -2.35137526 -2.35137526
-3.04452244 -2.35137526 -2.35137526 -3.04452244 -2.35137526 -2.35137526
-1.65822808 -3.04452244 -1.94591015 -2.35137526 -2.35137526 -3.04452244
-3.04452244 -3.04452244]
0.5
Process finished with exit code 0
输入测试数据的向量化表示,计算结果:
#执行分类任务
#输入变量 vec2Classify 为测试数据的向量化表示
#输入变量 p0Vec、p1Vec、pClass1 为 trainNB0 计算的结果
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
#使用对数运算 P(w1 | c2) * P(w2 | c2) * …… * P(wn | c2) * P(c2)
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
#使用对数运算 P(w1 | c1) * P(w2 | c1) * …… * P(wn | c1) * P(c1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
接着 STEP2 函数的分析:
根据条件独立假设,可得 P(W | ci) = P(w1 | ci) * P(w2 | ci) * …… * P(wn | ci),随意列举一个测试数据 ['love', 'my', 'dalmation'] 的向量化表示 w :
[0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0]
数值为 1 的位表示这个单词包含在内,将 w 与前面生成的 p0Vect 或 p1Vect 相乘,即在数值为 1 对应的位置分别得到 log( P(w1 | ci) )、log( P(w2 | ci) ) 和 log( P(w3 | ci) ) ,其他位全部都置 0。
又因为对数运算 log(a * b) = log(a) + log(b),这里进行 sum 运算,即得到了log( P(w1 | ci) ) + log( P(w2 | ci) ) + log( P(w3 | ci) ),即 log( P(W | ci) )。
再与 log( P(ci) ) 相加,就得到了log( P(w | ci) * P(ci) )。
又因为 log 函数严格单调递增,直接比较即可得到结果。
到这里所有的细节都描述完毕了,调用上述的函数进行测试即可:
#测试函数
def testingNB():
#生成训练集的特征列表和 label 列表
listOPosts,listClasses = loadDataSet()
#生成去重的包含测试集所有单词的列表
myVocabList = createVocabList(listOPosts)
#将测试集数据向量化,先生成一个空列表
trainMat=[]
#为每个测试集数据分别生成向量表达,组合成二维列表
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
#计算 P(w | c1)、P(w | c2) 的中间变量和 P(c2) 的值
p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
#测试"love my dalmation"
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
#测试"stupid garbage"
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
testingNB()
运行结果:
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1
Process finished with exit code 0
整个函数的最微妙的点就在于 P(w | ci) 的计算方式,理解了这个细节,也就理解了完整的程序过程。
朴素贝叶斯算法的一个经典应用。首先讲一下如何将文本段落切分成单词列表。
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M.L.', 'I', 'have', 'ever', 'laid', 'eyes', 'upon.']
以一个语句为例:
mySent = 'This book is the best book on Python or M.L. I have ever laid eyes upon.'
可以使用 Python 自带的 split() 进行切割:
print(mySent.split())
得到:
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M.L.', 'I', 'have', 'ever', 'laid', 'eyes', 'upon.']
这里会将标点符号也作为单词的一部分,显然不行。方法是引入 Python 的正则化模块 re,利用 re.split() 进行分割:
import re
listOfTokens = re.split('\W+', mySent)
print(listOfTokens)
得到:
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M', 'L', 'I', 'have', 'ever', 'laid', 'eyes', 'upon', '']
参数 '\W+' 代表分隔符为除了字母和数字以外的所有字符,这样处理的问题是:
于是进一步改进:
print([tok.lower() for tok in listOfTokens if len(tok) > 2])
得到:
['this', 'book', 'the', 'best', 'book', 'python', 'have', 'ever', 'laid', 'eyes', 'upon']
处理到这个程度,对于本例就足够了!
#数据预处理:文本切分
#文本段落 -> 词列表
def textParse(bigString):
import re
listOfTokens = re.split('\W+', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
将上一个实验的函数拿过来直接用,这里略去注释,不重复讲解了。唯一的区别是加了一个贝叶斯词袋模型,可以用于替换之前使用的贝叶斯词集模型,原理就是对每个单词的统计方式由记录出现与否改为记录出现的次数:
from numpy import *
#以下三个函数:词列表向量化
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)
#朴素贝叶斯词集模型
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
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
#以下两个函数:朴素贝叶斯分类器
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory)/float(numTrainDocs)
p0Num = ones(numWords); p1Num = ones(numWords)
p0Denom = 2.0; p1Denom = 2.0
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 = log(p1Num/p1Denom)
p0Vect = log(p0Num/p0Denom)
return p0Vect,p1Vect,pAbusive
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
这里的数据集有 25 个正样本,25 个负样本,方法是正负样本各随机取 15 个用做训练集,余下的各 10 个用于验证集,这种方法称为交叉验证。
#垃圾邮件测试函数
def spamTest():
docList=[]; classList=[];
#遍历 25 个正样本和 25 个负样本,交替存储
# docList 以二维列表形式储存 50 个样本
# fullText 以向量形式储存 label
for i in range(1,26):
wordList = textParse(open('email/spam/%d.txt' % i, encoding='ISO-8859-1').read())
docList.append(wordList)
classList.append(1)
wordList = textParse(open('email/ham/%d.txt' % i, encoding='ISO-8859-1').read())
docList.append(wordList)
classList.append(0)
#得到所有样本的单词汇总列表(去重)
vocabList = createVocabList(docList)
#分离训练集和验证集
#随机确定分入训练集和测试集的下标,分别保存在 trainingSet 和 testSet 中
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 中; label 保存在 trainClasses 中
trainMat=[]; trainClasses = []
for docIndex in trainingSet:
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
#计算三个概率
p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
#处理验证集
errorCount = 0
for docIndex in testSet:
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount += 1
print("classification error",docList[docIndex])
print('the error rate is: ',float(errorCount)/len(testSet))
用中间生成的数据格式讲解一下,首先是文本切割分别得到类似于第一个实验中的数据集格式。
特征集合很长,这里只显示了第一个文本数据的特征,docList :
[['codeine', '15mg', 'for', '203', 'visa', 'only', 'codeine', 'methylmorphine', 'narcotic', 'opioid', 'pain', 'reliever', 'have', '15mg', '30mg', 'pills', '15mg', 'for', '203', '15mg', 'for', '385', '15mg', 'for', '562', 'visa', 'only'], …… ]
得到的 label 向量(正负样本交叉存储), classList :
[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
所有单词汇总的列表(去重),vocabList :
['hold', 'winter', 'oem', 'julius', '366', 'tent', '15mg', 'had', 'questions', 'bike', 'jay', 'phone', …… ]
接着随机得到的训练集、交叉验证集的下标数组(每次执行可能结果都不同),trainingSet 和 testSet 分别为:
[0, 1, 2, 5, 6, 7, 9, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 24, 25, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 41, 42, 43, 44, 45, 47, 49]
[4, 3, 48, 40, 46, 26, 19, 10, 23, 8]
根据这个下标,创建向量化的训练集,这个数据很长,这里就不贴了,处理到这一步,就和上一个例子方法相同了。
执行程序:
pamTest()
得到结果:
classification error ['yay', 'you', 'both', 'doing', 'fine', 'working', 'mba', 'design', 'strategy', 'cca', 'top', 'art', 'school', 'new', 'program', 'focusing', 'more', 'right', 'brained', 'creative', 'and', 'strategic', 'approach', 'management', 'the', 'way', 'done', 'today']
the error rate is: 0.1
Process finished with exit code 0
过程和前一个实验类似,不过不是读取文档获得数据集,而是通过 RSS 源下载。
将前面编写过的,这里要使用到的函数贴出,这里要导入 feedparser 库,用于解析 RSS 数据:
from numpy import *
import operator
import feedparser
#以下三个函数:词列表向量化
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)
#朴素贝叶斯词袋模型
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)
p0Num = ones(numWords); p1Num = ones(numWords)
p0Denom = 2.0; p1Denom = 2.0
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 = log(p1Num/p1Denom)
p0Vect = log(p0Num/p0Denom)
return p0Vect,p1Vect,pAbusive
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
#数据预处理:文本切分
#文本段落 -> 词列表
def textParse(bigString):
import re
listOfTokens = re.split('\W+', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
测试函数和前一个实验基本相同,区别只有两点:
#通过排序,查找数据集中词频最高的 30 个单词
def calcMostFreq(vocabList,fullText):
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):
docList=[]; classList = []; fullText =[]
#取正负样本数相等的数据集
minLen = min(len(feed1['entries']),len(feed0['entries']))
#遍历数据集
# docList 以二维列表形式保存样本
# classList 以向量形式保存 label
# fullText 以一维列表形式保存样本(不去重)
for i in range(minLen):
wordList = textParse(feed1['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList = textParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
#创建包含样本所有单词的去重的列表
vocabList = createVocabList(docList)
#调用 calcMostFreq 函数,得到出现频率最高的 30 个单词
top30Words = calcMostFreq(vocabList,fullText)
#在所有样本中删除这 30 个单词
for pairW in top30Words:
if pairW[0] in vocabList: vocabList.remove(pairW[0])
#留存交叉验证
trainingSet = list(range(2*minLen)); testSet=[]
for i in range(20):
randIndex = int(random.uniform(0,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
#处理训练集
trainMat=[]; trainClasses = []
for docIndex in trainingSet:
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
#处理验证集
errorCount = 0
for docIndex in testSet:
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount += 1
print('the error rate is: ',float(errorCount)/len(testSet))
return vocabList,p0V,p1V
关于为什么要剔除词频最高的 30 个单词,这个涉及到一些语言学的概念,简单说就是语言中大部分内容都是冗余的、辅助性的,保留这些词还淹没具备区分性的中低频词汇。
执行:
def getTopWords(ny,sf):
vocabList,p0V,p1V=localWords(ny,sf)
topNY=[]; topSF=[]
for i in range(len(p0V)):
if p0V[i] > -4.2 : topSF.append((vocabList[i],p0V[i]))
if p1V[i] > -4.2 : 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])
ny = feedparser.parse('https://newyork.craigslist.org/search/res?format=rss')
sf = feedparser.parse('https://sfbay.craigslist.org/search/apa?format=rss')
getTopWords(ny,sf)
输出结果:
the error rate is: 0.25
SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**
painted
each
commence
31st
teammates
city
expire
friends
wanting
group
may
amenities
ideal
renew
year
where
ability
begin
thereafter
2021
that
NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**
evenings
currently
english
student
writing
review
tailored
willing
teacher
essay
tutoring
need
want
your
students
have
Process finished with exit code 0