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

#０．要するに
１．文章の類似度を手軽に数値化するには、**TF-IDF**が良い。
２．文章に現れる単語の頻度（**TF**）と希少性（**IDF**）を掛け合わせて類似度を求める。
３．簡便性に重きを置くなら、形態素解析には**Mecab**より**janome**、類似度判定には**doc2vec**より**TF-IDF**が　おススメ。
⇒**特許調査における先行文献との類似度を数値化したり、大学入学共通テストの記述式問題の回答を客観的に評価するのに、役立つと思う。**但し、意味が通る文章か否かは人が読まないと判定できない。まあ、当然だよね(笑)

#１．問題意識
　文章を読んでいて「何となく似た文章だな」と思うことがある。その何となく似てるという漠然とした感じを、客観的に数字で表すのに便利な手法が**TF-IDF**だ。
　TF-IDFについては、分かりやすく解説しているサイトがあるので、是非、ググってほしい。おススメのサイトを上げると、以下になる。　
[【初学者向け】TFIDFについて簡単にまとめてみた](https://www.takapy.work/entry/2019/01/14/141423)

　要は、①ある文章に頻繁に出てくる単語が（**TF:Term Frequency　頻度**）、②普通の文章には、あまり出てこないような珍しい単語であれば（**IDF:Inverse Document Frequency　希少性**）、その文章は、その単語に関連する話題でしょう、という考え方から出発している。
　その単語のTFとIDFを掛け合わせたものの合算を比較して、文章としての類似度を判定しましょう、というのがTF-IDFの考え方の基本だ。

#２．準備
　まあ、ゴタゴタ言っていても始まらないので、PythonでAIっぽいことをやるのに　よく使う**scikit-learn(sklearn)**を利用して計算してみよう。まずは、準備。

```python
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**を使うことにする。

```python
from janome.tokenizer import Tokenizer
```

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

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

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

```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の勝利を収めた。
```

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

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

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

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

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

```text5.txt
【新型コロナ】米大統領入院、ホワイトハウス「クラスター」とＷＨＯ
トランプ米大統領は２日、新型コロナウイルス感染症（ＣＯＶＩＤ１９）の治療を受けるため、ホワイトハウスから大統領専用ヘリコプターでワシントン近郊のウォルター・リード米軍医療センターに到着した。病状の程度を巡る懸念の広がりを示唆している。
```

　以上の文章を取り込んで分析することとする。
　文章１との類似度は、文章２＞文章３＞文章４＞文章５になると予想する。
　さて、その通りになるだろうか。

#４．やってみる
　まずは、文章を読み込む。形態素解析を忘れずに実行しよう。

```python
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.        ]]
```

#５．最後に
　Pythonで類似度を計算するのであれば、**doc2vec**も良いと思う。但し、こちらは、学習済みモデルの読み込みが大変。
　そういった意味では、**TF-IDF**が手軽に文章の類似度を計算できて良いと思う。

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

[Pythonでいろいろやってみる](https://tat-pytone.hatenablog.com/entry/2019/06/22/143042)