LightGBMを使ってレストランの売上を推定|Kaggle過去問によるサンプルコードつき

Python
Python

今回は、私が過去に習作として取り組んだデータセットから、Pythonによる機械学習について例を示していきたいと思います。文末に、私が作ったコードを掲載しておきます。

また、過去当ブログではPythonによる機械学習について学習方法やおすすめの書籍も紹介しています。

使用するデータ

データはKaggleというサイトの「Restaurant Revenue Prediction」というコンペのデータを使っていきましょう。レストランの売上という連続値を推定する回帰モデルになります。このコンペはFeatured、つまり過去実際に競争が行われて賞金が出たコンペです。もう期間は終わっているのでランキングは変動しませんが、スコアそのものは出るのでトレーニングにはうってつけです。

まずはデータ全体を見てみよう

まずはデータを見てみましょう。今回のデータは欠損が無いので、変数名とデータ型をとります。ちなみに今回のデータ、訓練データが137件に対しテストデータが100,000件あります。訓練データの不足は典型的な過学習のもとなのでこれをうまくさばくことが必要になりそうです。

# サンプルからデータ型を調べる関数
def Datatype_table(df):
        list_type = df.dtypes #データ型
        Datatype_table = pd.concat([list_type], axis = 1)
        Datatype_table_len = Datatype_table.rename(columns = {0:'データ型'})
        return Datatype_table_len
    
Datatype_table(train)

IDとRevenueはそれぞれキーと目的変数なので除外します。P1~P37は店舗の立地や人口動態とのことです。個々に意味を推測することは難しいのでいったん放置で。City、City Group、Typeは店舗のある町、町の大小、フランチャイズ等の経営形態です。今回はダミー変数に置き換えましょう。

Open Dateはお店が開いた日時ですが、このままでは文字列扱いなので日付データに置き換えましょう。また、ここでひと工夫して、日付データから現在までの営業日数を特徴量に加え入れます。また、この時点で訓練データとテストデータをラベルしてマージしておきます。

train['WhatIsData'] = 'Train'
test['WhatIsData'] = 'Test'
test['revenue'] = 9999999999
alldata = pd.concat([train,test],axis=0).reset_index(drop=True)

alldata["Open Date"] = pd.to_datetime(alldata["Open Date"])
alldata["Year"] = alldata["Open Date"].apply(lambda x:x.year)
alldata["Month"] = alldata["Open Date"].apply(lambda x:x.month)
alldata["Day"] = alldata["Open Date"].apply(lambda x:x.day)
alldata["kijun"] = "2015-04-27"
alldata["kijun"] = pd.to_datetime(alldata["kijun"])
alldata["BusinessPeriod"] = (alldata["kijun"] - alldata["Open Date"]).apply(lambda x: x.days)

alldata = alldata.drop('Open Date', axis=1)
alldata = alldata.drop('kijun', axis=1)

基準日は明示されていなかったため、Kaggleコンペに登録された日を仮に入れてあります。変換した後のOpen Dateと基準日はノイズになるので削除しておきましょう。

モデルにフィッティングしてみる

さて、前処理を終えましたのでフィッティングしてみます。今回は回帰モデルのため、①RandomForestRegresser、②Lasso回帰、③ElasticNet ④ElasticNetでグリッドサーチとクロスバリデーション、の4種類を試してみましょう。なかなか特徴的な結果になりましたので、それぞれのスコアを取得して表示もしてみましょう。

acc_dic = {}

X_train, X_test, y_train, y_test = train_test_split(
    x_, y_, random_state=0, train_size=0.7,shuffle=False)

# RandomForestRegressorによる予測
forest = RandomForestRegressor().fit(X_train, y_train)
prediction_rf = np.exp(forest.predict(test_feature))
acc_forest = forest.score(X_train, y_train)
acc_dic.update(model_forest = round(acc_forest,3))

# lasso回帰による予測
lasso = Lasso().fit(X_train, y_train)
prediction_lasso = np.exp(lasso.predict(test_feature))
acc_lasso = lasso.score(X_train, y_train)
acc_dic.update(model_lasso = round(acc_lasso,3))

# ElasticNetによる予測
En = ElasticNet().fit(X_train, y_train)
prediction_en = np.exp(En.predict(test_feature))
acc_ElasticNet = En.score(X_train, y_train)
acc_dic.update(model_ElasticNet = round(acc_ElasticNet,3))

# ElasticNetによるパラメータチューニング
parameters = {
        'alpha'      : [0.001, 0.01, 0.1, 1, 10, 100],
        'l1_ratio'   : [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
}

En2 = GridSearchCV(ElasticNet(), parameters)
En2.fit(X_train, y_train)
prediction_en2 = np.exp(En.predict(test_feature))
acc_ElasticNet_Gs = En2.score(X_train, y_train)
acc_dic.update(model_ElasticNet_Gs = round(acc_ElasticNet_Gs,3))

# 各モデルの訓練データに対する精度をDataFrame化
Acc = pd.DataFrame([], columns=acc_dic.keys())
dict_array = []
for i in acc_dic.items():
        dict_array.append(acc_dic)
Acc = pd.concat([Acc, pd.DataFrame.from_dict(dict_array)]).T
Acc[0]

分割した訓練データに対する精度がこちらになります。ランダムフォレスト以外極めて悪いですね。ランダムフォレストはもともと弱い学習機によるアンサンブル学習なのでノイズや過学習に強い傾向があります。しかしながら、ランダムフォレストでも精度は0.85程度と、高スコアを狙うには心もとないですね。なので、今回はまた別のモデルを試してみましょう。

lightGBMを使ってみよう

さて、ここでlightGBMというモデルを使ってみましょう。 lightGBMは2017年にMicrosoftから発表されたフレームワークで、Gradient Boostingという手法を採用しています。GBMはGradient Boosting Methodの略称ですね。詳しくは原著に譲りますが、ランダムフォレストのようなアンサンブル学習モデルの一種で、学習のたびに二乗誤差を最小化する処理が入るため非常に強力なモデルになっています。Kaggle MasterのBen Gorman氏は投稿でこんな風に述べられています(訳は筆者)

線形回帰モデルがトヨタのカムリだとしたら、Gradient BoostingはUH-60 ブラックホーク戦闘ヘリでしょう

https://www.gormanalysis.com/blog/gradient-boosting-explained/

突然のトヨタへの流れ弾がアツいですね。そんな lightGBMですがインストールはとても簡単です。例によりAnacondaのコマンドを使っていきます。

conda install -c conda-forge lightgbm

通常のconda install lightgbmですと上手くいかない場合がありますので上記方法推奨です。さて、先ほどのデータをlightGBMで学習してみましょう!
※今回なぜか回帰にも関わらず二値分類モデルの方でいいスコアが出てしまいました…。

# lightGBMによる予測
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

# LightGBM parameters
params = {
        'task' : 'train',
        'boosting_type' : 'gbdt',
        'objective' : 'regression',
        'metric' : {'l2'},
        'num_leaves' : 31,
        'learning_rate' : 0.1,
        'feature_fraction' : 0.9,
        'bagging_fraction' : 0.8,
        'bagging_freq': 5,
        'verbose' : 0,
        'n_jobs': 2
}

gbm = lgb.train(params,
            lgb_train,
            num_boost_round=100,
            valid_sets=lgb_eval,
            early_stopping_rounds=10)

prediction_lgb = np.exp(gbm.predict(test_feature))

コードも簡単ですね。lightGBMはPythonによるデータ分析のデファクトスタンダードであるScikit learnとの互換や文法の類似を考慮されていますのでとても書きやすいです。

さて、この結果をKaggleにアップロードしてスコアと(期間中と仮定した)順位を見てみましょう。

今回の私のスコアは1756116.37975でした。期間中であれば結構な好成績ですが、今はランキングが出ませんので順位の確認のみになります。

今回は以上になります。お読みいただきありがとうございました。

受講者満足度も90%以上!【WebCamp】

Appendix:今回紹介したコード

import pandas as pd
import numpy as np
from sklearn.metrics import classification_report
from sklearn import metrics
import matplotlib.pyplot as plt
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import Lasso, ElasticNet
from sklearn.ensemble import RandomForestRegressor
import lightgbm as lgb
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score, mean_squared_error, r2_score
import seaborn as sb

#データを読み込んでマージする
train = pd.read_csv("rest_train.csv")
test = pd.read_csv("rest_test.csv")

acc_dic = {}

train['WhatIsData'] = 'Train'
test['WhatIsData'] = 'Test'
test['revenue'] = 9999999999
alldata = pd.concat([train,test],axis=0).reset_index(drop=True)

alldata["Open Date"] = pd.to_datetime(alldata["Open Date"])
alldata["Year"] = alldata["Open Date"].apply(lambda x:x.year)
alldata["Month"] = alldata["Open Date"].apply(lambda x:x.month)
alldata["Day"] = alldata["Open Date"].apply(lambda x:x.day)
alldata["kijun"] = "2015-04-27"
alldata["kijun"] = pd.to_datetime(alldata["kijun"])
alldata["BusinessPeriod"] = (alldata["kijun"] - alldata["Open Date"]).apply(lambda x: x.days)

alldata = alldata.drop('Open Date', axis=1)
alldata = alldata.drop('kijun', axis=1)

# 訓練データ特徴量をリスト化
cat_cols = alldata.dtypes[alldata.dtypes=='object'].index.tolist()
num_cols = alldata.dtypes[alldata.dtypes!='object'].index.tolist()

other_cols = ['Id','WhatIsData']
# 余計な要素をリストから削除
cat_cols.remove('WhatIsData') #学習データ・テストデータ区別フラグ除去
num_cols.remove('Id') #Id削除

# カテゴリカル変数をダミー化
cat = pd.get_dummies(alldata[cat_cols])

# データ統合
all_data = pd.concat([alldata[other_cols],alldata[num_cols].fillna(0),cat],axis=1)

# plt.hist(np.log(train['revenue']), bins=50)
# plt.hist(train['revenue'], bins=50)

train_ = all_data[all_data['WhatIsData']=='Train'].drop(['WhatIsData','Id'], axis=1).reset_index(drop=True)
test_ = all_data[all_data['WhatIsData']=='Test'].drop(['WhatIsData','revenue'], axis=1).reset_index(drop=True)

x_ = train_.drop('revenue',axis=1)
y_ = train_.loc[:, ['revenue']]
y_ = np.log(y_)
test_feature = test_.drop('Id',axis=1)

X_train, X_test, y_train, y_test = train_test_split(
    x_, y_, random_state=0, train_size=0.7,shuffle=True)

# サンプルから欠損値と割合、データ型を調べる関数
def Missing_table(df):
    null_val = df.isnull().sum()
    # null_val = df.isnull().sum()[train.isnull().sum()>0].sort_values(ascending=False)
    percent = 100 * null_val/len(df)
    # list_type = df.isnull().sum().dtypes #データ型
    Missing_table = pd.concat([null_val, percent], axis = 1)
    missing_table_len = Missing_table.rename(
    columns = {0:'欠損値', 1:'%', 2:'type'})
    return missing_table_len.sort_values(by=['欠損値'], ascending=False)

Missing_table(train)

# サンプルからデータ型を調べる関数
def Datatype_table(df):
        list_type = df.dtypes #データ型
        Datatype_table = pd.concat([list_type], axis = 1)
        Datatype_table_len = Datatype_table.rename(columns = {0:'データ型'})
        return Datatype_table_len
    
Datatype_table(alldata)

# lightGBMによる予測
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

# LightGBM parameters
params = {
        'task' : 'train',
        'boosting_type' : 'gbdt',
        'objective' : 'regression',
        'metric' : {'l2'},
        'num_leaves' : 31,
        'learning_rate' : 0.1,
        'feature_fraction' : 0.9,
        'bagging_fraction' : 0.8,
        'bagging_freq': 5,
        'verbose' : 0,
        'n_jobs': 2
}

gbm = lgb.train(params,
            lgb_train,
            num_boost_round=100,
            valid_sets=lgb_eval,
            early_stopping_rounds=10)

prediction_lgb = np.exp(gbm.predict(test_feature))

# RandomForestRegressorによる予測
forest = RandomForestRegressor().fit(X_train, y_train)
prediction_rf = np.exp(forest.predict(test_feature))

acc_forest = forest.score(X_train, y_train)
acc_dic.update(model_forest = round(acc_forest,3))
print(f"training dataに対しての精度: {forest.score(X_train, y_train):.2}")

# lasso回帰による予測
lasso = Lasso().fit(X_train, y_train)
prediction_lasso = np.exp(lasso.predict(test_feature))

acc_lasso = lasso.score(X_train, y_train)
acc_dic.update(model_lasso = round(acc_lasso,3))
print(f"training dataに対しての精度: {lasso.score(X_train, y_train):.2}")

# ElasticNetによる予測
En = ElasticNet().fit(X_train, y_train)
prediction_en = np.exp(En.predict(test_feature))
print(f"training dataに対しての精度: {En.score(X_train, y_train):.2}")

acc_ElasticNet = En.score(X_train, y_train)
acc_dic.update(model_ElasticNet = round(acc_ElasticNet,3))

# ElasticNetによるパラメータチューニング
parameters = {
        'alpha'      : [0.001, 0.01, 0.1, 1, 10, 100],
        'l1_ratio'   : [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
}

En2 = GridSearchCV(ElasticNet(), parameters)
En2.fit(X_train, y_train)
prediction_en2 = np.exp(En.predict(test_feature))

acc_ElasticNet_Gs = En2.score(X_train, y_train)
acc_dic.update(model_ElasticNet_Gs = round(acc_ElasticNet_Gs,3))
print(f"training dataに対しての精度: {En.score(X_train, y_train):.2}")

# 各モデルの訓練データに対する精度をDataFrame化
Acc = pd.DataFrame([], columns=acc_dic.keys())
dict_array = []
for i in acc_dic.items():
        dict_array.append(acc_dic)
Acc = pd.concat([Acc, pd.DataFrame.from_dict(dict_array)]).T
Acc[0]

# Idを取得
Id = np.array(test["Id"]).astype(int)
# 予測データとIdをデータフレームへ落とし込む
result = pd.DataFrame(prediction_lgb, Id, columns = ["Prediction"])
# csvとして書き出し
result.to_csv("prediction_Restaurant.csv", index_label = ["Id"])

City_unique = alldata["City"].unique()
df_city = pd.DataFrame(City_unique)
# csvとして書き出し
df_city.to_csv("CityList.csv", index_label = ["City_name"])

この記事が気に入ったら
いいね ! しよう

Twitter で

IT企業で働いています。このブログではIT、キャリア、資格などについて発信しています。My opinion is my own.

Huliをフォローする
the Biztech blog

コメント

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