シソーラス距離(概念距離)をwordnetを使ってpython2.7系で実装(windows7)
論文によってはオントロジー距離ってなっている気がする。
tkdmahさんの記事をまずはトレース 。
http://tkdmah.hatenablog.com/entry/2013/01/22/233919
以下から日本語wordnetをDL
http://nlpwww.nict.go.jp/wn-ja/jpn/downloads.html
Just Japanese Words linked to Princeton WordNet Synsets を選択
wnjpn-ok.tab.gz をDL
英語のWordNet をDL
import nltk
nltk.download()
以下、5ファイルを作って、 jwn_driver.py を実行(先頭3つは参照元と同じ内容)
jwn_driver.py
sim.py
jwn_corpusreader.py
TagExamples.txt #入力ファイル1
WordExamples.txt #入力ファイル2
jwn_driver.py
これがメインプログラム、sim.pyをインポートする。
入力用のfin1とfin2に取り込むファイルは、\n区切りの単語の羅列だが、取り込むファイルはterapadでutf-8nで書き出す(重要)。改行コードはどれでも動きそう。。
こうしない場合は、wordLists[0][1]の頭に飛び込んでくる文字列”ufeff”を除かないとエラーになる。
list内なので取るの面倒。生成時に弾くか、わざわざ要素を取り出して弾くことになりそう。
簡単に取れる方法あるのかな?
他、どこかにnullが混じっているとエラーになる。末尾気をつけること。
fin1 = 'TagExamples.txt' #入力ファイル1 fin2 = 'WordExamples.txt' #入力ファイル2 fout = 'Output.txt' #出力ファイル import sim wordLists = sim.makeWordLists(fin1,fin2) #単語リストを作成 synLists = sim.convWords2Synsets(wordLists[0], wordLists[1]) #概念リストを作成 simMatrix = sim.calcSim(synLists[0], synLists[1]) #類似度行列を作成 sim.writeSim(wordLists[0],wordLists[1],simMatrix,fout) #ファイルへの書き込み
sim.py
ダウンロードしたwordnetのファイルの位置を二箇所指定する。 jwnの箇所。
インストール時に勝手にどこかに入っても、↑ファイルを記載の位置にコピペすれば動く。
jwn_corpusreaderをインポートしている。
syn1.path_similarity(syn2)を深追いしたいが、jwnを追わないとだめぽ。。。
# -*- coding: utf-8 -*- ''' 英語WordNetから類似度を算出するモジュール """ import codecs def makeWordLists(fin1,fin2): """ ファイル名を2つ受け取って単語リストのリストを返す """ fins = [fin1, fin2] wordLists = [[ ], [ ]] for i in [0,1]: f=codecs.open(fins[i], encoding="utf-8") for line in f: wordLists[i].append(line.strip("\r\n").strip("\n")) f.close() return wordLists def convWords2Synsets(wordList1, wordList2): """ 単語リストを2つ受け取って概念リストのリストを返す """ import jwn_corpusreader jwn = jwn_corpusreader.JapaneseWordNetCorpusReader('C:/LyricsWorkspace/nltk_data/corpora/wordnet', 'C:/LyricsWorkspace/WordNet/wnjpn-ok.tab') #英語WordNetと日本語WordNetを指定する synLists = [[ ],[ ]] wordLists = [wordList1, wordList2] for i in [0,1]: for j in range(len(wordLists[i])): synLists[i].append(jwn.synsets(wordLists[i][j])) return synLists def calcSim(synList1,synList2): """ 概念リストを2つ受け取って類似度の行列を返す """ import numpy as np simMatrix = np.zeros( (len(synList1), len(synList2))) for i in range(len(synList1)): for j in range(len(synList2)): sims = [ ] for syn1 in synList1[i]: for syn2 in synList2[j]: sims.append(syn1.path_similarity(syn2)) simMatrix[i,j] = max(sims) return simMatrix def writeSim(wordList1, wordList2, simMatrix,fout): """ 単語リストを2つと類似度行列とファイル名を受け取ってファイルに出力する """ f = codecs.open(fout,'w', encoding="utf-8") for i in range(len(wordList1)): for j in range(len(wordList2)): f.write(wordList1[i] + "-" + wordList2[j] +": " + str(simMatrix[i][j])+"\r\n") f.close()
jwn_corpusreader.py
# -*- coding: utf-8 -*- """ 日本語リーダー """ from nltk.corpus.reader.wordnet import WordNetCorpusReader class JapaneseWordNetCorpusReader(WordNetCorpusReader): def __init__(self, root, filename): WordNetCorpusReader.__init__(self, root) import codecs f=codecs.open(filename, encoding="utf-8") self._jword2offset = {} for line in f: _cells = line.strip().split('\t') _offset_pos = _cells[0] _word = _cells[1] if len(_cells)>2: _tag = _cells[2] _offset, _pos = _offset_pos.split('-') try: self._jword2offset[_word].append({'offset': int(_offset), 'pos': _pos}) except: self._jword2offset[_word]=[{'offset': int(_offset), 'pos': _pos}] def synsets(self, word): if word in self._jword2offset: results = [ ] for offset in (self._jword2offset[word]): results.append(WordNetCorpusReader._synset_from_pos_and_offset( self, offset['pos'], offset['offset'] )) return results else: return None
他、日本語の単語がないか、日本語に対応した英語単語がないとエラーぽい。
英語単語のsynsetsがあっても、計算値がnanになる場合がある。
完全に概念ツリーが交差しているわけではないのかも
(何かの辞書では、単語さえあれば、絶対どこかでぶつかるとしているものも以前はあったと記憶しているが。。辞書が違うのかな)
計算結果
Output.txtに出力される。勝手に作られる。
同じ単語同士は1.0とでるが、
近さが謎な単語対や反対の意味の単語対の値はさまざま、0.3くらいまで上がるケースがある。
人間とサルは0.5だった。
この辺りに何かが見える。
同じ類型の相対距離はなんとなくでている。
狼-鳥: 0.142857142857
狼-猿: 0.166666666667
狼-猫: 0.2
狼-犬: 0.333333333333
狼-恐竜: 0.1
狼-狼: 1.0
狼-狐: 0.333333333333
狼-山: 0.0714285714286
狼-川: 0.0588235294118
狼-好き: 0.0526315789474
狼-嬉しい: nan
狼-苦しい: nan
狼-嫌い: 0.0526315789474