和文から主語・動詞（述語）・目的語のSVO（SPO）Triple setを抽出する関数

##作ったもの
#####（ *get_svo_triple.py* ）

係り受け解析器を用いて、テキストデータから、SVO（SPO)のTriple set（[主語, 動詞（述語）, 目的語]）を返す処理を行う実装コードを探してみたところ、なかなか見当たりませんでした。方々を探してみたところ、見つけた以下のコードがうまく動きました。

・ __[東邦大学 「2014-01-29 CaboChaによる係り受け解析の利用 ～ 文の主節の骨組を取り出す」](https://pepper.is.sci.toho-u.ac.jp/pepper/index.php?%A5%CE%A1%BC%A5%C8%2F%A5%C6%A5%AD%A5%B9%A5%C8%A5%DE%A5%A4%A5%CB%A5%F3%A5%B0%2FCaboCha%A4%C7%BC%E7%C0%E1%A4%CE%B9%FC%C1%C8%A4%F2%BC%E8%A4%EA%BD%D0%A4%B9)__

この記事では、__東邦大学の研究室が公開中の上記のコードを、表題の目的を達成する関数（メソッド）に書き換え__てみました。

書き換えたコードの全体は、__この記事の後半に掲載__しました。

###（ 文を1個、*parse*する例 ）
```Python3:Python3.9.0
>>> from get_svo_triple import *
>>>
>>> sentence = "ムスカさんは昨日、ジェラート屋さんでアフォガードをたくさんた食べた。"
>>> (result1, result2) = return_svo_triple(sentence)
>>> 
>>> print(result1)
{'主語': 'ムスカさんは', '述語': '食べた。'}
>>> 
>>> print(result2)
['ムスカさんは->食べた。', 'たくさんた->食べた。']
>>>
```

```Python3:Python3.9.0
>>> sentence = "ムスカさんは昨日、アフォガードをたくさん食べた。"
>>> (result1, result2) = return_svo_triple(sentence)
>>> 
>>> print(result1)
{'主語': 'ムスカさんは', '目的語': 'アフォガードを', '述語': '食べた。'}
>>> 
>>> print(result2)
['ムスカさんは->食べた。', '昨日、->食べた。', 'アフォガードを->食べた。', 'たくさん->食べた。']
>>>
```

```Python3:Python3.9.0
>>> sentence = "ムスカさんは昨日、赤い屋根のジェラート屋さんでアフォガードをたくさん食べた。"
>>> (result1, result2) = return_svo_triple(sentence)
>>> 
>>> print(result1)
{'主語': 'ムスカさんは', '目的語': 'アフォガードを', '述語': '食べた。'}
>>> 
>>> print(result2)
['ムスカさんは->食べた。', '昨日、->食べた。', 'ジェラート屋さんで->食べた。', 'アフォガードを->食べた。', 'たくさん->食べた。']
>>>
```

```Python3:Python3.9.0
>>> sentence = "ムスカさんはおとといと昨日、赤い屋根のジェラート屋さんで、アフォガードをたくさん食べた。"
>>> (result1, result2) = return_svo_triple(sentence)
>>> 
>>> print(result1)
{'目的語': 'アフォガードを', '述語': '食べた。'}
>>> 
>>> print(result2)
['ジェラート屋さんで、->食べた。', 'アフォガードを->食べた。', 'たくさん->食べた。']
>>>
```

```Python3:Python3.9.0
>>> sentence = "ムスカさんはおとといと昨日、赤い屋根のジェラート屋さんでアフォガードをたくさん食べた。"
>>> (result1, result2) = return_svo_triple(sentence)
>>> 
>>> print(result1)
{'主語': 'ムスカさんは', '目的語': 'アフォガードを', '述語': '食べた。'}
>>> 
>>> print(result2)
['ムスカさんは->食べた。', '昨日、->食べた。', 'ジェラート屋さんで->食べた。', 'アフォガードを->食べた。', 'たくさん->食べた。']
>>> 
```

###（ 文を複数件、*parse*する例 ）

__（サンプルテキストの出典）__

・ [NHK NewsWeb（国際面）2020年11月30日 22時03分付配信記事](https://www3.nhk.or.jp/news/html/20201130/k10012739201000.html?utm_int=news_contents_news-main_001)

```Python3:Python3.9.0
>>> sentence1 = "イランで核科学者が何者かに銃撃されて死亡した事件で、地元の国営テレビは関係者の話として、殺害に使用された武器はイスラエル製だと伝えました。"
>>> sentence2 = "この事件について、国営テレビの英語チャンネルは、30日、関係者の話として「現場で回収された武器にはイスラエルの軍事産業につながる特徴があり、イスラエル製の武器が使われていた」と伝えました。"
>>> 
>>> 
>>> result_list = [return_svo_triple(sentence) for sentence in [sentence1, sentence2]]
>>> 
>>> from pprint import pprint
>>> 
>>> pprint(result_list)
[({'主語': '武器は', '目的語': 'イスラエル製だと', '述語': '伝えました。'},
  ['事件で、->伝えました。',
   '国営テレビは->伝えました。',
   '話として、->伝えました。',
   '武器は->伝えました。',
   'イスラエル製だと->伝えました。']),
 ({'主語': '英語チャンネルは、', '目的語': '使われていた」と', '述語': '伝えました。'},
  ['事件について、->伝えました。',
   '英語チャンネルは、->伝えました。',
   '30日、->伝えました。',
   '話として->伝えました。',
   '使われていた」と->伝えました。'])]
>>> 
```

####（ ニュース記事の全文を処理する場合 ）

```Python3:Python3.9.0
>>> document = """
... イランで核科学者が何者かに銃撃されて死亡した事件で、地元の国営テレビは関係者の話として、殺害に使用された武器はイスラエル製だと伝えました。イランの国防軍需相は「暗殺に対しては必ずやり返す」と改めて報復を警告しています。
... 
... イラン政府によりますと、27日、国防軍需省の研究開発部門トップで、核開発技術を研究していたファクリザデ氏が何者かに銃撃されて死亡しました。
... 
... この事件について、国営テレビの英語チャンネルは、30日、関係者の話として「現場で回収された武器にはイスラエルの軍事産業につながる特徴があり、イスラエル製の武器が使われていた」と伝えました。
... 
... また、国営テレビのアラビア語チャンネルも「衛星を通じて、操作されたイスラエル製の武器が使われた」と伝えています。
... 
... 一方、保守系のファルス通信は、現場に実行犯の姿はなく、遠隔操作で作動する機関銃が使われた可能性があると伝えています。
... 
... この事件について30日、イスラエルの諜報相は地元のラジオ番組で「誰がやったのかは知らない」とコメントしています。
... 
... イランの首都テヘランで30日、ファクリザデ氏の葬儀が営まれ、参列したハタミ国防軍需相は「イラン国民の暗殺に対しては必ずやり返す。犯罪を企て、実行したものたちに罰を与える」と述べ、改めて報復を警告しています。"""
>>> 
>>> document = document.replace(" ", "").replace("　", "").replace("\n", "")
>>> sentence_list = document.split("。")
>>> pprint(len(sentence_list))
10
>>> sentence_list = [sentence for sentence in sentence_list if not(sentence == "")] 
>>> pprint(sentence_list)
['イランで核科学者が何者かに銃撃されて死亡した事件で、地元の国営テレビは関係者の話として、殺害に使用された武器はイスラエル製だと伝えました',
 'イランの国防軍需相は「暗殺に対しては必ずやり返す」と改めて報復を警告しています',
 'イラン政府によりますと、27日、国防軍需省の研究開発部門トップで、核開発技術を研究していたファクリザデ氏が何者かに銃撃されて死亡しました',
 'この事件について、国営テレビの英語チャンネルは、30日、関係者の話として「現場で回収された武器にはイスラエルの軍事産業につながる特徴があり、イスラエル製の武器が使われていた」と伝えました',
 'また、国営テレビのアラビア語チャンネルも「衛星を通じて、操作されたイスラエル製の武器が使われた」と伝えています',
 '一方、保守系のファルス通信は、現場に実行犯の姿はなく、遠隔操作で作動する機関銃が使われた可能性があると伝えています',
 'この事件について30日、イスラエルの諜報相は地元のラジオ番組で「誰がやったのかは知らない」とコメントしています',
 'イランの首都テヘランで30日、ファクリザデ氏の葬儀が営まれ、参列したハタミ国防軍需相は「イラン国民の暗殺に対しては必ずやり返す',
 '犯罪を企て、実行したものたちに罰を与える」と述べ、改めて報復を警告しています']
>>> 
>>> result_list = [return_svo_triple(sentence) for sentence in sentence_list]
>>> pprint(result_list)
[({'主語': '武器は', '目的語': 'イスラエル製だと', '述語': '伝えました'},
  ['事件で、->伝えました',
   '国営テレビは->伝えました',
   '話として、->伝えました',
   '武器は->伝えました',
   'イスラエル製だと->伝えました']),
 ({'主語': '国防軍需相は', '目的語': '報復を', '述語': '警告しています'},
  ['国防軍需相は->警告しています', 'やり返す」と->警告しています', '改めて->警告しています', '報復を->警告しています']),
 ({'述語': '死亡しました'}, ['よりますと、->死亡しました', '27日、->死亡しました', '銃撃されて->死亡しました']),
 ({'主語': '英語チャンネルは、', '目的語': '使われていた」と', '述語': '伝えました'},
  ['事件について、->伝えました',
   '英語チャンネルは、->伝えました',
   '30日、->伝えました',
   '話として->伝えました',
   '使われていた」と->伝えました']),
 ({'主語': 'アラビア語チャンネルも', '目的語': '使われた」と', '述語': '伝えています'},
  ['また、->伝えています', 'アラビア語チャンネルも->伝えています', '使われた」と->伝えています']),
 ({'目的語': 'あると', '述語': '伝えています'}, ['一方、->伝えています', 'あると->伝えています']),
 ({'主語': '諜報相は', '目的語': '知らない」と', '述語': 'コメントしています'},
  ['事件について->コメントしています',
   '30日、->コメントしています',
   '諜報相は->コメントしています',
   'ラジオ番組で->コメントしています',
   '知らない」と->コメントしています']),
 ({'主語': '暗殺に対しては', '目的語': '暗殺に対しては', '述語': 'やり返す'},
  ['営まれ、->やり返す', 'ハタミ国防軍需相は->やり返す', '暗殺に対しては->やり返す', '必ず->やり返す']),
 ({'目的語': '報復を', '述語': '警告しています'},
  ['述べ、->警告しています', '改めて->警告しています', '報復を->警告しています'])]
>>> 
```


##問題意識
__係り受け解析器__を用いて、__テキストデータから、SVO（SPO)のTriple set（[主語, 動詞（述語）, 目的語]）を返す処理__を行う実装コードを探してみたところ、__なかなか見当たりませんでした__。

そのため、備忘録まで、この記事を立ててみました。

時間をかけて方々を探してみたところ、見つけた以下のコードがうまく動きました。

・ [東邦大学 「2014-01-29 CaboChaによる係り受け解析の利用 ～ 文の主節の骨組を取り出す 」](https://pepper.is.sci.toho-u.ac.jp/pepper/index.php?%A5%CE%A1%BC%A5%C8%2F%A5%C6%A5%AD%A5%B9%A5%C8%A5%DE%A5%A4%A5%CB%A5%F3%A5%B0%2FCaboCha%A4%C7%BC%E7%C0%E1%A4%CE%B9%FC%C1%C8%A4%F2%BC%E8%A4%EA%BD%D0%A4%B9)

東邦大学の研究室が公開中のコードを、__以下の関数（メソッド）__に書き換えてみました。

```text:定義した関数（メソッド)のInput->Outputの入出力仕様
・引数と返り値：　str -> Tuple[Dict, List]
・引数に渡せるのは、1つの文（sentence）だけです。
・引数に渡すstr型のデータは、分かち書きにする必要はありません。平文のまま渡します。
```

##東邦大学のコードを関数（メソッド）で包んだスクリプト

```Python3:get_svo_triple.py
import CaboCha
import sys
import codecs
# https://docs.python.org/ja/3.6/library/typing.html
from typing import Dict, Tuple, List

def return_svo_triple(sentence:str) -> Tuple[Dict, List]:
    c = CaboCha.Parser()
    tree = c.parse(sentence)
    size = tree.size()
    myid = 0
    ku_list = []
    ku = ''
    ku_id = 0
    ku_link = 0
    kakari_joshi = 0
    kaku_joshi = 0
    
    for i in range(0, size):
        token = tree.token(i)
        if token.chunk:
            if (ku!=''):
                ku_list.append((ku, ku_id, ku_link, kakari_joshi, kaku_joshi))  #前 の句をリストに追加

            kakari_joshi = 0
            kaku_joshi = 0
            ku = token.normalized_surface
            ku_id = myid
            ku_link = token.chunk.link
            myid=myid+1
        else:
            ku = ku + token.normalized_surface
        
        m = (token.feature).split(',')
        if (m[1] == u'係助詞'):
            kakari_joshi = 1
        if (m[1] == u'格助詞'):
            kaku_joshi = 1
    
    ku_list.append((ku, ku_id, ku_link, kakari_joshi, kaku_joshi))  # 最後にも前の句をリストに追加
    for k in ku_list:
        if (k[2]==-1):  # link==-1?      # 述語である
            jutsugo_id = ku_id  # この時のidを覚えておく
    #述語句
    predicate_word = [k[0] for k in ku_list if (k[1]==jutsugo_id)]
    #for k in ku_list:
    #	if (k[1]==jutsugo_id):  # jutsugo_idと同じidを持つ句を探す
    #		print(k[1], k[0], k[2], k[3], k[4])
    #述語句に係る句
    # jutsugo_idと同じidをリンク先に持つ句を探す
    word_to_predicate_list = [k[0] for k in ku_list if k[2]==jutsugo_id]
    #　述語句に係る句 -> 述語句
    svo_arrow_text = [str(word_to_predicate) + "->" + str(predicate_word[0]) for word_to_predicate in word_to_predicate_list]
    #print(svo_arrow_text)
    
    svo_dict = {}
    for num, k in enumerate(ku_list):
        if (k[2]==jutsugo_id):  # jutsugo_idと同じidをリンク先に持つ句を探す
            if (k[3] == 1):
                subject_word = k[0]
                svo_dict["主語"] = subject_word
                #print(subject_word)
            if (k[4] == 1):
                object_word = k[0]
                svo_dict["目的語"] = object_word
                #print(object_word)
        if (k[1] == jutsugo_id):
                predicate_word = k[0]
                svo_dict["述語"] = predicate_word
                #print(predicate_word)
        
    return (svo_dict, svo_arrow_text)
        
```

##今後やりたいこと

######<１つ目>
・ 以下の論文ほかを参考に、__Web空間上のテキストデータから、出来事間の時系列因果構造を抽出__したい。
・ さらに、獲得した出来事間の時系列因果構造を、__視覚的に把握しやすいように、グラフ構造のネットワーク図に描画__したい。


・ [ 石井ほか 「SVO構造を用いた因果関係ネットワーク構築手法について」](https://ipsj.ixsq.nii.ac.jp/ej/?action=repository_action_common_download&item_id=66690&item_no=1&attribute_id=1&file_no=1)

######<2つ目>

・__[主語 -> 述語（動詞など） -> 目的語・補語]の三つ組データ（*Triple set*）__を、__*[Prolog](https://qiita.com/tags/prolog)*などの論理推論ができるプログラミング言語__に与えることで、__複数の三つ組データ（*Triple set*）間の因果関係や概念意味関係を用いた知識推論__を行ってみたい。

######<3つ目>

・ __*[MaskedTransformer](https://openaccess.thecvf.com/content_cvpr_2018/papers/Zhou_End-to-End_Dense_Video_CVPR_2018_paper.pdf)*__や__*[VideoBERT](https://arxiv.org/pdf/1904.01766.pdf)*__などの学習済みモデルを用いて、静止画像や動画の内容説明文（内容要約文、キャプション文）を生成し、その説明文に含まれる[主語 -> 述語（動詞など） -> 目的語・補語]の三つ組データ（*Triple set*）を抽出したい。
・ 動画の説明文から抜き出した「主語」や「目的語」や「補語」の単語を、DB（データベース）に格納して、__固有表現抽出器（*Named Entitity Recogniton*）__を用いて、__「人名」や「組織名」や「地名」といった固有表現ラベルを紐付け__たい。
・ その結果、__特定の人物や組織や地名が登場する静止画や動画__を、__容易に検索__できるプラットフォームを構築したい。

__*MaskedTransformer*の「再現実装コード」__は、Albert社さんのリポジトリから取得可能です。

・ [ （ GitHub ）ALBERT-Inc/blog_masked_transformer](https://github.com/ALBERT-Inc/blog_masked_transformer)

> 公開されている学習済み重みファイルは__こちら__からダウンロードできます。これを解凍し、yc2-2L-e2e-maskというディレクトリの下にある重みファイルを利用して学習してください。

・ [Albert社ブログ 「動画認識手法の紹介とキャプション生成手法Masked Transformerについての解説」](https://blog.albert2005.co.jp/2020/06/05/video-captioning/)

・ __*Show and Tell*論文__の__学習済みモデル__の利用方法は以下が参考になる。
： [@48saaanさんのQiita記事 「「Show and Tell」の TensorFlow をCPUでお試し」](https://qiita.com/48saaan/items/1661a912c740308d3fd5)

> トレーニング済みモデルの準備
GitHubのtensorflow/modelsのIssues「Pretrained model for img2txt? #466」で「here are links to a pre-trained model:」を検索すると、その書き込みの下に3つのリンクがあります。
そこから「im2txt_2016_10_11.2000000.tar.gz」(←finetuned)と「word_counts.txt」を~/test/models/research/im2txtにコピー
>
>解凍もしておきます
>
>```bash
tar xf im2txt_2016_10_11.2000000.tar.gz
rm im2txt_2016_10_11.2000000.tar.gz
>```