Cosine Similarityから似ている文書を見つける

 

ベクトル間の類似度を計測するひとつの手法にコサイン類似度(Cosine Similarity)というものがあります。

今回はこのscikit-learnで実装されているCosine Similarityを用いて以前収集したツイートに類似しているツイートを見つけてみたいと思います。

 

関連リンク

手順

まずはデータが必要になります。今回は以前私が収集したロシアワールドカップ期間中に集めたツイートをデータとして使います。

www.pytry3g.com

 

データを単語に分割する必要があるのでMeCabを使って形態素解析します。

インストールしていない方はインストールしてください。

www.pytry3g.com

 

形態素解析したら、次にTF-IDFからツイートごとに文書ベクトルを計算していきます。

www.pytry3g.com

 

最後にCosine Similarityを用いて文書ベクトル同士の類似度を計算します。

これから手順に沿って具体的に方法を書いていきます。

1. データの用意

こちらの記事で用意したものを使います。データの中身はロシアワールドカップに関係のあるツイートになっていて、ファイル名はpositive.txtにしました。

このメンツなら原口だな。
コロンビアってサッカーつよいんですか
そうなんか今日日本と試合するんしょ?

改行区切りでひとつのツイートが記述されています。

これをcorpusに改行区切りで読み込んだものを入れます。

import codecs

corpus = codecs.open('positive.txt', 'r', 'utf-8').read().splitlines()
# corpus[0]
# '原口と槙野も先発だと期待してます。'

2. 形態素解析

corpusの中にあるツイートをすべて形態素解析分かち書きにします。

import MeCab
tagger = MeCab.Tagger('-Owakati')
corpus = [tagger.parse(sentence).strip() for sentence in corpus]
# corpus[0]
# '原口 と 槙野 も 先発 だ と 期待 し て ます 。'

3. TF-IDFを計算する

scikit-learnで実装されているものを使います。

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

vectorizer = CountVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
transformer = TfidfTransformer()

tf = vectorizer.fit_transform(corpus) # 単語の出現頻度を計算
tfidf = transformer.fit_transform(tf) # 各ドキュメントのtfidfを計算
# tfidf.shape
# (742, 2309) -> (文書の数、単語の数)

まず、CountVectorizerfit_transform()を使って、渡されたコーパスから単語辞書の作成と、単語の出現頻度を計算します。

次に、単語の出現頻度からTfidfTransformerfit_transform()を使ってTF-IDFを計算しています。

Cosine Similarityから類似しているテキストを見つける。

はじめに、Cosine Similarityについてかるく説明してみます。

Cosine Similarityを使えばベクトル同士が似ているか似てないかを計測することができます。

2つのベクトルx=(x1, x2, x3) とy=(y1, y2, y3) があるとき、Cosine Similarityは次の式で定義されます。

  類似度 (x, y) = xとyの内積 / xとyのノルムを掛けたもの

分かりにくいと思うので下のページを参考にしてください。

sklearn.metrics.pairwise.cosine_similarity — scikit-learn 0.19.2 documentation

Cosine Similarityは値が1に近いほど類似していて、0に近いほど類似していません。

本田半端ねぇに似ているツイートを見つける

Cosine Similarityを使って本田半端ねぇに似ているツイートを見つけてみます。

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

sample = "本田半端ねぇ"
# 分かち書きしたものをリストに入れて渡す
sample_tf = vectorizer.transform([tagger.parse(sample).strip()])
# 本田半端ねぇのTF-IDFを計算する
sample_tfidf = transformer.transform(sample_tf)
# コサイン類似度の計算
similarity = cosine_similarity(sample_tfidf, tfidf)[0]

はじめに、本田半端ねぇを形態素解析分かち書きにしてからリストに入れてvectorizerに渡しています。

ここで気を付けなければいけないことがあります。

本田半端ねぇの単語の出現頻度を数えるときにfit_transformではなくtransformを使っている点です。fit_transform()は学習データから単語辞書を生成し、単語の出現頻度をカウントします。一方のtransform()fit_transform()で生成された単語辞書から単語の出現頻度をカウントします。

もし、ここでfit_transform()を使ってしまうと以下のようになります。

sample = "本田半端ねぇ"
sample_tf = vectorizer.fit_transform([tagger.parse(sample).strip()])
# sample_tf.shape
# (1, 3) -> (文書の数、単語の数)

単語辞書に3つの単語しか登録されていません。したがって、ここではtransform()を使うのが正解です。

TF-IDFの計算も同様にtransform()を使います。

最後に、scikit-learnで実装されているCosine Similarityを使って本田半端ねぇと学習データとして渡した全てのツイートの類似度を計算しています。

結果

類似したツイート10個を見つけてみます。similarityにはCosine Similarityの計算結果が入っています。それをソートし類似度が高い順からそのインデックスを取得しています。

# 本田半端ねぇに類似しているツイート上から順に10個見つける
topn_indices = np.argsort(similarity)[::-1][:10]
for sim, tweet in zip(similarity[topn_indices], np.array(corpus)[topn_indices]):
    print("({:.2f}): {}".format(sim, "".join(tweet.split())))

計算結果を出力するとこうなりました。

(0.57): 大迫今日まじ半端ねぇ!
(0.33): 大迫半端ないって!
(0.33): 大迫半端なかった
(0.28): 乾だって半端ない!
(0.25): 川島のイエローも半端ない
(0.23): ね!本田も入れた意味あったし上手く相手に刺さったからいいわ大迫半端ないって。
(0.23): セネガル人半端ないって泣
(0.22): うーん宇佐美と酒井高徳全然輝いてねぇ
(0.22): 香川のPKも半端なかったよ!笑
(0.22): 1-0でコロンビア勝ちってのはベストな展開なんすけどねぇ…

本田半端ねぇに一番類似しているツイートは大迫今日まじ半端ねぇ!でした。一番類似しているツイートでCosine Similarityは0.57という結果になりました。1に近いほど類似しているのでこの結果はどうなんだろう?

良いのか悪いのかわかりません。

TfidfVectorizer

ちなみに、TfidfVectorizerでも同じことができます。

TfidfVectorizerはCountVectorizerとTfidfTransformerの機能を持っているので、下の部分を変更します。

(変更前)

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

vectorizer = CountVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
transformer = TfidfTransformer()

tf = vectorizer.fit_transform(corpus) # 単語の出現頻度を計算
tfidf = transformer.fit_transform(tf) # 各ドキュメントのtfidfを計算
# tfidf.shape
# (742, 2309) -> (文書の数、単語の数)

 

(変更後)

from sklearn.feature_extraction.text import TfidfVectorizer

vectorizer = TfidfVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
tfidf = vectorizer.fit_transform(corpus)

あとの手順は同じです。

ソースコード

データについては各自用意してください。

CountVectorizerとTfidfTransformerを使った例

import MeCab
import codecs
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

### データの用意 ###
corpus = codecs.open('positive.txt', 'r', 'utf-8').read().splitlines()
# corpus[0]
# '原口と槙野も先発だと期待してます。'

### 形態素解析 ###
tagger = MeCab.Tagger('-Owakati')
corpus = [tagger.parse(sentence).strip() for sentence in corpus]
# corpus[0]
# '原口 と 槙野 も 先発 だ と 期待 し て ます 。'

### TF-IDFの計算 ###
vectorizer = CountVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
transformer = TfidfTransformer()
tf = vectorizer.fit_transform(corpus) # 単語の出現頻度を計算
tfidf = transformer.fit_transform(tf) # 各ドキュメントのtfidfを計算
# tfidf.shape
# (742, 2309) -> (文書の数、単語の数)

### テスト ###
sample = "本田半端ねぇ"
# 分かち書きしたものをリストに入れて渡す
sample_tf = vectorizer.transform([tagger.parse(sample).strip()])
# 本田半端ねぇのTF-IDFを計算する
sample_tfidf = transformer.transform(sample_tf)
# コサイン類似度の計算
similarity = cosine_similarity(sample_tfidf, tfidf)[0]

### 結果 ###
# 本田半端ねぇに類似しているツイート上から順に10個見つける
topn_indices = np.argsort(similarity)[::-1][:10]
for sim, tweet in zip(similarity[topn_indices], np.array(corpus)[topn_indices]):
    print("({:.2f}): {}".format(sim, "".join(tweet.split())))

TfidfVectorizerを使った例

import MeCab
import codecs
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import TfidfVectorizer

### データの用意 ###
corpus = codecs.open('positive.txt', 'r', 'utf-8').read().splitlines()
# corpus[0]
# '原口と槙野も先発だと期待してます。'

### 形態素解析 ###
tagger = MeCab.Tagger('-Owakati')
corpus = [tagger.parse(sentence).strip() for sentence in corpus]
# corpus[0]
# '原口 と 槙野 も 先発 だ と 期待 し て ます 。'

### TF-IDFの計算 ###
vectorizer = TfidfVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
tfidf = vectorizer.fit_transform(corpus)

### テスト ###
sample = "本田半端ねぇ"
# 分かち書きしたものをリストに入れて渡す
sample_tf = vectorizer.transform([tagger.parse(sample).strip()])
# 本田半端ねぇのTF-IDFを計算する
sample_tfidf = transformer.transform(sample_tf)
# コサイン類似度の計算
similarity = cosine_similarity(sample_tfidf, tfidf)[0]

### 結果 ###
# 本田半端ねぇに類似しているツイート上から順に10個見つける
topn_indices = np.argsort(similarity)[::-1][:10]
for sim, tweet in zip(similarity[topn_indices], np.array(corpus)[topn_indices]):
    print("({:.2f}): {}".format(sim, "".join(tweet.split())))