自分のキャリアをあれこれ考えながら、Pythonで様々なデータを分析していくブログです

Pythonで最も似た属性をもつ商品をコサイン類似度で計算し取得する

Python
Python

今回はコサイン類似度の算出方法をまとめておきたいと思います。

コサイン類似度とは2つのベクトルがどれくらい似ているか表す指標になります。

コサイン類似度とは2つのベクトルが「どのくらい似ているか」という類似性を表す尺度で、具体的には2つのベクトルがなす角のコサイン値のこと。1なら「似ている」を、-1なら「似ていない」を意味する。主に文書同士の類似性を評価するために使われている。
引用: https://atmarkit.itmedia.co.jp/ait/articles/2112/08/news020.html

詳細はITMedia社のコサイン類似度(Cosine Similarity)とは?という記事に説明があります。

基本的にベクトルを作成してコサイン類似度を計算するだけになります。

簡単なリコメンドを作成したいときにコサイン類似度のスコア順に表示するだけでも十分ビジネスに適用可能ではないかと思います。

ちなみに私が実際に利用した時はコールドスタート問題の解決方法として、まだ未販売の商品がどれくらいの売上になりそうか予測するためコサイン類似度で似た属性をもつ商品を探索し分析の補足情報として活用したことがあります。

それでは、ボストンの住宅価格のデータセットを例にやってみたいと思います。(タイトルである商品は住宅と置き換えてください 笑)

スポンサーリンク

データセットの読み込み

# データの読み込み
import pandas as pd
df = pd.read_csv("http://lib.stat.cmu.edu/datasets/boston_corrected.txt",skiprows=9,sep="\t",encoding='Windows-1252')
# infoメソッドで概要を把握
df.info()
Out[0]
RangeIndex: 506 entries, 0 to 505
Data columns (total 21 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   OBS.     506 non-null    int64  
 1   TOWN     506 non-null    object 
 2   TOWN#    506 non-null    int64  
 3   TRACT    506 non-null    int64  
 4   LON      506 non-null    float64
 5   LAT      506 non-null    float64
 6   MEDV     506 non-null    float64
 7   CMEDV    506 non-null    float64
 8   CRIM     506 non-null    float64
 9   ZN       506 non-null    float64
 10  INDUS    506 non-null    float64
 11  CHAS     506 non-null    int64  
 12  NOX      506 non-null    float64
 13  RM       506 non-null    float64
 14  AGE      506 non-null    float64
 15  DIS      506 non-null    float64
 16  RAD      506 non-null    int64  
 17  TAX      506 non-null    int64  
 18  PTRATIO  506 non-null    float64
 19  B        506 non-null    float64
 20  LSTAT    506 non-null    float64
dtypes: float64(14), int64(6), object(1)
memory usage: 83.1+ KB
# 上から5件を表示
df.head()
Out[0]

OBS. TOWN TOWN# TRACT LON LAT MEDV CMEDV CRIM ZN ... CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT
0 1 Nahant 0 2011 -70.955 42.2550 24.0 24.0 0.00632 18.0 ... 0 0.538 6.575 65.2 4.0900 1 296 15.3 396.90 4.98
1 2 Swampscott 1 2021 -70.950 42.2875 21.6 21.6 0.02731 0.0 ... 0 0.469 6.421 78.9 4.9671 2 242 17.8 396.90 9.14
2 3 Swampscott 1 2022 -70.936 42.2830 34.7 34.7 0.02729 0.0 ... 0 0.469 7.185 61.1 4.9671 2 242 17.8 392.83 4.03
3 4 Marblehead 2 2031 -70.928 42.2930 33.4 33.4 0.03237 0.0 ... 0 0.458 6.998 45.8 6.0622 3 222 18.7 394.63 2.94
4 5 Marblehead 2 2032 -70.922 42.2980 36.2 36.2 0.06905 0.0 ... 0 0.458 7.147 54.2 6.0622 3 222 18.7 396.90 5.33

5 rows × 21 columns

スポンサーリンク

(オプション) デバッグしやすいようにキーを作成。(数値+名称とかだと分かりやすい)

# キーを作成
df["KEY"] = df["OBS."].astype(str) + ":" + + df["TRACT"].astype(str) + ":" + df["TOWN"] 
# 最初の1件目を見てみる
df["KEY"].iloc[0]
Out[0]
'1:2011:Nahant'
スポンサーリンク

コサイン類似度の計算

sklearnのcosine_similarityを使って計算します。

# ライブラリのインポート
import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
from sklearn import preprocessing

# 部屋数、住宅価格、一人当たりの犯罪数 で類似度を計算させる
anacols=[
 'RM'
,'CMEDV'
,'CRIM'
]

# 標準化 (そのままだと大きい距離に引っ張られてしまうのでやる)
ss = preprocessing.StandardScaler()
cos_sim_df = ss.fit_transform(df[anacols].fillna(0))

# 行列形式でコサイン類似度を算出 (内部的に正規化してくれてるっぽいので標準化しなくてもいいのかも?)
cos_similarity_matrix = pd.DataFrame(cosine_similarity(cos_sim_df))
# コサイン類似度
cos_similarity_matrix
Out[0]

0 1 2 3 4 5 6 7 8 9 ... 496 497 498 499 500 501 502 503 504 505
0 1.000000 0.831274 0.794872 0.795805 0.762981 0.737662 0.052109 0.358176 -0.440196 -0.143702 ... -0.496160 -0.354028 -0.043342 -0.456986 -0.171561 0.957843 0.136849 0.918292 0.906766 -0.091469
1 0.831274 1.000000 0.324073 0.330033 0.279203 0.379009 0.343320 0.689690 0.089463 0.398219 ... -0.085274 0.168528 0.394906 0.042103 0.402635 0.911625 0.606606 0.685997 0.806016 0.447163
2 0.794872 0.324073 1.000000 0.997586 0.996947 0.860640 -0.255047 -0.120216 -0.832800 -0.666185 ... -0.727768 -0.770294 -0.481194 -0.806270 -0.727181 0.628431 -0.412253 0.800792 0.648947 -0.631034
3 0.795805 0.330033 0.997586 1.000000 0.998548 0.893724 -0.192112 -0.071169 -0.800265 -0.634587 ... -0.678795 -0.733532 -0.430616 -0.767701 -0.711886 0.617587 -0.373439 0.774831 0.622369 -0.599954
4 0.762981 0.279203 0.996947 0.998548 1.000000 0.884390 -0.221409 -0.116234 -0.822584 -0.671200 ... -0.691029 -0.759575 -0.465575 -0.787463 -0.748318 0.578471 -0.417971 0.752668 0.590936 -0.638649
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
501 0.957843 0.911625 0.628431 0.617587 0.578471 0.514325 -0.004914 0.367919 -0.324555 -0.010782 ... -0.479815 -0.250842 -0.005722 -0.372179 0.021113 1.000000 0.227409 0.924298 0.969679 0.043216
502 0.136849 0.606606 -0.412253 -0.373439 -0.417971 -0.039529 0.856839 0.944545 0.828483 0.953918 ... 0.735924 0.877936 0.959815 0.815317 0.886690 0.227409 1.000000 -0.161454 0.025850 0.965643
503 0.918292 0.685997 0.800792 0.774831 0.752668 0.540474 -0.338681 0.003960 -0.654453 -0.385620 ... -0.774099 -0.598835 -0.381249 -0.697093 -0.327998 0.924298 -0.161454 1.000000 0.971938 -0.335484
504 0.906766 0.806016 0.648947 0.622369 0.590936 0.412433 -0.247381 0.139516 -0.473212 -0.180345 ... -0.656927 -0.417827 -0.225447 -0.533547 -0.101018 0.969679 0.025850 0.971938 1.000000 -0.128108
505 -0.091469 0.447163 -0.631034 -0.599954 -0.638649 -0.292493 0.776408 0.829889 0.930855 0.998541 ... 0.806897 0.954552 0.938542 0.907043 0.968738 0.043216 0.965643 -0.335484 -0.128108 1.000000

506 rows × 506 columns

行列だと確認するのが大変なので、各indexごとに最も類似度が高いindexを探索してみます。

スポンサーリンク

コサイン類似度の算出結果から各index毎に最も類似度が高いindexを探索する

# 最終アウトプット格納用
maxindex_list=[]
maxvalue_list=[]

# 行列のデータから各index毎に最も類似度が高いindexを探索する
for j in range(0,len(cos_similarity_matrix)):
    print("---- index",j,"と類似するindexを探索 (昇順で表示)")
    # 初期設定
    max_index=-2
    max_val=-2
    for i in range(0,len(cos_similarity_matrix)):
        # 対角線上はスキップ
        if i == j:
            continue
        # 類似度が1を超える場合はスキップ
        elif np.abs(cos_similarity_matrix[i][j]) >= 1.0:
            continue
        # 重複する名称である場合はスキップ
        elif df["KEY"].iloc[i] == df["KEY"].iloc[j]:
            print(df["KEY"].iloc[i])
            continue
        else:
            # 類似度が大きいindexとvalueを更新
            if cos_similarity_matrix[i][j] >= max_val:
                max_val = cos_similarity_matrix[i][j]
                max_index = i
                print("index =",max_index,"->",max_val)
        # 対象index/valueと一番類似度が高いindex/valueを格納
        if i == (len(cos_similarity_matrix)-1):
                maxindex_list.append(max_index)
                maxvalue_list.append(max_val)
        else:
                pass
Out[0]
---- index 0 と類似するindexを探索 (昇順で表示)
index = 1 -> 0.8312739440395608
index = 41 -> 0.9425180497277477
index = 52 -> 0.9725328096870871
index = 81 -> 0.9967538297456999
index = 314 -> 0.9990954456804517
index = 351 -> 0.9997019884255598
---- index 1 と類似するindexを探索 (昇順で表示)
index = 0 -> 0.8312739440395608
index = 62 -> 0.9847946373605043
index = 91 -> 0.9950713979704345
・・・省略・・・
---- index 505 と類似するindexを探索 (昇順で表示)
index = 0 -> -0.09146897154913142
index = 1 -> 0.4471633696478723
index = 6 -> 0.7764083643742422
index = 7 -> 0.829889262174972
index = 8 -> 0.9308547923887059
index = 9 -> 0.9985405186741478
index = 11 -> 0.9991814496178155
index = 117 -> 0.9992918539565818
# 最終結果出力 (各indexと最も類似度が高いindex番号を表示)
pd.DataFrame(zip(maxindex_list, maxvalue_list))
Out[0]

0 1
0 351 0.999702
1 91 0.995071
2 189 0.999955
3 303 0.999995
4 192 0.999934
... ... ...
500 47 0.999795
501 301 0.997360
502 51 0.999805
503 88 0.999306
504 111 0.991154

505 rows × 2 columns

index0はindex351、index1はindex91と部屋数、住宅価格、一人当たりの犯罪数が類似しているようです。

# index 0とindex 351の属性情報を確認
df.iloc[[0,351]].transpose()
Out[0]

0 351
OBS. 1 352
TOWN Nahant Marshfield
TOWN# 0 71
TRACT 2011 5061
LON -70.955 -70.83
LAT 42.255 42.0775
MEDV 24.0 24.1
CMEDV 24.0 24.1
CRIM 0.00632 0.0795
ZN 18.0 60.0
INDUS 2.31 1.69
CHAS 0 0
NOX 0.538 0.411
RM 6.575 6.579
AGE 65.2 35.9
DIS 4.09 10.7103
RAD 1 4
TAX 296 411
PTRATIO 15.3 18.3
B 396.9 370.78
LSTAT 4.98 5.49
KEY 1:2011:Nahant 352:5061:Marshfield

部屋数と住宅価格がとても似ているようです。CRIMはindex351番の方が数値が高いですね。
同じ地域ではないのに似ている属性をもつレコードを抽出することができました。これはこのままレコメンドシステムに流用できそうですね。

タイトルとURLをコピーしました