我一直在玩自然语言解析树并以各种方式操纵它们。我一直在使用斯坦福大学的Tregex和Tsurgeon工具,但代码很混乱,并不适合我的大多数Python环境(这些工具是Java,不适合调整)。我想要一个工具集,当我需要更多功能时,它可以轻松进行黑客攻击。还有其他工具非常适合在树上进行模式匹配,然后操纵那些匹配的分支吗?
例如,我想将以下树作为输入:
(ROOT
(S
(NP
(NP (NNP Bank))
(PP (IN of)
(NP (NNP America))))
(VP (VBD used)
(S
(VP (TO to)
(VP (VB be)
(VP (VBN called)
(NP
(NP (NNP Bank))
(PP (IN of)
(NP (NNP Italy)))))))))))
和(这是一个简化的例子):
例如,采取树的这一部分:
(NP
(NP (NNP Bank))
(PP (IN of)
(NP (NNP America))))
并将其转换为:
(NP
(NP (NNP Bank) (IN of) (NP (NNP America))))
由于我的输入树是S表达式,我考虑使用Lisp(嵌入到我的Python程序中)但是我已经写了很长时间,我在Lisp中编写了一些重要内容,我不知道从哪里开始。
描述模式的好方法是什么?什么是描述操纵的好方法?什么是思考这个问题的好方法?
答案 0 :(得分:11)
美丽是旁观者的眼睛。但是你永远不会说Tregex或Tsurgeon的代码是如此 。这听起来更像是你无法处理Java或更大的抽象,因此你正在寻找用Python编写的具体内容。
手写树匹配和转换函数没有任何问题。实际上,我们过去常常这样做。但在前几百个之后,似乎必须有一个更好的方法,因此我们开始使用Tregex和Tsurgeon的领域特定语言。这通常被视为一种值得称赞的编程风格。见Wikipedia。它们是具有精确语法规范的精心设计的语言,等等。以下是使用它们的示例。
Tree t = Tree.valueOf("(ROOT (S (NP (NP (NNP Bank)) (PP (IN of) (NP (NNP America)))) (VP (VBD used) (S (VP (TO to) (VP (VB be) (VP (VBN called) (NP (NP (NNP Bank)) (PP (IN of) (NP (NNP Italy)))))))))))");
TregexPattern pat = TregexPattern.compile("NP <1 (NP << Bank) <2 PP=remove");
TsurgeonPattern surgery = Tsurgeon.parseOperation("excise remove remove");
Tsurgeon.processPattern(pat, surgery, t).pennPrint();
请注意,Java代码实际上比Lisp代码更短,正是因为使用了特定于域的语言。很难看出这可能更简单:指定模式,指定操作,应用。
但是,如果您更喜欢手工编写与树上的模式匹配的方法并将其更改为Python中的其他树,那么欢迎您继续这样做。
答案 1 :(得分:8)
这是使用Lisp的典型案例。您需要一个在树上映射另一个函数的函数。
这是使用Common Lisp的过程匹配示例。 Lisp中的匹配器可用于列表结构,可以替代使用。使用列表匹配器可以简化示例(请参阅我使用模式匹配器的示例的其他答案)。
代码:
(defun node-children (node)
(rest node))
(defun node-name (node)
(second node))
(defun node-type (node)
(first node))
(defun treemap (tree matcher transformer)
(cond ((null tree) nil)
((consp tree)
(if (funcall matcher tree)
(funcall transformer tree)
(cons (node-type tree)
(mapcar (lambda (child)
(treemap child matcher transformer))
(node-children tree)))))
(t tree))))
示例:
(defvar *tree*
'(ROOT
(S
(NP
(NP (NNP Bank))
(PP (IN of)
(NP (NNP America))))
(VP (VBD used)
(S
(VP (TO to)
(VP (VB be)
(VP (VBN called)
(NP
(NP (NNP Bank))
(PP (IN of)
(NP (NNP Italy))))))))))))
(defun example ()
(pprint
(treemap *tree*
(lambda (node)
(and (= (length (node-children node)) 2)
(eq (node-type (first (node-children node))) 'np)
(some (lambda (node)
(eq (node-name node) 'bank))
(children (first (node-children node))))
(eq (first (second (node-children node))) 'pp)))
(lambda (node)
(list (node-type node)
(append (first (node-children node))
(node-children (second (node-children node)))))))))
运行示例:
CL-USER 75 > (example)
(ROOT
(S
(NP
(NP (NNP BANK) (IN OF) (NP (NNP AMERICA))))
(VP
(VBD USED)
(S
(VP
(TO TO)
(VP
(VB BE)
(VP
(VBN CALLED)
(NP
(NP
(NNP BANK)
(IN OF)
(NP (NNP ITALY)))))))))))
答案 2 :(得分:5)
这是Common Lisp的第二个版本。这次我使用模式匹配器。
我正在使用一个匹配Lisp数据模式的函数。 PMATCH:MATCH是Winston / Horn,Lisp,3rd Edition一书中的模式匹配器的增强版本。有类似的模式匹配功能。
数据与我的其他答案一样。
更改树映射功能以使用模式匹配器。如果匹配成功,PMATCH:MATCH函数返回T或绑定的关联列表。如果匹配不成功,则返回NIL。 PMATCH:INSTANTIATE-PATTERN采用模式和一组绑定。它返回一个新的列表结构,其中模式变量被绑定替换。
(defun treemapp (tree pattern transformer)
(cond ((null tree) nil)
((consp tree)
(let ((bindings (pmatch:match pattern tree)))
(if bindings
(pmatch:instantiate-pattern transformer bindings)
(cons (node-type tree)
(mapcar (lambda (child)
(treemapp child pattern transformer))
(node-children tree))))))
(t tree)))
示例现在使用模式。
模式是列表结构。 #?符号匹配单个项目并为符号创建绑定。 #$ symbol匹配项目列表并为符号创建绑定。
变换器是一种将使用绑定实例化的模式。
(defun example1 ()
(pprint (treemapp *tree*
'(NP (NP (#?type bank)) (PP #$children))
'(NP (NP (#?type bank) #$children)))))
运行此代码会返回与我的其他答案相同的结果。