レコメンドは普通、評価値(レーティング)を使った手法がメインだが、今回は都合でレーティングがない環境下で、レコメンドアルゴリズムを作らなきゃならなかった。
そこで、アソシエーション分析を使ったレコメンドアルゴリズムを作ったので、その過程をまとめてく
目次
・アソシエーション分析のレコメンドについて
・特徴量エンジニアリング
・アプリオリアルゴリズムでメタラベル作成
・協調フィルダリング
・ランクネット
・レコメンド結果
・最後に
・気づいたこと、テクメモ
アソシエーション分析のレコメンドについて
主にレコメンドは評価値(レーティグ)を使う/使わないの2手法で2つに大別できる。
①レーディングを使うアルゴリズム→ユーザーの購入意図を推測するレコメンド
②レーティングを使わないアルゴリズム→トランザクション履歴などの過去の購買データから、商品間の一般的な購買ルール(依存関係)を見つけ、そこからレコメンドを作成する
今回はレーティングがないので、②のタイプのレコメンドモデルを作成。
このレコメンドの対象商品は主に下のタイプ。
・トレンドアイテム
・ニューリリースアイテム
・類似アイテム
・頻出アイテム
この手の手法は個人の趣向を考えない(パーソナライズしない)タイプのレコメンドに該当する。
今回のレコメンドは「類似アイテム」をレコメンドすることが目的。
対象ユーザーは、購入済みユーザーだけに類似アイテムをレコメンドし、新規ユーザーは対象外。
使うアルゴリズムは、協調フィルダリングとランクネットを使った複合アルゴリズム
具体的なやり方は4つのパラメータ(モデル、価格帯、性別、評価値)を使って、類似アイテムを取り出す。
まず協調フィルダリングで3パラメータ「モデル、価格帯、性別」を使い100万単位のアイテム候補から百~千単位に絞ったあと、ランクネットで評価値パラメータを使い、高評価順に並び替え、上位N個表示する。
youtubeは似たような複合アルゴリズムを使っていたから。
amazonは、データ分析計の本を購入したら、「同シリーズ、同カテゴリ、ほぼ同価格帯、高評価」の商品をレコメンドしてきて、感激したので。
その流れで複合アルゴリズムで、amazonみたいな4パラメータで類似商品をレコメンドするアルゴリズムを作ることにした。
コードはgithubに上げてあります
特徴量エンジニアリング
選んだ特徴量
特徴量は一回の取引に付属するユーザーの行動・アイテムのデータを使用。
(SQLで取り出すときは商品系、会員情報、取引系のテーブルのレコードをgroup by)。
ユーザーが必ずしない行動を含んだテーブルのレコードは「LEFT OUTER JOIN」か「FULL JOIN」して、後はpandasとかで欠損値を0にして使った。
最近のレコメンドはfactorizition machinesのようにいろんな特徴量を含めれるようになってるので、特徴量はユーザーデータとアイテムデータ関係を使った。
ユーザーデータ → ユーザーの行動データ、ログイン端末(スマホとか)、ユーザーの個人情報、ログインした場所・時間とか
アイテムデータ→アイテムデータ関係で使えそうなものは全部。
分析例
タイムデータはヒートマップで分析するとログイン時間は朝方が多いっぽい端末はスマホが圧倒的に多かった
地理データは、今回はほぼ日本ユーザーしかいないので必要なかった。(youtubeみたいな世界規模なら必要だけど)
地理データを調べてみたら、ログインした場所 (県)は「東京、大阪」の大都市が最も多かったので、そういう場合は、
・SQLのCASE文で多い箇所を1、他0
みたいに、「多い場所を特別扱いする」ように加工すると使いやすいかと思う。
あと特徴量選択でやったこと
①レコメンドは「UXのデザイナーみたいに仮説立てるのが大事」みたいな記事読んで、UX関係の人に広告で重要なデータ、立てた仮説、ユーザーのニーズとかを聴いた
②YouTubeの論文とかkaggleで使われてる情報を参考に特徴量選択
③特徴量選びは、下の順序でやった
1.会員、取引、商品関係のテーブル表眺めて、使えそうなものを選ぶ
2.xgboostと重回帰分析にかけて、説明変数と目的変数の関係の仮説を立てる。
3.仮説と分析結果から総括して選択
④広告に重要なデータの傾向
→ userごとによる違いが顕著なデータ(セッション回数に関係するものとか)
→Userのニーズ判断に重要な過去行動、ネガポジフィードバックの情報
→UXで立てた & 改善した仮説で重要な「クリック率、表示速度」とか
⑤youtubeレコメンドの特徴量分析
予測対象:視聴時間
特徴量:動画の視聴時間の予測に関係する特徴量は全部使ってる
動画情報=>動画関係は必須
地理情報=> 世界的ユーザーがいるので
性別 => 男女で好みがあるから
探した情報=> 見たい動画に探す行動は大きく反映するから
年 => 年齢層で見るもの違うから
今回はlabelに使用するレーティングのデータがないので、labelはバスケット分析の一つ「アソシエーション分析(by アプリオリアルゴリズム)」からルールを見つけ作った。
目的は自社サイト独自の購買ルールを探し、そこで見つけたルールに沿ったベストなlabelを作成するため。
ラベルの作成は
「同じカテゴリの商品を買った人=似ている」
と仮定して、会員IDとカテゴリIDからトランザクション履歴を作り、アプリオリアルゴリズムで分析。
どうやら
「似た用途のカテゴリを買った人は似た用途の商品を購入する」
ルールがあるみたいなので、これを使った。
そこで、カテゴリIDは1000~9000まであるので、似た用途のカテゴリIDを91この新たなカテゴリに分類。0~91の新たなメタデータを作ってラベルにした。
ラベルは協調フィルダリングでtop5を予測する形で使う。
協調フィルダリング
普通レコメンドではfactrizavtion machines(FM)みたいなアルゴリズムを使うことが多い。しかし、FMは計算量が多いし、最近ではyoutubeのようなRNN系のneural network(NN)を使ったケースが人気らしい
(参考→youtubeの論文)
今回はレーティングのデータがないからyoutubeを真似したNNをFMの代わりに使った。
特徴量はユーザーとアイテム関係のデータともに商品を推測するのに有益な構成になった。
youtubeの論文だと層が厚い方が精度がよくなるらしいので、層は2048層
def c_filtering(INPUT_DIM, weight_path): classes=91 model = Sequential() model.add(Dense(2048, input_dim=INPUT_DIM, activation='relu')) model.add(Dense(1024, activation='relu')) model.add(Dense(512, activation='relu')) model.add(Dense(256, activation='relu')) model.add(Dense(classes, activation='softmax')) model.load_weights(weight_path) model.compile(optimizer='Adam', loss='categorical_crossentropy', metrics=['accuracy']) return model
NNの結果
正解率は85.7%
類似したラベルのtop5の確率値出すので、間違ってもほとんど問題ないと思う
類似アイテムを取り出す順序
①まず予測したlabel(メタデータ)上位5つと同じやつに絞る
②高評価以外のモデル、価格帯、性別の3つのパラメータの抽出条件の関数作り、抽出
③最終的に絞ったアイテム数は876142件 => 185件。
④あとはランキング学習で評価パラメータを使い、高評価順に並び替える
抽出条件の関数
def kbn_type(target_df, dataset): kbn_colmuns=['model', 'line','series','kigata'] kbn_type=[] for kbn in kbn_colmuns: for value in target_df[kbn]: if value==1: kbn_type.append(kbn) kbn_type=','.join(kbn_type) return dataset[dataset[kbn_type]==1] def tanka(target_df, dataset): price=int(target_df['tanka']) yen=2000 tankset=dataset[(dataset['tanka']>=(price-yen)) & (dataset['tanka']<=(price+yen))] return pd.DataFrame(tankset) def sex(target_df, dataset): sex_colmuns=['man', 'women'] sex_type=[] for sex in sex_colmuns: for value in target_df[sex]: if value==1: sex_type.append(sex) sex_type=','.join(sex_type) return dataset[dataset[sex_type]==1]
ランクネット
レーティングがないので、ランクネットの評価値は、「高評価の指標に関係しそうな特徴量」に重み付けして代用。もちろん使うときは、正規化した。
Ranknetを使う発想はyoutubeがNNの後にRankNetを使う複合アルゴリズムを使ってたのを参考にした
def Ranknet(INPUT_DIM): model = Sequential() model.add(Dense(INPUT_DIM, input_dim=INPUT_DIM, activation='relu')) model.add(Dropout(0.1)) model.add(Dense(64, activation='relu')) model.add(Dropout(0.1)) model.add(Dense(32, activation='relu')) model.add(Dense(1)) model.compile(optimizer='Adam', loss='binary_crossentropy', metrics=['accuracy']) return model
Ranknetの関係でわかったこと、やったこと
・YouTubeは見た感じ、次のタイプの特徴量を使ってるよう
→印象的な・視聴したビデオid
→ユーザーの母国語
→ビデオの言語
→最後に見てからの時間
→(印象的な)動画に関係する過去のユーザー情報
・CTR(クリック率)は「ユーザーとの相性や貢献度が高い/クリックしたい衝動の指標」であって、評価とはほぼ関係なく、ラベルに使わなかった
・特徴量選択は協調フィルダリングと同じ分析手法を使った。評価に関係ありそうなユーザーの行動データ、アイテムのデータを選択
評価値の作成と訓練・評価
まず特徴量の内訳popularity →人気度の係数
minyuka →日本未入荷の商品タグ
teikei_cd→特集ページからの流入
ps →新規か常連ユーザーか
add_favarit →お気に入りに登録
view_detail →商品詳細を見た
good →セラーへの評価
safety →安心プラス
評価値の作成は計算式を作った。重回帰分析の寄与度のスコアから、重み付けの値を決定。(普通に足し算するより、レコード数で割った方が精度がよかった)
・計算式
rank= (rating×0.01+ popularity×2+ minyuka×3 + teikei_cd×3 + ps×3 + add_favarit×4 + view_detail×3 + good×3) /8
・評価指標はNDCG
結果は100%(計算式を使ってるから当たり前)
あとはふつうにranknetでtrain/test
def ndcg(y_true, y_score, k=100): y_true = y_true.ravel() y_score = y_score.ravel() y_true_sorted = sorted(y_true, reverse=True) ideal_dcg = 0 for i in range(k): ideal_dcg += (2 ** y_true_sorted[i] - 1.) / np.log2(i + 2) dcg = 0 argsort_indices = np.argsort(y_score)[::-1] for i in range(k): dcg += (2 ** y_true[argsort_indices[i]] - 1.) / np.log2(i + 2) ndcg = dcg / ideal_dcg return ndcg print(ndcg(y_test, pred))
レコメンド結果
ターゲットユーザーの商品内容は
ブランド→シャネル
カテゴリ→コインケース・小銭入れ
レコメンドした商品内容は下図
ちゃんとアソシエーション分析で見つけたルール通り、同じメタカテゴリに含まれてるカテゴリ商品がレコメンドされてる。形式的にはうまくいったと思う
最後に
今回は初めてレコメンドアルゴリズムを作成した。レーティングがない状況下で、チートなレベルだったけど、なんとか形になるものができた。
レコメンドの設計方法、特にアルゴリズムの組み合わせ、類似度抽出の方法とかは、アルゴリズム作成者の思案次第で無限に組み合わせがある。
老舗の業者見てるともっと凝ったレコメンドはかなりあるので、他サイトを見てみるのは、レコメンド作成でかなり参考になった。
気づいたこと、テクメモ
・今回は商品の個体を識別するデータがなかった
→iPhone 10のような商品の個体を識別するデータがない、ので同一商品をレコメンドする可能性があり困った
・ID系の膨大なカテゴリ変数の加工テク
→word2vecで類似度変換して量的変数にするとか
→一番頻度の高い順にでランク付け、頻度が少ないやつは0として扱う
→別々の変数同士を組み合わせて新たな変数を作る(ex:カウント数順で上位112目
brand_id=256, model_id=0, cate_id=3105 => cnt=112)