【转】"《机器学习实战》之朴素贝叶斯(4)从个人广告中获取区域倾向"



前言:

  书中的最后一个例子,是分别从美国的两个城市中选取一些人,通过分析这些人发布的征婚广告信息,来比较这两个城市的人们在广告用词上是否不同。如果结论确实是不同,那么他们各自的常用词是哪些?从人们的用词当中,我们能否对不同城市的人所关心的内容有所了解?

示例:使用朴素贝叶斯来发现地域相关的用词。

  • 收集数据:从RSS源收集内容,这里需要对RSS创建一个接口。
  • 准备数据:将文本文件解析成词条向量。
  • 分析数据:检查词条确保解析的正确性。
  • 训练算法:使用我们之间建立的trainNB0()函数。
  • 测试算法:观察错误率,确保分类器可用,可以修改切分程序,以降低错误率,提高分类结果。
  • 使用算法:构建一个完整的程序,封装所有内容,给定两个RSS源,该程序会显示最常用的公共词。

  下面我们将使用来自不同城市的广告训练一个分类器,然后观察分类器的效果。注意,我们并不是要使用该分类器进行分类,而是通过观察单词和条件概率值来发现与特定城市相关的内容。

收集数据:导入RSS源

  第一件事当然就是要准备数据,我们可以Python下载文本。利用RSS,这些文本很容易得到。现在所需要的是一个RSS阅读器。Universal Feed Parser是Python中最常用的RSS程序库。你可以在http://code.google.com/p/feedparser/下浏览相关文档,然后和其他Python包一样来安装feedparser,即 pip3 install feedparse。

书中使用的是Craigslist上的个人广告,当然希望是在服务条款允许的条件下。

测试算法:RSS源分类器

  我们还是先来构建一个类似于spamTest()的函数来对测试过程自动化吧。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def calcMostFreq(vocabList, fullText):
"""
Function: 计算出现频率

Args: vocabList:词汇表
fullText:全部词汇

Returns: sortedFreq[:30]:出现频率最高的30个词
"""
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):
"""
Function: RSS源分类器

Args: feed1:RSS源
feed0:RSS源

Returns: vocabList:词汇表
p0V:类别概率向量
p1V:类别概率向量
"""
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)
#切分文本
wordList = textParse(feed0['entries'][i]['summary'])
#切分后的文本以原始列表形式加入文档列表
docList.append(wordList)
#切分后的文本直接合并到词汇列表
fullText.extend(wordList)
#标签列表更新
classList.append(0)
#获得词汇表
vocabList = createVocabList(docList)
#获得30个频率最高的词汇
top30Words = calcMostFreq(vocabList, fullText)
#去掉出现次数最高的那些词
for pairW in top30Words:
if pairW[0] in vocabList: vocabList.remove(pairW[0])
trainingSet = range(2*minLen); testSet = []
#随机构建测试集,随机选取二十个样本作为测试样本,并从训练样本中剔除
for i in range(20):
#随机得到Index
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(array(trainMat), array(trainClasses))
#初始化错误计数
errorCount = 0
#遍历测试集进行测试
for docIndex in testSet:
#词表转换到向量
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
#判断分类结果与原标签是否一致
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
#如果不一致则错误计数加1
errorCount += 1
#并且输出出错的文档
print("classification error",docList[docIndex])
#打印输出信息
print('the erroe rate is: ', float(errorCount)/len(testSet))
#返回词汇表和两个类别概率向量
return vocabList, p0V, p1V

  上述代码类似于spamTest(),只不过添加了新的功能,引入calcMostFreq()来返回出现频率最高的30个单词,再狠心将他们剔除。当我们注释掉移除高频词的三行代码,会发现错误率提高了百分之二十左右。有趣的是,vocalList的大小约为3000个词,前30个高频词却占了30%,也就是说词汇表中的这一小部分单词,占据了所有文本用词的一大部分。产生这种现象的原因是语言中大部分都是冗余和结构辅助性内容,另一个常用的方法不仅移除高频词,同时从某个预定词表中移除结构上的辅助词。该词表为停用词表(stop word list),网上可以找到很多,例如http://www.ranks.nl/resources/stopwords.html。

输出结果:

1
2
3
4
5
6
7
8
9
10
>>> reload(bayes)
<module 'bayes' from 'E:\\机器学习实战\\mycode\\Ch04\\bayes.py'>
>>> ny = feedparser.parse('http://newyork.craiglist.org/stp/index.rss')
>>> sf = feedparser.parse('http://sfbay.craiglist.org/stp/index.rss')
>>> vocabList, pSF, pNY = bayes.localWords(ny, sf)
the erroe rate is: 0.5
>>> vocabList, pSF, pNY = bayes.localWords(ny, sf)
the erroe rate is: 0.35
>>> vocabList, pSF, pNY = bayes.localWords(ny, sf)
the erroe rate is: 0.5

  需要注意的是RSS源要在函数外导入,因为网站上的RSS源会随着时间而改变。
  为了得到错误率的精确估计,应该多次进行上述实验,然后取平均值。这里的错误率很高,但是我们关注的是单词概率而不是实际分类,因此这个问题倒不严重,可以改变要移除的单词数目,然后观察错误率的变化。

分析数据:显示地域性相关单词

  可以先对pSF和pNY进行排序,然后按照顺序将词打印出来。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def getTopWords(ny, sf):
"""
Function: 最具表征性的词汇显示函数

Args: ny:RSS源
sf:RSS源

Returns: 打印信息
"""
import operator
#RSS源分类器返回概率
vocabList, p0V, p1V=localWords(ny, sf)
#初始化列表
topNY = []; topSF = []
#设定阈值,返回大于阈值的所有词,如果输出信息很多,就提高一下阈值
for i in range(len(p0V)):
if p0V[i] > -4.5 : topSF.append((vocabList[i], p0V[i]))
if p1V[i] > -4.5 : 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])

  可以通过调节阈值的大小来改变输出量的多少,记住这里的阈值时取自然对数之后的概率哦,所以是负数。

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> reload(bayes)
<module 'bayes' from 'E:\\机器学习实战\\mycode\\Ch04\\bayes.py'>
>>> bayes.getTopWords(ny,sf)
the erroe rate is: 0.45
SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**
very
let
hello
email
sure
was
enjoy
new
well
dates
walk
NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**
down
them
girl
times
great
what
feet
most
been

  值得注意的是程序输出了大量的停用词,有兴趣的同学可以试着把停用词去掉,然后看看分类错误率会不会降低。

总结:

  对于分类而言,使用概率有时要比使用硬规则更为有效,贝叶斯概率及贝叶斯准则提供了一种利用已知值来估计未知概率的有效方法。尽管条件独立性假设并不正确,但是朴素贝叶斯仍然是一种有效的分类器。

  朴素贝叶斯:

  • 优点:在数据较少的情况下仍然有效,可以处理多类别问题。
  • 缺点:对于输入数据的准备方式较为敏感。
  • 适用数据类型:标称型数据。

系列教程持续发布中,欢迎订阅、关注、收藏、评论、点赞哦~~( ̄▽ ̄~)~

完的汪(∪。∪)。。。zzz