本节书摘来异步社区《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书中的第1章,第1.3节,作者:Nitin Hardeniya,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.3 向NLTK迈进
尽管在这里,我们并不打算深入探讨自然语言处理理论,但也会尽快让你实际接触一下NLTK。因此,我打算先介绍一些NLTK的基本用例,这是一个很好的机会,你可以先为今后做类似事情做一些准备。下面,我们会从一个Python程序员习惯的处理方式切入,演示如何用NLTK将该方式转换成一个更为高效、可靠、简洁的解决方案。
我们先来看一个纯文本分析的例子。这个例子是我们要从Python官方主页上摘取部分内容。
>>>import urllib2
>>># urllib2 is use to download the html content of the web link
>>>response = urllib2.urlopen('http://python.org/')
>>># You can read the entire content of a file using read() method
>>>html = response.read()
>>>print len(html)
47020
目前,我们还没有得到任何关于该URL所讨论话题的线索,所以接下来,我们要先做一次探索性数据分析(EDA)。通常对于一段文本域而言,EDA可能包含了多重含义,但这里只会涉及其中的一个简单用例,即该文档的主体术语类型。主题是什么?它们的出现频率如何?整个分析过程还会或多或少地涉及一些预处理层面的步骤。我们会试着先用纯Python的方式来实现它,然后用NLTK再将其实现一次。
我们先要清理掉其中的html标签。一种可行的做法是只选取其中的标记,包括数字和字符。如果之前有在工作中使用过正则表达式,你应该可以轻松地将这些html字符串转换成一个标记列表:
>>># Regular expression based split the string
>>>tokens = [tok for tok in html.split()]
>>>print "Total no of tokens :"+ str(len(tokens))
>>># First 100 tokens
>>>print tokens[0:100]
Total no of tokens :2860
['<!doctype', 'html>', '<!--[if', 'lt', 'IE', '7]>', '<html', 'class="no-
js', 'ie6', 'lt-ie7', 'lt-ie8', 'lt-ie9">', '<![endif]-->', '<!--[if',
'IE', '7]>', '<html', 'class="no-js', 'ie7', 'lt-ie8', 'lt-ie9">',
'<![endif]-->', ''type="text/css"', 'media="not', 'print,', 'braille,'
...]
如你所见,上面列出了我们在处理文本内容时用不到的HTML标签和其他多余字符。当然,这个任务还有个更为简洁的版本:
>>>import re
>>># using the split function
>>>#https://docs.python.org/2/library/re.html
>>>tokens = re.split('\W+',html)
>>>print len(tokens)
>>>print tokens[0:100]
5787
['', 'doctype', 'html', 'if', 'lt', 'IE', '7', 'html', 'class', 'no',
'js', 'ie6', 'lt', 'ie7', 'lt', 'ie8', 'lt', 'ie9', 'endif', 'if',
'IE', '7', 'html', 'class', 'no', 'js', 'ie7', 'lt', 'ie8', 'lt', 'ie9',
'endif', 'if', 'IE', '8', 'msapplication', 'tooltip', 'content', 'The',
'official', 'home', 'of', 'the', 'Python', 'Programming', 'Language',
'meta', 'name', 'apple' ...]
这样看上去已经简洁多了吧?但其实它还可以更简洁一点。在这里,我们所做的努力是尽可能地去除干扰,但那些被清理的HTML标记还是会如雨后春笋般地冒出来,而且我们可能也想以单词长度为标准,删除某一特定长度的单词——如说移除像7、8这样的元素,因为在目前情况下,这些都只是干扰词。现在,我们要做的不是用NLTK来重复相同的任务,完成这些预处理步骤。因为所有的清理工作都可以通过调用clean_html()函数[3]来完成:
>>>import nltk
>>># http://www.nltk.org/api/nltk.html#nltk.util.clean_html
>>>clean = nltk.clean_html(html)
>>># clean will have entire string removing all the html noise
>>>tokens = [tok for tok in clean.split()]
>>>print tokens[:100]
['Welcome', 'to', 'Python.org', 'Skip', 'to', 'content', '& #9660;',
'Close', 'Python', 'PSF', 'Docs', 'PyPI', 'Jobs', 'Community', '& #9650;',
'The', 'Python', 'Network', '≡', 'Menu', 'Arts', 'Business' ...]
很酷吧?而且,这无疑让我们的代码更简洁易行了。
下面再来看看如何获得这些术语的频率分布。当然,我们还是要从纯Python的方式做起,之后再告诉你NLTK的方式。
>>>import operator
>>>freq_dis={}
>>>for tok in tokens:
>>> if tok in freq_dis:
>>> freq_dis[tok]+=1
>>> else:
>>> freq_dis[tok]=1
>>># We want to sort this dictionary on values ( freq in this case )
>>>sorted_freq_dist= sorted(freq_dis.items(), key=operator.itemgetter(1),
reverse=True)
>>>print sorted_freq_dist[:25]
[('Python', 55), ('>>>', 23), ('and', 21), ('to', 18), (',', 18), ('the',
14), ('of', 13), ('for', 12), ('a', 11), ('Events', 11), ('News', 11),
('is', 10), ('2014-', 10), ('More', 9), ('#', 9), ('3', 9), ('=', 8),
('in', 8), ('with', 8), ('Community', 7), ('The', 7), ('Docs', 6),
('Software', 6), (':', 6), ('3:', 5), ('that', 5), ('sum', 5)]
由于目标是Python的官方主页,Python和(>>>)解释器符号自然就成了最常用的术语,这也符合该网站给人的感觉。
当然,这个任务还有一个更好用、也更有效的方法,即调用NLTK中的FreqDist()函数。在此,我们可以来看看调用后前相同代码的比对:
>>>import nltk
>>>Freq_dist_nltk=nltk.FreqDist(tokens)
>>>print Freq_dist_nltk
>>>for k,v in Freq_dist_nltk.items():
>>> print str(k)+':'+str(v)
< FreqDist: 'Python': 55, '>>>': 23, 'and': 21, ',': 18, 'to': 18, 'the':
14, 'of': 13, 'for': 12, 'Events': 11, 'News': 11, ...>
Python:55
>>>:23
and:21
,:18
to:18
the:14
of:13
for:12
Events:11
News:11
注意 小技巧:
下载示例代码
你在http://www.packtpub.com
中登录你的账户,从中可以下载你所购买的、由Packt出版的所有书籍的示例代码。如果你在别处购得此书,也可以在http://www. packtpub.com/support
上注册相关文件,我们会用E-mail将其直接发送给你。
现在,让我们来做一些更时髦的事。我们来绘制这样的一张图,如图1-2所示。
>>>Freq_dist_nltk.plot(50, cumulative=False)
>>># below is the plot for the frequency distributions
在图1-2中,我们可以看到累积频率的即时增长,在某些点上曲线会进入一条长长的尾巴。其中依然存在着一些干扰,有些类似于the、of、for以及=这样的词都是属于无用词,这些词有一个专用术语:停用词。如the、a、an这样的词也都属于停用词。由于冠词、代词在大多数文档中都是普遍存在的,因而对信息的识别没有帮助。在大多数NLP及信息检索任务中,人们通常都会先删除掉这些停用词。下面,让我们再次回到之前运行的那个例子中,绘制结果如图1-3所示。
>>>stopwords=[word.strip().lower() for word in open("PATH/english.stop.
txt")]
>>>clean_tokens=[tok for tok in tokens if len(tok.lower())>1 and (tok.
lower() not in stopwords)]
>>>Freq_dist_nltk=nltk.FreqDist(clean_tokens)
>>>Freq_dist_nltk.plot(50, cumulative=False)
注意 提示:
如果想知道关于词云的更多信息,请访问http://www. wordle.net/advanced
。
现在,代码看起来简洁多了吧!在完成这么多事后,你可以去Wordle网站上将其频率分布以CSV形式显示出来,可以得到如图1-4所示词云图。