はじめに
本ブログ記事は、以下の読者を想定しております。
- TreasureDataにサイトのアクセス履歴など、学習に利用できるデータを保持している方
- TreasureDataに蓄積されたデータの活用方法に困っている方
本ブログ記事では、Webのアクセス履歴や商品の購入履歴をHivemallを利用して学習し、ユーザのライフタイムバリュー(以降LTV)を予測する流れをまとめます。 また、今回の内容は、Treasure Data様が公開している”Hivemall を利用した機械学習実践入門(第一回:ドラッグストアのセールス予測)”を応用したものになります。
今回の目的
TreasureDataをDMPとしてデータを集約すると、データの活用を当然考えたくなると思います。その活用の一つとして、機械学習を用いた将来予測があります。 Hivemallは、TreasureDataで利用できる機械学習ライブラリです。今回は、このHivemallを用いて、LTVの高いロイヤルカスタマーの予測を行ってみましょう。
推測に用いるデータ
今回は、以下のような会員情報と商品の購入履歴を想定します。
会員情報
カラム名 | 意味 |
---|---|
member_id | 会員番号のUID |
regist_date | 会員登録日 |
zip | 郵便番号 |
pref | 都道府県 |
sex | 性別 |
birth | 誕生日 |
regist_reason | 会員登録の理由 |
devicetype | デバイス種別 |
last_login_date | 最終ログイン日 |
device_name | 利用デバイス名 |
購入履歴
カラム名 | 意味 |
---|---|
order_id | 購入履歴のID |
order_date | 購入日 |
total | 合計購入金額 |
member_id | 購入者の会員番号UID |
上記のような情報から、推測に会員の要素と、推測の対象のLTVを定義していきます。
まず、LTVは、”会員登録から1年間の購入合計金額”と定義します。また、LTVを予測できそうな要素として、会員情報の要素と、購入後の初回購入金額を”first_sales”として利用してみます。
上記の購入履歴と会員情報を集計して学習に利用するデータを用意します。
学習に利用するデータ
カラム名 | 特性 | 意味 |
---|---|---|
member_id | 質的変数 | 会員番号のUID |
ltv | 目的変数/量的変数 | 会員登録日から1年間の合計購入金額 |
regist_date | 量的変数 | 会員登録日 |
first_sales | 量的変数 | 初日購入金額 |
zip | 質的変数 | 郵便番号 |
pref | 質的変数 | 都道府県 |
sex | 質的変数 | 性別 |
birth | 質的変数 | 誕生日 |
regist_reason | 質的変数 | 会員登録の理由 |
devicetype | 質的変数 | デバイス種別 |
last_login_date | 量的変数 | 最終ログイン日 |
device_name | 質的変数 | 利用デバイス名 |
- 量的変数: 値の間に連続性のある変数(例: 100円、150円、200円)
- 質的変数: 値の間に連続性のない変数(例: 男、女、未回答)
- 目的変数: 推測の目的となる変数
学習に利用するモデル
今回は、”Hivemall を利用した機械学習実践入門(第一回:ドラッグストアのセールス予測)”と同じく、”Random Forest”をモデルとして利用します。
Hivemallを利用した学習
今回は、”学習に利用するデータ”がTreasureDataのテーブルとして、用意できている前提で、以下の流れでHivemallを利用した学習モデルの作成を行います。
(1) ”学習に利用するデータ”を一部をtrainテーブルとして抽出
(2) trainテーブルの特徴ベクトルを作成し、train2テーブルに保存
(3) train2テーブルを利用して、Random Forestモデルを作成し、modelテーブルに保存
また、学習結果から、どの要素がLTVに大きな影響を与えると学習したかを可視化します。
(4) 予測に影響が大きい要素をmodelテーブルから可視化
さらに、実際にできたモデルの以下の流れで実施に予測に利用して精度を確認します。
(5) ”学習に利用するデータ”の残りをtestテーブルとして抽出
(6) testテーブルの特徴ベクトルを作成し、test2テーブルに保存
(7) test2テーブルのLTV以外の情報から、Random Forestモデルを利用してLTVを予測
以降、上記の流れの詳細について説明していきます。
(1)”学習に利用するデータ”を一部をtrainテーブルとして抽出
”学習に利用するデータ”が100万レコード用意できたとしたら、その内90万行を学習に利用します。学習に利用する90万行は、”member_id”のハッシュ値をとって、ランダムに抽出します。学習に利用しない10万行は、学習モデルの予測精度評価に利用します。 ”学習に利用するデータ”を”member”テーブルに入れている場合は、以下のようなSQLになります。
create table train as ( select * from members_all order by TD_MD5(CAST( member_id AS varchar(10) )) limit 900000 );
(2)trainテーブルの特徴ベクトルを作成し、train2テーブルに保存
trainテーブル内の質的変数を数値に変換するためHivemallに用意された”quantify”関数を利用します。 また、値がNULLの項目は、-1とします。 さらに、推測するLTVは値を小さくするため”LN”関数で対数をとります。これは、参考ブログと同じく対数をとった場合、そのままの場合で交差検定を行ったところ精度が良かったためです。そのまま扱う方が良い場合もあります。 最後に、Hivemallの関数に適用できる形に、テーブルを整形してtrain2テーブルに保存します。
WITH train_ordered as ( select * from train order by member_id asc ), train_quantified as ( select t0.member_id, t0.ltv, t0.first_sales, unix_timestamp(t0.birth) as birth_timestamp, unix_timestamp(t0.last_login_date) as last_login_date_timestamp, t2.* from train_ordered t0 -- indexing non-number columns LATERAL VIEW quantify(true, zip, pref, sex, regist_reason, devicetype, device_name) t2 as zip, pref, sex, regist_reason, devicetype, device_name ) INSERT OVERWRITE TABLE train2 SELECT t1.member_id, ARRAY( -- padding zero for missing values t1.birth_timestamp, t1.last_login_date_timestamp,t1.first_sales, IF(t1.zip IS NULL, -1, t1.zip), IF(t1.pref IS NULL, -1, t1.pref), IF(t1.sex IS NULL, -1, t1.sex), IF(t1.regist_reason IS NULL, -1, t1.regist_reason), IF(t1.devicetype IS NULL, -1, t1.devicetype), IF(t1.device_name IS NULL, -1, t1.device_name) ) AS features, LN(1 + t1.ltv) AS label, -- log scale conversion t1.ltv FROM train_quantified t1 ;
(3)train2テーブルを利用して、Random Forestモデルを作成し、modelテーブルに保存
Hivemallで用意された”randomforest_regressor”関数を利用して、100本の決定木で構成されたRandom Forestモデルを作成します。引数には、特徴ベクトル、目的変数、Random Forestの木の数と特徴ベクトルの変数種別を与えます。本来、一回のtrain_randomforest_regressorで100本決定木を作っても良いのですが、下記のように20本ずつ作ってunion allを使うことで、5並列に学習を行うことができます。
INSERT OVERWRITE TABLE model SELECT -- C: Categorical Variable, Q: Quantitative Variable train_randomforest_regressor(features, label, '-trees 20 -attrs Q,Q,Q,C,C,C,C,C,C') FROM train2 UNION ALL SELECT train_randomforest_regressor(features, label, '-trees 20 -attrs Q,Q,Q,C,C,C,C,C,C') FROM train2 UNION ALL SELECT train_randomforest_regressor(features, label, '-trees 20 -attrs Q,Q,Q,C,C,C,C,C,C') FROM train2 UNION ALL SELECT train_randomforest_regressor(features, label, '-trees 20 -attrs Q,Q,Q,C,C,C,C,C,C') FROM train2 UNION ALL SELECT train_randomforest_regressor(features, label, '-trees 20 -attrs Q,Q,Q,C,C,C,C,C,C') FROM train2;
(4)予測に影響が大きい要素をmodelテーブルから可視化
学習結果が入ったmodelテーブルを解析して、どのような要素がLTVが大きいという判定に利用されているか可視化します。可視化にはjupyter notebookを利用します。以下のようなプログラムを用意して、TDのアクセスキーを埋め込めば、棒グラフとして影響が大きい要素が表示されます。
!pip install pandas_td %matplotlib inline import os import pandas as pd import pandas_td as td import matplotlib.pyplot as plt con = td.connect(apikey='XXXXXXXXXXXXXXXXXXXX',endpoint='https://api.treasuredata.com/') hive = con.query_engine(database='tutorial_hivemall', type='hive') var_imp=td.read_td(''' WITH var_imp AS( SELECT array_sum(var_importance) AS var_importance FROM model ) SELECT var_importance[0] AS birth, var_importance[1] AS last_login_date, var_importance[2] AS first_sales, var_importance[3] AS zip, var_importance[4] AS pref, var_importance[5] AS sex, var_importance[6] AS regist_reason, var_importance[7] AS devicetype, var_importance[8] AS device_name FROM var_imp ''', hive) X=range(var_imp.shape[1]) plt.barh(X,var_imp.values[0],align='center') plt.yticks(X,var_imp.columns) plt.savefig('var_imp.png')
例えば、以下のようなグラフが表示された場合は、初回の購入金額、誕生日、最終ログイン日が、LTVの予測に重要度が高いとわかります。
(5)”学習に利用するデータ”の残りをtestテーブルとして抽出
学習に利用しなかった、10万レコードはハッシュ値の逆順で抽出します。
create table test as ( select * from members_all order by TD_MD5(CAST( member_id AS varchar(10) )) desc limit 100000 );
(6)testテーブルの特徴ベクトルを作成し、test2テーブルに保存
ここの基本的な処理は、”2.trainテーブルの特徴ベクトルを作成し、train2テーブルに保存”と同じです。 ただし、testテーブルのみからquantify関数を使って採番すると、train2と採番対応が違ってしまう可能性があります。 そこで、trainテーブルをtestテーブルの先頭につけて、quantify関数を実行して、後でtrainテーブルを行を捨てます。 trainテーブルの列削除は、前回はtrueを与えていたquantify関数の第一引数に、"output_row"というtrainとtestを識別する列を与えることで実現しています。
WITH train_test as ( select 1 as train_first, false as output_row, member_id, ltv, first_sales, birth, last_login_date, zip, pref, sex, regist_reason, devicetype, device_name from train union all select 2 as train_first, true as output_row, member_id, ltv, first_sales, birth, last_login_date, zip, pref, sex, regist_reason, devicetype, device_name from test ), train_test_ordered as ( select * from train_test order by train_first asc, member_id asc ), test_quantified as ( select t0.member_id, t0.ltv, t0.first_sales, unix_timestamp(t0.birth) as birth_timestamp, unix_timestamp(t0.last_login_date) as last_login_date_timestamp, t2.* from train_test_ordered t0 -- indexing non-number columns LATERAL VIEW quantify(output_row, zip, pref, sex, regist_reason, devicetype, device_name) t2 as zip, pref, sex, regist_reason, devicetype, device_name ) INSERT OVERWRITE TABLE test2 SELECT t1.member_id, ARRAY( -- padding zero for missing values t1.birth_timestamp, t1.last_login_date_timestamp,t1.first_sales, IF(t1.zip IS NULL, 0, t1.zip), IF(t1.pref IS NULL, 0, t1.pref), IF(t1.sex IS NULL, 0, t1.sex), IF(t1.regist_reason IS NULL, 0, t1.regist_reason), IF(t1.devicetype IS NULL, 0, t1.devicetype), IF(t1.device_name IS NULL, 0, t1.device_name) ) AS features, LN(1 + t1.ltv) AS label, -- log scale conversion t1.ltv FROM test_quantified t1 ;
(7)test2テーブルのLTV以外の情報から、Random Forestモデルを利用してLTVを予測
test2テーブルとmodelテーブルをHivemallで用意された”tree_predict”関数に与えることで、LTVの予測値を取得することができます。複数の決定木で推測された値の平均をLTVの予測値とします。group byを使う都合上、本来のLTVの平均を計算していますが、全て同じ値なので結果は変わりません。 最後に、特徴ベクトル作成時に、LTVを対数に変換していたため、”EXP”関数で元の値に戻します。
INSERT OVERWRITE TABLE prediction SELECT member_id, EXP(predicted)-1 as predicted, ltv FROM( SELECT member_id, avg(predicted) AS predicted, avg(ltv) AS ltv FROM( SELECT t.member_id, tree_predict(p.model_id, p.model, t.features, false) as predicted t.ltv FROM model p LEFT OUTER JOIN test2 t ) t1 group by member_id ) t2;
上記を実行すると以下のようなpredictedテーブルを得ることができます。
以上で、trainのデータから学習したRandom Forestモデルで、testのデータのLTVを推測することができました。
推測精度の評価
先の流れで、一連のHivemallのRandom Forestモデルを用いた推測を行うことができました。ここから学習に使うデータやモデルの最適化を行って、推測の精度を上げていくことになるかと思います。 今回は、”RMSPE(Root Mean Square Percentage Error”という評価指標で、実際のLTVと予測LTVの解離を評価します。
SQLは以下になります。
SELECT sqrt( AVG( POW((prediction.predicted - prediction.ltv)/prediction.ltv, 2) ) ) FROM prediction;
上記のSQLから、今回は”1.2027529136957467e”という値を得ました。ここから、この値が0に近づく学習モデルを目指します。
まとめ
HivemallのRandom Forestモデルを用いたお客様毎のLTVの推測を行いました。LTV以外にも、上記の流れで値の推測を行うことができるので、皆さんも蓄積されたデータの活用をご検討ください。