#0.要するに
1.文章の類似度を手軽に数値化するには、TF-IDFが良い。
2.文章に現れる単語の頻度(TF)と希少性(IDF)を掛け合わせて類似度を求める。
3.簡便性に重きを置くなら、形態素解析にはMecabよりjanome、類似度判定にはdoc2vecよりTF-IDFが おススメ。
⇒**特許調査における先行文献との類似度を数値化したり、大学入学共通テストの記述式問題の回答を客観的に評価するのに、役立つと思う。**但し、意味が通る文章か否かは人が読まないと判定できない。まあ、当然だよね(笑)
#1.問題意識
文章を読んでいて「何となく似た文章だな」と思うことがある。その何となく似てるという漠然とした感じを、客観的に数字で表すのに便利な手法がTF-IDFだ。
TF-IDFについては、分かりやすく解説しているサイトがあるので、是非、ググってほしい。おススメのサイトを上げると、以下になる。
【初学者向け】TFIDFについて簡単にまとめてみた
要は、①ある文章に頻繁に出てくる単語が(TF:Term Frequency 頻度)、②普通の文章には、あまり出てこないような珍しい単語であれば(IDF:Inverse Document Frequency 希少性)、その文章は、その単語に関連する話題でしょう、という考え方から出発している。
その単語のTFとIDFを掛け合わせたものの合算を比較して、文章としての類似度を判定しましょう、というのがTF-IDFの考え方の基本だ。
#2.準備
まあ、ゴタゴタ言っていても始まらないので、PythonでAIっぽいことをやるのに よく使う**scikit-learn(sklearn)**を利用して計算してみよう。まずは、準備。
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
TF-IDFの計算では、上に書いたTfidfVectorizerではなく、CountVectorizerとTfidfTransformerを組み合わせて使うこともできる(実際に後でやってみる)。組み合わせる際は、CountVectorizerで単語の頻度をベクトル化した上で、TfidfTransformerでTF-IDFを計算するわけだ。
但し、TF-IDFを計算するのであれば、TfidfVectorizerで一度で計算した方が楽なので、今回はTfidfVectorizerを使った。
なお、TfidfVectorizerを使ったケースと、CountVectorizerとTfidfTransformerを組み合わせたケースでは、微妙に結果が違った(詳細は後述)。パラメータのせいなのかと思うが、残念ながら理由は不明。分かる方は、是非、コメント下さい。
類似度は、いずれの場合でもベクトルのコサインcosを計算して求める。
それから、日本語を扱う際は、形態素解析が必要になる。英語であれば、単語が切り離されているので、そのまま解析できるが、日本語は文章中に単語が切れ目なくついているので、単語毎に切り離す必要がある。
形態素解析で有名なのは、pythonではMecabだけど、扱いやすいのはjanomeなので、今回はjanomeを使うことにする。
from janome.tokenizer import Tokenizer
#3.分析対象
比較する文章だが、分かりやすいニュースを使ってやってみよう。独断と偏見で、好きなサッカーを中心にトライしてみよう。
最初の文章は、首位を走るフロンターレが2位セレッソに勝ったというニュース。
川崎F、2位C大阪を3発撃破で首位独走!勝ち点差を「14」に広げる8連勝
明治安田生命J1リーグは第20節が3日に行われ、セレッソ大阪が川崎フロンターレをホームのヤンマースタジアム長居に迎えた。
ともにこれまで20試合を消化し、首位を快走する川崎F(勝ち点53)と追いかける2位・C大阪(勝ち点42)。ホームのC大阪は前節に3試合ぶりの勝利を収めた状況、対する川崎Fは7連勝中という圧倒的な勢いを携えてこの頂上対決に臨んだ。
次に、内容が同じである別のニュースを使ってみよう。
この最初の2つのニュースの類似度は高いという仮説を立て、検証してみよう。
勝ち点差は14に…交代策ズバリの川崎F、C大阪を退けて8連勝!!
J1リーグは3日、第20節を開催し、ヤンマースタジアム長居では2位のセレッソ大阪(勝ち点42)と勝ち点11差をつけて首位に立つ川崎フロンターレ(勝ち点53)が対戦。前半37分にオウンゴールで川崎Fが先制するが、後半17分にFW奥埜博亮の得点でC大阪が追い付く。しかし、同38分にFWレアンドロ・ダミアン、同39分にMF三笘薫が立て続けにネットを揺らし、川崎Fが3-1の勝利を収めた。
3番目は、同じサッカーのニュースだが、別の内容のニュース。
G大阪・遠藤保仁、磐田移籍!「決意」の裏にあった「宮本監督との人間関係」
一部スポーツ紙が報じたガンバ大阪、そして日本代表のレジェンド・遠藤保仁のJ2磐田への期限付き移籍。 このニュースが流れるやネット上では、G大阪サポーターのみならず、多くのサッカーファンから驚きの声が上がった。遠藤といえば、京都から移籍してきた2001年以降、G大阪の主力として活躍。G大阪の7番として、司令塔として、チームが獲得した全タイトルに中心選手として貢献してきた。
それから、ジャンルはスポーツで一緒だが、野球のニュースとも比べてみよう。
今オフFAの田中将大は「価値に見合った投手」
今季終了後にフリーエージェント(FA)となるヤンキース田中将大投手には、早くからチーム内や地元メディアから残留を望む声が上がっている。昨季までプレーオフで圧巻投球を見せていた右腕は、9月30日(日本時間1日)にインディアンスとのワイルドカードシリーズ第2戦で先発。降雨という悪条件の中、4回6失点と苦戦したが、チーム一丸の勝利で地区シリーズ進出を決めた。
最後は、ニュースはニュースだが、全く異なるジャンル。
【新型コロナ】米大統領入院、ホワイトハウス「クラスター」とWHO
トランプ米大統領は2日、新型コロナウイルス感染症(COVID19)の治療を受けるため、ホワイトハウスから大統領専用ヘリコプターでワシントン近郊のウォルター・リード米軍医療センターに到着した。病状の程度を巡る懸念の広がりを示唆している。
以上の文章を取り込んで分析することとする。
文章1との類似度は、文章2>文章3>文章4>文章5になると予想する。
さて、その通りになるだろうか。
#4.やってみる
まずは、文章を読み込む。形態素解析を忘れずに実行しよう。
filenames=['text1.txt','text2.txt','text3.txt','text4.txt','text5.txt']
wakati_list = []
for filename in filenames: # テキストファイルを読み出しtextに代入
with open(filename,mode='r',encoding = 'utf-8-sig') as f:
text = f.read()
wakati = ''
t = Tokenizer()
for token in t.tokenize(text): # 形態素解析
hinshi = (token.part_of_speech).split(',')[0] # 品詞情報
hinshi_2 = (token.part_of_speech).split(',')[1]
if hinshi in ['名詞']: # 品詞が名詞の場合のみ以下実行
if not hinshi_2 in ['空白','*']:
# 品詞情報の2項目目が空白か*の場合は以下実行しない
word = str(token).split()[0] # 単語を取得
if not ',*,' in word: # 単語に*が含まれない場合は以下実行
wakati = wakati + word +' '
# オブジェクトwakatiに単語とスペースを追加
wakati_list.append(wakati) # 分かち書き結果をリストに追加
wakati_list_np = np.array(wakati_list) # リストをndarrayに変換
いよいよ、類似度の計算。TfidfVectorizerを使ってみよう。
vectorizer = TfidfVectorizer(token_pattern=u'\\b\\w+\\b')
transformer = TfidfTransformer()# transformerの生成。TF-IDFを使用
tf = vectorizer.fit_transform(wakati_list_np) # ベクトル化
tfidf = transformer.fit_transform(tf) # TF-IDF
tfidf_array = tfidf.toarray()
cs = cosine_similarity(tfidf_array,tfidf_array) # cos類似度計算
print(cs)
結果は、以下の通り。類似度の相対的な大きさは、当然ながら、予想通り。
[[1. 0.48812198 0.04399067 0.02065671 0.00164636]
[0.48812198 1. 0.02875532 0.01380959 0.00149348]
[0.04399067 0.02875532 1. 0.02595705 0. ]
[0.02065671 0.01380959 0.02595705 1. 0.00350631]
[0.00164636 0.00149348 0. 0.00350631 1. ]]
ちなみに、冒頭で書いたCountVectorizerとTfidfTransformerを組み合わせたケースは、以下の通り。使う前に、importをしないといけない。
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
# vectorizerの生成。token_pattern=u'\\b\\w+\\b'で1文字の語を含む設定
vectorizer = CountVectorizer(token_pattern=u'\\b\\w+\\b')
# transformerの生成。TF-IDFを使用
transformer = TfidfTransformer()
tf = vectorizer.fit_transform(wakati_list_np) # ベクトル化
tfidf = transformer.fit_transform(tf) # TF-IDF
tfidf_array = tfidf.toarray()
cs = cosine_similarity(tfidf_array,tfidf_array) # cos類似度計算
print(cs)
結果は、以下の通り。こちらの方が、類似度の数値が少し大きくなっている。
[[1. 0.59097619 0.07991729 0.03932476 0.00441963]
[0.59097619 1. 0.05323053 0.03037231 0.00418569]
[0.07991729 0.05323053 1. 0.03980858 0. ]
[0.03932476 0.03037231 0.03980858 1. 0.01072682]
[0.00441963 0.00418569 0. 0.01072682 1. ]]
#5.最後に
Pythonで類似度を計算するのであれば、doc2vecも良いと思う。但し、こちらは、学習済みモデルの読み込みが大変。
そういった意味では、TF-IDFが手軽に文章の類似度を計算できて良いと思う。
なお、コードについては、以下のサイトを参考にさせて頂きました。この場を借りて、御礼を申し上げます。