購入履歴データを使って「この商品を購入した方はこんな商品も購入してます」的なレコメンドツールを作成してみます。
作成しようと思ったきっかけ
レコメンド関連で多いと感じたのは、データに評点が付いているものでした。しかし私が持っている購入履歴データには評点が付いていないため同様のロジックでは作成できませんでした。
よって、今回は単純に商品を購入したデータのみを使ってレコメンドを取得するロジックを作成していきたいと思います。
レコメンドロジックについて
今回は色々検討した結果、以下のようなロジックにすることにしました。
1. 購入した情報を使って類似ユーザーを抽出
2. 類似ユーザーが選択した他の商品を抽出
3. 2で選択率の高いもの順に返す
くどいかもしれませんが、今回扱う購入履歴データには評点
がありません。
よって今回は
同じ商品を選択している率が高い = 類似度が高い
とすることにしました。
よって、ベクトルでの類似度を求めるユークリッド距離
やピアソンの積率相関係数
よりも、集合を使ったJaccard係数
を使うのが良さそうなのでそちらを採用しました。
ちなみに類似度
について調べた際に、こちらのサイトの解説がとても分かりやすく参考になりました。
https://mieruca-ai.com/ai/jaccard_dice_simpson/
作成するプログラム
今回作成するファイルは2つです。
├── data.py ← 購入履歴データ
└── get_recommend.py ← レコメンド取得プログラム
ある商品id(複数可)を指定すると、オススメな商品idを返してくれるイメージです。
購入履歴データは説明しやすいためにこのようにさせていただきましたが、履歴データをファイルからDBに変えるとより実用的かと思います。
履歴データの準備
購入履歴データから以下のような形式のデータをあらかじめ作成しておきます。
data_set = {
'user1': [111, 222, 333, 444, 555],
'user2': [111, 222, 333, 444, 555, 666, 777],
'user3': [222, 444, 666],
'user4': [111, 333, 555, 777],
...
}
各ユーザー毎に購入した商品idをまとめています。
user1
の部分は購入したユーザーが特定できれば良いので、メールアドレスなどが良いかもしれません。
購入した情報を使って類似ユーザーを抽出
最初に商品idと履歴データから類似ユーザーを取得するメソッドを作成します。
def get_similar_user(item_ids, sales_data):
"""
類似度が高いユーザーを、指定した商品idから抽出する
Parameters
----------
item_ids : list
指定した商品id
sales_data : dict
購入したユーザーデータ
Returns
-------
target_users : list
類似度が高い順にソートされたユーザー情報
"""
target_users = {};
# 全ての購入履歴データから類似度を検証させる
for user in sales_data:
# 類似度を算出
jaccard = jaccard_distance(set(item_ids), set(sales_data[user]))
# 全く同じデータは省く
if jaccard != 1.0:
target_users[jaccard] = user
# 類似度が高い順にソートして返す
return sorted(target_users.items())
類似度の算出部分にはJaccard係数を算出してくれるライブラリがあったので使わせていただきました。
詳細はこちらをご覧ください。
http://www.nltk.org/_modules/nltk/metrics/distance.html
類似ユーザーが選択した他の商品を抽出
次に類似ユーザーが購入した商品idを取得するメソッドを作成します。
def get_target_item_ids(item_ids, target_user, sales_data):
"""
類似度が高いユーザーを対象に、非選択の商品idを抽出する
Parameters
----------
item_ids : list
指定した商品id
target_user : list
類似度の高いユーザー
sales_data : dict
購入したユーザーデータ
Returns
-------
target_item_ids : list
非選択の商品id
"""
target_item_ids = []
# 類似度が高いユーザー分処理
for jaccard, user in target_user:
# 指定した商品id以外の商品idを取得
diff = list(set(sales_data[user]) - set(item_ids))
# 取得した製品idをリストに追加
target_item_ids.extend(diff)
return target_item_ids
購入率の高いもの順に返す
類似度の高いユーザーが購入した商品情報から購入数が多いもの順に並べ替えたものを取得するメソッドを作成しようと思ったのですが...なんと1行で書けました(笑)
counter = collections.Counter(target_product_ids).most_common()
これだからPythonは素敵ですよね。
使い方はこちらを参考にして下さい。
https://docs.python.jp/3/library/collections.html#collections.Counter
完成したもの
import argparse
import collections
from nltk.metrics import jaccard_distance
from data import data_set
# コマンドライン引数の設定
parser = argparse.ArgumentParser(description='This is a script that returns product id with high similarity')
parser.add_argument('-d', '--data', help = 'Please specify product id with comma separator', required = True)
def get_similar_user(item_ids, sales_data):
"""
類似度が高いユーザーを、指定した商品idから抽出する
Parameters
----------
item_ids : list
指定した商品id
sales_data : dict
購入したユーザーデータ
Returns
-------
target_users : list
類似度が高い順にソートされたユーザー情報
"""
target_users = {};
# 全ての購入履歴データから類似度を検証させる
for user in sales_data:
# 類似度を算出
jaccard = jaccard_distance(set(item_ids), set(sales_data[user]))
# 全く同じデータは省く
if jaccard != 1.0:
target_users[jaccard] = user
# 類似度が高い順にソートして返す
return sorted(target_users.items())
def get_target_item_ids(item_ids, target_user, sales_data):
"""
類似度が高いユーザーを対象に、非選択の商品idを抽出する
Parameters
----------
item_ids : list
指定した商品id
target_user : list
類似度の高いユーザー
sales_data : dict
購入したユーザーデータ
Returns
-------
target_item_ids : list
非選択の商品id
"""
target_item_ids = []
# 類似度が高いユーザー分処理
for jaccard, user in target_user:
# 指定した商品id以外の商品idを取得
diff = list(set(sales_data[user]) - set(item_ids))
# 取得した製品idをリストに追加
target_item_ids.extend(diff)
return target_item_ids
if __name__== '__main__':
# コマンドライン引数から指定したデータをセット
args = parser.parse_args()
item_ids = [int(id.strip()) for id in args.data.split(',')]
# 類似度が高いユーザーを、指定した商品idから抽出する
target_users = get_similar_user(item_ids, data_set)
# 類似度が高いユーザーを対象に、非選択の商品idを抽出する
target_item_ids = get_target_item_ids(item_ids, target_users, data_set)
# 選択数を算出
counter = collections.Counter(target_item_ids).most_common()
# 商品idのみをカンマ区切りの文字列にして表示
print(','.join([str(id) for id, count in counter]))
実行結果
$ python get_recommend.py -d 777,222
555,333,111,444,666