※Kaggleのその他の記事はこちら
・Kaggleから学ぶ最新の機械学習実践Tips2018
・「KaggleのMercari Challengeでdeeplearningを駆使して上位10%(Bronze)入り」
データサイエンティストの業務は、
華やかなPythonでの機械学習よりも、
HiveQLなどでの地道なデータ収集・データ集計に時間が割かれるのでは、と思います。
※もちろん、機械学習とデータ集計、どちらが華やかなのかには異論があり、
芸術的なHiveQLを目のあたりにして、嗚咽を漏らすことは、ままあるとして。
日々の業務がデータ集計の割合が多くとも、最終的な成果は、機械学習の出来に左右されることが多く、
コンスタントに各手法の扱いに慣れておくことが必要です。
※単純に使える、ということよりも、各手法の長所・短所を把握し、
実践的なオルタナティブを持つ、ということ。
そこで、機械学習手法のトライアルの場として、うってつけの、
データ分析コンペサイト、Kaggleにおいて、
House Pricesチュートリアルにチャレンジした過程を記します。
手法については特に今回使用した、決定木系の進化に焦点を当てます。
※Kaggleについては過去記事ご参照くださいませ。
※チャレンジした時期は、本執筆時期より前の、2017年1月のため、チュートリアル参加者数、順位などは、
2017年1月時点のものとなります。(すなわち、ブログの執筆を半年間サボっていたことになります汗)
House Pricesチュートリアルとは、米国アイオワ州のエイムズという都市の物件価格を予測する問題となります。
データは、”築年数”、”設備”、”広さ”、”エリア”、”ガレージに入る車の数”など79個の変数および物件価格による
1460戸の学習データが与えられます。
そのデータをもとにモデルを作成し、1459戸の家の価格を予測します。
判定基準は平均二乗誤差(RMSE)でして、要は、正解データとの差が小さいほど上位となります。
参加者数は2017年1月時点で3,716人となり、Kaggleのチュートリアルではtitanicに次いで活況となります。
実際に投稿してみると実感しますが、参加者のレベルは、titanicより、かなり高いです。
※手応えのある高精度の予測をポストしても、titanicより順位が上がりづらい感覚です。
※ベンチマーク(ランダムフォレスト)のRMSEが0.409に対して、
上位50%でのRMSEが0.133となります。それゆえ、上位50%に入るのも、なかなか一苦労です。
では取り組んでいきましょう。
まずは、テストデータをインポートして、今回予測する”アイオワ州エイムズ市の物件価格”が、
どのような分布になっているかを確認しておきましょう。
※学習データに異常値が含まれていないかの確認、および、
日本人には肌感が持ちづらい、エイムズ市の物件価格帯を把握しておくためとなります。
1 2 3 4 5 6 7 8 9 10 11 12 |
import pandas as pd df_train = pd.read_csv('train2.csv') df_train_price = df_train.iloc[:,80] import matplotlib.pyplot as plt import seaborn as sns plt.style.use('ggplot') fig = plt.figure() ax = fig.add_subplot(1,1,1) ax.hist(df_train_price,bins=50) ax.set_ylim(0,200) fig.show() |
13万ドルあたりをピークに、高価格帯の物件にゆるやかに広がって分布しているようです。
※pythonでは、df_train_priceというdataframeに対して、”df_train_price.describe()”で詳細を確認できますが、
平均は180,151ドルとなります。
さてここから各種機械学習手法を適用してモデルを作成しておきたいところですが、
本データはカテゴリ変数が多く含まれております。
その中には名義尺度(エリア名など)だけではなく、順序尺度(”Ex”:Excellent、”Gd”:Good、、などの段階評価)もあります。
まずは変数をのちのち処理しやすいように、数値に変換しておきます。
単純に、BsmtQualという変数が”Ex”であるかどうか、”Gd”であるかどうかを、
0,1のダミー変数に変換することもできますが、
それだと変数が無駄に増え、のちの予測精度に悪影響を与えてしまいます。
そこで、たとえば”Ex”,”Gd”,”TA”,”Fa”,”Po”の5段階評価であれば、
それぞれ、5,4,3,2,1という値に置き換えることが考えられます。
しかし、ある変数が”Ex”であることと”Gd”であることの差は小さいものの、
“Gd”と”TA”である差は非常に大きいかもしれません。
今回は、少し手間はかかりますが、各変数(たとえば”BsmtQual”)に対して、
“Ex”、”Gd”、”TA”などの値ごとの平均物件価格を算出し、
その値に置き換える、ということをしました。
平均値を求めるプログラムは冗長なので割愛するとして、例えば下記のような関数を作っておき、
各変数を数値(平均物件価格)に置換していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def convert_from_sl(sl_df,num_df_name): num_df = pd.DataFrame(columns={num_df_name}) for sl in sl_df: if isinstance(sl, str): if ('ExterQual' in num_df_name): if 'Ex' in sl: df = pd.DataFrame([[367361]],columns={num_df_name}) elif 'Gd' in sl: df = pd.DataFrame([[231634]],columns={num_df_name}) elif 'TA' in sl: df = pd.DataFrame([[144341]],columns={num_df_name}) elif 'Fa' in sl: df = pd.DataFrame([[87985]],columns={num_df_name}) (...略...) |
1 2 3 4 5 |
ExterQual_dum = convert_from_sl(df_train['ExterQual'],'ExterQual') ExterCond_dum = convert_from_sl(df_train['ExterCond'],'ExterCond') BsmtQual_dum = convert_from_sl(df_train['BsmtQual'],'BsmtQual') BsmtCond_dum = convert_from_sl(df_train['BsmtCond'],'BsmtCond') (...略...) |
さらに、欠損値が多い変数はdropしておきます。
※前述の処理を終えたdataframeを、新たにdf_train2としておいた。
※その他、細かい前処理は割愛します。
※ここまでの処理は、学習データだけではなく、予測するテストデータにも同様にかけておきます。
1 2 3 4 5 6 |
df_train2 = df_train2.drop('Alley',axis=1) df_train2 = df_train2.drop('MasVnrArea',axis=1) df_train2 = df_train2.drop('GarageYrBlt',axis=1) df_train_na = df_train2.dropna() df_train_x=df_train_na.iloc[:,1:77] df_train_y=df_train_na.loc[:,['SalePrice']] |
さて、ここまででようやく機械学習をするための準備が整いました。
近年、Kaggleでは、XGBoostというboosting treeの機械学習手法が多く用いられており、
上位入賞には必須のスキルとなっております。
boosting treeとは何か、について超ざっくり言いますと、
ランダムフォレストが決定木を並列に作って多数決をとるのに対して、
boosting treeは、逐次的に決定木を作り、各決定木で誤った分類に重みをかけて、
次の決定木では、その誤った分類を訂正できるように作っていく、ことを繰り返す手法となります。
※より詳しい(正しい)、boosting treeとは何か、については各種ブログを参照いただくか、
個人的にはワシントン大学の“Introduction to Boosted Tree”というpdfが
大変わかりやすかったです。
今回のHousePricesでもいくつかXGBoostを用いた投稿が見られます。
しかし、boosting tree系の実装は、XGBoostが最新ではありません。
その後、FastBDT、LightGBMという実装も開発されております。
LightGBMは、またまた超ざっくり言いますと、各決定木の分割を、
(XGBoostなどよりも)ある程度ざっくり行うことで、より汎用性能を向上させる、
というものだと理解しております。
ちなみに、、本チャレンジでは用いておりませんが、
tree系の、さらなる最新手法として、“Deep Forest”というものが、
2017年2月に提案されております。
※発表当時、twitterでバズっていた記憶があります(”ディープラーニングを超えた”とか”ネーミングがいいよね”とか)
※またまたまた超ざっくりいうと、カスケード構造というニューラルネット的な多段階構造を構築し、
各層ごとに、各決定木でもとめた結果と元の入力を組み合わせたものを次の層の入力にする、ということを繰り返すものです。
今回はXGBoostとLightGBMを組み合わせることになります。
まずはXGBoostを用いてみます。
インストールが完了したあとは、通常の各種手法と同様に下記のように用いることができます。
※k-foldに関する記述(後述)がありますが、要は1行目のimportと、最後の3行のような記述をすれば動きます。
1 2 3 4 5 6 7 8 9 10 11 |
import xgboost as xgb from sklearn.cross_validation import StratifiedKFold from sklearn.cross_validation import train_test_split X_train,X_test,y_train,y_test = train_test_split(df_train_x,df_train_y,test_size=0.1,random_state=0) kfold = StratifiedKFold(y=y_train['SalePrice'].as_matrix(),n_folds=10,random_state=1) for k,(train,test) in enumerate(kfold): mod = xgb.XGBRegressor(max_depth=5,min_child_weight=1,gamma=0,subsample=0.2,colsample_bytree=0.2,learning_rate=0.1) mod.fit(X_train.as_matrix()[train], cv_train_y[train]) y_test_pred_xgb = mod.predict(X_train.as_matrix()[test]) |
あとは、上記の各パラメータの値の最適値をチューニングしていくことになります。
ちなみに、XGBoostをクロスバリデーションで最適化することで、
RMSE:0.12655、順位:1404位(上位39%)までは、いけました。
さて、ここでXGBoostの推計について、
学習データをさらにk-foldで分割し、
既知のデータ(学習データのうちモデル作成に用いたデータ)、未知のデータ(学習データのうち検証用に用いたデータ)、
それぞれに適用した結果を見てみます。
横軸が正解、縦軸が予測、の物件価格としたときのグラフが下記となります。
左側の図はモデル作成用のデータに当てはめた時の様子ですが、
綺麗すぎる直線、すなわち過学習が起きているように思えます。
一方、その作成したモデルを検証用データに当てはめた時の様子が右図となります。
概ね制度は良いのですが、時々大外れしております。
過学習が起きているときの対策常套手段として下記3つが考えられます。
(1) 学習データから異常値を取り除く。(異様に庭やガレージが広いなどのサンプルを除去)
(2) クロスバリデーションをかける。
(3) 複数の手法を組み合わせる。
(1)については、もう、地道にチェックしていくことが必要です。
(2)については、k-foldを用いることで簡単に実装できます。
※上記記述済み。学習データを分割して、一方を学習データ、一方を検証データとすることを繰り返す検証方法。
今回は、特に(3)について述べます。
XGBoostの過学習を避けるために、より汎化性能が高い(と言われている)LightGBMと
アンサンブルすることにします。
LightGBMはインストール自体が、やや大変ですが、
インストールしたあとは、下記のように通常の機械学習手法と同じ形式で用いることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import lightgbm as lgb lgb_train = lgb.Dataset(np.array(X_train),np.array(y_train['SalePrice'])) lgb_eval = lgb.Dataset(np.array(X_test),np.array(y_test['SalePrice']),reference=lgb_train) params = { 'task': 'train', 'boosting_type': 'gbdt', 'objective': 'regression', 'metric': {'l2'}, 'num_leaves': 128, 'learning_rate': 0.01, 'num_iterations':100, 'feature_fraction': 0.38, 'bagging_fraction': 0.68, 'bagging_freq': 5, 'verbose': 0 } gbm = lgb.train(params, lgb_train, num_boost_round=1500, valid_sets=lgb_eval, early_stopping_rounds=15) y_test_pred_lgb = gbm.predict(np.array(X_test), num_iteration=gbm.best_iteration) |
XGBoostとLightGBMをどのように組み合わせるか、ですが、
今回はXGBoostを2つ、LightGBMを1つの計3つを、下記のように組み合わせました。
組み合わせ方は、基本はXGBoost1の結果に従うものの、
たまにXGBoost1が、汎化性能が高いXGBoost2やLightGBMの結果と大きく外れる時は、
LightGBMの結果を用いる、ということをしました。
※これは結果論でしかないのですが、上記のような組み合わせ方が、
単純に各手法の平均などをとるよりも精度が高かったということになります。
上記の手法を用いた最終結果が下記となります。
RMSE:0.12030
順位:778位(上位21%)
あと少し何か改善できたら、一気に順位が上がりそうなポジションですね。
ここからは機械学習手法のパラメータチューニングというよりは、
前処理などに時間をかけることでもっと精度を上げることができると思います。
※例えば、欠損値がある場合は、
単純に全体の平均値から補完するのではなく、エリアごとの平均値から補完することが考えられます。
(物件価格はエリアによる影響が大きいと思われるため)
以上、細かい箇所は、はしょりながらとなりましたが、HousePricesへのチャレンジでした。
さて、ちなみにデータサイエンティストにとって、
データ収集や機械学習手法よりも、
ストラテジックプラニングや思想が大事だという意見もありますが、
個人的には、テクニカルな手法を知ることで、思想(思考)が深まる(こともある)、というスタンスです。
という現実逃避を経て、
エラーが吐き出されたHiveQLの結果に、下唇を噛みながら、
データ集計業務に戻るとします。
※Kaggleのその他の記事はこちら
・Kaggleから学ぶ最新の機械学習実践Tips2018
・「KaggleのMercari Challengeでdeeplearningを駆使して上位10%(Bronze)入り」