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

日本語WordNetを使って、何か面白いことができないか調べていきたいと思います。
今回は、とりあえず日本語WordNetについて簡単に情報をまとめ、類義語を検索できるツールをpython3で作ってみました。

# WordNetとは？

同義語、上位・下位語などがまとめられている、概念の辞書です。
元々英語で整備が進んでいました。
日本語版は、2006～2010に、国立研究開発法人情報通信研究機構（NICT）が整備を進めました。

詳しくはWikipediaで
https://ja.wikipedia.org/wiki/WordNet

# WordNetはどうやったら使えるの？

以下の日本語WordNetのHPから、ダウンロードできます。商用フリーです。申し込み不要です。

日本語 WordNet：
http://compling.hss.ntu.edu.sg/wnja/

私はpython経由で使いやすそうな「Japanese Wordnet and English WordNet in an sqlite3 database」をダウンロードしました。
以降では、sqliteDB形式のWordNetをpythonから呼ぶ方式で説明します。

# WordNetをpythonから呼ぶ

WordNetをダウンロードしたら、pythonからWordNetを呼びます。
sqlite3という標準ライブラリを使えば、通常のSQLの感覚で操作できます。

まず、WordNetのDBに接続します。

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

どのようなテーブルが含まれているか確認してみます。

```python
# 含まれるテーブルの確認
cur = conn.execute("select name from sqlite_master where type='table'")
for row in cur:
    print(row)
```
出力：
('pos_def',)
('link_def',)
('synset_def',)
('synset_ex',)
('synset',)
('synlink',)
('ancestor',)
('sense',)
('word',)
('variant',)
('xlink',)
***
出力を確認すると、
'pos_def','link_def','synset_def','synset_ex','synset','synlink','ancestor','sense','word','variant','xlink'
の計11のテーブルが含まれることが分かりました。（思ったより、多い。。）
調べた感じだと、以下の5つを、とりあえず押さえておけば良いようです。

|テーブル名|内容|
|:---|:---|
|word|単語と品詞に関する情報。単語と単語IDを紐づけている|
|sense|単語とsynset（概念）に関する情報。単語IDとsynsetのIDを紐づけている|
|synset|概念に関する情報。synsetのIDと概念名称を紐づけている|
|synset_def|概念の定義に関する情報。synsetのIDと概念の定義を紐づけている|
|synlink|2つの概念の関連性の情報。上位・下位、包含・被包含などが定義されている|

以降で簡単に中身を見てみます。

# WordNetからデータを抽出する

以下のようにSQL感覚で、データ抽出できます。

```python
# Wordnetデータの内容の確認
cur = conn.execute("select * from word limit 10")
for row in cur:
    print(row)
```
出力：
(1, 'eng', 'expletive', None, 'n')
(2, 'eng', 'measles', None, 'n')
(3, 'eng', 'contras', None, 'n')
(4, 'eng', 'beef_man', None, 'n')
(5, 'eng', 'dwelling', None, 'n')
(6, 'eng', 'acetum', None, 'n')
(7, 'eng', 'mount_carmel', None, 'n')
(8, 'eng', 'class_taxopsida', None, 'n')
(9, 'eng', 'vascular_ray', None, 'n')
(10, 'eng', 'genus_salamandra', None, 'n')
***

実行結果は、sqlite3内で定義されているオブジェクトで返ってきますが、for文で回せばtupleで要素を取り出すことができます。
ちなみに列名が取得したくなった場合は、以下のようにします。
（このあたりの書き方は、SQLiteとMySQLとかで違う感じです）

```python
# Wordnetデータに含まれるカラムの確認
cur = conn.execute("PRAGMA TABLE_INFO(word)")
for row in cur:
    print(row)
```
出力：
(0, 'wordid', 'integer', 0, None, 1)
(1, 'lang', 'text', 0, None, 0)
(2, 'lemma', 'text', 0, None, 0)
(3, 'pron', 'text', 0, None, 0)
(4, 'pos', 'text', 0, None, 0)
***

一応データ数のオーダーも確認しておきます。

```python
# Wordnetに登録されているデータ数の確認(wordDB)
cur = conn.execute("select count(*) from word")
for row in cur:
    print("Wordnetに登録されているWordDBの単語数：%s" % row[0])
```
出力：
Wordnetに登録されている単語数：247528
***

# 日本語版WordNetについて分かったこと

前述のような感じで、少し触ってみて分かったことをまとめます。

* 基本的に、英語と日本語の単語が混在している（一部英語のみ）
* 標準形を前提として整備されている
 - 形態素解析で標準形に変換しておく必要がありそう 
* 名詞（n）、動詞（v）、形容詞（a）、副詞（r）の4種類の品詞が対象
* 一部の情報は英語にしか付与されていない（senseテーブルのfreqカラムなど）

どうも本家の英語版のWordNetに日本語の情報を足しているようで、一部の情報は日本語版では不足しているようです。今後活用を検討する上で注意が必要そうです。

# とりあえず類義語を検索できるツールを作ってみた

本当は、上位・下位などの関係性を上手く使ってみたかったのですが、まずはシンプルな類義語の検索処理部分をツールにしてみたいと思います。

以下のように類義語検索関数を書きました。

```python
# 特定の単語を入力とした時に、類義語を検索する関数
def SearchSimilarWords(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」の類似語を出力します】\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
        cur3 = conn.execute("select wordid from sense where (synset='%s' and wordid!=%s)" % (synset,word_id))
        sub_no = 1
        for row3 in cur3:
            target_word_id = row3[0]
            cur3_1 = conn.execute("select lemma from word where wordid=%s" % target_word_id)
            for row3_1 in cur3_1:
                print("類義語%s : %s" % (sub_no, row3_1[0]))
                sub_no += 1
        print("\n")
        no += 1
```

この関数に単語を入力することで、類義語を検索できます。
初めに「ネコ」の類義語を検索してみます。

```python
# 「ネコ」の類義語を検索する
SearchSimilarWords("ネコ")
```
出力：
【「ネコ」の類似語を出力します】

1つめの概念 : true_cat
意味1 : 通常、厚く柔らかい毛皮を持ち、吠えることのできないネコ科の哺乳類：家ネコ
意味2 : ヤマネコ
類義語1 : true_cat
類義語2 : cat
類義語3 : ねんねこ
類義語4 : にゃんにゃん
類義語5 : 猫
類義語6 : キャット

***

何となく上手くいってそうです。
「ねんねこ」とか「にゃんにゃん」もWordNetに含まれているのですね。。恐るべし。

別の単語でも確認してみます。

```python
# 「月」の類義語を検索する
SearchSimilarWords("月")
```
出力：
【「月」の類似語を出力します】

1つめの概念 : moonlight
意味1 : 月の光
類義語1 : moonlight
類義語2 : moon
類義語3 : moonshine
類義語4 : 月灯かり
類義語5 : 月色
類義語6 : 月明り
類義語7 : ムーンライト
類義語8 : 月あかり
類義語9 : 月影
類義語10 : 月桂
類義語11 : 月明
類義語12 : 月光
類義語13 : 月明かり


2つめの概念 : month
意味1 : 暦年を12に分けた中の1つ
類義語1 : month
類義語2 : calendar_month
類義語3 : 暦月


3つめの概念 : month
意味1 : 約30日間という時間単位
類義語1 : month


4つめの概念 : moon
意味1 : 地球の天然衛星
類義語1 : moon
類義語2 : 月代
類義語3 : 御月様
類義語4 : お月さま
類義語5 : 玉兎
類義語6 : ムーン
類義語7 : 玉桂
類義語8 : 月球
類義語9 : 桂月
類義語10 : お月様
類義語11 : 月夜見
類義語12 : 月の輪
類義語13 : 太陰
類義語14 : 月桂
類義語15 : 月読
類義語16 : 月輪
類義語17 : 月読み

***

一見上手くいってそうですが、「月曜日=monday」の概念がないので、実用上は問題が生じるかもしれません。

# まとめと今後

日本語版WordNetは利用は簡単ではあるものの、英語版ではない固有の問題（日本語の構造自体に起因するものも含め）がありそうなことが分かりました。
今後は、上位・下位の概念を用いるなど、WordNetならではの解析に挑戦してみたいです。

次回：
[「日本語WordNetを使って、上位語を検索できるツールをpythonで作ってみた」](
https://qiita.com/pocket_kyoto/items/f2382b9821d32624e6bc)

# 参考：
[1] [日本語 WordNet]
(http://compling.hss.ntu.edu.sg/wnja/)
[2] [[自然言語] Wordnet × Pythonで類義語を抽出する]
(https://www.yoheim.net/blog.php?q=20160201)
[3] [【API】日本語WordNetの全内容にアクセスできるWebAPIを公開した]
(http://blog.apitore.com/2017/05/17/api-wordnet-detail/)