ナード戦隊データマン

データサイエンスを用いて悪と戦うぞ

corefとopenieで知識グラフを自動構築

知識グラフとは、主語・述語・目的語のトリプルからなるグラフです。主語と目的語はエンティティであり、述語はリレーションとなります。ここでは、CoreNLPのco-reference resolutionとopenieを使って知識グラフの自動構築をします。

事前準備

  1. CoreNLPサーバーを立ち上げる。
  2. サンプル文をexample_sentence.txtという名前で保存する。

サンプル文: https://github.com/sugiyamath/openie_experiment/blob/master/example_sentence.txt

コード

# coding: utf-8
import json

def fix_corefs(corefs):
    out = {}
    for _, coref in corefs.items():
        keys = []
        prop = ""
        for item in coref:
            n = item["sentNum"]-1
            s = item["startIndex"]-1
            e = item["endIndex"]-1
            if item["type"] == "PROPER":
                if item["text"] > prop:
                    prop = item["text"]
                    if prop.endswith("'s"):
                        prop = prop.replace("'s", "").strip()
            
            keys.append((n,s,e))
        for key in keys:
            out[key] = prop
    return out


def fix_openie(sents, crdict):
    out = []
    for i, sent in enumerate(sents):
        sent = sent["openie"]
        for er in sent:
            sbjs = er["subjectSpan"]
            objs = er["objectSpan"]
            sidx = tuple([i] + sbjs)
            oidx = tuple([i] + objs)
            if sidx in crdict:
                er["subject"] = crdict[sidx]
            if oidx in crdict:
                er["object"] = crdict[oidx]
            out.append((er["subject"], er["relation"], er["object"]))
    return out

def tuple2dict(data):
    out = {}
    for d in data:
        if d[0] not in out:
            out[d[0]] = {}
        if d[1] not in out[d[0]]:
            out[d[0]][d[1]] = {}
        if d[2] not in out[d[0]][d[1]]:
            out[d[0]][d[1]][d[2]] = 0
        out[d[0]][d[1]][d[2]] += 1
    return out
                

if __name__ == "__main__":
    from pprint import pprint
    from pycorenlp import StanfordCoreNLP
    import pickle
    
    nlp = StanfordCoreNLP("http://localhost:9000")
    pros = {"annotators": "coref,openie", "outputFormat": "json", "timeout": 500000}
    with open("./example_sentence.txt") as f:
        text = f.read().replace("\n", " ")
    res = nlp.annotate(text, pros)
    with open("data.pkl", "wb") as f:
        pickle.dump(res, f)
    crdict = fix_corefs(res["corefs"])
    out = fix_openie(res["sentences"], crdict)
    out = tuple2dict(out)
    pprint(out)

実行

python openie.py > out.txt

結果

{'Barack Obama': {'is': {'U.S.': 1, 'president': 1}},
 'Canada': {'has': {'Parliament': 1}, 'in': {'capital': 1}},
 'Canada 2020 chairman Tom Pitfield': {'has called': {'generation-defining leader': 1,
                                                      'generation-defining political leader': 1,
                                                      'leader': 1,
                                                      'political leader': 1}},
 'Canada chairman Tom Pitfield': {'has called': {'generation-defining leader': 1,
                                                 'generation-defining political leader': 1,
                                                 'leader': 1,
                                                 'political leader': 1}},
 'Donald Trump': {'is on behalf of': {'President': 1}},
 'Justin Trudeau': {'is': {'Prime Minister': 1}},
 'Mike Pence': {'is': {'U.S.': 1, 'Vice-President': 1}},
 'Obama': {"'s become": {"Obama 's presidency ended": 1,
                         "Obama 's presidency ended in January 2017": 1,
                         'big name': 1,
                         'big name on paid speaking circuit': 1,
                         'big name on speaking circuit': 1,
                         'name': 1,
                         'name on paid speaking circuit': 1,
                         'name on speaking circuit': 1},
           'addressed': {"Canada 's Parliament": 1},
           'also visited': {'the think-tank Canada 2020': 1},
           'also visited Canada in': {'winter': 1, 'winter of 2009': 1},
           'appeared at': {'event': 1,
                           'event in Calgary': 1,
                           'similar event': 1,
                           'similar event in Calgary': 1},
           'appeared in': {'March': 1},
           'big name on': {'paid speaking circuit': 1, 'speaking circuit': 1},
           'give': {'answer questions': 1, 'speech': 1},
           'give answer questions at': {'event': 1,
                                        "event in Canada 's capital": 1},
           'give answer questions later tonight at': {'event': 1,
                                                      "event in Canada 's capital": 1},
           'give answer questions tonight at': {'event': 1,
                                                "event in Canada 's capital": 1},
           'give later tonight': {'answer questions': 1, 'speech': 1},
           'give speech at': {'event': 1, "event in Canada 's capital": 1},
           'give speech later tonight at': {'event': 1,
                                            "event in Canada 's capital": 1},
           'give speech tonight at': {'event': 1,
                                      "event in Canada 's capital": 1},
           'give tonight': {'answer questions': 1, 'speech': 1},
           'has': {'become': 1, 'presidency': 1},
           'is': {'generation-defining': 1,
                  'generation-defining leader': 1,
                  'generation-defining political leader': 1,
                  'leader': 1,
                  'political': 1,
                  'political leader': 1,
                  'set': 1},
           'name on': {'paid speaking circuit': 1, 'speaking circuit': 1},
           'received': {'cheers': 1, 'standing ovation': 1},
           'received cheers in': {'House': 1, 'House of Commons': 1},
           'received standing ovation in': {'House': 1, 'House of Commons': 1},
           'shortly taking': {'office': 1},
           'taking': {'office': 1},
           'visited': {'the think-tank Canada 2020': 1},
           'visited Canada in': {'winter': 1, 'winter of 2009': 1}},
 "Obama 's presidency": {'ended in': {'January 2017': 1}},
 'Ottawa': {'at': {'Tire Centre': 1}},
 'Tom Pitfield': {'is': {'the think-tank Canada 2020': 1}},
 'Trudeau': {'to': {'leadership': 1}},
 'Trump': {'Tweeting': {'One': 1},
           'called': {'dishonest': 1, 'very dishonest': 1},
           'pay': {'solo visit': 1, 'visit': 1},
           'pay visit to': {'Ottawa': 1}},
 'U.S. Vice-President Mike Pence': {'brought': {'greetings': 1,
                                                'warm greetings': 1},
                                    'brought greetings from': {'Trump': 1},
                                    'give': {'momentum': 1},
                                    'give momentum to': {'American trade deal': 1,
                                                         'North American trade deal': 1,
                                                         'new American trade deal': 1,
                                                         'new North American trade deal': 1,
                                                         'new trade deal': 1,
                                                         'trade deal': 1},
                                    'was in': {'On Thursday Ottawa': 1,
                                               'Ottawa': 1,
                                               'town': 1}},
 'event': {'hosted by': {'think-tank Canada 2020': 1},
           'is in': {"Canada 's capital": 1}},
 'office': {'is in': {'2016': 1}},
 'similar event': {'is in': {'Calgary': 1}},
 'today': {'for': {'event': 1}}}

説明

co-reference resolutionとは、代名詞が指す名詞を特定するタスクです。もっと正確に言うと、テキスト内で同じエンティティを指し示す表現を抽出するタスクです。

一方、OpenIEとは、主語・述語・目的語というトリプルを抽出するタスクです。

OpenIEで抽出すると、主語や目的語が代名詞の状態で抽出されることがあります。知識グラフを構築する上では、これらの代名詞を名詞に変換したほうが便利なので、corefで変換しています。

corefとopenieは、CoreNLPのパラメータとして指定することが可能です。

考察

リレーションやエンティティを、構築済みの知識グラフに対して参照することによって、既存の知識グラフを拡大する目的でこのツールを使うことも考えられます。その場合、抽出された名詞・述語を、既存のエンティティとリレーションのどれにあたるのかを特定するという別のタスクを解く必要が出てくると思います。

一方、抽出された知識グラフをそのまま使うようなユースケースでは、検索エンジンのクエリの拡張やオートコンプリートに役立てたりできるかもしれません。ただし、Webからクロールした情報から自動的に知識グラフを構築する場合、2つの問題があるような気はします:

  1. Webページからコンテンツのみを自動抽出する。
  2. 知識グラフ全体のデータのサイズ的な問題。

Webページからコンテンツを自動抽出するために、私はdomextractというツールを開発したことがありますが、精度はまだ改善の余地があります。

GitHub - sugiyamath/domextract: DOM based web content extractor for Japanese websites

  1. Webからクロール
  2. コンテンツタイプを判定。
  3. コンテンツタイプが「記事」ならコンテンツ本文を抽出。
  4. 抽出された本文をcorefとopenieを使って抽出。
  5. 抽出されたエンティティとリレーションを知識グラフへ挿入。

というような自動化が行えれば、世界の知識の関係を自動的にグラフ形式で構築することができます。

参考