LoginSignup
11
17

More than 3 years have passed since last update.

【初心者向け】文章の類似度をTF-IDFで数値化する

Last updated at Posted at 2020-10-03

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ではなく、CountVectorizerTfidfTransformerを組み合わせて使うこともできる(実際に後でやってみる)。組み合わせる際は、CountVectorizerで単語の頻度をベクトル化した上で、TfidfTransformerでTF-IDFを計算するわけだ。
 但し、TF-IDFを計算するのであれば、TfidfVectorizerで一度で計算した方が楽なので、今回はTfidfVectorizerを使った。
 なお、TfidfVectorizerを使ったケースと、CountVectorizerとTfidfTransformerを組み合わせたケースでは、微妙に結果が違った(詳細は後述)。パラメータのせいなのかと思うが、残念ながら理由は不明。分かる方は、是非、コメント下さい。
 類似度は、いずれの場合でもベクトルのコサインcosを計算して求める。

 それから、日本語を扱う際は、形態素解析が必要になる。英語であれば、単語が切り離されているので、そのまま解析できるが、日本語は文章中に単語が切れ目なくついているので、単語毎に切り離す必要がある。
 形態素解析で有名なのは、pythonではMecabだけど、扱いやすいのはjanomeなので、今回はjanomeを使うことにする。

from janome.tokenizer import Tokenizer

3.分析対象

 比較する文章だが、分かりやすいニュースを使ってやってみよう。独断と偏見で、好きなサッカーを中心にトライしてみよう。
 最初の文章は、首位を走るフロンターレが2位セレッソに勝ったというニュース。

text1.txt
川崎F、2位C大阪を3発撃破で首位独走!勝ち点差を「14」に広げる8連勝
明治安田生命J1リーグは第20節が3日に行われ、セレッソ大阪が川崎フロンターレをホームのヤンマースタジアム長居に迎えた。
ともにこれまで20試合を消化し、首位を快走する川崎F(勝ち点53)と追いかける2位・C大阪(勝ち点42)。ホームのC大阪は前節に3試合ぶりの勝利を収めた状況、対する川崎Fは7連勝中という圧倒的な勢いを携えてこの頂上対決に臨んだ。

 次に、内容が同じである別のニュースを使ってみよう。
この最初の2つのニュースの類似度は高いという仮説を立て、検証してみよう。

text2.txt
勝ち点差は14に…交代策ズバリの川崎F、C大阪を退けて8連勝!!
J1リーグは3日、第20節を開催し、ヤンマースタジアム長居では2位のセレッソ大阪(勝ち点42)と勝ち点11差をつけて首位に立つ川崎フロンターレ(勝ち点53)が対戦。前半37分にオウンゴールで川崎Fが先制するが、後半17分にFW奥埜博亮の得点でC大阪が追い付く。しかし、同38分にFWレアンドロ・ダミアン、同39分にMF三笘薫が立て続けにネットを揺らし、川崎Fが3-1の勝利を収めた。

 3番目は、同じサッカーのニュースだが、別の内容のニュース。

text3.txt
G大阪・遠藤保仁、磐田移籍!「決意」の裏にあった「宮本監督との人間関係」
一部スポーツ紙が報じたガンバ大阪、そして日本代表のレジェンド・遠藤保仁のJ2磐田への期限付き移籍。 このニュースが流れるやネット上では、G大阪サポーターのみならず、多くのサッカーファンから驚きの声が上がった。遠藤といえば、京都から移籍してきた2001年以降、G大阪の主力として活躍。G大阪の7番として、司令塔として、チームが獲得した全タイトルに中心選手として貢献してきた。

 それから、ジャンルはスポーツで一緒だが、野球のニュースとも比べてみよう。

text4.txt
今オフFAの田中将大は「価値に見合った投手」
今季終了後にフリーエージェント(FA)となるヤンキース田中将大投手には、早くからチーム内や地元メディアから残留を望む声が上がっている。昨季までプレーオフで圧巻投球を見せていた右腕は、9月30日(日本時間1日)にインディアンスとのワイルドカードシリーズ第2戦で先発。降雨という悪条件の中、4回6失点と苦戦したが、チーム一丸の勝利で地区シリーズ進出を決めた。

 最後は、ニュースはニュースだが、全く異なるジャンル。

text5.txt
【新型コロナ】米大統領入院、ホワイトハウス「クラスター」と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が手軽に文章の類似度を計算できて良いと思う。

 なお、コードについては、以下のサイトを参考にさせて頂きました。この場を借りて、御礼を申し上げます。

Pythonでいろいろやってみる

11
17
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
17