fbpx

CL LAB

HOME > Blog > Natheer Alabsi > Kaggle challenge: Santanders product recommendations #dataanalytics #kaggle #machinelearning

Kaggle challenge: Santanders product recommendations #dataanalytics #kaggle #machinelearning

 ★ 104

本記事は翻訳です。原文は英語のものをご参照ください

こんにちは!データサイエンティストのナゼイルです

Natheer Alabsiです。東京大学大学院のフロンティアサイエンス研究科の環境科学専攻の修士号と博士号をそれぞれ2011年と2015年に取得し、統合リモートセンシング、GPS、GISを使った沿岸漁業資源のモニタリング技術を開発しました。現在私はCreationline INCのデータ科学者およびデータ分析専門家として働いています。私の仕事は、Python、R、Apache Sparkでのデータの調査、分析、機械学習です。

Kaggleをご存知ですか?

Kaggleは、データサイエンスに携わる技術者の中で最も有名なウェブサイトであり、まさに the Home of Data Science と言えます。企業や組織が抱えるさまざまな課題を機械学習のコンペティションという形で共有し、データセットを提供しています。優秀な解決を生み出したデータサイエンティストには賞金が与えられることもあります!世界中のデータサイエンティストが切磋琢磨し、最高のソリューションを日々生み出しています。

例えば現在では以下のようなコンペティションが開催されています。

  1. Data Science Bowl 2017: 肺のDICOM医療画像から肺がんの発生を予測するコンペティションです。上位10人のデータサイエンティストに総額100万ドルの賞金が提供されます!

  2. Dstl Satellite Imagery Feature Detection: 衛星画像からの地表状況判断を自動化するコンペティションです。上位3人のデータサイエンティストに賞金10万ドルが提供されます

  3. The Nature Conservancy Fisheries Monitoring: 漁船から撮影された画像を元に魚とその種類を判別するコンペティションです。上位5人のデータサイエンティストに賞金15万ドルが提供されます

Santanders product recommendationsについて

サンタンデール銀行(Santander Bank)は、2016年10月26日から12月21日の期間、製品リコメンドに関するコンペティションをKaggle上で開催しました。残念ながらその期間私は実際の案件対応のため最終的なリコメンドロジックを応募することができませんでした。

このコンペティションでは、過去18ヶ月間に顧客に提供された商品と顧客の購買データをもとに、追加された新製品に対する顧客の反応を予測することが目的になっています。

顧客が次に何を購入するのか?顧客の今のニーズを満たす製品を適切にリコメンドできるように、顧客の行動を理解しようとする企業が増えています。リコメンドが適切に行えるのであれば、競合他社よりの優位に立ち、顧客ニーズを理解しロイヤルティを高めることが可能になります。

課題解決のための戦略

コンペティションの直接的な目標は、翌月に提供される新製品に対して、顧客が購入するか、しないかを予測することです。プログラムの中では、顧客を add または not_add (以下 add=1, not_add=0 で示す) に分類するという手法に落としこむことができます。

ただし異なる製品は異なるパラメータの影響を受けます。収入や年齢によって影響を受けるものもあれば、顧客セグメンテーションや性別などによって影響を受けるものもあります。従って私はモデルを作成する前に、各製品の最も重要な機能を選択するためのプロセスを組み込むことにしました。それぞれのモデルは、製品によって異なる挙動をとります。従って製品ごとに最も良いモデルを選択することが重要です。私は一般的な分類モデルのうち10個をテストし、最終的に製品ごとに最も良い精度を出すモデルを選択するようにしました。

最後にアンサンブル(複数モデル)を使用します。アンサンブルを使用することによって精度は明確に向上することが、多くのコンペティションの結果わかっています。通常コンペティションの上位入賞者はアンサンブルを組み込んでいることが一般的です。従って私もアンサンブルを使用することで精度を向上させるようにします。ここでのアンサンブルとは、複数のモデルを使用し、それぞれのモデルから導かれた予測を結合して、最終出力の精度を向上させることを意味しています。

ツールの準備

今回はAnacondaを使用します。Anacondaはデータサイエンティストに最適化されたPythonツールセットで、100以上の有名なデータサイエンス用パッケージを含む、720以上のソフトウェアパッケージが提供されています。Anaconda自体はPython, R, Scalaを使用することができるのですが、今回はPythonを使用することにします。

まずはAnacondaをダウンロードします。Pythonのバージョンは3.6にします。

ダウンロードした .exe ファイルをダブルクリックし、画面の支持に従ってインストールします。インストールが完了するとデスクトップにAnacondaのアイコンが配置されますので、ダブルクリックしてAnaconda Navigatorを起動してください。

起動すると以下のような画面が表示されます。このAnaconda NavigatorからiPython Notebookを使用するためのノートブックサーバを起動することができます。ノートブックサーバを起動するには、下図のとおりJupyter notebook アイコンを選択します。

iPython Notebookの画面がブラウザ上に開いたら、ローカルホスト上のファイルにアクセスすることができるようになります。これでデータファイルや、過去に作成したノートブックを開くことができるようになります。

画面の右側にある New をクリックして、新規ノートブックを作成します。 "Notebooks" リストには既にインストールされているカーネルが一覧表示されており、自分の使用したい環境を選択することができます。

新規ノートブック作成直後の画面は以下のようになっています。

Anaconda には Pythonが組み込まれており、既に多くのパッケージがインストールされています。後々エラーなどで悩まないように、まずは全てのパッケージを更新することをおすすめします。全てのパッケージは以下の手順で更新します。

  • Anaconda Navigator の左メニューから Environments を選択
  • Open Terminalを選択
  • プロンプトから以下のコマンドを実行

conda upgrade conda
conda upgrade --all

新しいパッケージをインストールする場合は、 conda install package_name コマンドを実行します。例えば Pandas パッケージをインストールする場合は、 conda install pandas とコマンドを実行します。

既存のパッケージを更新する場合は、 conda update package_name コマンドを実行します。全てのパッケージを更新する場合(実際これをよくやるのですが)は、 conda update --all というコマンドを実行します。

パッケージを導入する

それでは Jupyter Notebook 上で作業を進めていきましょう。この記事の中で使用するパッケージは以下のものです。

import numpy as np
import pandas as pd
from pickle import dump
import sklearn.utils
from sklearn.metrics import confusion_matrix, accuracy_score, recall_score
from sklearn import preprocessing, ensemble
from sklearn.tree import DecisionTreeClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.feature_selection import RFE

データの準備

データの読み込み

提供されているデータファイルを読み込みます。

df = pd.read_csv("C:/Users/m-alabsi/train_ver2.csv/train_ver2.csv",
dtype={"sexo":str, "ind_nuevo":str, "ult_fec_cli_1t":str, "indext":str})

# sort the data by user_id and date
df.sort_values(by=["ncodpers", "fecha_dato"], ascending=[True, False], inplace=True)
df["fecha_dato"] = pd.to_datetime(df["fecha_dato"],format="%Y-%m-%d") # change the date format

product カラムにおける全ての欠損データを 0(zero) で置き換えます

df.loc[: "ind_cco_fin_ult1":"ind_recibo_ult1"].fillna(0, inplace = True)

Kaggleで提供された生データには、毎月の製品の使用または不使用に関するデータが含まれています。これを use/nonuse から added/not_added 形式に変換する必要があります。特定の顧客によって数ヶ月連続して使用された製品は、最初の月にのみ追加されたものとみなされ、破棄されて再度追加された場合は、再度追加されたものと見なすようにします。

前月のデータは翌月のデータと比較され、追加(1を示す)は、前月の製品データが0で、翌月の製品データが1だった場合です。

dates_1 = df["fecha_dato"].unique() # Extract unique dates in the dataset
df1 = pd.DataFrame()
for i in range(16): # loop over the series of 17 months
    month1 = dates_1[i] # get the date of the first month
    month0 = dates_1[i+1] # # get the date of the following month
    df4 = df[df["fecha_dato"] == month1] # Extract the data of the first month
    df3 = df[df["fecha_dato"] == month0] # Extract the data of the following month

    # Extract common users between consecutive months
    ncodpers1 = df4.ncodpers # get user IDs of the first month
    ncodpers0 = df3.ncodpers # get user IDs of the following month
    ncodpers = pd.Series(list(set(ncodpers1).intersection(set(ncodpers0)))) # get common IDs

    # create common data between consecutive months
    df4 = df4[df4["ncodpers"].isin(ncodpers)]
    df3 = df3[df3["ncodpers"].isin(ncodpers)]

    # create empty dataframe with only customer data filled in and products data removed.
    df2 = pd.DataFrame(index=df4.index,columns=df4.columns) 
    df2.loc[:, "fecha_dato": "segmento"] = df4.loc[:, "fecha_dato": "segmento"]
    df2.iloc[:,21:48] = 0 # fill in product data with 0("zero") 

    dfnames = [df4, df3, df2]
    for j in dfnames:  
        j.set_index('ncodpers', inplace=True) # set user ID "ncodpers" as an index 

    # fill in products additions
    for f in range(0, len(df4.index)):
        for h in range(21, len(df3.columns)):
            # cases where the product was 0 in any month and 1 in the next month
            if df4.iloc[f,h] == 1 and df3.iloc[f,h] == 0: 
                df2.iloc[f,h] = 1  

    df1 = df1.append(df2)

データクレンジング

# delete some unneeded columns 
df1.drop(["ult_fec_cli_1t", "fecha_alta", "conyuemp", "ind_ahor_fin_ult1", "ind_aval_fin_ult1"], 
         axis = 1, inplace = True)

# sort by user ID("ncodpers") and date("fecha_dato")
df1.sort_values(by=["ncodpers", "fecha_dato"], ascending=[True, False], inplace=True)
df1["fecha_dato"] = pd.to_datetime(df1["fecha_dato"],format="%Y-%m-%d") # change the date format

df1.age = df1.age.astype(str).map(str.strip) # remove the whitespace in the age data
df1.age = df1.age.replace("NA", 999) 
df1.age = df1.age.astype(int)

df1.antiguedad = df1.antiguedad.astype(str).map(str.strip) # remove the whitespace 
df1.antiguedad = df1.antiguedad.replace("NA", 999)
df1.antiguedad = df1.antiguedad.astype(int)
df1.antiguedad.replace(-999999, 0, inplace = True) # replace negative antiguedad values by zero

df1.renta = df1.renta.replace("NA", 0)
df1.renta = df1.renta.replace(" ", 0)
df1.renta = df1.renta.fillna(0)
df1.renta = df1.renta.astype(int)

df1 = df1.fillna(999) # Fill in missing values with 999

それぞれの機能の作りこみ

顧客に対する製品追加(購入)回数

顧客がどの商品を購入(add)したかを作りこみます。それぞれの製品は当月に使用されず、翌月に使用された場合のみ購入(add)とみなします。この機能を 製品の追加 と呼ぶことにしましょう。

dates = df1.fecha_dato.unique() # get the unique dates
df2 = df1[df1["fecha_dato"] != dates[0]]  # exclude the last month(test data) from the dataset
df2 = pd.concat([df2.loc[:, "ncodpers"], df2.loc[:, "ind_cco_fin_ult1":"ind_recibo_ult1"]], axis = 1, 
                join_axes=[df2.index])
# calculate every product’s number of additions per customer during the whole period
df2 = df2.groupby("ncodpers").agg("sum") 
df1.set_index("ncodpers", inplace = True)
# some customers are only added in the last month and will be missed in df2
df1 = df1[df1.index.isin(df2.index)] # to solve this we extract customers exist in both df1 and df2
df2.columns = [str(col) + '_2' for col in df2.columns] # change column names

顧客ごとの製品使用回数

次に顧客ごとの製品使用回数を算出します。この機能を 製品の利用 と呼ぶことにしましょう。

usecols = ['ncodpers', 'fecha_dato', 'ind_cco_fin_ult1', 'ind_cder_fin_ult1', 'ind_cno_fin_ult1', 
           'ind_ctju_fin_ult1', 'ind_ctma_fin_ult1', 'ind_ctop_fin_ult1', 'ind_ctpp_fin_ult1', 
           'ind_deco_fin_ult1', 'ind_deme_fin_ult1', 'ind_dela_fin_ult1', 'ind_ecue_fin_ult1', 
           'ind_fond_fin_ult1', 'ind_hip_fin_ult1', 'ind_plan_fin_ult1', 'ind_pres_fin_ult1', 
           'ind_reca_fin_ult1', 'ind_tjcr_fin_ult1', 'ind_valo_fin_ult1', 'ind_viv_fin_ult1', 
           'ind_nomina_ult1', 'ind_nom_pens_ult1', 'ind_recibo_ult1']

# import the original data before product additions were calculated
df3 = pd.read_csv('C:/train_ver2.csv/train_ver2.csv', encoding='latin-1', usecols=usecols) 
df3["fecha_dato"] = pd.to_datetime(df3["fecha_dato"],format="%Y-%m-%d") # change the date format

df3 = df3[df3["fecha_dato"] != dates[0]] # exclude the last month(test data) from the dataset
df3 = pd.concat([df3.loc[:, "ncodpers"], df3.loc[:, "ind_cco_fin_ult1":"ind_recibo_ult1"]], 
                axis =1, join_axes=[df3.index])

# Since each 'ind_nomina_ult1' and 'ind_nom_pens_ult1' columns had 1920 entries as NaN, 
# we will replace with zero
df3 = df3.fillna(0) 
df3.ind_nom_pens_ult1 = df3.ind_nom_pens_ult1.astype(int) # change data type from float to integer
df3.ind_nomina_ult1 = df3.ind_nomina_ult1.astype(int) # change data type from float to integer

# calculate every product’s number of uses per customer during the whole period
df3 = df3.groupby("ncodpers").agg("sum")

# some customers are only added in the last month and will be missed in df3
df1 = df1[df1.index.isin(df3.index)] # to solve this we extract customers exist in both df1 and df3
df3.columns = [str(col) + '_3' for col in df3.columns] # change column names
df3 = df3[df3.index.isin(df1.index)]  

製品の追加(df2)製品の利用(df3) 機能ができました。2つのデータフレームdf2, df3 を df1 にマージしましょう。

# First, let us merge df2 and df3 into one data frame 
df2 = pd.concat([df2, df3], axis = 1, join_axes=[df2.index])

# Now, we will merge the df2 with df1
df1 = pd.concat([df1, df2], axis = 1, join_axes=[df1.index])
del df2, df3

年齢、年数(antiguedad)、収入(renta)

これらの機能をカテゴリ変数に変形します。年齢と年数は合わせると379のユニークな値となり、カテゴリ変数になります。cut機能を使用して実装します。

age_bins = [0, 12, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 
            54, 56, 58, 60, 63, 65, 70, 75, 80, 85, 90, 95, 200, 1000]
age_labels = pd.Series(range(1,39))
df1['age_cat'] = pd.cut(df1['age'], age_bins, labels=age_labels, right=True, include_lowest=True)

antiguedad_bins = [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 124, 
                   130, 136, 142, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 270, 1000]
antiguedad_labels = pd.Series(range(1,37))
df1['antiguedad_cat'] = pd.cut(df1['antiguedad'], antiguedad_bins, labels=antiguedad_labels, 
    right=True, include_lowest=True)

次に、収入をカテゴリ変数に変形し、収入カテゴリを機能に組み合わせることができるようになります。

renta_bins = [0, 1, 20000, 40000, 60000, 80000, 100000, 120000, 140000, 160000, 180000, 200000, 220000, 240000, 
              270000, 300000, 330000, 360000, 400000, 440000, 480000, 3e7]
renta_labels = pd.Series(range(1,22))
df1['renta_cat'] = pd.cut(df1['renta'], renta_bins, labels=renta_labels, right=True, include_lowest=True)

# delete these three columns as they are not needed anymore
df1.drop(["age", "antiguedad", "renta"], axis = 1, inplace = True) 
df1.iloc[:, 0:41] = df1.iloc[:, 0:40].fillna(999) # Fill in missing values with 999
# Train-Test Split
test = df1[df1["fecha_dato"] == dates[0]] # extract last month data for testing
df1 = df1[df1["fecha_dato"] != dates[0]] # extract the remaining months for training
df1 = sklearn.utils.shuffle(df1)  # sparse the data in a consistent way

全てのカラムの型をstringに変更します。

for k in test.loc[:, "ind_cco_fin_ult1_2":"ind_recibo_ult1_3"]:
    test[k] = test[k].astype(str) 

test = pd.get_dummies(test) # dummycode all features of the test data

get_dummies関数を使用するとデータフレームの次数が増え、全データに対して適用すると非常に大きなメモリを使用します。十分なメモリを搭載していない場合はエラーになるかもしれません。

これを解決するために、データのサブセットを作成します。

商品追加時の購入予測

データ全てに対して関数を実行する代わりに、各製品における2つのクラスのサブセットに対して、 get_dummies 関数を実行できるようにします。

# We will reorder the products from the most added to the least added 
# Any products with less than 10 dditions per month will be excluded
# Below are the products and the index of the best performing model for each product 

products = [['ind_recibo_ult1', 4], ['ind_nom_pens_ult1', 4], ['ind_nomina_ult1', 3], ['ind_tjcr_fin_ult1', 5],
        ['ind_cco_fin_ult1', 3], ['ind_ecue_fin_ult1', 4], ['ind_cno_fin_ult1', 5], ['ind_reca_fin_ult1', 5], 
        ['ind_ctop_fin_ult1', 7], ['ind_ctma_fin_ult1', 7], ['ind_valo_fin_ult1', 6], ['ind_ctpp_fin_ult1', 0], 
        ['ind_fond_fin_ult1', 6], ['ind_dela_fin_ult1', 1], ['ind_plan_fin_ult1', 2], ['ind_pres_fin_ult1', 2], 
              ['ind_viv_fin_ult1', 3], ['ind_cder_fin_ult1', 0]]
for prod in products:
    # the data has two classes (1 , 0)
    # we will take a subset of data from both classes 
    df3 = df1[df1[prod[0]] == 1].head(3000) # a subset of class 1
    s = len(df3)
    df4 = df1[df1[prod[0]] == 0].head(5) # a subset of class 0
    df = df3.append(df4) # merge the data

    # change datatype of all columns to strings in order to be dummycoded
    for k in df.loc[:, "ind_cco_fin_ult1_2":"ind_recibo_ult1_3"]:
        df[k] = df[k].astype(str) 

    # run the dummycode function which return every unique entry into a feature
    df = pd.get_dummies(df) 

    print()
    print("Classification accuracy of future product additions:")
    print("The best ranked 150 features for each product were used")

    # The models to be used
    models = []
    model1 = GradientBoostingClassifier()
    models.append(('Gradient Boosting Classifier', model1))
    model2 = DecisionTreeClassifier()
    models.append(('Decision Tree Classifier', model2))
    model3 = RandomForestClassifier()
    models.append(('Random Forest Classifier', model3))
    model4 = LogisticRegression()
    models.append(('Logistic Regression', model4))
    model5 = GaussianNB()
    models.append(('GaussianNB', model5))
    model6 = ExtraTreesClassifier()
    models.append(('Extra Trees Classifier', model6))
    model7 = BaggingClassifier()
    models.append(('Bagging Classifier', model7))
    model8 = MLPClassifier()
    models.append(('Multi-layer Perceptron Classifier', model8))

    print("product_name:", prod[0])
    product = "14trydf_" + prod[0]
    path = "C:/Kaggle/last_data/subsets/nice_models/for_all_customers/"
    save_to = path + product + ".csv"
    df1 = pd.read_csv(save_to, encoding='latin-1')
    df1.set_index("ncodpers", inplace = True)

    # Condition not to proceed unless we have both classes in the data
    if len(df[prod[0]].unique()) == 2 & len(test[prod[0]].unique()) == 2:

        X = df.loc[:, df.columns[23]:].values 
        Y = df.loc[:, prod[0]].values 

        add = [] # a list to hold the selected features

        # Feature extraction
        # Since products are not included in feature extraction, df dimensions is shrinked
        # df2 were created to store the new dataframe with the new dimensions 
        df2 = df.loc[:, df.columns[23]:].copy() 
        model = LogisticRegression()
        rfe = RFE(model, 150) # 150 is number of features to use in the model
        fit = rfe.fit(X, Y)
        item_index = np.where(fit.ranking_==1) # get item index of the best 200 features
        for j in item_index:
            add1 = df2.columns[j] # get feature names by their index
            add = add1.append(add) # contain the selected best 200 features 

        # use only the best selected features in both training and test data
        X_train = df.loc[:, set(test).intersection(add)].values 
        Y_train = df.loc[:, prod[0]].values 

        X_test_2 = test.loc[:, set(test).intersection(add)].values
        Y_test_2 = test.loc[:, prod[0]].values

        model = models[prod[1]][1]
        model.fit(X_train, Y_train) 
        predictions = model.predict(X_test_2) 

        cmtrx = confusion_matrix(Y_test_2, predictions) 
        print(cmtrx) 

        accuracy = accuracy_score(Y_test_2, predictions)
        recall = recall_score(Y_test_2, predictions)
        accuracy = float("{0:.3f}".format(accuracy))
        recall = float("{0:.3f}".format(recall))
        print("Model:", models[prod[1]][0])
        print("accuracy:", accuracy)
        print("recall", recall)
        print() 

        # save the models for later use
        filename = prod[0] + '_model.sav'
        path = 'C:/Kaggle/last_results/models/'
        save_to = path + filename
        dump(model, open(save_to, 'wb')) 

結果は以下のようになります。

    Classification accuracy of future product additions:
    The best ranked 150 features for each product were used
    product_name: ind_recibo_ult1
    [[699253 213199]
     [  1095   8761]]
    Model: GaussianNB
    accuracy: 0.768
    recall 0.889

    product_name: ind_nom_pens_ult1
    [[713130 203802]
     [   599   4777]]
    Model: GaussianNB
    accuracy: 0.778
    recall 0.889

    product_name: ind_nomina_ult1
    [[800259 116691]
     [   914   4444]]
    Model: Logistic Regression
    accuracy: 0.872
    recall 0.829

    product_name: ind_tjcr_fin_ult1
    [[822598  95474]
     [   462   3774]]
    Model: Extra Trees Classifier
    accuracy: 0.896
    recall 0.891

    product_name: ind_cco_fin_ult1
    [[ 50562 868724]
     [     0   3022]]
    Model: Logistic Regression
    accuracy: 0.058
    recall 1.0

    product_name: ind_ecue_fin_ult1
    [[697614 222022]
     [   513   2159]]
    Model: GaussianNB
    accuracy: 0.759
    recall 0.808

    product_name: ind_cno_fin_ult1
    [[837508  82575]
     [   922   1303]]
    Model: Extra Trees Classifier
    accuracy: 0.909
    recall 0.586

    product_name: ind_reca_fin_ult1
    [[837229  84804]
     [   192     83]]
    Model: Extra Trees Classifier
    accuracy: 0.908
    recall 0.302

    product_name: ind_ctop_fin_ult1
    [[645697 276385]
     [    22    204]]
    Model: Multi-layer Perceptron Classifier
    accuracy: 0.7
    recall 0.903

    product_name: ind_ctma_fin_ult1
    [[626012 296118]
     [    21    157]]
    Model: Multi-layer Perceptron Classifier
    accuracy: 0.679
    recall 0.882

    product_name: ind_valo_fin_ult1
    [[898430  23700]
     [    74    104]]
    Model: Bagging Classifier
    accuracy: 0.974
    recall 0.584

    product_name: ind_ctpp_fin_ult1
    [[920351   1826]
     [    95     36]]
    Model: Gradient Boosting Classifier
    accuracy: 0.998
    recall 0.275

    product_name: ind_fond_fin_ult1
    [[905128  17126]
     [    41     13]]
    Model: Bagging Classifier
    accuracy: 0.981
    recall 0.241

    product_name: ind_dela_fin_ult1
    [[875607  46656]
     [    10     35]]
    Model: Decision Tree Classifier
    accuracy: 0.949
    recall 0.778

    product_name: ind_plan_fin_ult1
    [[798377 123911]
     [     8     12]]
    Model: Random Forest Classifier
    accuracy: 0.866
    recall 0.6

    product_name: ind_pres_fin_ult1
    [[871137  51164]
     [     2      5]]
    Model: Random Forest Classifier
    accuracy: 0.945
    recall 0.714

    product_name: ind_viv_fin_ult1
    [[914786   7515]
     [     1      6]]
    Model: Logistic Regression
    accuracy: 0.992
    recall 0.857

    product_name: ind_cder_fin_ult1
    [[921902    401]
     [     2      3]]
    Model: Gradient Boosting Classifier
    accuracy: 1.0
    recall 0.6

ビジネスに関する考察

リコメンドプログラムの目的は、次にリリースされる商品に対する顧客を発見することです。一般的にはリコメンドプログラムには2種類の問題があります。ひとつは本来潜在的な顧客が顧客ではないと判断されること。もうひとつはその逆で、本来顧客とならない人が、あたかも顧客の候補として扱われることです。しかもある(商品に対するリコメンドロジックである)クラスの精度を向上させていくと、結果的に他のクラスの精度を低下させてしまうこともあるのです。最終的にはこれらの精度の問題はビジネスにおける判断に委ねられることになります。(ひとつのクラスの精度を突き詰めた結果)もうひとつのクラスの精度が10%になってしまっては意味がありません。このようなケースの場合、厳密な意味での「精度(Accuracy)」だけを使用することは適切ではありません。統計における用語である、「再現率(Recall)」を合わせて使用することが良いでしょう。

モデルの精度を向上させる方法は一般的にいくつかあります。もっとも一般的なアプローチは特徴選択(Feature selection)、ハイパーパラメータ・チューニング、アンサンブル学習です。例えば商品について最もよいランクの異なる特徴("ind_recibo_ult1")を選択する場合、特徴の選択数を増やすほどより推定購入顧客に関する精度が向上していきます。下のグラフの "Recall(赤色)" が増えていることがわかると思います。

下のグラフは上位10個から450個までの特徴を選択し、2つのクラスの予測性能がどのように変化するかを示しています。最初10個の特徴を選択した状態では Recall の値は低くとどまっていますが、より多くの特徴を選択するごとに向上し、150個の特徴を選択した時点で Recall の値が安定することがわかると思います。

df = pd.read_csv("OneDrive/Feature_selection_2.csv") # import the result of feature selection example

ax = sns.pointplot(x = 'Num_Features',y='Accuracy', color = "blue", data = df)
sns.pointplot(x = 'Num_Features', y = 'Recall', data = df, color="red", ax = ax) 

ax.set(xlabel='Number of Best Ranked Features', ylabel='Accuracy(Blue) & Recall(Red)')
plt.show()

まとめ

この記事では「次の月に発売される商品を購入するか、しないか」について予測してみました。全ての商品で最高の予測パフォーマンスを出すことができる単一のモデルは存在しないため、商品ごとに最高の予測パフォーマンスを出すことができるモデルを使用しました。

異なる商品には顧客の選択を後押しするような異なる特徴があります。時には年齢が重要な役割を果たし、特には顧客のセグメンテーションや収入が重要な役割を果たすこともあります。そのため様々な異なる特徴が顧客を特定の商品の選択へと導くのです。

この記事では特徴選択を使って最も高いランクを示す特徴を、商品ごとに選択しました。私はそれぞれの商品に対して最も高い150の特徴を使用しましたが、ここでは簡単にするために記事内で全てを扱うことはしませんでした。従って商品ごとに最も適する特徴数を選択することをおすすめします。いくつかの商品ではわずか2,3個の特徴で異存変数(予測変数)のトレンドを捉えることができますが、何百個の特徴を使用しなければ異存変数のトレンドを捉えることができない商品もあります。それゆえ、商品ごとに最善の特徴数を注意深く選択する必要があります。

この記事では予測性能を最大化するための特徴の取り扱いについてフォーカスを当てました。もし機会があれば、モデルのパラメータチューニングについても別の記事で取り上げることができるといいでしょう。

CL LAB Mail Magazine

CL LABの情報を逃さずチェックしよう!

メールアドレスを登録すると記事が投稿されるとメールで通知します。

メールアドレス: 登録

※登録後メールに記載しているリンクをクリックして認証してください。

Related post

新規CTA