Pythonで日本語WordNetと英語WordNetを利用して、単語間の類似度を測る
目的
「Mr.Childrenの歌詞分析(4): 単語の意味を考慮した、シングル曲のクラスタリング」では、文書の距離を計測するのに、同一語や同一概念の出現頻度を用いていた。この場合、"似ている"単語は考慮されておらず、クラスタリングの精度もいまひとつであった。
この問題を解決するために、単語間の距離をWordNetを用いて計測したい。今回は、2つのテキストファイルを元に、それぞれの単語の類似度を出力するプログラムを作成した。日本語WordNetと英語WordNetを用いて、多義語に対応した単語間の距離を計測している。
ちなみに、わざわざ英語版WordNetを用いているのは、類似度を測るAPIが日本語WordNet(のPython API)には用意されていないから。また、「Python による日本語自然言語処理 12.1.5 日本語WordNet」 には日本語 WordNet に対するリーダーが掲載されているが、複数のSynset(概念)を持つ多義語に対応できていない。
手法
環境設定
前準備として、日本語WordNetと英語WordNetの設定を行う。
- 日本語WordNet をダウンロードする。今回はタブ区切りテキスト(Just Japanese Words linked to Princeton WordNet Synsets)を用いる。
- 英語WordNet をダウンロードする。Pythonインタープリタから以下のコマンドでダウンロードできる。
- 今回はEclipseで開発を行った。依存関係や入力補助など便利。
Pythonスクリプト
単語のリストが書かれた2つのテキストファイルを入力として、その間の類似度を出力するスクリプトは、以下のとおり。
・jwn_driver.py
'''
メインモジュール
"""
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
""" ファイル名を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(WordNetCorpusReaderを継承)
"""
日本語リーダー
"""
結果
以下のような出力が得られた。多義語に対して最も類似度の高い類似度を採用した結果がsim_max.txt, 類似度の平均値を採用した結果がsim_average.txtである。
類似度の高い組み合わせとして、「楽しむ-好き」「楽しむ-失望」「楽しむ-空」が挙がっている。なぜか「楽しむ-失望」が類似している・・・これはいただけない・・・。また、「別れる」と類似しているのは「楽しむ」だと・・・。
そもそもWordNetの類似度(path_similarity)は、共通の上位語階層の概念への最短経路の長さを表す。つまり、名詞-動詞間の類似度や、系統的でない関係を持つ単語間の類似度は、人間が感じる心理的な類似度と乖離してしまっている。考えてみると当然と言えるかもしれない。これを解決するには、WebテキストやTwitterから共起を検出するしかないのかなぁ。