株式会社スマレジの開発部でスマレジのサーバサイドを作っています

チケットの見える化にチャレンジしてみる(2-4: 準備編 コサイン類似度の実装)

こんばんは!株式会社スマレジ 、開発部のmasaです。

再び緊急事態宣言が出てしまい、masaもリモートワークをしています。 ワクチンの摂取もまだ始まったばかりなので、集団免疫の獲得にはまだ時間がかかりそうな様子です。 出口の見えない不安な日々が続きますが、スマレジ では引き続き小売・飲食店のお客様へ向けて、 私たちにできることで、サポートしていきますので、レジやお店の運営で相談したいことがあれば、 弊社までお問い合わせください。

また、ウェビナーなどを通じて情報発信も行なっております。詳しくは弊社ホームページをご覧ください。

smaregi.jp

さて、今回は前回解説したコサイン類似度をpythonで計算してみます。

コサイン類似度の計算

ソースコード

from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.charfilter import *
from janome.tokenfilter import *

import json
import requests
import math

def getDescriptionOfRedmineIssueByIssueId(id):
    """
    redmineのチケットをID指定で取得して、説明を返す。
    :param id: int
    :return: Bool|string 取得成功なら文字列、失敗ならFalse
    """

    headers = {'X-Redmine-API-Key': "<redmineのキー>"}
    response = requests.get("https://<redmineのドメイン>/issues/" + str(id) + ".json", headers=headers)
    statusCode = response.status_code
    if statusCode >= 400:
        return False
    text = response.text
    if len(text) < 1:
        return False

    resJson = json.loads(text)
    if "issue" not in resJson:
        return False

    issue = resJson["issue"]
    sentence = issue["description"]

    return sentence

# 辞書定義
char_filters = [UnicodeNormalizeCharFilter(), RegexReplaceCharFilter('<("[^"]*"|\'[^\']*\'|[^\'">])*>', u'')]
tokenizer = Tokenizer()
token_filters = [CompoundNounFilter(), POSKeepFilter(['名詞']), LowerCaseFilter(), TokenCountFilter()]
analyzeInfo = Analyzer(char_filters=char_filters, tokenizer=tokenizer, token_filters=token_filters)

# redmineチケットの説明を取得(ID指定)
sentence_v1 = getDescriptionOfRedmineIssueByIssueId(比較したいチケット番号1)
sentence_v2 = getDescriptionOfRedmineIssueByIssueId(比較したいチケット番号2)

# ベクトル化
vector1 = []
for result in analyzeInfo.analyze(sentence_v1):
    vector1.append(result[0])

vector2 = []
for result in analyzeInfo.analyze(sentence_v2):
    vector2.append(result[0])

# ベクトルの大きさを計算
scalar_1 = math.sqrt(len(vector1))
scalar_2 = math.sqrt(len(vector2))

# 内積の計算
innerProduct = 0
for word_1 in vector1:
    if word_1 in vector2:
        innerProduct += 1

# コサイン類似度を計算
cosineSimilarity = innerProduct / (scalar_1 * scalar_2)

# 出力
print(cosineSimilarity)

文書ベクトルの作成

vector1 = []
for result in analyzeInfo.analyze(sentence_v1):
    vector1.append(result.surface)

janome形態素解析された情報をresult変数一つ一つ取り出しています。 今回はTokenCountFilter()をフィルターに指定しているので、result[0]形態素そのもの、result[1]にその出現回数が入っています。 なので、result[0]を配列に入れていけば文書ベクトルの完成です。 これを比較したいチケットごとに実施しています。(vector1,vector2)

ベクトルの大きさの計算

masa2019.hatenablog.com

前回のブログで紹介したように、各ベクトルの大きさは単語の種類数、つまりそれぞれvector1,vector2の長さの平方根になります。 そのため、scalar_1 = math.sqrt(len(vector1))で計算されます。

内積の計算

# 内積の計算
innerProduct = 0
for word_1 in vector1:
    if word_1 in vector2:
        innerProduct += 1

内積は両方のベクトルでダブっている単語の数になるので、一方のベクトルでループを生成し、それぞれの形態素がもう一方のベクトルに入っているかを調べることで算出できます。

コサイン類似度を算出

最後に二つのベクトルのcosを算出します。

# コサイン類似度を計算
cosineSimilarity = innerProduct / (scalar_1 * scalar_2)

コサイン類似度は内積÷ベクトル1の大きさ×ベクトルにの大きさで算出されます。 詳しくは前回のブログをご覧ください。

実際に上記のプログラムで同じチケットを指定すると、出力は1になり、 類似しているチケットほど数値は1に近くなっていることを確認できると思います。