2015/07/15

dorothy(Graphviz)で木構造(構文解析木)を可視化

 dorothyは, GraphvizのClojure向けのラッパーです. シーケンスによる(Graphvizの)DOT言語を用いて, グラフ構造を記述すると, そこから, そのグラフを可視化します.
 dorothyの基本的な使い方については, dorothyのREADMEか, 以下のページの説明が詳しいです.


 木構造もグラフの一種なので, パーサ(opennlp)によって生成された構文解析木を表示させてみることにします. Clojureのopennlpについては, にも触れましたが, 今回は, これにdorothyを合わせて使ってみます. 長文を構文解析し, dorothyで可視化すると, 以下のようになります.

 opennlpからdorothyへの変換は, 基本的に, 木構造のリンク関係を[src dst]のリストへ変換すればOKで, 書いてみると, こんな感じになります.
(require '[dorothy.core :refer :all];; [dorothy "0.0.6"]
         '[opennlp.treebank :refer :all]) ;; [clojure-opennlp "0.3.2"]

(def treebank-parser
  (make-treebank-parser "en-parser-chunking.bin"))

(defn gensym- [tag]
  (keyword (gensym (str (name tag) "-"))))

(defn ungensym- [tag]
  (first (clojure.string/split (name tag) #"-")))

(defn tree->links [tree]
  (if (string? tree)
    [(gensym- tree)]
    (let [{tag :tag chunk :chunk} tree
          children (map tree->links chunk)
          top (keyword (gensym- tag))]
      (concat [top]
              (map (fn [x] [top (first x)]) children)
              (apply concat (map rest children))))))

(defn treeviz [en-text]
  (letfn [(remove-sym [node]
            (subgraph [[:node {:label (ungensym- node)}] node]))]
    (let [link-pairs
          (->> (treebank-parser [en-text]) first make-tree tree->links)
          subnodes
          (->> link-pairs flatten distinct (map remove-sym))
          params
          [(graph-attrs {:size "5, 5"}) (node-attrs {:fontsize 10})]]
      (->> link-pairs (concat params subnodes) digraph dot show!))))
 Graphviz側では, 親ノードから, 子ノードへのリンクの表現のリストで, 木構造を表すわけですが, ノードを表す識別子(POS(品詞)や名詞)が重複する可能性があるのでgensymなどを使ってノードのアイデンティティを確立します. 再帰的にリンクを表すペアのリストを作って上に投げるという処理を繰り返すだけで生成できるdorothyの記法は, かなりお手軽です.
 dorothyには, 以下のような[srs dst]で表されるペアのリストを渡します.
([:TOP-2782 :S-2783] [:S-2783 :NP-2784] [:S-2783 :VP-2791] [:S-2783 :.-2801] [:NP-2784 :DT-2785] [:NP-2784 :NN-2787] [:NP-2784 :NN-2789] [:DT-2785 :This-2786] [:NN-2787 :parse-2788] [:NN-2789 :tree-2790] [:VP-2791 :VBZ-2792] [:VP-2791 :NP-2794] [:VBZ-2792 :visualizes-2793] [:NP-2794 :DT-2795] [:NP-2794 :NN-2797] [:NP-2794 :NN-2799] [:DT-2795 :a-2796] [:NN-2797 :parse-2798] [:NN-2799 :tree-2800] [:.-2801 :.-2802])
 これだけでも, 木構造は可視化できますが, これだと, シノニム部分がくっついて見づらいため, 各ノードのラベルは数値の部分を抜いた名前で表すことにします. 各ノードにラベル(ノードとして表示される名前)をつけるには, subgraphを使います. その他, 表示用のオプションがいくつか設定できますが, 今回は, フォントサイズと, 表示される画像のサイズを設定しました.
user> (treeviz "This tree visualizes a parse tree .")
こんな感じで実行するとして,
 上図の木構造が出てきます. 自然変換言語処理の教科書に乗ってそうな図ですね.
 TOPの下のSは, Statement(文)のSです. SubjectのSではないです. 主語は, NPで表されている左側の部分木です.

 ちなみに, 要素の順番は重要で, 上記の例だと, 綺麗に元の文の単語列が左から順に表示されていますが, リストの順序が異なれば, 可視化の結果も異なり, 木構造としては成立していても変な形で表示されたりもします.

0 件のコメント :