
こんにちは!コンサルティングサービス本部の野村です。
今回は機械学習の自動化ツールであるfeaturetoolsとTPOTを使って
SIGNATEのお弁当の需要予測をしてみたのでご紹介したいと思います。
SIGNATEとは
機械学習のコンペティションサイト(企業や政府がコンペ形式で課題を投稿し
賞金をかけて最適モデルを競い合うプラットフォーム)ではKaggleが有名ですが
そちらの日本版になります。
日本の様々な企業や政府からのコンペティションが既に投稿されており、
練習問題として機械学習のためのサンプル問題もいくつか掲載されています。
今回はその中からお弁当の需要予測にトライしてみたいと思います。
内容はこんな感じです。
千代田区四番町のとある会社のカフェフロアで販売されているお弁当の販売数を予測するモデルを作成していただきます。
お弁当の売上向上・廃棄削減のためには、正確な需要予測に基づき生産計画を立てる必要があります。
今回のコンペでは、曜日やメニュー等の複数の変数から最適なお弁当の量を推測していただき、 お弁当屋さんと、その利用者、そして環境に貢献していただきます。
環境 ~Google Colaboratory~
実施環境はGoogle Colabを利用しています。
Jupyter Notebookを無償で利用できるクラウドサービスで、機械学習でよく使われる主なパッケージが最初から入っていて
すぐに機械学習を試してみることができるので大変便利です。さらにGPUやTPUまでも無償で利用できます。
ただし90分毎にセッションが切れ、12時間でインスタンスがリセットされるので
Google Driveを利用するなどの工夫が必要です。
やってみる
では実際にやっていきたいと思います。
初期準備
まずはSIGNATEから対象のデータ(sample.csv、test.csv、train.csv)をDLし、Google Driveに格納しておきます。
次に、ColabでGoogle Driveをマウントします。
from google.colab import drive
drive.mount('/content/drive')
# データの確認
!ls "/content/drive/My Drive/指定フォルダ"
# ライブラリのインポート
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
import seaborn as sns
sns.set(font="IPAGothic",style="white")
from sklearn.linear_model import LinearRegression
from sklearn.feature_selection import RFE
from sklearn.metrics import mean_squared_error as MSE
from sklearn.ensemble import RandomForestRegressor
# データの読み込み
train = pd.read_csv('./drive/My Drive/指定フォルダ/train.csv',index_col=['datetime'])
test = pd.read_csv('./drive/My Drive/指定フォルダ/test.csv',index_col=['datetime'])
sample = pd.read_csv('./drive/My Drive/指定フォルダ/sample.csv', header=None)
print(train.shape,test.shape,sample.shape)
今回の対象データセットの内容はこんな感じです。
カラム | ヘッダ名称 | データ型 | 説明 |
---|---|---|---|
0 | datetid | datetime | インデックスとして使用する日付(yyyy-m-d) |
1 | y | int | 販売数(目的変数) |
2 | week | char | 曜日(月~金) |
3 | soldout | boolean | 完売フラグ(0:完売せず、1:完売) |
4 | name | varchar | メインメニュー |
5 | kcal | int | おかずのカロリー(kcal)欠損有り |
6 | remarks | varchar | 特記事項 |
7 | event | varchar | 13時開始お弁当持ち込み可の社内イベント |
8 | payday | boolean | 給料日フラグ(1:給料日) |
9 | weather | varchar | 天気 |
10 | precipitation | float | 降水量。ない場合は “–” |
11 | temperature | float | 気温 |
# データ内容の確認
train.head()
# 統計量の確認
train.describe()
前処理
データの前処理をしていきます。
# 訓練データとテストデータを結合
train['t_flg'] = 1
test['t_flg'] = 0
all_train = pd.concat([train,test], axis = 0, sort = False)
# 欠損値を確認
all_train.info()
欠損値を補完します。
# kcal : 平均値で埋める
all_train['kcal'].describe()
all_train['kcal'] = all_train['kcal'].fillna(all_train['kcal'].mean())
# remarks : ”なし”で埋める
all_train['remarks'] = all_train['remarks'].fillna('なし')
# event : ”なし”で埋める
all_train['event'] = all_train['event'].fillna('なし')
# payday : 0で埋める
all_train['payday'] = all_train['payday'].fillna(0)
# 再度、欠損値の確認
all_train.info()
all_train.head()
カテゴリ変数の内容を確認します。
all_train['week'].unique()
all_train['name'].unique()
all_train['remarks'].unique()
all_train['event'].unique()
all_train['weather'].unique()
all_train['precipitation'].unique()
カテゴリ変数をダミー変数化します。(nameは種類が多いので一旦外す)
ctg_col = ['week','remarks','event','weather','precipitation']
all_train_ctg = pd.get_dummies(all_train, columns = ctg_col, drop_first=False)
all_train_ctg.info()
# トレーニング用データの準備
drop_col = ['y','name','t_flg']
X_train = all_train_ctg[all_train_ctg['t_flg']==1].drop(drop_col,axis=1)
y_train = all_train_ctg[all_train_ctg['t_flg']==1]['y'].copy()
ランダムフォレストで特徴選択をしてモデル作成
まずはランダムフォレストを使って特徴選択し、線形回帰でモデルを作成してみたいと思います。
# RandomForestのパラメータ設定
randomforest = RandomForestRegressor(n_estimators=100,max_depth=4,random_state=777)
randomforest.fit(X_train,y_train)
# 重要度が高い順にソートして表示
features = X_train.columns
importances = randomforest.feature_importances_
print(sorted(zip(map(lambda x: round(x, 2), randomforest.feature_importances_), features),
reverse=True))
重要度の高い、temperature、kcal、remarks_お楽しみメニュー、をグラフで確認してみます。
# temperature(気温)を散布図で確認
sns.jointplot(x='temperature',y='y',data=train)
# kcal(カロリー)を散布図で確認
sns.jointplot(x='kcal',y='y',data=train)
# remarks(特記事項)を箱ひげ図で確認
fig, ax = plt.subplots(1,1,figsize=(12,7))
sns.boxplot(x='remarks',y='y',data=train)
ax.set_xticklabels(ax.get_xticklabels(),rotation=30)
日本語が豆腐になってしまいました。。
フォントをインストールする必要があるようです。
# フォントのインストール
!apt-get -y install fonts-ipafont-gothic
# キャッシュの確認
import matplotlib
matplotlib.get_cachedir()
# キャッシュの削除
!rm -r /root/.cache/matplotlib/
ラインタイムを再起動し、再度表示させてみます。
fig, ax = plt.subplots(1,1,figsize=(12,7))
sns.boxplot(x='remarks',y='y',data=train)
ax.set_xticklabels(ax.get_xticklabels(),rotation=30)
うまく表示できました。
お楽しみメニューは特に高い相関関係がありそうです。
ということで、’temperature’、’kcal’、’remarks_お楽しみメニュー’を特徴量として線形回帰で学習させてみます。
X_train = X_train[['temperature','kcal','remarks_お楽しみメニュー']]
# 線形回帰モデル作成
linear = LinearRegression()
linear.fit(X_train,y_train)
# 予測
pred = linear.predict(X_train)
# 実際の値(y_train)と予測値(pred)のRMSE(平均二乗誤差)を出す
print("RMSE",np.sqrt(MSE(y_train,pred)))
# 線形回帰の予測値と実績値のグラフ
p = pd.DataFrame({"actual":y_train,"pred":pred})
p.plot(figsize=(13,3))
featuretoolsとTPOTを利用してモデル作成
次に特徴量エンジニアリングにfeaturetools、アルゴリズム選定にTPOTを利用してモデルを作成してみたいと思います。
まずはfeaturetoolsから。
# ライブラリのインポート
import featuretools as ft
# インデックスのリセット
all_train_ctg = all_train_ctg.reset_index()
# Entity Setの作成
es = ft.EntitySet(id='entityset')
# Entityの追加
es = es.entity_from_dataframe(entity_id='train',dataframe=all_train_ctg,index='index')
# 生成する特徴量の指定
trans_primitives = ["year", "month","day","weekday"]
# 特徴量の生成
feature_matrix, features_defs = ft.dfs(entityset=es,
target_entity="train",
trans_primitives=trans_primitives,
max_depth=1
)
# 特徴量の確認
feature_matrix.info()
元のdatetimeに対して新しい特徴量のYEAR、MONTH、DAY、WEEKDAYが自動的に追加されました。
続いてTPOTを使って、最適なアルゴリズムの選定と、ハイパーパラメータのチューニングを自動的に行ってみます。
# パッケージのインストール
!pip install tpot
# ライブラリのインポート
from tpot import TPOTRegressor
# TPOTのパラメータを設定
tpot = TPOTRegressor(generations=10, population_size=50, verbosity=2, random_state=700)
# トレーニング用データの準備
drop_col = ['y','name','t_flg']
X_train = feature_matrix[feature_matrix['t_flg']==1].drop(drop_col,axis=1)
y_train = feature_matrix[feature_matrix['t_flg']==1]['y'].copy()
tpot.fit(X_train,y_train)
2~3分程かかりましたが、最適なアルゴリズムとしてXGBoostが選定され、
そのハイパーパラメータも自動的にチューニングされたうえでモデルが作成されました。
# 予測
pred = tpot.predict(X_train)
# 実際の値(y_train)と予測値(pred)のRMSEを出す
print("RMSE",np.sqrt(MSE(y_train,pred)))
# 線形回帰の予測値と実績値のグラフ
p = pd.DataFrame({"actual":y_train,"pred":pred})
p.plot(figsize=(13,3))
featuretoolsとTPOTを使った時の方が
RMSEが9.07になり、グラフ的にもかなり改善されているのがわかります。
まとめ
以上、簡単ではありましたが、機械学習の自動化ツールであるfeaturetoolsとTPOTを使って
SIGNATEのお弁当の需要予測をやってみました。
チュートリアルでは関連性の低いデータをカットしたり、EDAによる特徴量エンジニアリング、
アンサンブル学習やcross validationでの検証等を行っており
実際の現場でさらに精度を上げていくためには、機械学習に関する様々な経験や
ビジネスドメインの知識を増やしていく必要あると感じました。
日々精進したいと思います!