ベクトル間の類似度を計測するひとつの手法にコサイン類似度(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
の中にあるツイートをすべて形態素解析し分かち書きにします。
import MeCab
tagger = MeCab.Tagger('-Owakati')
corpus = [tagger.parse(sentence).strip() for sentence in corpus]
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)
まず、CountVectorizerのfit_transform()
を使って、渡されたコーパスから単語辞書の作成と、単語の出現頻度を計算します。
次に、単語の出現頻度からTfidfTransformerのfit_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()])
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()])
単語辞書に3つの単語しか登録されていません。したがって、ここではtransform()
を使うのが正解です。
TF-IDFの計算も同様にtransform()
を使います。
最後に、scikit-learnで実装されているCosine Similarityを使って本田半端ねぇと学習データとして渡した全てのツイートの類似度を計算しています。
結果
類似したツイート10個を見つけてみます。similarity
にはCosine Similarityの計算結果が入っています。それをソートし類似度が高い順からそのインデックスを取得しています。
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)
(変更後)
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
tfidf = vectorizer.fit_transform(corpus)
あとの手順は同じです。
データについては各自用意してください。
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()
tagger = MeCab.Tagger('-Owakati')
corpus = [tagger.parse(sentence).strip() for sentence in corpus]
vectorizer = CountVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
transformer = TfidfTransformer()
tf = vectorizer.fit_transform(corpus)
tfidf = transformer.fit_transform(tf)
sample = "本田半端ねぇ"
sample_tf = vectorizer.transform([tagger.parse(sample).strip()])
sample_tfidf = transformer.transform(sample_tf)
similarity = cosine_similarity(sample_tfidf, tfidf)[0]
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()
tagger = MeCab.Tagger('-Owakati')
corpus = [tagger.parse(sentence).strip() for sentence in corpus]
vectorizer = TfidfVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
tfidf = vectorizer.fit_transform(corpus)
sample = "本田半端ねぇ"
sample_tf = vectorizer.transform([tagger.parse(sample).strip()])
sample_tfidf = transformer.transform(sample_tf)
similarity = cosine_similarity(sample_tfidf, tfidf)[0]
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())))