日本語 WordNet を使って複数単語に共通する上位語を取得する

# はじめに
文章を書いている際に，幾つかの単語に共通した上位語が何かを思い悩んだ経験は誰しもあるのではないでしょうか．具体例を挙げて説明すると，「猫と犬のような【上位語】は普段肉を食べている」などの文章を書きたいと思った時に，当てはまる上位語として何が適切なのかを考えることになると思います．ちなみにこの場合は【肉食動物】などが当てはまります．この記事では，例えば犬と猫という単語を元にして上位語を取得し，共通した単語を探すことによって適切な上位語を見つけることを目指します．

# やりたいこと
入力した２つの単語に共通した上位語，すなわち，２つの概念に共通する，抽象化した上位概念の全てを取得することを目指します．

# 例題
「犬」と「猫」という単語に対して WordNet を利用し，ある特定の意味において共通する上位語を全て取得します．

# 準備
WordNet から，データベースである wnjpn.db.gz をダウンロードします．
http://compling.hss.ntu.edu.sg/wnja/jpn/downloads.html の「Japanese Wordnet and English WordNet in an sqlite3 database」からダウンロード可能となっています．
ダウンロード後，7zip などで解凍して以下のようなファイル構成にするとデータベースが利用可能になります．
> project ファイル/
> ├ main.py
> └ wnjpn.db

# スクリプト
```python:main.py
#! env python
# -*- coding: utf-8 -*-
import sqlite3
conn = sqlite3.connect("wnjpn.db")

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

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

    # 問い合わせしたい単語が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」の上位概念を取得しました】" % 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

        # 更新
        print()
        no += 1
    mean_num = int(input("【「%s」の何番目の概念を利用しますか？】\n" % word))
    return synsets[mean_num - 1]

def MakeUpperConceptWordList(synset, hierarchy_dict):
    # 上位語を保管するリストを作成
    upper_words_list = []
    # 上位語を取得する対象のsynsetを表示
    print('------------------%s---------------------' % synset_name_dict[synset])
    # 現在のsynsetの上位語をすべて取得するため，tmp_synsetに代入
    tmp_synset = synset
    # synsetの上位語を最上位まで列挙する
    while (tmp_synset in hierarchy_dict.values()):
        # エラーが発生=最上位語を取得したら終了
        try:
            upper_words_list.append(synset_name_dict[hierarchy_dict[tmp_synset]])
            # print(synset_name_dict[hierarchy_dict[tmp_synset]])
            tmp_synset = hierarchy_dict[tmp_synset]
        except:
            break
    print(upper_words_list, "\n")
    return upper_words_list

def GetCommonUpperConceptWords(word1, word2, hierarchy_dict):

    synset1 = GetUpperConceptWordID(word1)
    upper_words_list1 = MakeUpperConceptWordList(synset1, hierarchy_dict)

    synset2 = GetUpperConceptWordID(word2)
    upper_words_list2 = MakeUpperConceptWordList(synset2, hierarchy_dict)

    common_upper_words_list = []
    for upper_word1 in upper_words_list1:
        if upper_word1 in upper_words_list2:
            common_upper_words_list.append(upper_word1)
    # 共通した上位語を取得する
    print("【「%s」と「%s」に共通した上位語を取得しました】" % (word1, word2))
    print('------------------common_upper_words---------------------')
    print(common_upper_words_list)

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

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

# 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]

#犬と猫という単語に共通した上位語をすべて取得する
GetCommonUpperConceptWords("犬", "猫", hierarchy_dict)
```

```:実行結果
【「犬」の上位概念を取得しました】
1つめの概念 : spy
意味1 : 敵の情報を得るために国家に雇われた、または競合他社の企業秘密を得るために会社に雇われた秘密諜報部員

2つめの概念 : canis_familiaris
意味1 : 有史以前から人間に家畜化されて来た（おそらく普通のオオカミを先祖とする）イヌ属の動物
意味2 : 多数の品種がある

【「犬」の何番目の概念を利用しますか？】
2　（2番目の意味を利用するため，2を入力）
------------------canis_familiaris---------------------
['canid', 'carnivore', 'placental', 'mammalian', 'vertebrate', 'chordate', 'animate_being', 'being', 'living_thing', 'whole', 'physical_object', 'physical_entity', 'entity']

【「猫」の上位概念を取得しました】
1つめの概念 : true_cat
意味1 : 通常、厚く柔らかい毛皮を持ち、吠えることのできないネコ科の哺乳類：家ネコ
意味2 : ヤマネコ

【「猫」の何番目の概念を利用しますか？】
1（1番目の意味を利用するため，1を入力）
------------------true_cat---------------------
['feline', 'carnivore', 'placental', 'mammalian', 'vertebrate', 'chordate', 'animate_being', 'being', 'living_thing', 'whole', 'physical_object', 'physical_entity', 'entity']

【「犬」と「猫」に共通した上位語を取得しました】
------------------common_upper_words---------------------
['carnivore', 'placental', 'mammalian', 'vertebrate', 'chordate', 'animate_being', 'being', 'living_thing', 'whole', 'physical_object', 'physical_entity', 'entity']
```

取得した語句は下位概念から順に並べられています．これらの単語に共通する最も下位の概念である carnivore は肉食動物を意味していて，当初の目標通りの単語が取得できたと言えます．

# おわりに
WordNet を利用して，２つの単語に共通した上位語を取得できるようになりました．今回は犬と猫という語句を利用しましたが，関数内の単語を入れ替えたり，プログラムを書き変えて単語の入力受け付けするなどして他の語句に対しても利用できるでしょう．また，３つ以上の単語に対応するような拡張も可能と考えられます．このプログラムを利用して執筆作業を効率化できたら幸いです．

#参考
WordNet を利用する上で参考にしたサイトもまとめておきます．
http://compling.hss.ntu.edu.sg/wnja/jpn/downloads.html
https://qiita.com/pocket_kyoto/items/f2382b9821d32624e6bc
https://bondo.hateblo.jp/entry/2019/04/25/115102