日本語WordNetを使って、上位語を検索できるツールをpythonで作ってみた

「日本語WordNetを使って、何か面白いことができないか」の第二弾です。

前回：
[日本語WordNetを使って、類義語を検索できるツールをpythonで作ってみた](https://qiita.com/pocket_kyoto/items/1e5d464b693a8b44eda5)

今回は、前回の知識を使って、上位語を検索できるツールをpython3で作ってみました。

# WordNetを利用するまで

WordNetを利用するまでに必要な準備を振り返ります。
WordNetは、同義語、上位・下位語などがまとめられている、概念の辞書です。
sqliteDB形式のWordNetを、[日本語WordNetのページ](http://compling.hss.ntu.edu.sg/wnja/)からダウンロードして利用します。

WordNetのDBに接続する際は、sqlite3という標準ライブラリを使います。

```python
import sqlite3
conn = sqlite3.connect("wnjpn.db")
```

# WordNetに含まれる単語を階層化する

日本語WordNetには、上位語、下位語などの概念の階層関係に関する情報が含まれています。
まずは、この情報をWordNetから抽出してみます。

Pythonでの木構造を定義し、その中にWordNetの概念情報を格納します。
木構造の実装は、[藤井章博　研究室](https://sites.google.com/site/hoseifujii/news/pythonniyorumugouzaonozaiguidingyiniyorushizhuang)に倣います。

```python
class node:
    def __init__(self, name, children=None):
        self.name = name  # String
        self.children = children  # List of Class node

    # 結果表示用
    def display(self, indent = 0):
        if self.children != None:
            print(' '*indent + self.name)
            for c in self.children:
                c.display(indent+1)
        else:
            print(' '*indent + self.name)
```

WordNetの中のsynlinkテーブルにアクセスし、link = 'hypo'に該当するものに絞ることで、
上位-下位の関係にある概念だけを抽出します。

```python
# 上位-下位の関係にある概念の抽出
hierarchy_dict = {}  # key:上位語(String), value:下位語(List of String)
n_term_set = set()  # 下位語に含まれる単語集合

cur = conn.execute("select synset1,synset2 from synlink where link='hypo'")  # 上位語-下位語の関係にあるものを抽出
for row in cur:
    b_term = row[0]
    n_term = row[1]
    
    if b_term not in hierarchy_dict:
        hierarchy_dict[b_term] = []
        
    hierarchy_dict[b_term].append(n_term) 
    n_term_set.add(n_term)

print("上位語に含まれる単語の数 ： %s" % len(hierarchy_dict))
    
top_concepts = list(set(hierarchy_dict.keys()) - n_term_set)
print("上位語に含まれる単語の中で下位語に含まれない単語の数 ： %s" % len(top_concepts))
```
出力：
上位語に含まれる単語の数 ： 20008
上位語に含まれる単語の中で下位語に含まれない単語の数 ： 346
***

synsetのIDからsynsetの名称への変換を楽にするために、辞書を定義します。
synsetテーブルから情報を抽出します。
synsetの名称は全て英語です。

```python
# synset(概念)のIDから、概念の名称に変換する辞書の作成
synset_name_dict = {}  # key:synsetのID, value:synsetの名称
cur = conn.execute("select synset,name from synset")
for row in cur:
    synset_name_dict[row[0]] = row[1]
for k,v in synset_name_dict.items():
    print("%s : %s" % (k,v))
    break
```
出力：
07125096-n : expletive
***

ここまでの定義を用いて、上位-下位の関係を木構造に入れていきます。
詳細の説明は省きますが、下位の語が未登録の場合、再帰的に呼ばれる関数を用いて、データを投入します。

```python
# 下位の語が未登録の場合、再帰的に呼ばれる関数
def recrusive_register(term, synset_name_dict, hierarchy_dict, node_tree_dict):
    for term in hierarchy_dict[term]:
        if term not in node_tree_dict:
            if term in hierarchy_dict:
                recrusive_register(term, synset_name_dict, hierarchy_dict, node_tree_dict)
                node_tree_dict[term] = node(synset_name_dict[term], [node_tree_dict[t] for t in hierarchy_dict[term]])
            else:
                node_tree_dict[term] = node(synset_name_dict[term])

# データ投入
node_tree_dict = {}
for k in top_concepts:  # 最上位の語を起点として木構造を作成
    recrusive_register(k, synset_name_dict, hierarchy_dict, node_tree_dict)
    node_tree_dict[k] = node(synset_name_dict[k], [node_tree_dict[term] for term in hierarchy_dict[k]])

```
node_tree_dictの中に木構造の情報が保存されています。試しに、ある1つの上位語を起点とした木構造を出力してみます。

```python
node_tree_dict[top_concepts[10]].display()
```
出力：
experience
-incline
-recapture
-pride
-smolder
-harbor
-cool_off
-see_red
--bridle
--steam
:
(中略)
:
-rejoice
--cheer_up
---triumph
----glory
---buoy_up
--gladden
--walk_on_air
---triumph
-sympathize
-pride_oneself
-burn
-die
-fly_high
-shine
-glow
***

experienceという上位概念にあたる単語の下位に属する概念を、木構造で出力することができました。

# 上位語を検索できるツールを作ってみた

ここまでの知識を用いて、指定語の上位概念を検索するツールを作ってみたいと思います。
まずは、下位-上位の関係にある概念を抽出します。
上位語を検索できるようしたいため、下位語がKey、上位語がValueとなるようにデータを持ちます。

```python
# 下位-上位の関係にある概念の抽出
cur = conn.execute("select synset1,synset2 from synlink where link='hypo'")

hierarchy_dict = {}  # key:下位語(String), value:上位語(String)

for row in cur:
    b_term = row[0]
    n_term = row[1]
    
    if n_term not in hierarchy_dict:
        hierarchy_dict[n_term] = b_term
```

上位語を検索できる関数を以下のように書きました。前回作成した関数を追加・修正しています。

```python
# 特定の単語を入力とした時に、上位語を検索する関数
def SearchTopConceptWords(word, hierarchy_dict):

    # 問い合わせしたい単語がWordnetに存在するか確認する
    cur = conn.execute("select wordid from word where lemma='%s'" % word)
    word_id = 99999999  #temp 
    for row in cur:
        word_id = row[0]
    
    # Wordnetに存在する語であるかの判定
    if word_id==99999999:
        print("「%s」は、Wordnetに存在しない単語です。" % word)
        return
    else:
        print("【「%s」の最上位概念を出力します】\n" % word)
    
    # 入力された単語を含む概念を検索する
    cur = conn.execute("select synset from sense where wordid='%s'" % word_id)
    synsets = []
    for row in cur:
        synsets.append(row[0])
    
    # 概念に含まれる単語を検索して画面出力する
    no = 1
    for synset in synsets:
        cur1 = conn.execute("select name from synset where synset='%s'" % synset)
        for row1 in cur1:
            print("%sつめの概念 : %s" %(no, row1[0]))
        cur2 = conn.execute("select def from synset_def where (synset='%s' and lang='jpn')" % synset)
        sub_no = 1
        for row2 in cur2:
            print("意味%s : %s" %(sub_no, row2[0]))
            sub_no += 1
        
        # 上位語の検索部分
        b_term = ""
        while(synset in hierarchy_dict.keys()):
            synset = hierarchy_dict[synset]
            
        cur1 = conn.execute("select name from synset where synset='%s'" % synset)
        for row1 in cur1:
            print("最上位概念 : %s" % row1[0])
            
        cur2 = conn.execute("select def from synset_def where (synset='%s' and lang='jpn')" % synset)
        sub_no = 1
        for row2 in cur2:
            print("意味%s : %s" %(sub_no, row2[0]))
            sub_no += 1

        # 更新          
        print("\n")
        no += 1
```

この関数に単語と、下位-上位の関係に関する情報を入力することで、指定した単語の最上位の概念を検索できます。
初めに、前回と同様に「ネコ」の上位語を検索してみます。

```python
SearchTopConceptWords("ネコ", hierarchy_dict)
```
出力：
【「ネコ」の最上位概念を出力します】

1つめの概念 : true_cat
意味1 : 通常、厚く柔らかい毛皮を持ち、吠えることのできないネコ科の哺乳類：家ネコ
意味2 : ヤマネコ
最上位概念 : entity
意味1 : それ自身の確かな存在（生きているか、死んでいるの）を持つために認められる、知られている、あるいは推測される
***

ネコを最上位の概念にまで抽象化させると、entity（存在）まで行きついてしまいました。

次いで、前回と同様に「月」の上位語を検索してみます。

```python
SearchTopConceptWords("月", hierarchy_dict)
```
出力：
【「月」の最上位概念を出力します】

1つめの概念 : moonlight
意味1 : 月の光
最上位概念 : entity
意味1 : それ自身の確かな存在（生きているか、死んでいるの）を持つために認められる、知られている、あるいは推測される


2つめの概念 : month
意味1 : 暦年を12に分けた中の1つ
最上位概念 : entity
意味1 : それ自身の確かな存在（生きているか、死んでいるの）を持つために認められる、知られている、あるいは推測される


3つめの概念 : month
意味1 : 約30日間という時間単位
最上位概念 : entity
意味1 : それ自身の確かな存在（生きているか、死んでいるの）を持つために認められる、知られている、あるいは推測される


4つめの概念 : moon
意味1 : 地球の天然衛星
最上位概念 : moon
意味1 : 地球の天然衛星

***

「月」で検索した場合にも、大体entity（存在）に行きつきました。
moonに対しては、WordNetで上位語が定義されていなかったため、moon自体が最上位の概念となりました。

最後に、毛色の違う単語として、「忖度」で検索したいと思います。

```python
SearchTopConceptWords("忖度", hierarchy_dict)
```
出力：
【「忖度」の最上位概念を出力します】

1つめの概念 : hypothesise
意味1 : 特に不確かであるか仮の根拠で信じる
最上位概念 : cerebrate
意味1 : 推論、決定をするためまたは、結論や判断に達するために心、あるいは理性を使うまたはを発揮する
***

一応それっぽい結果になりました。

# まとめと今後
今回は、WordNetの上位-下位の概念を用いて動作するツールを作って遊んでみました。
次回は、実際のデータを用いた解析に何らかチャレンジしてみたいと思います。

# 参考
[1] [日本語WordNetのページ](http://compling.hss.ntu.edu.sg/wnja/)
[2] [藤井章博研究室 : Python による　木構造の再帰定義による実装](https://sites.google.com/site/hoseifujii/news/pythonniyorumugouzaonozaiguidingyiniyorushizhuang)
[3] [[自然言語] Wordnet × Pythonで類義語を抽出する](
https://www.yoheim.net/blog.php?q=20160201)