言語処理100本ノック-58:タプルの抽出


言語処理100本ノック 2015「第6章: 英語テキストの処理」58本目「タプルの抽出」記録です。
前回ノックは全体の係り受け可視化でしたが、今回は特定の係り受けを抽出して出力です。8割くらいはやっていること同じです。

参考リンク

リンク 備考
058.タプルの抽出.ipynb 回答プログラムのGitHubリンク
素人の言語処理100本ノック:58 多くのソース部分のコピペ元
Stanford Core NLP公式 最初に見ておくStanford Core NLPのページ

環境

種類 バージョン 内容
OS Ubuntu18.04.01 LTS 仮想で動かしています
pyenv 1.2.16 複数Python環境を使うことがあるのでpyenv使っています
Python 3.8.1 pyenv上でpython3.8.1を使っています
パッケージはvenvを使って管理しています
Stanford CoreNLP 3.9.2 インストールしたのが1年前で詳しく覚えていないです・・・
1年たってもそれが最新だったのでそのまま使いました
openJDK 1.8.0_242 他目的でインストールしていたJDKをそのまま使いました

第6章: 英語テキストの処理

学習内容

Stanford Core NLPを用いた英語のテキスト処理を通じて,自然言語処理の様々な基盤技術を概観します.

Stanford Core NLP, ステミング, 品詞タグ付け, 固有表現抽出, 共参照解析, 係り受け解析, 句構造解析, S式

ノック内容

英語のテキスト(nlp.txt)に対して,以下の処理を実行せよ.

58. タプルの抽出

Stanford Core NLPの係り受け解析の結果(collapsed-dependencies)に基づき,「主語 述語 目的語」の組をタブ区切り形式で出力せよ.ただし,主語,述語,目的語の定義は以下を参考にせよ.

  • 述語: nsubj関係とdobj関係の子(dependant)を持つ単語
  • 主語: 述語からnsubj関係にある子(dependent)
  • 目的語: 述語からdobj関係にある子(dependent)

課題補足(「タプル」について)

「タプル」と聞くとPythonのTupleを想起してしまいましたが、今回はどうも違うようです。
まず、Wikipedia「タプル」では以下のように書かれています。「複数の構成要素からなる組」のことです。

タプルまたはチュープル(英: tuple)とは、複数の構成要素からなる組を総称する一般概念。

Stanford CoreNLPでは、Stanford Open Information Extractionの中でTupleについて触れています。

Open information extraction (open IE) refers to the extraction of relation tuples, typically binary relations, from plain text, such as (Mark Zuckerberg; founded; Facebook).

そして、同じページにある以下の図が「タプル」についてわかりやすいです。

回答

回答プログラム 058.タプルの抽出.ipynb

import xml.etree.ElementTree as ET

texts = []
# sentence列挙、1文ずつ処理
for sentence in ET.parse('./nlp.txt.xml').iterfind('./document/sentences/sentence'):

    output = {}

    # dependencies列挙
    for dep in sentence.iterfind('./dependencies[@type="collapsed-dependencies"]/dep'):

        # 関係チェック
        dep_type = dep.get('type')
        if dep_type == 'nsubj' or dep_type == 'dobj':

            # 述語の辞書に追加
            governor = dep.find('./governor')
            index = governor.get('idx')
            if index in output:
                texts = output[index]
            else:
                texts = [governor.text, '', '']

            # 主語 or目的語に追加(同じ述語なら後勝ち)
            if dep_type == 'nsubj':
                texts[1] = dep.find('./dependent').text
            else:
                texts[2] = dep.find('./dependent').text
            output[index] = texts

    for key, texts in output.items():
        if texts[1] != '' and texts[2] != '':
            print(sentence.get('id'), '\t', '\t'.join(texts))

回答解説

XMLファイルのパス

以下のXMLファイルのパスと目的とする係り元、係り先のマッピングです。第5階層のdependenciesタグは属性typecollapsed-dependenciesのものを対象とします。
また、第6階層の属性typensubjdobjのものが対象です。

出力 第1階層 第2階層 第3階層 第4階層 第5階層 第6階層 第7階層
係り元 root document sentences sentence dependencies dep governor
係り先 root document sentences sentence dependencies dep dependent

XMLファイルはGitHubに置いています。

nlp.txt.xml(抜粋)
<root>
  <document>
    <docId>nlp.txt</docId>
    <sentences>
      <sentence id="1">

--中略--

        <dependencies type="collapsed-dependencies">
          <dep type="root">
            <governor idx="0">ROOT</governor>
            <dependent idx="18">field</dependent>
          </dep>

--中略--

          <dep type="nsubj">
            <governor idx="18">field</governor>
            <dependent idx="12">processing</dependent>
          </dep>

--中略--

          <dep type="dobj">
            <governor idx="13">enabling</governor>
            <dependent idx="14">computers</dependent>
          </dep>

出力用辞書作成

出力用の辞書型変数outputを1文ごとに作成している箇所です。述語(governor)のインデックスを辞書のキーとして、辞書の値はリスト型にしていて中身は「述語テキスト」、「主語テキスト」、「目的語テキスト」を入れています。もし複数の主語、目的語を保つ場合は後勝ち方式にしています。

# dependencies列挙
for dep in sentence.iterfind('./dependencies[@type="collapsed-dependencies"]/dep'):

    # 関係チェック
    dep_type = dep.get('type')
    if dep_type == 'nsubj' or dep_type == 'dobj':

        # 述語の辞書に追加
        governor = dep.find('./governor')
        index = governor.get('idx')
        if index in output:
            texts = output[index]
        else:
            texts = [governor.text, '', '']

        # 主語 or目的語に追加(同じ述語なら後勝ち)
        if dep_type == 'nsubj':
            texts[1] = dep.find('./dependent').text
        else:
            texts[2] = dep.find('./dependent').text
        output[index] = texts

出力部

主語または、目的語があれば出力しています。

for key, texts in output.items():
    if texts[1] != '' and texts[2] != '':
        print(sentence.get('id'), '\t', '\t'.join(texts))

出力結果(実行結果)

プログラム実行すると以下の結果が出力されます。

出力結果
3    involve    understanding   generation
5    published  Turing  article
6    involved   experiment  translation
11   provided   ELIZA   interaction
12   exceeded   patient base
12   provide    ELIZA   response
14   structured which   information
19   discouraged    underpinnings   sort
19   underlies  that    approach
20   produced   Some    systems
21   make   which   decisions
23   contains   that    errors
34   involved   implementations coding
38   take   algorithms  set
39   produced   Some    systems
40   make   which   decisions
41   have   models  advantage
41   express    they    certainty
42   have   Systems advantages
43   make   procedures  use
44   make   that    decisions