Python中一些自然语言工具的使用的入门教程


Posted in Python onApril 13, 2015

NLTK 是使用 Python 教学以及实践计算语言学的极好工具。此外,计算语言学与人工 智能、语言/专门语言识别、翻译以及语法检查等领域关系密切。
NLTK 包括什么

NLTK 会被自然地看作是具有栈结构的一系列层,这些层构建于彼此基础之上。那些熟悉人工语言(比如 Python)的文法 和解析的读者来说,理解自然语言模型中类似的 —— 但更深奥的 —— 层不会有太大困难。
术语表

全集(Corpora):相关文本的集合。例如,莎士比亚的作品可能被统称为一个 文集(corpus); 而若干个作者的作品称为 全集。

直方图(Histogram):数据集中不同单词、字母或其他条目的出现频率的统计分布。

结构(Syntagmatic):对语段的研究;也就是全集中字母、单词或短语连续出现的统计关系。

上下文无关语法(Context-free grammar): 由四类形式语法构成的 Noam Chomsky 层级中的第二类。参阅 参考资料 以获得 详尽描述。

尽管 NLTK 附带了很多已经预处理(通常是手工地)到不同程度的全集,但是概念上每一层 都是依赖于相邻的更低层次的处理。首先是断词;然后是为单词加上 标签;然后将成组 的单词解析为语法元素,比如名词短语或句子(取决于几种技术中的某一种,每种技术都有其优缺点); 最后对最终语句或其他语法单元进行分类。通过这些步骤,NLTK 让您可以生成关于不同元素出现情况 的统计,并画出描述处理过程本身或统计合计结果的图表。

在本文中,您将看到关于低层能力的一些相对完整的示例,而对大部分高层次能力将只是进行简单抽象的描述。 现在让我们来详细分析文本处理的首要步骤。

断词(Tokenization)

您可以使用 NLTK 完成的很多工作,尤其是低层的工作,与使用 Python 的基本数据结构来完成相比,并 没有 太 大的区别。不过,NLTK 提供了一组由更高的层所依赖和使用的系统化的接口,而不只是 简单地提供实用的类来处理加过标志或加过标签的文本。

具体讲, nltk.tokenizer.Token 类被广泛地用于存储文本的有注解的片断;这些 注解可以标记很多不同的特性,包括词类(parts-of-speech)、子标志(subtoken)结构、一个标志(token) 在更大文本中的偏移位置、语形词干 (morphological stems)、文法语句成分,等等。实际上,一个 Token 是一种 特别的字典 —— 并且以字典形式访问 —— 所以它可以容纳任何您希望的键。在 NLTK 中使用了一些专门的键, 不同的键由不同的子程序包所使用。

让我们来简要地分析一下如何创建一个标志并将其拆分为子标志:
清单 1. 初识 nltk.tokenizer.Token 类

>>> from nltk.tokenizer import *
>>> t = Token(TEXT='This is my first test sentence')
>>> WSTokenizer().tokenize(t, addlocs=True) # break on whitespace
>>> print t['TEXT']
This is my first test sentence
>>> print t['SUBTOKENS']
[<This>@[0:4c], <is>@[5:7c], <my>@[8:10c], <first>@[11:16c],
<test>@[17:21c], <sentence>@[22:30c]]
>>> t['foo'] = 'bar'
>>> t
<TEXT='This is my first test sentence', foo='bar',
SUBTOKENS=[<This>@[0:4c], <is>@[5:7c], <my>@[8:10c], <first>@[11:16c],
<test>@[17:21c], <sentence>@[22:30c]]>
>>> print t['SUBTOKENS'][0]
<This>@[0:4c]
>>> print type(t['SUBTOKENS'][0])
<class 'nltk.token.SafeToken'>

概率(Probability)

对于语言全集,您可能要做的一件相当简单的事情是分析其中各种 事件(events) 的 频率分布,并基于这些已知频率分布做出概率预测。NLTK 支持多种基于自然频率分布数据进行概率预测的方法。 我将不会在这里介绍那些方法(参阅 参考资料 中列出的概率教程), 只要说明您肯定会 期望的那些与您已经 知道的 那些(不止是显而易见的 缩放比例/正规化)之间有着一些模糊的关系就够了。

基本来讲,NLTK 支持两种类型的频率分布:直方图和条件频率分布(conditional frequency)。 nltk.probability.FreqDist 类用于创建直方图;例如, 可以这样创建一个单词直方图:
清单 2. 使用 nltk.probability.FreqDist 创建基本的直方图

>>> from nltk.probability import *
>>> article = Token(TEXT=open('cp-b17.txt').read())
>>> WSTokenizer().tokenize(article)
>>> freq = FreqDist()
>>> for word in article['SUBTOKENS']:
...   freq.inc(word['TEXT'])
>>> freq.B()
1194
>>> freq.count('Python')
12

概率教程讨论了关于更复杂特性的直方图的创建,比如“以元音结尾的词后面的词的长度”。 nltk.draw.plot.Plot 类可用于直方图的可视化显示。当然, 您也可以这样分析高层次语法特性或者甚至是与 NLTK 无关的数据集的频率分布。

条件频率分布可能比普通的直方图更有趣。条件频率分布是一种二维直方图 —— 它按每个初始条件或者“上下文”为您显示 一个直方图。例如,教程提出了一个对应每个首字母的单词长度分布问题。我们就以这样分析:
清单 3. 条件频率分布:对应每个首字母的单词长度

>>> cf = ConditionalFreqDist()
>>> for word in article['SUBTOKENS']:
...   cf[word['TEXT'][0]].inc(len(word['TEXT']))
...
>>> init_letters = cf.conditions()
>>> init_letters.sort()
>>> for c in init_letters[44:50]:
...   print "Init %s:" % c,
...   for length in range(1,6):
...     print "len %d/%.2f," % (length,cf[c].freq(n)),
...   print
...
Init a: len 1/0.03, len 2/0.03, len 3/0.03, len 4/0.03, len 5/0.03,
Init b: len 1/0.12, len 2/0.12, len 3/0.12, len 4/0.12, len 5/0.12,
Init c: len 1/0.06, len 2/0.06, len 3/0.06, len 4/0.06, len 5/0.06,
Init d: len 1/0.06, len 2/0.06, len 3/0.06, len 4/0.06, len 5/0.06,
Init e: len 1/0.18, len 2/0.18, len 3/0.18, len 4/0.18, len 5/0.18,
Init f: len 1/0.25, len 2/0.25, len 3/0.25, len 4/0.25, len 5/0.25,

条件频率分布在语言方面的一个极好应用是分析全集中的语段分布 —— 例如,给出一个特定的 词,接下来最可能出现哪个词。当然,语法会带来一些限制;不过,对句法选项的选择的研究 属于语义学、语用论和术语范畴。

词干提取(Stemming)

nltk.stemmer.porter.PorterStemmer 类是一个用于从英文单词中 获得符合语法的(前缀)词干的极其便利的工具。这一能力尤其让我心动,因为我以前曾经用 Python 创建了一个公用的、全文本索引的 搜索工具/库(见 Developing a full-text indexer in Python 中的描述,它已经用于相当多的其他项目中)。

尽管对大量文档进行关于一组确切词的搜索的能力是非常实用的( gnosis.indexer 所做的工作), 但是,对很多搜索用图而言,稍微有一些模糊将会有所帮助。也许,您不能特别确定您正在寻找的电子邮件是否使用了单词 “complicated”、“complications”、“complicating”或者“complicates”,但您却记得那是大概涉及的内容(可能与其他一些 词共同来完成一次有价值的搜索)。

NLTK 中包括一个用于单词词干提取的极好算法,并且让您可以按您的喜好定制词干提取算法:
清单 4. 为语形根(morphological roots)提取单词词干

>>> from nltk.stemmer.porter import PorterStemmer
>>> PorterStemmer().stem_word('complications')
'complic'

实际上,您可以怎样利用 gnosis.indexer 及其衍生工具或者完全不同的索引工具中的词干 提取功能,取决于您的使用情景。幸运的是,gnosis.indexer 有一个易于进行专门定制的 开放接口。您是否需要一个完全由词干构成的索引?或者您是否在索引中同时包括完整的单词 和词干?您是否需要将结果中的词干匹配从确切匹配中分离出来?在未来版本的 gnosis.indexer 中我将引入一些种类词干的提取能力,不过,最终用户可能仍然希望进行不同的定制。

无论如何,一般来说添加词干提取是非常简单的:首先,通过特别指定 gnosis.indexer.TextSplitter 来从一个文档中获得词干;然后, 当然执行搜索时,(可选地)在使用搜索条件进行索引查找之前提取其词干,可能是通过定制 您的 MyIndexer.find() 方法来实现。

在使用 PorterStemmer 时我发现 nltk.tokenizer.WSTokenizer 类确实如教程所警告的那样不好用。它可以胜任概念上的角色,但是对于实际的文本而言,您可以更好地识别出什么是一个 “单词”。幸运的是, gnosis.indexer.TextSplitter 是一个健壮的断词工具。例如:
清单 5. 基于拙劣的 NLTK 断词工具进行词干提取

>>> from nltk.tokenizer import *
>>> article = Token(TEXT=open('cp-b17.txt').read())
>>> WSTokenizer().tokenize(article)
>>> from nltk.probability import *
>>> from nltk.stemmer.porter import *
>>> stemmer = PorterStemmer()
>>> stems = FreqDist()
>>> for word in article['SUBTOKENS']:
...   stemmer.stem(word)
...   stems.inc(word['STEM'].lower())
...
>>> word_stems = stems.samples()
>>> word_stems.sort()
>>> word_stems[20:40]
['"generator-bas', '"implement', '"lazili', '"magic"', '"partial',
'"pluggable"', '"primitives"', '"repres', '"secur', '"semi-coroutines."',
'"state', '"understand', '"weightless', '"whatev', '#', '#-----',
'#----------', '#-------------', '#---------------', '#b17:']

查看一些词干,集合中的词干看起来并不是都可用于索引。很多根本不是实际的单词,还有其他一些是 用破折号连接起来的组合词,单词中还被加入了一些不相干的标点符号。让我们使用更好的断词工具 来进行尝试:
清单 6. 使用断词工具中灵巧的启发式方法来进行词干提取

>>> article = TS().text_splitter(open('cp-b17.txt').read())
>>> stems = FreqDist()
>>> for word in article:
...   stems.inc(stemmer.stem_word(word.lower()))
...
>>> word_stems = stems.samples()
>>> word_stems.sort()
>>> word_stems[60:80]
['bool', 'both', 'boundari', 'brain', 'bring', 'built', 'but', 'byte',
'call', 'can', 'cannot', 'capabl', 'capit', 'carri', 'case', 'cast',
'certain', 'certainli', 'chang', 'charm']

在这里,您可以看到有一些单词有多个可能的扩展,而且所有单词看起来都像是单词或者词素。 断词方法对随机文本集合来说至关重要;公平地讲,NLTK 捆绑的全集已经通过 WSTokenizer() 打包为易用且准确的断词工具。要获得健壮的实际可用的索引器,需要使用健壮的断词工具。

添加标签(tagging)、分块(chunking)和解析(parsing)

NLTK 的最大部分由复杂程度各不相同的各种解析器构成。在很大程度上,本篇介绍将不会 解释它们的细节,不过,我愿意大概介绍一下它们要达成什么目的。

不要忘记标志是特殊的字典这一背景 —— 具体说是那些可以包含一个 TAG 键以指明单词的语法角色的标志。NLTK 全集文档通常有部分专门语言已经预先添加了标签,不过,您当然可以 将您自己的标签添加到没有加标签的文档。

分块有些类似于“粗略解析”。也就是说,分块工作的进行,或者基于语法成分的已有标志,或者基于 您手工添加的或者使用正则表达式和程序逻辑半自动生成的标志。不过,确切地说,这不是真正的解析 (没有同样的生成规则)。例如:
清单 7. 分块解析/添加标签:单词和更大的单位

>>> from nltk.parser.chunk import ChunkedTaggedTokenizer
>>> chunked = "[ the/DT little/JJ cat/NN ] sat/VBD on/IN [ the/DT mat/NN ]"
>>> sentence = Token(TEXT=chunked)
>>> tokenizer = ChunkedTaggedTokenizer(chunk_node='NP')
>>> tokenizer.tokenize(sentence)
>>> sentence['SUBTOKENS'][0]
(NP: <the/DT> <little/JJ> <cat/NN>)
>>> sentence['SUBTOKENS'][0]['NODE']
'NP'
>>> sentence['SUBTOKENS'][0]['CHILDREN'][0]
<the/DT>
>>> sentence['SUBTOKENS'][0]['CHILDREN'][0]['TAG']
'DT'
>>> chunk_structure = TreeToken(NODE='S', CHILDREN=sentence['SUBTOKENS'])
(S:
 (NP: <the/DT> <little/JJ> <cat/NN>)
 <sat/VBD>
 <on/IN>
 (NP: <the/DT> <mat/NN>))

所提及的分块工作可以由 nltk.tokenizer.RegexpChunkParser 类使用伪正则表达式来描述 构成语法元素的一系列标签来完成。这里是概率教程中的一个例子:
清单 8. 使用标签上的正则表达式进行分块

>>> rule1 = ChunkRule('<DT>?<JJ.*>*<NN.*>',
...        'Chunk optional det, zero or more adj, and a noun')
>>> chunkparser = RegexpChunkParser([rule1], chunk_node='NP', top_node='S')
>>> chunkparser.parse(sentence)
>>> print sent['TREE']
(S: (NP: <the/DT> <little/JJ> <cat/NN>)
 <sat/VBD> <on/IN>
 (NP: <the/DT> <mat/NN>))

真正的解析将引领我们进入很多理论领域。例如,top-down 解析器可以确保找到每一个可能的产品,但 可能会非常慢,因为要频繁地(指数级)进行回溯。Shift-reduce 效率更高,但是可能会错过一些产品。 不论在哪种情况下,语法规则的声明都类似于解析人工语言的语法声明。本专栏曾经介绍了其中的一些: SimpleParse 、 mx.TextTools 、 Spark 和 gnosis.xml.validity (参阅 参考资料)。

甚至,除了 top-down 和 shift-reduce 解析器以外,NLTK 还提供了“chart 解析器”,它可以创建部分假定, 这样一个给定的序列就可以继而完成一个规则。这种方法可以是既有效又完全的。举一个生动的(玩具级的)例子:
清单 9. 为上下文无关语法定义基本的产品

>>> from nltk.parser.chart import *
>>> grammar = CFG.parse('''
...  S -> NP VP
...  VP -> V NP | VP PP
...  V -> "saw" | "ate"
...  NP -> "John" | "Mary" | "Bob" | Det N | NP PP
...  Det -> "a" | "an" | "the" | "my"
...  N -> "dog" | "cat" | "cookie"
...  PP -> P NP
...  P -> "on" | "by" | "with"
...  ''')
>>> sentence = Token(TEXT='John saw a cat with my cookie')
>>> WSTokenizer().tokenize(sentence)
>>> parser = ChartParser(grammar, BU_STRATEGY, LEAF='TEXT')
>>> parser.parse_n(sentence)
>>> for tree in sentence['TREES']: print tree
(S:
 (NP: <John>)
 (VP:
  (VP: (V: <saw>) (NP: (Det: <a>) (N: <cat>)))
  (PP: (P: <with>) (NP: (Det: <my>) (N: <cookie>)))))
(S:
 (NP: <John>)
 (VP:
  (V: <saw>)
  (NP:
   (NP: (Det: <a>) (N: <cat>))
   (PP: (P: <with>) (NP: (Det: <my>) (N: <cookie>))))))

probabilistic context-free grammar(或者说是 PCFG)是一种上下文无关语法, 它将其每一个产品关联到一个概率。同样,用于概率解析的解析器也捆绑到了 NLTK 中。

您在等待什么?

NLTK 还有其他本篇简短介绍中不能涵盖的重要功能。例如,NLTK 有一个完整的框架,用于通过类似于“naive Bayesian” 和“maximum entropy”等模型的统计技术进行文本分类。 即使还有篇幅,现在我也还不能解释其本质。不过,我认为,即使是 NLTK 较低的层,也可以成为一个既可用于教学应用程序 也可用于实际应用程序的实用框架。

Python 相关文章推荐
python不带重复的全排列代码
Aug 13 Python
python支持断点续传的多线程下载示例
Jan 16 Python
python实现下载文件的三种方法
Feb 09 Python
Python中的命令行参数解析工具之docopt详解
Mar 27 Python
Python中使用支持向量机SVM实践
Dec 27 Python
Python读取properties配置文件操作示例
Mar 29 Python
python创建文件备份的脚本
Sep 11 Python
使用PM2+nginx部署python项目的方法示例
Nov 07 Python
简单了解Django应用app及分布式路由
Jul 24 Python
详解python3 GUI刷屏器(附源码)
Feb 18 Python
Python基础之进程详解
May 21 Python
python之PySide2安装使用及QT Designer UI设计案例教程
Jul 26 Python
用Python的SimPy库简化复杂的编程模型的介绍
Apr 13 #Python
Python中用Decorator来简化元编程的教程
Apr 13 #Python
在Python的setuptools框架下生成egg的教程
Apr 13 #Python
简单介绍Python中的RSS处理
Apr 13 #Python
Python2.x和3.x下maketrans与translate函数使用上的不同
Apr 13 #Python
使用Pyrex来扩展和加速Python程序的教程
Apr 13 #Python
在Python中使用itertools模块中的组合函数的教程
Apr 13 #Python
You might like
用PHP 4.2书写安全的脚本
2006/10/09 PHP
php下载远程文件类(支持断点续传)
2008/11/14 PHP
php页面跳转代码 输入网址跳转到你定义的页面
2013/03/28 PHP
ThinkPHP自定义函数解决模板标签加减运算的方法
2015/07/03 PHP
PHP类和对象相关系统函数与运算符小结
2016/09/28 PHP
Symfony2创建基于域名的路由相关示例
2016/11/14 PHP
Prototype Selector对象学习
2009/07/23 Javascript
jquery中dom操作和事件的实例学习-表单验证
2011/11/30 Javascript
javascript中checkbox使用方法简单实例演示
2015/11/17 Javascript
AngularJS使用ngMessages进行表单验证
2015/12/27 Javascript
JS控制静态页面传递参数并获取参数应用
2016/08/10 Javascript
微信小程序 教程之数据绑定
2016/10/18 Javascript
JS生成一维码(条形码)功能示例
2017/01/19 Javascript
非常实用的vue导航钩子
2017/03/20 Javascript
解决vue-router中的query动态传参问题
2018/03/20 Javascript
Vue插件打包与发布的方法示例
2018/08/20 Javascript
vue elementUI 表单校验功能之数组多层嵌套
2019/06/04 Javascript
用webpack4开发小程序的实现方法
2019/06/04 Javascript
Vue通过for循环随机生成不同的颜色或随机数的实例
2019/11/09 Javascript
基于VUE实现判断设备是PC还是移动端
2020/07/03 Javascript
vue render函数动态加载img的src路径操作
2020/10/26 Javascript
Python描述器descriptor详解
2015/02/03 Python
Python面向对象类的继承实例详解
2018/06/27 Python
python3 mmh3安装及使用方法
2019/10/09 Python
Flask 上传自定义头像的实例详解
2020/01/09 Python
TensorFlow:将ckpt文件固化成pb文件教程
2020/02/11 Python
HTML5+Canvas+CSS3实现齐天大圣孙悟空腾云驾雾效果
2016/04/26 HTML / CSS
美国最大的在线水培用品商店:GrowersHouse.com
2018/08/14 全球购物
全球性的在线婚纱礼服工厂:27dress.com
2019/03/21 全球购物
人力资源经理的岗位职责范本
2014/02/28 职场文书
工会换届选举方案
2014/05/21 职场文书
供电工程专业求职信
2014/08/09 职场文书
王金山在党的群众路线教育实践活动总结大会上的讲话稿
2014/10/25 职场文书
群众路线教育实践活动学习笔记内容
2014/11/06 职场文书
2015年幼儿园大班工作总结
2015/04/25 职场文书
SpringBoot工程下使用OpenFeign的坑及解决
2021/07/02 Java/Android