下面会介绍和实现一些用于理解文本语法和结构的概念和技术。这些算法在 NLP 中很是有用,它一般在文本处理和标准化以后执行。主要关注一下技术:php
文章的做者针对读者是文本分析实践人员,能够执行并住处在实际问题中使用技术和算法的最佳解决方案。因此,下面将介绍利用现有库(如 nltk 和 spacy)来实现和执行一些技术的最佳方法。此外因为许多读者可能对技术的内部构建感兴趣,而且可能会尝试本身实现部分技术,也会介绍如何作到这一点。请记住,主要关注的是以实际的例子来研究实现概念的方法,而不是重写方法。html
下面是所须要的依赖库:java
若是以为 nltk 安装包可能依赖的过多,及其繁琐的下载,可执行下面代码所有如今:node
In [
94
]:
import
nltk
In [
95
]: nltk.download(
"all"
, halt_on_error
=
False
)
|
安装 pattern 库,请执行:python
$ pip
install
pattern
|
下载并安装及其必要的依赖项。git
对于 spacy 库,须要先安装软件包,而后单独安装及其依赖项(也称为语言模型)。请在终端执行:github
$ pip
install
spacy
|
安装完成后,请使用命令:正则表达式
$ python -m spacy download en
|
从终端下载英文语言模型(大约 500MB),存储与 spacy 包的根目录下。更多详情,参见:https://spacy.io/models/,其上包含 spacy 库的使用说明。算法
斯坦福分析器是由斯坦福大学开发的基于 Java 的语言分析器,它可以帮助咱们解析句子以了解底层结构。咱们将使用斯坦福分析器和 nltk 来执行基于依存关系的解析以及基于成分结构的分析。nltk 提供了一个出色的封装,它可与利用 Python 自己的分析器,于是无需在 Java 中变成,能够从:https://github.com/nltk/nltk/wiki/Installing-Third-Party-Software ,其上介绍了如何下载和安装斯坦福分析器并将其 nltk 集成。bash
Graphviz 并非一个必要库,仅仅使用它来查看斯坦福分析器生成的依存关系分析树。能够从其官方网站 http://www.graphvize.org/download 下载并安装 Graphviz 库。而后,安装 pygraphviz,能够根据本身的系统架构和 Python 版本从 https://www.lfd.uci.edu/~gohlke/pythonlibs/#pygraphviz 网站上的而下载 wheel 文件。接下来,使用命令
$ pip
install
pygraphviz-1.3.1.._amd64.whl
|
安装它(适用于 64 位系统 Python2.7.x 环境)。安装完成后 pygraphviz 就能够正常工做了。可能有其余安装过程当中遇到的问题,如执行如下代码段:
$ pip
install
pydot-ng
$ pip
install
graphviz
|
利用一些与先构建好的标签其来训练本身的标签器。为了更好理解实现过程,必须知道以下与数据分析和机器学习相关的重要概念。
知道了这些术语应该知足如下学习的基本内容。
词性(POS)是基于语法语境和词语做用的具体词汇分类。主要的 POS,包括名词、动词形容词和副词。对单词进行分类并标记 POS 标签称为词性标注或 POS 标注。POS 标签用于标注单词并描述其词性,当须要在基于 NPL 的程序中使用注释文本时,这是很是有用的,由于能够经过特定的词性过滤数据并利用该信息来执行具体的分析,例如将词汇范围缩小至名词,分析哪些是最冲突的词语,消除分歧并进行语法分析。
下面将使用 Penn Treebank 进行 POS 标注。能够在 http://www.cis.uni-muenchen.de/schmid/tools/TreeTagger/data/Penn-Treebank-Tagset.pdf 中找到关于各类 POD 标签及其标注的更多信息,其上包含了详细的说明文档,举例说明了每一项标签。Penn Treebank 项目是宾夕法尼亚大学的一个项目,该项目网站 http://www.cis.upenn.edu/index.php 提供了更多的相关信息。目前,有各类各样的标签以知足不一样的应用场景,例如 POS 标签是分配给单词标记词性的标签,语块标签一般是分配给短语的标签,还一些标签时用于描述关系的次级标签。下表给出了词性标签的详细描述机器示例,若是不想花费力气请查看 Penn Treebank 标签的详细文档,能够随时用它做为参考,以便更好的理解 POS 标签和分析树:
序号
|
TAG
|
描述
|
示例
|
---|---|---|---|
1 | CC | 条件链接词 | and, or |
2 | CD | 基本数量词 | dive, one, 2 |
3 | DT | 限定词 | a, the |
4 | EX | 存在量词 | there were two cars |
5 | FW | 外来词 | d'hoevre, mais |
6 | IN | 介词/从句链接词 | of, in, on, that |
7 | JJ | 形容词 | quick, lazy |
8 | JJR | 比较级形容词 | quicker, laziest |
9 | JJS | 最高级形容词 | quickest, laziest |
10 | LS | 列表项标记符 | 2) |
11 | MD | 情态动词 | could, should |
12 | NN | 单数或不可数名词 | fox, dog |
13 | NNS | 复数名词 | foxes, dogs |
14 | NNP | 专有名词单数 | John, Alice |
15 | NNPS | 专有名词复数 | Vikings, Indians, Germans |
16 | PDT | 前置限定词 | both the cats |
17 | POS | 全部格 | boss's |
18 | PRP | 人称代词 | me, you |
19 | PRP$ | 全部格代词 | our, my, your |
20 | RB | 副词 | naturally, extremely, hardly |
21 | RBR | 比较级副词 | better |
22 | RBS | 最高级级副词 | best |
23 | RP | 副词小品词 | about, up |
24 | SYM | 符号 | %, $ |
25 | TO | 不定词 | how to, what to do |
26 | UH | 感叹词 | oh, gosh, wow |
27 | VB | 动词原形 | run, give |
28 | VBD | 动词过去式 | ran, gave |
29 | VBG | 动名词 | running, giving |
30 | VBN | 动词过去分词 | given |
31 | VBP | 动词非第三人称通常如今时 | I think, I take |
32 | VBZ | 动词第三人称通常如今时 | he thinks, he takes |
33 | WDT | WH 限定词 | which, whatever |
34 | WP | WH 人称代词 | who, what |
35 | WP$ | WH 物主代词 | whose |
36 | WRB | WH 副词 | where, when |
37 | NP | 名词短语 | the brown fox |
38 | PP | 介词短语 | in between, over the dog |
39 | VP | 动词短语 | was jumping |
40 | ADJP | 形容词短语 | warm and snug |
41 | ADVP | 副词短语 | also |
42 | SBAR | 主从句链接词 | whether or not |
43 | PRT | 小品词 | up |
44 | INTJ | 语气词 | hello |
45 | PNP | 介词名词短语 | over the dog, as of today |
46 | -SBJ | 主句 | the fox jumped over the dog |
47 | -OBJ | 从句 | the fox jumped over the dog |
该表显示了 Penn Treebank 中主要的 POS 标签集,也是各种文本分析和天然语言处理程序中使用最普遍的 POS 标签集合。
这里讨论一些标记句子的推荐方法。第一种方法是使用 nltk 推荐的 post_tag() 函数,它基于 Penn Treebank。如下代码段展现了使用 nltk 获取句子 POS 标签的方法:
In [
96
]: sentence
=
'The brown fox is quick and he is jumping over the lazy dog'
In [
97
]:
# recommended tagger based on PTB
In [
98
]:
import
nltk
In [
99
]: tokens
=
nltk.word_tokenize(sentence)
In [
100
]: tagged_sent
=
nltk.pos_tag(tokens, tagset
=
'universal'
)
In [
101
]:
print
(tagged_sent)
[(
'The'
,
'DET'
), (
'brown'
,
'ADJ'
), (
'fox'
,
'NOUN'
), (
'is'
,
'VERB'
), (
'quick'
,
'ADJ'
), (
'and'
,
'CONJ'
), (
'he'
,
'PRON'
), (
'is'
,
'VERB'
), (
'jumping'
,
'VERB'
), (
'over'
,
'ADP'
), (
'the'
,
'DET'
), (
'lazy'
,
'ADJ'
), (
'dog'
,
'NOUN'
)]
|
上面的输出显示了句子中每一个单词的 POS 标签,能够发现其与上表的标签很是类似。其中一些做为通用/广泛标签也在前面提过。还可使用 pattern 模块经过如下代码获取句子的 POS 标签:
In [
102
]:
from
pattern.en
import
tag
In [
103
]: tagged_sent
=
tag(sentence)
In [
104
]:
print
(tagged_sent)
[(
'The'
,
'DT'
), (
'brown'
,
'JJ'
), (
'fox'
,
'NN'
), (
'is'
,
'VBZ'
), (
'quick'
,
'JJ'
), (
'and'
,
'CC'
), (
'he'
,
'PRP'
), (
'is'
,
'VBZ'
), (
'jumping'
,
'VBG'
), (
'over'
,
'IN'
), (
'the'
,
'DT'
), (
'lazy'
,
'JJ'
), (
'dog'
,
'NN'
)]
|
该输出提过了严格遵循 Penn Treebank 格式的标签,指出了形容词,名词或动词并给除了详细信息。
下面将探讨一些构建本身的 POS 标签器的技术,并利用 nltk 提过的一些类来实现它们。为了评估标签器性能,会使用 nltk 中 treebank 语料库的一些测试数据。还将使用一些训练数据来训练标签器。首先,经过读取已标记的 treebank 语料库,能够得到训练和评估标签器的必要数据:
In [
135
]:
from
nltk.corpus
import
treebank
In [
136
]: data
=
treebank.tagged_sents()
In [
137
]: train_data
=
data[:
3500
]
In [
138
]: test_data
=
data[
3500
:]
In [
139
]:
print
(train_data[
0
])
[(
'Pierre'
,
'NNP'
), (
'Vinken'
,
'NNP'
), (
','
,
','
), (
'61'
,
'CD'
), (
'years'
,
'NNS'
), (
'old'
,
'JJ'
), (
','
,
','
), (
'will'
,
'MD'
), (
'join'
,
'VB'
), (
'the'
,
'DT'
), (
'board'
,
'NN'
), (
'as'
,
'IN'
), (
'a'
,
'DT'
), (
'nonexecutive'
,
'JJ'
), (
'director'
,
'NN'
), (
'Nov.'
,
'NNP'
), (
'29'
,
'CD'
), (
'.'
,
'.'
)]
|
In [
140
]: tokens
=
nltk.word_tokenize(sentence)
In [
141
]:
print
(tokens)
[
'The'
,
'brown'
,
'fox'
,
'is'
,
'quick'
,
'and'
,
'he'
,
'is'
,
'jumping'
,
'over'
,
'the'
,
'lazy'
,
'dog'
]
|
将使用测试数据来评估标签器,并使用例句的标识做为输入来验证标签器的工做效果。在 nltk 中使用的全部标签器均来自 nltk.tag 包。每一个标签器都是基类 TaggerI 类的子类,而且每一个相同单词标签器都执行一个 tag() 函数,它将一个句子的标签列表做为输入,返回带有 POS 标签的相同单词列表做为输出。除了标记外,还有一个 evaluate() 函数用于评估标签器的性能。它铜鼓标记每一个输入测试语句,而后将输出结果与句子的实际标签进行对比来完成评论。下面将使用该函数来测试咱们的标签器在 test_data 上的性能。
首先,看看从 SequentialBackoffTagger 基类集成的 DefaultTagger,并为每一个单词分配相同的用户输入 POS 标签。这看起来可能很简单,但它是构建 POS 标签器基准的好方法:
In [
142
]:
from
nltk.tag
import
DefaultTagger
In [
143
]: dt
=
DefaultTagger(
'NN'
)
In [
144
]:
print
(dt.evaluate(test_data))
0.1454158195372253
In [
145
]:
print
(dt.tag(tokens))
[(
'The'
,
'NN'
), (
'brown'
,
'NN'
), (
'fox'
,
'NN'
), (
'is'
,
'NN'
), (
'quick'
,
'NN'
), (
'and'
,
'NN'
), (
'he'
,
'NN'
), (
'is'
,
'NN'
), (
'jumping'
,
'NN'
), (
'over'
,
'NN'
), (
'the'
,
'NN'
), (
'lazy'
,
'NN'
), (
'dog'
,
'NN'
)]
|
从上面的输出能够看出,在数库(treebank)测试数据集中,已经得到了 14% 的单词正确标记率,这个结果并非很理想,而且正如预期的那样,例句中的输出标记都为名词,由于给标签器输入的都是相同的标签。
如今,将使用正则表达式和 RegexpTagger 来尝试构建一个性能更好的标签器:
# regex tagger
from
nltk.tag
import
RegexpTagger
# define regex tag patterns
patterns
=
[
(r
'.*ing$'
,
'VBG'
),
# gerunds
(r
'.*ed$'
,
'VBD'
),
# simple past
(r
'.*es$'
,
'VBZ'
),
# 3rd singular present
(r
'.*ould$'
,
'MD'
),
# modals
(r
'.*\'s$'
,
'NN$'
),
# possessive nouns
(r
'.*s$'
,
'NNS'
),
# plural nouns
(r
'^-?[0-9]+(.[0-9]+)?$'
,
'CD'
),
# cardinal numbers
(r
'.*'
,
'NN'
)
# nouns (default) ...
]
rt
=
RegexpTagger(patterns)
|
In [
161
]:
print
(rt.evaluate(test_data))
0.24039113176493368
In [
162
]:
print
(rt.tag(tokens))
[(
'The'
,
'NN'
), (
'brown'
,
'NN'
), (
'fox'
,
'NN'
), (
'is'
,
'NNS'
), (
'quick'
,
'NN'
), (
'and'
,
'NN'
), (
'he'
,
'NN'
), (
'is'
,
'NNS'
), (
'jumping'
,
'VBG'
), (
'over'
,
'NN'
), (
'the'
,
'NN'
), (
'lazy'
,
'NN'
), (
'dog'
,
'NN'
)]
|
该输出显示如今的标准率已经达到了 24%,应该能够作的更好,如今将训练一些 n 元分词标签器。n 元分词是来自文本序列或语音序列的 n 个连续想。这些项能够由单词、音素、字母、字符或音节组成。Shingles 是只包含单词的 n 元分词。将使用大小为 一、2 和 3 的 n 元分词,它们分别也称为一元分词(unigrarn)、二元分词(bigram)和三元分词(trigram)。UnigramTagger、BigramTagger 和 TrigramTagger 继承自基类 NgramTagger,NGramTagger 类则集成自 ContextTager 类,该类又集成自 SequentialBackoffTagger 类。将使用 train_data 做为训练数据,根据语句标识机器 POS 标签来训练 n 元分词标签器。而后将在 test_data 上评估训练后的标签器,并查看语句的标签结果。
## N gram taggers
from
nltk.tag
import
UnigramTagger
from
nltk.tag
import
BigramTagger
from
nltk.tag
import
TrigramTagger
ut
=
UnigramTagger(train_data)
bt
=
BigramTagger(train_data)
tt
=
TrigramTagger(train_data)
|
In [
170
]:
print
(ut.evaluate(test_data))
0.860683512440701
In [
171
]:
print
(ut.tag(tokens))
[(
'The'
,
'DT'
), (
'brown'
,
None
), (
'fox'
,
None
), (
'is'
,
'VBZ'
), (
'quick'
,
'JJ'
), (
'and'
,
'CC'
), (
'he'
,
'PRP'
), (
'is'
,
'VBZ'
), (
'jumping'
,
'VBG'
), (
'over'
,
'IN'
), (
'the'
,
'DT'
), (
'lazy'
,
None
), (
'dog'
,
None
)]
|
In [
172
]:
print
(bt.evaluate(test_data))
0.13486300706747992
In [
173
]:
print
(bt.tag(tokens))
[(
'The'
,
'DT'
), (
'brown'
,
None
), (
'fox'
,
None
), (
'is'
,
None
), (
'quick'
,
None
), (
'and'
,
None
), (
'he'
,
None
), (
'is'
,
None
), (
'jumping'
,
None
), (
'over'
,
None
), (
'the'
,
None
), (
'lazy'
,
None
), (
'dog'
,
None
)]
|
In [
174
]:
print
(tt.evaluate(test_data))
0.08084035240584761
In [
175
]:
print
(tt.tag(tokens))
[(
'The'
,
'DT'
), (
'brown'
,
None
), (
'fox'
,
None
), (
'is'
,
None
), (
'quick'
,
None
), (
'and'
,
None
), (
'he'
,
None
), (
'is'
,
None
), (
'jumping'
,
None
), (
'over'
,
None
), (
'the'
,
None
), (
'lazy'
,
None
), (
'dog'
,
None
)]
|
上面的输出清楚的说明,仅使用 UnigramTagger 标签器就能够在测试集上得到 86% 的准确率,这个结果与前一个标签器相对要好不少。标签 None 表示标签器没法标记该单词,由于它在训练数据中未能获取相似的标识。二元分词和三元分词模式的准确性远不及一元分词模型,由于在训练数据中观察到的二元词组和三元词组不必定在测试数据中以相同的方式出现。
如今,经过建立一个包含标签列表的组合标签器以及使用 backoff 标签器,将尝试组合运用全部的标签器。本质上,将建立一个标签器链,对于每个标签器,若是它不能标记输入的标识,则标签器的下一步将会推出到 backoff 标签器:
def
combined_tagger(train_data, taggers, backoff
=
None
):
for
tagger
in
taggers:
backoff
=
tagger(train_data, backoff
=
backoff)
return
backoff
ct
=
combined_tagger(train_data
=
train_data,
taggers
=
[UnigramTagger, BigramTagger, TrigramTagger],
backoff
=
rt)
|
In [
181
]:
print
(ct.evaluate(test_data))
0.909768612644012
In [
182
]:
print
(ct.tag(tokens))
[(
'The'
,
'DT'
), (
'brown'
,
'NN'
), (
'fox'
,
'NN'
), (
'is'
,
'VBZ'
), (
'quick'
,
'JJ'
), (
'and'
,
'CC'
), (
'he'
,
'PRP'
), (
'is'
,
'VBZ'
), (
'jumping'
,
'VBG'
), (
'over'
,
'IN'
), (
'the'
,
'DT'
), (
'lazy'
,
'NN'
), (
'dog'
,
'NN'
)]
|
如今在测试数据上得到了 91% 的准确率,效果很是好。另外也看到,这个新标签器可以成功的标记例句中的全部标识(即便它们中一些不正确,好比 brown 应该是一个形容词)。
对于最终的标签器,将使用有监督的分类算法来训练它们。ClassfierBasedPOSTTagger 类使咱们可以使用 classifier_builder 参数中的有监督机器学习算法来训练标签器。该类继承自 classifierBasedTagger,并拥有构成训练过程核心部分的 feature_detector() 函数。该函数用于从训练数据(如单词。前一个单词、标签、前一个标签,大小写等)中生成各类特征。实际上,在实例化 ClassifierBasedPOSTagger 类对象时,也能够构建本身的特征检测器函数,将其床底给 feature_detector 参数。在这里,使用的分类器是 NaiveBayesClassifier,它使用贝叶斯定理构建几率分类器,假设特征之间是独立的。相关算法超出了讨论范围,想了解够多,请参见:https://en.wikipedia.org/wiki/Naive_Bayes_classifier
如下代码段展现了如何基于分类方法构建 POS 标签器并对其进行评估:
from
nltk.classify
import
NaiveBayesClassifier, MaxentClassifier
from
nltk.tag.sequential
import
ClassifierBasedPOSTagger
nbt
=
ClassifierBasedPOSTagger(train
=
train_data,
classifier_builder
=
NaiveBayesClassifier.train)
|
In [
14
]:
print
(nbt.evaluate(test_data))
print
()^[[Db0.
9306806079969019
In [
15
]:
print
(nbt.tag(tokens))
[(
'The'
,
'DT'
), (
'brown'
,
'JJ'
), (
'fox'
,
'NN'
), (
'is'
,
'VBZ'
), (
'quick'
,
'JJ'
), (
'and'
,
'CC'
), (
'he'
,
'PRP'
), (
'is'
,
'VBZ'
), (
'jumping'
,
'VBG'
), (
'over'
,
'IN'
), (
'the'
,
'DT'
), (
'lazy'
,
'JJ'
), (
'dog'
,
'VBG'
)]
|
使用上面的标签器,在检测数据上的准确率达到了 93%,这在全部的标签器中是最高的。此外,若是仔细观察例句的输出标签,会发现它们不只仅是正确的,而且是彻底合理的。基于分类器的 POS 标签器是多么强大和有效。也能够尝试使用其余的分类器,如 MaxentClassifier,并将其性能与此标签器性能进行比较。此外,还有几种使用 nltk 和其余程序包构建或使用 POS 标签器的方法。以上内容应该知足对于 POS 标签器的需求。
浅层分析(shallow parsing)也称为浅分析(light parsing)或块分析(chunking),是将它们组合成更高级的短语。在浅层分析中,主要的关注焦点是识别这些短语或语块,而不是挖掘每一个块内语法和语句关系的深层细节,正如在基于深度分析得到的分析树中看到的。浅层分析的主要目的是得到语义上有意义的短语,并观察它们之间的关系。
接下来,姜葱一些值得推荐的、简单易用的浅层分析器开始,研究进行浅层分析的各类方法。还将使用如正则表达式、分块、加缝隙和基于标签的训练等技术,来实现本身的浅层分析器。
在这里,将使用 pattern 包建立一个浅层分析器,用以从句子中提取有意义的语块。如下代码段展现了如何在例句上执行浅层分析:
from
pattern.en
import
parsetree, Chunk
from
nltk.tree
import
Tree
sentence
=
'The brown fox is quick and he is jumping over the lazy dog'
tree
=
parsetree(sentence)
|
In [
23
]: tree
Out[
23
]: [Sentence(
'The/DT/B-NP/O brown/JJ/I-NP/O fox/NN/I-NP/O is/VBZ/B-VP/O quick/JJ/B-ADJP/O and/CC/O/O he/PRP/B-NP/O is/VBZ/B-VP/O jumping/VBG/I-VP/O over/IN/B-PP/B-PNP the/DT/B-NP/I-PNP lazy/JJ/I-NP/I-PNP dog/NN/I-NP/I-PNP'
)]
|
上面的输出就是例句的原始浅层分析语句树。若是将它们与以前的 POS 标签表进行对比,会发现许多标签时很是类似的。上面的输出中有一些新的符号,前缀 I、O 和 B,即分块技术领域里十分流行的 IOB 标注,I、O 和 B 分别表示内部、外部和开头。标签前面的 B,前缀表示它是块的开始,而 I,前缀则表示它在快内。O 标签表示标识不属于任何块。当后续标签跟当前语块的标签类型相同,而且它它们以前不存在 O 标签时,则对当前块使用 B 标签。
如下代码段显示了如何简单易懂地得到语块:
In [
24
]:
for
sentence_tree
in
tree:
....:
print
(sentence_tree.chunks)
....:
[Chunk(
'The brown fox/NP'
), Chunk(
'is/VP'
), Chunk(
'quick/ADJP'
), Chunk(
'he/NP'
), Chunk(
'is jumping/VP'
), Chunk(
'over/PP'
), Chunk(
'the lazy dog/NP'
)]
|
In [
25
]:
for
sentence_tree
in
tree:
....:
for
chunk
in
sentence_tree.chunks:
....:
print
(chunk.
type
,
'->'
, [(word.string, word.
type
)
....:
for
word
in
chunk.words])
....:
NP
-
> [(
'The'
,
'DT'
), (
'brown'
,
'JJ'
), (
'fox'
,
'NN'
)]
VP
-
> [(
'is'
,
'VBZ'
)]
ADJP
-
> [(
'quick'
,
'JJ'
)]
NP
-
> [(
'he'
,
'PRP'
)]
VP
-
> [(
'is'
,
'VBZ'
), (
'jumping'
,
'VBG'
)]
PP
-
> [(
'over'
,
'IN'
)]
NP
-
> [(
'the'
,
'DT'
), (
'lazy'
,
'JJ'
), (
'dog'
,
'NN'
)]
|
上面的输出是例句的浅层分析结果,该结果十分简单明了,其中的每一个短语及其组成部分都被清楚地显示出来。
能够构建一些通用函数,更好地解析和可视化浅层分析的语句树,还能够在分析常见句子时重复使用它们,以下列代码所示:
def
create_sentence_tree(sentence, lemmatize
=
False
):
sentence_tree
=
parsetree(sentence,
relations
=
True
,
lemmata
=
lemmatize)
return
sentence_tree[
0
]
def
get_sentence_tree_constituents(sentence_tree):
return
sentence_tree.constituents()
def
process_sentence_tree(sentence_tree):
tree_constituents
=
get_sentence_tree_constituents(sentence_tree)
processed_tree
=
[(item.
type
,[(w.string, w.
type
)
for
w
in
item.words])
if
type
(item)
=
=
Chunk
else
(
'-'
, [(item.string, item.
type
)])
for
item
in
tree_constituents]
return
processed_tree
def
print_sentence_tree(sentence_tree):
processed_tree
=
process_sentence_tree(sentence_tree)
processed_tree
=
[ Tree( item[
0
],[ Tree(x[
1
], [x[
0
]])
for
x
in
item[
1
]])
for
item
in
processed_tree ]
tree
=
Tree(
'S'
, processed_tree )
print
(tree)
def
visualize_sentence_tree(sentence_tree):
processed_tree
=
process_sentence_tree(sentence_tree)
processed_tree
=
[ Tree( item[
0
], [ Tree(x[
1
], [x[
0
]])
for
x
in
item[
1
]])
for
item
in
processed_tree ]
tree
=
Tree(
'S'
, processed_tree )
tree.draw()
|
执行如下代码段,能够看出上述函数是如何在例句中发挥做用的:
In [
38
]: t
=
create_sentence_tree(sentence)
In [
39
]: t
Out[
39
]: Sentence(
'The/DT/B-NP/O/NP-SBJ-1 brown/JJ/I-NP/O/NP-SBJ-1 fox/NN/I-NP/O/NP-SBJ-1 is/VBZ/B-VP/O/VP-1 quick/JJ/B-ADJP/O/O and/CC/O/O/O he/PRP/B-NP/O/NP-SBJ-2 is/VBZ/B-VP/O/VP-2 jumping/VBG/I-VP/O/VP-2 over/IN/B-PP/B-PNP/O the/DT/B-NP/I-PNP/O lazy/JJ/I-NP/I-PNP/O dog/NN/I-NP/I-PNP/O'
)
|
In [
40
]: pt
=
process_sentence_tree(t)
In [
41
]: pt
Out[
41
]:
[(
'NP'
, [(
'The'
,
'DT'
), (
'brown'
,
'JJ'
), (
'fox'
,
'NN'
)]),
(
'VP'
, [(
'is'
,
'VBZ'
)]),
(
'ADJP'
, [(
'quick'
,
'JJ'
)]),
(
'-'
, [(
'and'
,
'CC'
)]),
(
'NP'
, [(
'he'
,
'PRP'
)]),
(
'VP'
, [(
'is'
,
'VBZ'
), (
'jumping'
,
'VBG'
)]),
(
'PP'
, [(
'over'
,
'IN'
)]),
(
'NP'
, [(
'the'
,
'DT'
), (
'lazy'
,
'JJ'
), (
'dog'
,
'NN'
)])]
|
In [
42
]: print_sentence_tree(t)
(S
(NP (DT The) (JJ brown) (NN fox))
(VP (VBZ
is
))
(ADJP (JJ quick))
(
-
(CC
and
))
(NP (PRP he))
(VP (VBZ
is
) (VBG jumping))
(PP (IN over))
(NP (DT the) (JJ lazy) (NN dog)))
In [
43
]: visualize_sentence_tree(t)
|
上面的输出显示了从例句中建立、表示和可视化浅层分析树的方法。对于同一个例句,可视化结果与树形很是类似。最低一级表示实际的标识值;上一级别表示每一个标识的 POS 标签;而再上一级表示语块短语的标签。
下面将会使用正则表达式,基于标签的学习器等技术构建本身的浅层分析器。与以前的 POS 标签相似,若是须要的话,会使用一些训练数据来训练分析器,而后使用测试数据和例句对分析器进行评估。在 nltk 中,可使用 treebank 语料库,它带有语块标注。首先,加载语料库,并使用如下代码段准备训练数据集合测试数据集:
from
nltk.corpus
import
treebank_chunk
data
=
treebank_chunk.chunked_sents()
train_data
=
data[:
4000
]
test_data
=
data[
4000
:]
|
In [
31
]:
print
(train_data[
7
])
(S
(NP A
/
DT Lorillard
/
NNP spokewoman
/
NN)
said
/
VBD
,
/
,
``
/
``
(NP This
/
DT)
is
/
VBZ
(NP an
/
DT old
/
JJ story
/
NN)
.
/
.)
|
从上面的输出能够看出,数据点是使用短语和 POS 标签完成标注的句子,这将有助于训练浅层分析器。下面将从使用正则表达式开始进行浅层分析,同时还会使用分块和加缝隙的概念。经过分块,可使用并指定特定的模式来识别想要在句子中分块或分段的内容,例如一些基于特定元数据(如每一个标识的 POS 标签)的短语。加缝隙过程与分块过程相反,在该过程当中,指定一些特定的标识使其不属于任何语块,而后造成除这些标识以外的必要语块。一块儿看一个简单的例句,经过使用 RegexpParser 类,能够利用正则表达式建立浅层分析器,以说明名词短语的分块和加缝隙过程,以下所示:
simple_sentence
=
'the quick fox jumped over the lazy dog'
from
nltk.chunk
import
RegexpParser
from
pattern.en
import
tag
tagged_simple_sent
=
tag(simple_sentence)
|
In [
36
]:
print
(tagged_simple_sent)
[(
'the'
,
'DT'
), (
'quick'
,
'JJ'
), (
'fox'
,
'NN'
), (
'jumped'
,
'VBD'
), (
'over'
,
'IN'
), (
'the'
,
'DT'
), (
'lazy'
,
'JJ'
), (
'dog'
,
'NN'
)]
|
chunk_grammar
=
"""
NP: {<DT>?<JJ>*<NN.*>}
"""
rc
=
RegexpParser(chunk_grammar)
c
=
rc.parse(tagged_simple_sent)
|
In [
41
]:
print
(c)
(S
(NP the
/
DT quick
/
JJ fox
/
NN)
jumped
/
VBD
over
/
IN
(NP the
/
DT lazy
/
JJ dog
/
NN))
|
chink_grammar
=
"""
NP: {<.*>+} # chunk everything as NP
}<VBD|IN>+{
"""
rc
=
RegexpParser(chink_grammar)
c
=
rc.parse(tagged_simple_sent)
|
In [
45
]:
print
(c)
(S
(NP the
/
DT quick
/
JJ fox
/
NN)
jumped
/
VBD
over
/
IN
(NP the
/
DT lazy
/
JJ dog
/
NN))
|
从上面的输出中能够看出,在试验性 NP(名词短语)浅层分析器上使用分块和加缝隙方法获得了相同的结果。请记住,短语是包含在组块(语块)集合中的标识序列,缝隙则是被排除在语块以外的标识和标识序列。
如今,要训练一个更为通用的基于正则表达式的浅层分析器,并在测试 treebank 数据上测试其性能。在程序内部,须要执行几个步骤来完成此分析器。首先,须要将 nltk 中用于表示被解析语句的 Tree 结构转换为 ChunkString 对象。而后,使用定义好的分块和加缝隙规则建立一个 RegexpParser 对象。最后,使用 ChunkRule 和 ChinkRule 类及其对象建立完整的、带有必要语块的浅层分析树。如下代码段展现了基于正则表达式的浅层分析器:
In [
46
]: tagged_sentence
=
tag(sentence)
In [
47
]:
print
(tagged_sentence)
[(
'The'
,
'DT'
), (
'brown'
,
'JJ'
), (
'fox'
,
'NN'
), (
'is'
,
'VBZ'
), (
'quick'
,
'JJ'
), (
'and'
,
'CC'
), (
'he'
,
'PRP'
), (
'is'
,
'VBZ'
), (
'jumping'
,
'VBG'
), (
'over'
,
'IN'
), (
'the'
,
'DT'
), (
'lazy'
,
'JJ'
), (
'dog'
,
'NN'
)]
|
grammar
=
"""
NP: {<DT>?<JJ>?<NN.*>}
ADJP: {<JJ>}
ADVP: {<RB.*>}
PP: {<IN>}
VP: {<MD>?<VB.*>+}
"""
rc
=
RegexpParser(grammar)
c
=
rc.parse(tagged_sentence)
|
In [
51
]:
print
(c)
(S
(NP The
/
DT brown
/
JJ fox
/
NN)
(VP
is
/
VBZ)
(ADJP quick
/
JJ)
and
/
CC
he
/
PRP
(VP
is
/
VBZ jumping
/
VBG)
(PP over
/
IN)
(NP the
/
DT lazy
/
JJ dog
/
NN))
|
In [
52
]:
print
(rc.evaluate(test_data))
ChunkParse score:
IOB Accuracy:
54.5
%
%
Precision:
25.0
%
%
Recall:
52.5
%
%
F
-
Measure:
33.9
%
%
|
上面输出的例句分析树很是相似于前面分析器给出的分析树。此外,测试数据的正特标准率达到了 54.5%,这是一个很不错的开头。
还记得以前提到的带注释的文本标记元数据在许多方面都是颇有用的吗?接下来,将使用分好块并标记好的 treebank 训练数据,构建一个浅层分析器。会用到两个分块函数:一个是 tree2conlltags 函数,它能够为每一个词元获取三组数据,单词、标签和块标签;另外一个是 conlltags2tree 函数,它能够从上述三元组数据中生成分析树。稍后,将使用这些函数来训练分析器。首先,一块儿来看看这两个函数是如何工做的。请记住,块标签使用前面提到的 IOB 格式:
from
nltk.chunk.util
import
tree2conlltags, conlltags2tree
|
In [
37
]: train_sent
=
train_data[
7
]
...:
print
(train_sent)
...:
...:
(S
(NP A
/
DT Lorillard
/
NNP spokewoman
/
NN)
said
/
VBD
,
/
,
``
/
``
(NP This
/
DT)
is
/
VBZ
(NP an
/
DT old
/
JJ story
/
NN)
.
/
.)
|
In [
38
]: wtc
=
tree2conlltags(train_sent)
...: wtc
...:
...:
Out[
38
]:
[(
'A'
,
'DT'
,
'B-NP'
),
(
'Lorillard'
,
'NNP'
,
'I-NP'
),
(
'spokewoman'
,
'NN'
,
'I-NP'
),
(
'said'
,
'VBD'
,
'O'
),
(
','
,
','
,
'O'
),
(
'``'
,
'``'
,
'O'
),
(
'This'
,
'DT'
,
'B-NP'
),
(
'is'
,
'VBZ'
,
'O'
),
(
'an'
,
'DT'
,
'B-NP'
),
(
'old'
,
'JJ'
,
'I-NP'
),
(
'story'
,
'NN'
,
'I-NP'
),
(
'.'
,
'.'
,
'O'
)]
|
In [
39
]: tree
=
conlltags2tree(wtc)
In [
40
]:
print
(tree)
(S
(NP A
/
DT Lorillard
/
NNP spokewoman
/
NN)
said
/
VBD
,
/
,
``
/
``
(NP This
/
DT)
is
/
VBZ
(NP an
/
DT old
/
JJ story
/
NN)
.
/
.)
|
如今,已经知道了这些函数是如何工做的,接下来,定义一个函数 conll_tag_chunks() 从分块标注好的句子中提取 POS 和块标签。从 POS 标注到使用组合标签器(包含 backoff 标签器)训练数据的过程当中,还能够再次使用 combined_taggers() 函数,如如下代码段所示:
def
conll_tag_chunks(chunk_sents):
tagged_sents
=
[tree2conlltags(tree)
for
tree
in
chunk_sents]
return
[[(t, c)
for
(w, t, c)
in
sent]
for
sent
in
tagged_sents]
def
combined_tagger(train_data, taggers, backoff
=
None
):
for
tagger
in
taggers:
backoff
=
tagger(train_data, backoff
=
backoff)
return
backoff
|
如今,定义一个 NGramTagChunker 类,将标记好的句子做为训练输入,获取他们的 WTC 三元组,即单词(word)、POS 标签(POS tag)和块标签(Chunk tag)三元组,并使用 UnigramTagger 做为 backoff 标签器训练一个 BigramTagger。还将定义一个 parse() 函数来对新的句子执行浅层分析:
from
nltk.tag
import
UnigramTagger, BigramTagger
from
nltk.chunk
import
ChunkParserI
class
NGramTagChunker(ChunkParserI):
def
__init__(
self
, train_sentences,
tagger_classes
=
[UnigramTagger, BigramTagger]):
train_sent_tags
=
conll_tag_chunks(train_sentences)
self
.chunk_tagger
=
combined_tagger(train_sent_tags, tagger_classes)
def
parse(
self
, tagged_sentence):
if
not
tagged_sentence:
return
None
pos_tags
=
[tag
for
word, tag
in
tagged_sentence]
chunk_pos_tags
=
self
.chunk_tagger.tag(pos_tags)
chunk_tags
=
[chunk_tag
for
(pos_tag, chunk_tag)
in
chunk_pos_tags]
wpc_tags
=
[(word, pos_tag, chunk_tag)
for
((word, pos_tag), chunk_tag)
in
zip
(tagged_sentence, chunk_tags)]
return
conlltags2tree(wpc_tags)
|
在上述类中,构造函数使用基于语句 WTC 三元组的 n 元分词标签训练浅层分析器。在程序内部,它将一列训练语句做为输入,这些训练句使用分好块的分析树元数据做标注。该函数使用以前定义的 conll_tag_chunks() 函数来获取全部分块分析树的 WTC 三元组数据列表。而后,该函数使用这些三元组数据来训练一个 Bigram 标签器,它使用 Unigram 标签器做为 backoff 标签器,而且将训练模型存储在 self.chunk_tagger 中。请记住,能够在训练中使用 tagger_classes 参数来分析其余 n 元分词标签。完成训练后,可使用 parse() 函数来评估测试数据上的标签并对新的句子进行浅层分析。在程序内部,该函数使用经 POS 标注的句子做为输入,从句子中分离出 POS 标签,并使用训练完的 self.chunk_tagger 获取句子的 IOB 块标签。而后,将其与原始句子标识相结合,并使用 conlltags2tree() 函数获取最终的浅层分析树。
如下代码段展现了分析器:
In [
43
]: ntc
=
NGramTagChunker(train_data)
...:
print
(ntc.evaluate(test_data))
...:
...:
ChunkParse score:
IOB Accuracy:
99.6
%
%
Precision:
98.4
%
%
Recall:
100.0
%
%
F
-
Measure:
99.2
%
%
|
In [
45
]: tree
=
ntc.parse(tagged_sentence)
...:
print
(tree)
...:
...:
(S
(NP The
/
DT brown
/
JJ fox
/
NN)
is
/
VBZ
(NP quick
/
JJ)
and
/
CC
(NP he
/
PRP)
is
/
VBZ
jumping
/
VBG
over
/
IN
(NP the
/
DT lazy
/
JJ dog
/
NN))
|
从以上输出能够看出,在 treebank 测试集数据上,分析器整体准确率达到了 99.6%!
如今,一块儿在 conll2000 语料库上对分析器进行训练和苹果。conll2000 语料库是一个更大的语料库,它包含了 "华尔街日报" 摘录。将在前 7500 个句子上训练分析器,并在其他 3448 个句子上进行性能测试:
from
nltk.corpus
import
conll2000
wsj_data
=
conll2000.chunked_sents()
train_wsj_data
=
wsj_data[:
7500
]
test_wsj_data
=
wsj_data[
7500
:]
print
(train_wsj_data[
10
])
|
In [
46
]:
print
(train_wsj_data[
10
])
...:
...:
(S
(NP He
/
PRP)
(VP reckons
/
VBZ)
(NP the
/
DT current
/
JJ account
/
NN deficit
/
NN)
(VP will
/
MD narrow
/
VB)
(PP to
/
TO)
(NP only
/
RB
#/# 1.8/CD billion/CD)
(PP
in
/
IN)
(NP September
/
NNP)
.
/
.)
|
In [
47
]: tc
=
NGramTagChunker(train_wsj_data)
...:
print
(tc.evaluate(test_wsj_data))
...:
...:
ChunkParse score:
IOB Accuracy:
89.4
%
%
Precision:
80.8
%
%
Recall:
86.0
%
%
F
-
Measure:
83.3
%
%
|
上面的程序输出显示,分析器总体准确率大概是 89%。
在基于依存关系的分析中,会使用依存语法来分析和推断语句中每一个标识在结构和语义上的关系。基于依存关系的语法能够帮助咱们使用依存标签标注句子。依存标签是标记之间的一对一的映射,标识预存之间的依存关系。基于依存语法的分析树是一个有标签且有方向的树或图,能够更加精确地标识语句。分析树中的节点始终是词汇分类的标识,有标签的边表示起始点及其从属项(依赖起始点的标识)的依存关系。有向边上的标签表示依存关系中的语法角色。
将使用几个库来生成基于依存关系的分析树,并对例句进行检测。首先,将使用 spacy 库来分析例句,生成全部标识机器依存关系。
如下代码段展现了如何从例句中获取每一个标识的依存关系:
sentence
=
'The brown fox is quick and he is jumping over the lazy dog'
from
spacy.lang.en
import
English
parser
=
English()
parsed_sent
=
parser(sentence)
dependency_pattern
=
'{left}<---{word}[{w_type}]--->{right}\n--------'
for
token
in
parsed_sent:
print
(dependency_pattern.
format
(word
=
token.orth_,
w_type
=
token.dep_,
left
=
[t.orth_
for
t
in
token.lefts],
right
=
[t.orth_
for
t
in
token.rights]))
|
输出结果:
[]<
-
-
-
The[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
brown[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
fox[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
is
[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
quick[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
and
[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
he[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
is
[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
jumping[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
over[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
the[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
lazy[]
-
-
-
>[]
-
-
-
-
-
-
-
-
[]<
-
-
-
dog[]
-
-
-
>[]
-
-
-
-
-
-
-
-
|
若是出现以下错误,
...
ModuleNotFoundError: No module named
'spacy.en'
|
请执行:
$ python
-
m spacy download en
>>>
import
spacy
>>> nlp
=
spacy.load(
'en'
)
|
import
os
java_path
=
'/usr/local/jdk/bin/java'
os.environ[
'JAVAHOME'
]
=
java_path
from
nltk.parse.stanford
import
StanfordDependencyParser
from
nltk.parse.corenlp
import
StanforCoreNLPDependencyParser
sdp
=
StanfordDependencyParser(path_to_jar
=
'/root/stanford-parser-full-2018-02-27/stanford-parser.jar'
,
path_to_models_jar
=
'/root/stanford-parser-full-2018-02-27/stanford-english-corenlp-2018-02-27-models.jar'
)
result
=
list
(sdp.raw_parse(sentence))
|
In [
43
]: result[
0
]
Out[
43
]: <DependencyGraph with
14
nodes>
|
In [
44
]: [item
for
item
in
result[
0
].triples()]
Out[
44
]:
[((
'quick'
,
'JJ'
),
'nsubj'
, (
'fox'
,
'NN'
)),
((
'fox'
,
'NN'
),
'det'
, (
'The'
,
'DT'
)),
((
'fox'
,
'NN'
),
'amod'
, (
'brown'
,
'JJ'
)),
((
'quick'
,
'JJ'
),
'cop'
, (
'is'
,
'VBZ'
)),
((
'quick'
,
'JJ'
),
'cc'
, (
'and'
,
'CC'
)),
((
'quick'
,
'JJ'
),
'conj'
, (
'jumping'
,
'VBG'
)),
((
'jumping'
,
'VBG'
),
'nsubj'
, (
'he'
,
'PRP'
)),
((
'jumping'
,
'VBG'
),
'aux'
, (
'is'
,
'VBZ'
)),
((
'jumping'
,
'VBG'
),
'nmod'
, (
'dog'
,
'NN'
)),
((
'dog'
,
'NN'
),
'case'
, (
'over'
,
'IN'
)),
((
'dog'
,
'NN'
),
'det'
, (
'the'
,
'DT'
)),
((
'dog'
,
'NN'
),
'amod'
, (
'lazy'
,
'JJ'
))]
|
In [
49
]: dep_tree
=
[parse.tree()
for
parse
in
result][
0
]
In [
50
]:
print
(dep_tree)
(quick (fox The brown)
is
and
(jumping he
is
(dog over the lazy)))
In [
51
]: dep_tree.draw()
|
上面的输出结果展现了如何轻松地为例句生成依存分析树,并分析和理解标识间的关系。斯坦福分析器是十分强大且稳定的,它可以很好的与 nltk 集成。在这里,有一点须要说明,那就是须要安装 graphviz 才可以生成图形。
从头开始构建本身的依存关系分析器并不容易,由于须要大量的、充足的数据、而且仅仅按照语法产生式规则检查并不老是可以很好地苹果分析器效果。下面的代码段展现了如何构建本身的依存关系分析器。
import
nltk
tokens
=
nltk.word_tokenize(sentence)
dependency_rules
=
"""
'fox' -> 'The' | 'brown'
'quick' -> 'fox' | 'is' | 'and' | 'jumping'
'jumping' -> 'he' | 'is' | 'dog'
'dog' -> 'over' | 'the' | 'lazy'
"""
dependency_grammar
=
nltk.grammar.DependencyGrammar.fromstring(dependency_rules)
|
In [
62
]:
print
(dependency_grammar)
...:
...:
Dependency grammar with
12
productions
'fox'
-
>
'The'
'fox'
-
>
'brown'
'quick'
-
>
'fox'
'quick'
-
>
'is'
'quick'
-
>
'and'
'quick'
-
>
'jumping'
'jumping'
-
>
'he'
'jumping'
-
>
'is'
'jumping'
-
>
'dog'
'dog'
-
>
'over'
'dog'
-
>
'the'
'dog'
-
>
'lazy'
|
dp
=
nltk.ProjectiveDependencyParser(dependency_grammar)
res
=
[item
for
item
in
dp.parse(tokens)]
tree
=
res[
0
]
|
In [
64
]:
print
(tree)
(quick (fox The brown)
is
and
(jumping he
is
(dog over the lazy)))
|
能够看出,上面的依存关系分析树与斯坦福分析器生成的分析树是相通的。事实上,可使用 tree.drwa() 来可视化树形结构,并将其与上一个树形结构进行比较。分析器的扩展性一直是一个挑战,在大型项目中,大量工做用来生成基于依存语法规则的系统。一些例子包括词汇功能语法(Lexical Functional Grammar,LFG)Pargram 项目和词汇化树形联合语法(Lexicalized Tree Adjoining Grammar)XTAG 项目。
基于成分结构的语法经常使用来分析和肯定语句的组成部分。此外,这种语法的另外一个重要用途是找出这些组成成分的内部结构以及它们之间的关系。对于不一样类型的短语,根据其包含的组件类型,一般会有几种不一样的短语规则,可使用它们来构建分析树。若是须要温习相关内容请查阅一些分析树示例。
基于成分结构的语法能够帮助咱们将句子分解成各类成分。而后,能够进一步将这些成分分解成更细的细分项,而且重复这个过程直至将各类成分分解成独立的标识或单词。这些语法具备各类产生式规则,通常而言,一个与上下文语境无关的语法(CFG)或短语结构语法就知足以完成上述操做。
一旦拥有了一套语法规则,就能够构建一个成分结构分析器,它根据这些规则处理输入的语句,并辅助咱们构建分析树。分析器是为语法赋予生命的东西,也能够说是语法的程序语言解释。目前,有各类类型的分析算法。包括以下几类:
如须要,请参见更详细的信息:http://www.nltk.org/book/ch08.html
稍后,当咱们只想本身的分析器是,会简要的介绍部分分析器,并重点介绍 PCFG 解析。递归降低解析一般遵循自上而下的解析方法,它从输入语句中读取表示,而后尝试将其与语法产生式规则中的最终符进行匹配。它始终超前一个标识,并在每次得到匹配时,将输入读取指针前移。
位移归约解析遵循自下而上的解析方法,它找出与语法产生式规则右侧一致的标识序列(单词或短语),而后用该规则左侧的标识替换它。这个过程一直持续,直到整个句子只剩下造成分析树的必要项。
图标解析采用动态规则,它存储中间结果,并在须要时从新使用这些结果,以得到显著的效能提高。这种状况下,图标分析器存储部分解决方案,并在须要时查找它们已得到完整的解决方案。
在这里,将使用 nltk 和 StanfordParser 来生成分析数。在运行代码以解析例句以前,首先设置 Java 路径,而后将像是并可视化分析树:
import
os
java_path
=
'/usr/local/jdk/bin/java'
os.environ[
'JAVAHOME'
]
=
java_path
from
nltk.parse.stanford
import
StanfordDependencyParser
sdp
=
StanfordDependencyParser(path_to_jar
=
'/root/stanford-parser-full-2018-02-27/stanford-parser.jar'
,
path_to_models_jar
=
'/root/stanford-parser-full-2018-02-27/stanford-english-corenlp-2018-02-27-models.jar'
)
result
=
list
(sdp.raw_parse(sentence))
|
构建本身的成分结构分析器有各类各样的方法,包括场景 CFG 产生式规则,而后使用该语法规则构建分析器等。想要构建本身的 CFG,可使用 nltk.CFG.fromstring 函数来输入产生式规则,而后在使用 ChartParser 或 RecursuveDescentParser 分析器(它们均属于 nltk 包)。
将会考虑构建一个扩展性良好且运行高效的成分结构分析器。常规 CFG 分析器(如图表分析器、递归降低分析器)的问题是解析语句时很容易被大量的解析工做量所压垮,致使运行速度很是缓慢。而这正像是 PCFG(几率上下文无关语法,Probabilistic Context Free Grammer)这样的加权语法和像维特比分析器这样的几率分析器在运行中被证实更有效的地方。PCFG 是一种上下文无关的语法,它将每一个产生式规则与一个几率值相关联。从 PCFG 产生一个分析树的几率是每一个产生式几率的乘积。
下面将使用 nltk 的 ViterbiParser 来训练 treebank 语料库中的分析器,treebank 语料库为每一个句子提供了带注释的分析树。这个分析器是一个自下而上的 PCFG 分析器,它使用动态规则来查找每一个步骤中最有可能的分析结果。能够经过加载必要的训练数据和依存关系来开始构建分析器:
import
nltk
from
nltk.grammar
import
Nonterminal
from
nltk.corpus
import
treebank
training_set
=
treebank.parsed_sents()
|
In [
71
]:
print
(training_set[
1
])
(S
(NP
-
SBJ (NNP Mr.) (NNP Vinken))
(VP
(VBZ
is
)
(NP
-
PRD
(NP (NN chairman))
(PP
(IN of)
(NP
(NP (NNP Elsevier) (NNP N.V.))
(, ,)
(NP (DT the) (NNP Dutch) (VBG publishing) (NN group))))))
(. .))
|
如今,从标记和注释完的训练句子中获取规则,并构建语法的产生式规则:
In [
72
]: treebank_productions
=
list
(
...:
set
(production
...:
for
sent
in
training_set
...:
for
production
in
sent.productions()
...: )
...: )
...:
...: treebank_productions[
0
:
10
]
...:
...:
Out[
72
]:
[NN
-
>
'literature'
,
NN
-
>
'turnover'
,
NP
-
> DT NNP NNP JJ NN NN,
VP
-
> VB NP PP
-
CLR ADVP
-
LOC,
CD
-
>
'2.375'
,
S
-
HLN
-
> NP
-
SBJ
-
1
VP :,
VBN
-
>
'Estimated'
,
NN
-
>
'Power'
,
NNS
-
>
'constraints'
,
NNP
-
>
'Wellcome'
]
|
# add productions for each word, POS tag
for
word, tag
in
treebank.tagged_words():
t
=
nltk.Tree.fromstring(
"("
+
tag
+
" "
+
word
+
")"
)
for
production
in
t.productions():
treebank_productions.append(production)
# build the PCFG based grammar
treebank_grammar
=
nltk.grammar.induce_pcfg(Nonterminal(
'S'
),
treebank_productions)
|
如今有了必要的语法和生产式评估,将使用如下代码段,经过语法训练建立本身的分析器,而后使用例句对分析器进行评估:
In [
74
]:
# build the parser
...: viterbi_parser
=
nltk.ViterbiParser(treebank_grammar)
...:
...:
# get sample sentence tokens
...: tokens
=
nltk.word_tokenize(sentence)
...:
...:
# get parse tree for sample sentence
...: result
=
list
(viterbi_parser.parse(tokens))
...:
...:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ValueError Traceback (most recent call last)
<ipython
-
input
-
74
-
78944f4bb64d
>
in
<module>()
6
7
# get parse tree for sample sentence
-
-
-
-
>
8
result
=
list
(viterbi_parser.parse(tokens))
/
usr
/
local
/
lib
/
python3.
6
/
dist
-
packages
/
nltk
/
parse
/
viterbi.py
in
parse(
self
, tokens)
110
111
tokens
=
list
(tokens)
-
-
>
112
self
._grammar.check_coverage(tokens)
113
114
# The most likely constituent table. This table specifies the
/
usr
/
local
/
lib
/
python3.
6
/
dist
-
packages
/
nltk
/
grammar.py
in
check_coverage(
self
, tokens)
658
missing
=
', '
.join(
'%r'
%
(w,)
for
w
in
missing)
659
raise
ValueError(
"Grammar does not cover some of the "
-
-
>
660
"input words: %r."
%
missing)
661
662
def
_calculate_grammar_forms(
self
):
ValueError: Grammar does
not
cover some of the
input
words:
"'brown', 'fox', 'lazy', 'dog'"
.
|
很不幸的是,在尝试用新建的分析器解析例句的标识时,收到了一个错误提示。错误的缘由很明显:例句中的一些单词不包含在基于 treebank 的语法中,由于这些单词并不在咱们的 breebank 语料库中。用于该语法使用 POS 标签和短语标签来构建基于训练数据的分析树,将在语法中为例句添加标识和 POS 标签,而后从新构建分析器:
In [
75
]:
# get tokens and their POS tags
...:
from
pattern.en
import
tag as pos_tagger
...: tagged_sent
=
pos_tagger(sentence)
...:
...:
print
(tagged_sent)
...:
...:
[(
'The'
,
'DT'
), (
'brown'
,
'JJ'
), (
'fox'
,
'NN'
), (
'is'
,
'VBZ'
), (
'quick'
,
'JJ'
), (
'and'
,
'CC'
), (
'he'
,
'PRP'
), (
'is'
,
'VBZ'
), (
'jumping'
,
'VBG'
), (
'over'
,
'IN'
), (
'the'
,
'DT'
), (
'lazy'
,
'JJ'
), (
'dog'
,
'NN'
)]
|
# extend productions for sample sentence tokens
for
word, tag
in
tagged_sent:
t
=
nltk.Tree.fromstring(
"("
+
tag
+
" "
+
word
+
")"
)
for
production
in
t.productions():
treebank_productions.append(production)
# rebuild grammar
treebank_grammar
=
nltk.grammar.induce_pcfg(Nonterminal(
'S'
),
treebank_productions)
# rebuild parser
viterbi_parser
=
nltk.ViterbiParser(treebank_grammar)
# get parse tree for sample sentence
result
=
list
(viterbi_parser.parse(tokens))
|
如今,成功的为例句生成了分析树。请记住,这是一个 PCFG 分析器,能够在以前的输出结果中看到这棵树的整体几率。这里的标签注释均基于咱们前面讨论过的树库注释。