WordNetの構造と類義語検索

# はじめに

[日本語を弱らせるプログラム](https://qiita.com/rawHam/items/d8eceadc700363cfd494) で類義語を取得する用があり、調べてみた結果WordNetを必要があるとわかったので、WordNetについて調べて見ました。

# WordNetとは

日本語WordNetは、「日本語の概念辞書で、個々の概念が"synset"という単位にまとめられており、それらが他のsynsetと意味的に結びついている」ものです(by 提供サイト)。主な用途は類義語検索かと思います。そのほかの用途については要調査です。


# WordNetの構造
WordNetは[こちら](http://compling.hss.ntu.edu.sg/wnja/)にて提供されており、いくつか種類があるようですが、ここでは、 "*Japanese Wordnet and English WordNet in an sqlite3 database*" をのぞいてみます。

WordNetに含まれているテーブルは以下の１１テーブル。

> 
* ancestor
* link_def
* pos_def
* sense
* synlink
* synset_def
* synset_ex
* synset
* variant
* word
* xlink
>

このうち、特定の単語の類義語を取得するために必要な最低限のテーブルは、word、synset、senseの3つでした。ただ、それだけですと意味的な情報の関連がなくてさみしいのでsynset_defも入れてあげたうえで、これらのテーブルを抜粋したデータモデルは以下の通りです。
![ER_WordNet_noKeys.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253933/14c3eeba-1a31-cd8e-7732-4c6fda9fd4ac.png)

※参考：http://compling.hss.ntu.edu.sg/wnja/pubs/2009-alr-wn.pdf

単語 word<sub>i</sub> は概念 synset<sub>j</sub> に属していて、それらをsenseという項目でつなぎ合わせているイメージです。
ちなみに、"暖かい"という単語を例に、synset と synset_defを出力した結果は以下のようになりました。

![wordnet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253933/a79cd7fb-8865-2328-14ba-afdb87d460c1.png)




# 類義語の取得

それでは、WordNetを用いて類義語一覧を取得する手順とプログラムについて説明します。処理の流れは以下の通りです。

1. 対象の単語のwordidを取得する
2. そのwordidが属するsynsetのsenseを取得する
3. synsetに属する単語を類義語として取得する

コードの全体像は以下の通りです。

```python
def search_synonyms(lemma, lang="jpn"):
    synonym_list = []
    # 1. 単語のwordidを取得する
    wobj = get_word(lemma)
    if wobj:
        word = wobj[0]
        # 2. そのwordidが属するsynsetのsenseを取得する
        senses = get_senses(word, lang)
        for s in senses:
            # 3. synsetに属する単語を類義語として取得する
            synonyms = get_words_from_synset(s.synset, word, lang)
            for syn in synonyms:
                if syn.lemma not in synonym_list:
                    synonym_list.append(syn.lemma)
    else:
        print(f"'{lemma}'の類義語は見つかりませんでした。")
    
    return synonym_list
```




＊ WordNetには英単語も含まれていますが、今回は日本語のみを対象としています。
＊ 類義語はsynsetごとに存在していますが、synsetの違いは考慮せず全てマージした形で類義語一覧として取得します。


以降、1~3それぞれの処理について述べていきます。

## 1. 対象の単語のwordidを取得する

対象の単語のwordidを取得する関数```get_word(lemma)```の処理は以下です。
なお、ここではwordid単体ではなく、Wordオブジェクトを丸ごと取得しています。(可読性、拡張性の観点からということで。)

```python
Word = namedtuple('Word', 'wordid lang lemma pron pos')

def get_word(lemma):
    cur = conn.execute("select * from word where lemma=?", (lemma,))
    return [Word(*row) for row in cur]
```

## 2. そのwordidが属するsynsetのsenseを取得する

word(id)からsenseを取得する関数```get_senses(word[, lang])```の処理は以下です。


```python
Sense = namedtuple('Sense', 'synset wordid lang rank lexid freq src')

def get_senses(word, lang):
    cur = conn.execute("select * from sense where wordid=? and lang=?", (word.wordid, lang))
    return [Sense(*row) for row in cur]
```

言語の限定(```lang="jpn"```)は次の処理だけでもいいかもしれませんが一応入れておきました。

## 3. synsetに属する単語を類義語として取得する

synsetからそれに属する単語を取得する関数```get_words_from_synset(synset, word[, lang])```の処理は以下です。

```python
def get_words_from_synset(synset, word):
    cur = conn.execute("select * from word where wordid in (select wordid from sense where synset=? and lang=?) and wordid<>?;", (synset, lang, word.wordid))
    return [Word(*row) for row in cur]
```
最後の```wordid<>{word.wordid}```は、その対象の単語自身を除外するために入れています。SQLの書き方はいくつかパターンがあるかと思います。

<br>

※参考１：[日本語WordNetのフロントエンドを弄る](http://sucrose.hatenablog.com/entry/20120305/p1)
※参考２：[[自然言語] Wordnet × Pythonで類義語を抽出する](https://www.yoheim.net/blog.php?q=20160201)



## おまけ

1〜3だけで類義語が取得できましたが、それぞれの類義語がどういう概念で類似しているのか見たい場合は```synset_def```も取得すれば実現できます。

```python
SynsetDef = namedtuple('SynsetDef', 'synset lang defi sid')
# defは予約語で使えないのでdefiにしています

def get_synset_def_from_synset(synset, lang):
    cur = conn.execute("select * from synset_def where synset=? and lang=?", (synset, lang))
    return [SynsetDef(*row) for row in cur]
```


# さいごに
まあたらしい情報がなく申し訳ありませんがほんの少しでも参考になれば。以上です。