2

我的问题与词性标记和解析的自然语言句子的后处理有关。具体来说,我正在编写一个 Lisp 后处理器的组件,该组件将一个句子解析树(例如,由斯坦福解析器生成的)作为输入,从该解析树中提取为生成解析而调用的短语结构规则,然后生成规则和规则计数表。输入和输出的示例如下:

(1) 句子:

John said that he knows who Mary likes

(2) 解析器输出:

(ROOT
  (S
    (NP (NNP John))
    (VP (VBD said)
      (SBAR (IN that)
        (S
          (NP (PRP he))
          (VP (VBZ knows)
            (SBAR
              (WHNP (WP who))
              (S
                (NP (NNP Mary))
                (VP (VBZ likes))))))))))

(3) 此解析树的我的 Lisp 程序后处理器输出:

(S --> NP VP)             3
(NP --> NNP)              2
(VP --> VBZ)              1
(WHNP --> WP)             1
(SBAR --> WHNP S)         1
(VP --> VBZ SBAR)         1
(NP --> PRP)              1
(SBAR --> IN S)           1
(VP --> VBD SBAR)         1
(ROOT --> S)              1

注意句子(1)中没有标点符号。那是故意的。我无法在 Lisp 中解析标点符号——正是因为某些标点符号(例如逗号)是为特殊目的而保留的。但是解析没有标点符号的句子会改变解析规则的分布以及这些规则中包含的符号,如下所示:

(4) 输入句:

I said no and then I did it anyway

(5) 解析器输出:

(ROOT
  (S
    (NP (PRP I))
    (VP (VBD said)
      (ADVP (RB no)
        (CC and)
        (RB then))
      (SBAR
        (S
          (NP (PRP I))
          (VP (VBD did)
            (NP (PRP it))
            (ADVP (RB anyway))))))))

(6) 输入句子(带标点):

I said no, and then I did it anyway.

(7) 解析器输出:

 (ROOT
   (S
     (S
       (NP (PRP I))
       (VP (VBD said)
         (INTJ (UH no))))
     (, ,)
     (CC and)
     (S
       (ADVP (RB then))
       (NP (PRP I))
       (VP (VBD did)
         (NP (PRP it))
         (ADVP (RB anyway))))
     (. .)))

请注意,包含标点符号如何完全重新排列解析树并且还涉及不同的 POS 标签(因此,意味着调用了不同的语法规则来生成它)所以包含标点符号很重要,至少对于我的应用程序而言。

我需要的是找到一种在规则中包含标点符号的方法,这样我就可以生成如下规则,例如,在 (3) 之类的表中,如下所示:

(8) 期望规则:

S --> S , CC S .

对于我正在编写的特定应用程序,实际上需要像 (8) 这样的规则。

但是我发现在 Lisp 中这样做很困难:例如,在 (7) 中,我们观察到 (, ,) 和 (. .) 的出现,这两者在 Lisp 中处理都是有问题的。

我在下面包含了我的相关 Lisp 代码。请注意,我是一个新手 Lisp 黑客,所以我的代码不是特别漂亮或高效。如果有人能建议我如何修改下面的代码,以便我可以解析 (7) 以生成像 (3) 这样的表,其中包含像 (8) 这样的规则,我将不胜感激。

这是我与此任务相关的 Lisp 代码:

(defun WRITE-RULES-AND-COUNTS-SORTED (sent)
  (multiple-value-bind (rules-list counts-list)
      (COUNT-RULES-OCCURRENCES sent)
    (setf comblist (sort (pairlis rules-list counts-list) #'> :key #'cdr))
    (format t "~%")
    (do ((i 0 (incf i)))
        ((= i (length comblist)) NIL)
      (format t "~A~26T~A~%" (car (nth i comblist)) (cdr (nth i comblist))))
    (format t "~%")))


 (defun COUNT-RULES-OCCURRENCES (sent)
   (let* ((original-rules-list (EXTRACT-GRAMMAR sent))
          (de-duplicated-list (remove-duplicates original-rules-list :test #'equalp))
          (count-list nil))
     (dolist (i de-duplicated-list)
       (push (reduce #'+ (mapcar #'(lambda (x) (if (equalp x i) 1 0)) original-rules-list) ) count-list))
     (setf count-list (nreverse count-list))
    (values de-duplicated-list count-list)))


 (defun EXTRACT-GRAMMAR (sent &optional (rules-stack nil))
   (cond ((null sent) 
          NIL)
         ((and (= (length sent) 1)
               (listp (first sent))
               (= (length (first sent)) 2)
               (symbolp (first (first sent)))
               (symbolp (second (first sent))))
          NIL)
         ((and (symbolp (first sent)) 
               (symbolp (second sent)) 
               (= 2 (length sent)))
          NIL)
         ((symbolp (first sent))
          (push (EXTRACT-GRAMMAR-RULE sent) rules-stack)
          (append rules-stack (EXTRACT-GRAMMAR (rest sent)   )))
         ((listp (first sent))
          (cond ((not (and (listp (first sent)) 
                           (= (length (first sent)) 2) 
                           (symbolp (first (first sent))) 
                           (symbolp (second (first sent)))))
                 (push (EXTRACT-GRAMMAR-RULE (first sent)) rules-stack)
                 (append rules-stack (EXTRACT-GRAMMAR (rest (first sent))) (EXTRACT-GRAMMAR (rest sent) )))
               (t (append rules-stack (EXTRACT-GRAMMAR (rest sent)  )))))))


(defun EXTRACT-GRAMMAR-RULE (sentence-or-phrase)
  (append (list (first sentence-or-phrase))
          '(-->)
          (mapcar #'first (rest sentence-or-phrase))))

代码调用如下(使用(1)作为输入,产生(3)作为输出):

(WRITE-RULES-AND-COUNTS-SORTED  '(ROOT
  (S
    (NP (NNP John))
    (VP (VBD said)
      (SBAR (IN that)
        (S
          (NP (PRP he))
          (VP (VBZ knows)
            (SBAR
              (WHNP (WP who))
              (S
                (NP (NNP Mary))
                (VP (VBZ likes)))))))))))
4

2 回答 2

4

Common Lisp 中的 S 表达式

在 Common Lisp s 表达式中,像,,.和其他字符是默认语法的一部分。

如果你想要在 Lisp s 表达式中具有任意名称的符号,你必须对它们进行转义。使用反斜杠转义单个字符或使用一对竖线转义多个字符:

CL-USER 2 > (loop for symbol in '(\, \. | a , b , c .|)
                  do (describe symbol))

\, is a SYMBOL
NAME          ","
VALUE         #<unbound value>
FUNCTION      #<unbound function>
PLIST         NIL
PACKAGE       #<The COMMON-LISP-USER package, 76/256 internal, 0/4 external>

\. is a SYMBOL
NAME          "."
VALUE         #<unbound value>
FUNCTION      #<unbound function>
PLIST         NIL
PACKAGE       #<The COMMON-LISP-USER package, 76/256 internal, 0/4 external>

| a , b , c .| is a SYMBOL
NAME          " a , b , c ."
VALUE         #<unbound value>
FUNCTION      #<unbound function>
PLIST         NIL
PACKAGE       #<The COMMON-LISP-USER package, 76/256 internal, 0/4 external>
NIL

标记/解析

如果您想处理其他输入格式而不是 s 表达式,您可能需要自己标记/解析输入。

原始示例:

CL-USER 11 > (mapcar (lambda (string)
                       (intern string "CL-USER"))
                     (split-sequence " " "S --> S , CC S ."))
(S --> S \, CC S \.)
于 2015-09-05T15:19:58.277 回答
0

更新

感谢 Joswig 博士的评论和代码演示:两者都非常有帮助。

在上述问题中,我有兴趣克服 , 和 . 是 Lisp 的默认语法的一部分(或至少适应这一事实)。所以我最终做的是编写函数 PRODUCE-PARSE-TREE-WITH-PUNCT-FROM-FILE-READ。它的作用是从文件中读取一个解析树,作为一系列字符串;从字符串中修剪空白;将字符串连接在一起以形成解析树的字符串表示;然后逐个字符地扫描这个字符串,搜索要修改的标点符号实例。修改实现了 Joswig 博士的建议。最后,修改后的字符串被转换为树(列表表示),然后发送到提取器以生成规则表和计数。为了实现,我将 StackOverflow 上其他地方的一些代码与我自己的原始代码拼凑在一起。结果(当然不是所有的标点符号都可以处理,因为这只是一个演示):

(defun PRODUCE-PARSE-TREE-WITH-PUNCT-FROM-FILE-READ (file-name)
  (let ((result (make-array 1 :element-type 'character :fill-pointer 0 :adjustable T))
        (list-of-strings-to-process (mapcar #'(lambda (x) (string-trim " " x)) 
                                      (GET-PARSE-TREE-FROM-FILE file-name)))
        (concatenated-string nil)
        (punct-list '(#\, #\. #\; #\: #\! #\?))
        (testchar nil)
        (string-length 0))
    (setf concatenated-string (format nil "~{ ~A~}" list-of-strings-to-process))
    (setf string-length (length concatenated-string))
    (do ((i 0 (incf i)))
        ((= i string-length) NIL)
      (setf testchar (char concatenated-string i))
      (cond ((member testchar punct-list)
             (vector-push-extend #\| result)
             (vector-push-extend testchar result)
             (vector-push-extend #\| result))
            (t (vector-push-extend testchar result))))
    (reverse result)
    (with-input-from-string (s result)
      (loop for x = (read s nil :end) until (eq x :end) collect x))))


(defun GET-PARSE-TREE-FROM-FILE (file-name)
  (with-open-file (stream file-name)
    (loop for line = (read-line stream nil)
        while line
        collect line)))

请注意,GET-PARSE-TREE-FROM-FILE 仅从仅包含一棵树的文件中读取一棵树。当然,这两个功能还没有准备好迎接黄金时段!

最后,可以处理包含(Lisp-reserved)标点符号的解析树 - 因此达到了原始目标 - 如下(用户提供包含一个解析树的文件名):

 (WRITE-RULES-AND-COUNTS-SORTED 
              (PRODUCE-PARSE-TREE-WITH-PUNCT-FROM-FILE-READ filename))

产生以下输出:

(NP --> PRP)                  3
(PP --> IN NP)                2
(VP --> VB PP)                1
(S --> VP)                    1
(VP --> VBD)                  1
(NP --> NN CC NN)             1
(ADVP --> RB)                 1
(PRN --> , ADVP PP ,)         1
(S --> PRN NP VP)             1
(WHADVP --> WRB)              1
(SBAR --> WHADVP S)           1
(NP --> NN)                   1
(NP --> DT NN)                1
(ADVP --> NP IN)              1
(VP --> VBD ADVP NP , SBAR)   1
(S --> NP VP)                 1
(S --> S : S .)               1
(ROOT --> S)                  1

该输出是使用以下输入(另存为文件名)的结果:

(ROOT
  (S
    (S
      (NP (PRP It))
      (VP (VBD was)
        (ADVP
          (NP (DT the) (NN day))
          (IN before))
        (NP (NN yesterday))
        (, ,)
        (SBAR
          (WHADVP (WRB when))
          (S
            (PRN (, ,)
              (ADVP (RB out))
              (PP (IN of)
                (NP (NN happiness)
                  (CC and)
                  (NN mirth)))
              (, ,))
            (NP (PRP I))
            (VP (VBD decided))))))
    (: :)
    (S
      (VP (VB go)
        (PP (IN for)
          (NP (PRP it)))))
    (. !)))
于 2015-09-06T18:53:33.687 回答