3.理解文本语句和结构

理解文本语句和结构

下面会介绍和实现一些用于理解文本语法和结构的概念和技术。这些算法在 NLP 中非常有用,它通常在文本处理和标准化之后执行。主要关注一下技术:

  • 词性(POS)标签。
  • 浅层分析。
  • 基于依存关系的解析。
  • 基于成分结构的解析。

文章的作者针对读者是文本分析实践人员,可以执行并住处在实际问题中使用技术和算法的最佳解决方案。所以,下面将介绍利用现有库(如 nltk 和 spacy)来实现和执行一些技术的最佳方法。此外由于许多读者可能对技术的内部构建感兴趣,并且可能会尝试自己实现部分技术,也会介绍如何做到这一点。请记住,主要关注的是以实际的例子来研究实现概念的方法,而不是重写方法。

安装必要的依赖项

下面是所需要的依赖库:

  • nltk 库
  • spacy 库
  • pattern 库
  • 斯坦福分析器(Stanford parser)
  • Graphviz 及必要库。

如果觉得 nltk 安装包可能依赖的过多,及其繁琐的下载,可执行下面代码全部现在:

In [ 94 ]:  import  nltk
 
In [ 95 ]: nltk.download( "all" , halt_on_error = False )

安装 pattern 库,请执行:

$ pip  install  pattern

下载并安装及其必要的依赖项。

对于 spacy 库,需要先安装软件包,然后单独安装及其依赖项(也称为语言模型)。请在终端执行:

$ 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 集成。

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

机器学习重要概念

利用一些与先构建好的标签其来训练自己的标签器。为了更好理解实现过程,必须知道如下与数据分析和机器学习相关的重要概念。

  • 数据准备:通常包含特征提取以及训练前的数据预处理。
  • 特征提取:从原始数据中提取出有用特征以训练机器学习模式的过程。
  • 特征:数据的各种有用属性(以个人数据为例,可以是年龄、体重等)。
  • 训练数据:用于训练模式的一组数据。
  • 测试/效验数据:一组数据,经过预先训练的模拟使用该组数据进行测试和评估,以评估模型优劣。
  • 模型:使用数据/特征组合构建,一个机器学习算法可以是有监督的,也可以是无监督的。
  • 准确率:模式预测的准确程度(还有其他详细的评估标准,如精确率,召回率和 F1-score)。

知道了这些术语应该满足以下学习的基本内容。

词性标注

词性(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 标签集合。

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 标签器

下面将探讨一些构建自己的 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 元分词。将使用大小为 1、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  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  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  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)
=  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)
=  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)
=  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)或短语结构语法就满足以完成上述操作。

一旦拥有了一套语法规则,就可以构建一个成分结构分析器,它根据这些规则处理输入的语句,并辅助我们构建分析树。分析器是为语法赋予生命的东西,也可以说是语法的程序语言解释。目前,有各种类型的分析算法。包括如下几类:

  • 递归下降解析(Recursive Descent parsing)。
  • 移位归约解析(Shift Reduce parsing)。
  • 图表解析(Chart parsing)。
  • 自下而上解析(Bottom-up parsing)。
  • 自上而下解析(Top-down parsing)。
  • PCFG 解析(PCFG parsing)。

如需要,请参见更详细的信息: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))
 折叠源码
In [ 67 ]:  print (result[ 0 ])
defaultdict(<function DependencyGraph.__init__.< locals >.< lambda > at  0x7f91506c17b8 >,
             { 0 : { 'address' 0 ,
                  'ctag' 'TOP' ,
                  'deps' : defaultdict(< class  'list' >, { 'root' : [ 5 ]}),
                  'feats' None ,
                  'head' None ,
                  'lemma' None ,
                  'rel' None ,
                  'tag' 'TOP' ,
                  'word' None },
              1 : { 'address' 1 ,
                  'ctag' 'DT' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 3 ,
                  'lemma' '_' ,
                  'rel' 'det' ,
                  'tag' 'DT' ,
                  'word' 'The' },
              2 : { 'address' 2 ,
                  'ctag' 'JJ' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 3 ,
                  'lemma' '_' ,
                  'rel' 'amod' ,
                  'tag' 'JJ' ,
                  'word' 'brown' },
              3 : { 'address' 3 ,
                  'ctag' 'NN' ,
                  'deps' : defaultdict(< class  'list' >, { 'det' : [ 1 ],  'amod' : [ 2 ]}),
                  'feats' '_' ,
                  'head' 5 ,
                  'lemma' '_' ,
                  'rel' 'nsubj' ,
                  'tag' 'NN' ,
                  'word' 'fox' },
              4 : { 'address' 4 ,
                  'ctag' 'VBZ' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 5 ,
                  'lemma' '_' ,
                  'rel' 'cop' ,
                  'tag' 'VBZ' ,
                  'word' 'is' },
              5 : { 'address' 5 ,
                  'ctag' 'JJ' ,
                  'deps' : defaultdict(< class  'list' >,
                                      { 'cc' : [ 6 ],
                                       'conj' : [ 9 ],
                                       'cop' : [ 4 ],
                                       'nsubj' : [ 3 ]}),
                  'feats' '_' ,
                  'head' 0 ,
                  'lemma' '_' ,
                  'rel' 'root' ,
                  'tag' 'JJ' ,
                  'word' 'quick' },
              6 : { 'address' 6 ,
                  'ctag' 'CC' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 5 ,
                  'lemma' '_' ,
                  'rel' 'cc' ,
                  'tag' 'CC' ,
                  'word' 'and' },
              7 : { 'address' 7 ,
                  'ctag' 'PRP' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 9 ,
                  'lemma' '_' ,
                  'rel' 'nsubj' ,
                  'tag' 'PRP' ,
                  'word' 'he' },
              8 : { 'address' 8 ,
                  'ctag' 'VBZ' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 9 ,
                  'lemma' '_' ,
                  'rel' 'aux' ,
                  'tag' 'VBZ' ,
                  'word' 'is' },
              9 : { 'address' 9 ,
                  'ctag' 'VBG' ,
                  'deps' : defaultdict(< class  'list' >,
                                      { 'aux' : [ 8 ],
                                       'nmod' : [ 13 ],
                                       'nsubj' : [ 7 ]}),
                  'feats' '_' ,
                  'head' 5 ,
                  'lemma' '_' ,
                  'rel' 'conj' ,
                  'tag' 'VBG' ,
                  'word' 'jumping' },
              10 : { 'address' 10 ,
                   'ctag' 'IN' ,
                   'deps' : defaultdict(< class  'list' >, {}),
                   'feats' '_' ,
                   'head' 13 ,
                   'lemma' '_' ,
                   'rel' 'case' ,
                   'tag' 'IN' ,
                   'word' 'over' },
              11 : { 'address' 11 ,
                   'ctag' 'DT' ,
                   'deps' : defaultdict(< class  'list' >, {}),
                   'feats' '_' ,
                   'head' 13 ,
                   'lemma' '_' ,
                   'rel' 'det' ,
                   'tag' 'DT' ,
                   'word' 'the' },
              12 : { 'address' 12 ,
                   'ctag' 'JJ' ,
                   'deps' : defaultdict(< class  'list' >, {}),
                   'feats' '_' ,
                   'head' 13 ,
                   'lemma' '_' ,
                   'rel' 'amod' ,
                   'tag' 'JJ' ,
                   'word' 'lazy' },
              13 : { 'address' 13 ,
                   'ctag' 'NN' ,
                   'deps' : defaultdict(< class  'list' >,
                                       { 'amod' : [ 12 ],
                                        'case' : [ 10 ],
                                        'det' : [ 11 ]}),
                   'feats' '_' ,
                   'head' 9 ,
                   'lemma' '_' ,
                   'rel' 'nmod' ,
                   'tag' 'NN' ,
                   'word' 'dog' }})

构建自己的成分结构分析器

构建自己的成分结构分析器有各种各样的方法,包括场景 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():
     =  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  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:
     =  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))
 折叠源码
In [ 77 ]:  print (result[ 0 ])
defaultdict(<function DependencyGraph.__init__.< locals >.< lambda > at  0x7f91506c17b8 >,
             { 0 : { 'address' 0 ,
                  'ctag' 'TOP' ,
                  'deps' : defaultdict(< class  'list' >, { 'root' : [ 5 ]}),
                  'feats' None ,
                  'head' None ,
                  'lemma' None ,
                  'rel' None ,
                  'tag' 'TOP' ,
                  'word' None },
              1 : { 'address' 1 ,
                  'ctag' 'DT' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 3 ,
                  'lemma' '_' ,
                  'rel' 'det' ,
                  'tag' 'DT' ,
                  'word' 'The' },
              2 : { 'address' 2 ,
                  'ctag' 'JJ' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 3 ,
                  'lemma' '_' ,
                  'rel' 'amod' ,
                  'tag' 'JJ' ,
                  'word' 'brown' },
              3 : { 'address' 3 ,
                  'ctag' 'NN' ,
                  'deps' : defaultdict(< class  'list' >, { 'det' : [ 1 ],  'amod' : [ 2 ]}),
                  'feats' '_' ,
                  'head' 5 ,
                  'lemma' '_' ,
                  'rel' 'nsubj' ,
                  'tag' 'NN' ,
                  'word' 'fox' },
              4 : { 'address' 4 ,
                  'ctag' 'VBZ' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 5 ,
                  'lemma' '_' ,
                  'rel' 'cop' ,
                  'tag' 'VBZ' ,
                  'word' 'is' },
              5 : { 'address' 5 ,
                  'ctag' 'JJ' ,
                  'deps' : defaultdict(< class  'list' >,
                                      { 'cc' : [ 6 ],
                                       'conj' : [ 9 ],
                                       'cop' : [ 4 ],
                                       'nsubj' : [ 3 ]}),
                  'feats' '_' ,
                  'head' 0 ,
                  'lemma' '_' ,
                  'rel' 'root' ,
                  'tag' 'JJ' ,
                  'word' 'quick' },
              6 : { 'address' 6 ,
                  'ctag' 'CC' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 5 ,
                  'lemma' '_' ,
                  'rel' 'cc' ,
                  'tag' 'CC' ,
                  'word' 'and' },
              7 : { 'address' 7 ,
                  'ctag' 'PRP' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 9 ,
                  'lemma' '_' ,
                  'rel' 'nsubj' ,
                  'tag' 'PRP' ,
                  'word' 'he' },
              8 : { 'address' 8 ,
                  'ctag' 'VBZ' ,
                  'deps' : defaultdict(< class  'list' >, {}),
                  'feats' '_' ,
                  'head' 9 ,
                  'lemma' '_' ,
                  'rel' 'aux' ,
                  'tag' 'VBZ' ,
                  'word' 'is' },
              9 : { 'address' 9 ,
                  'ctag' 'VBG' ,
                  'deps' : defaultdict(< class  'list' >,
                                      { 'aux' : [ 8 ],
                                       'nmod' : [ 13 ],
                                       'nsubj' : [ 7 ]}),
                  'feats' '_' ,
                  'head' 5 ,
                  'lemma' '_' ,
                  'rel' 'conj' ,
                  'tag' 'VBG' ,
                  'word' 'jumping' },
              10 : { 'address' 10 ,
                   'ctag' 'IN' ,
                   'deps' : defaultdict(< class  'list' >, {}),
                   'feats' '_' ,
                   'head' 13 ,
                   'lemma' '_' ,
                   'rel' 'case' ,
                   'tag' 'IN' ,
                   'word' 'over' },
              11 : { 'address' 11 ,
                   'ctag' 'DT' ,
                   'deps' : defaultdict(< class  'list' >, {}),
                   'feats' '_' ,
                   'head' 13 ,
                   'lemma' '_' ,
                   'rel' 'det' ,
                   'tag' 'DT' ,
                   'word' 'the' },
              12 : { 'address' 12 ,
                   'ctag' 'JJ' ,
                   'deps' : defaultdict(< class  'list' >, {}),
                   'feats' '_' ,
                   'head' 13 ,
                   'lemma' '_' ,
                   'rel' 'amod' ,
                   'tag' 'JJ' ,
                   'word' 'lazy' },
              13 : { 'address' 13 ,
                   'ctag' 'NN' ,
                   'deps' : defaultdict(< class  'list' >,
                                       { 'amod' : [ 12 ],
                                        'case' : [ 10 ],
                                        'det' : [ 11 ]}),
                   'feats' '_' ,
                   'head' 9 ,
                   'lemma' '_' ,
                   'rel' 'nmod' ,
                   'tag' 'NN' ,
                   'word' 'dog' }})

现在,成功的为例句生成了分析树。请记住,这是一个 PCFG 分析器,可以在之前的输出结果中看到这棵树的总体概率。这里的标签注释均基于我们前面讨论过的树库注释。

猜你喜欢

转载自www.cnblogs.com/dalton/p/11353917.html