Pythonでコサイン類似度を使ってテキストの類似度を計算する

テキストの類似度って言っても出現する単語の回数比較でしか無いので文意解釈はしてないです。あくまで「出現した文字の一致度」ですね。

Word2Vecとかを使ってテキストを拡張してあげれば少しは文意を加味した類似度といえるのかもです。

from scipy.spatial.distance import cosine
import unittest


class SentenceSimilarity(object):

    def __init__(self):
        self._A = None
        self._B = None

    @property
    def A(self):
        return self._A

    @property
    def B(self):
        return self._B

    @A.setter
    def A(self, v):
        self._A = [i for i in v.replace(' ', ',').split(',') if len(i) > 0]

    @B.setter
    def B(self, v):
        self._B = [i for i in v.replace(' ', ',').split(',') if len(i) > 0]

    def distance(self):

        if self._A is None or self._B is None:
            return False

        if len(self._A) < 2 or len(self._B) < 2:
            return False

        return self._distance()

    def _distance(self):

        _words = []
        _words.extend(self._A)
        _words.extend(self._B)

        _words = list(set(_words))
        _words.sort()

        _listA = [self._A.count(_w) for _w in _words]
        _listB = [self._B.count(_w) for _w in _words]

        try:
            return 1 - cosine(_listA, _listB)
        except:
            return False


class TestSentenceSimilarity(unittest.TestCase):

    def setUp(self):
        pass

    def test_0(self):

        _sentence = SentenceSimilarity()
        _sentence.A = '今期 業績 予想 未定 期限切れ 肉 問題 販売減'
        _sentence.B = '都市 対抗 野球 西濃運輸 初優勝 佐伯 富士 重工'
        self.assertGreater(_sentence.distance(), 0.0)


    def test_1(self):

        _sentence = SentenceSimilarity()
        _sentence.A = '今期 業績 予想 未定 期限切れ 肉 問題 販売減'

        self.assertEqual(_sentence.distance(), False)

    def test_2(self):

        _sentence = SentenceSimilarity()
        _sentence.A = '今期'
        _sentence.B = '都市'

        self.assertEqual(_sentence.distance(), False)

    def test_3(self):

        _sentence = SentenceSimilarity()
        _sentence.A = '今期,業績,予想,未定'
        _sentence.B = '都市,業績,予想,未定'

        _sentence.B = '業績 対抗 野球 西濃運輸 初優勝'

        self.assertGreater(_sentence.distance(), 0.0)

if __name__ == '__main__':
    unittest.main()

使い方はテストをみていただければご理解いただけるかと。

コサイン類似度の計算なんて

return 1 - cosine(_listA, _listB)

の部分でしかやってなくて、残りは全部下ごしらえです。

_sentence = SentenceSimilarity()
_sentence.A = ‘今期 業績 予想 未定 期限切れ 肉 問題 販売減’
_sentence.B = ‘都市 対抗 野球 西濃運輸 初優勝 佐伯 富士 重工’

インスタンス作って、AとB(命名が安直ですいません)にカンマ切り、もしくはスペース切りの文字列を指定します。なので事前に分かち書きをしておく必要があります。

まあ文章を渡してメソッドの中で分かち書きしてもいいんですけどね。手元にあったのが分かち書き済みだったので今回はそのまま使いました。

_sentence.distance()

で、類似度を求めます。類似度を求める前に_Aと_Bの和集合を作って、和集合の単語リストにある単語が_Aと_Bそれぞれで幾つずつ出現するかを求めます。

上記例だと和集合は[今期,業績,予想,未定,期限切れ,肉,問題,販売減,都市,対抗,野球,西濃運輸,初優勝,佐伯,富士,重工]となり、
_listAは[1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0]
_listBは[0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1]
になるはずです。で、これらをベクトルにしてコサイン類似度を求めます。今は0と1だけですが複数回出現する単語があれば当然2以上の値も出てきます。

コサイン類似度は0から1の値で返ってきて、値が大きければ大きいほど類似しています。上記サンプルだと0が返ってくるはずです。ぜんぜん違う、ってことですね。

アイテムベースのレコメンドに使えると思います。サービスとして提供するレコメンドエンジンであればこんな単純な計算じゃないでしょうね。

Python 3.4.2, scipy 0.14.0 で書きました。