LoginSignup
9
13

More than 3 years have passed since last update.

Kaggle のHousePriceにチャレンジしてみた(上位50%)

Last updated at Posted at 2019-04-26

前回に引き続きkaggleのタイタニックチュートリアルをやっていきました、今回はHouse Priceという住宅価格のチュートリアルです。圧倒的に前回より難易度が上がっていましたが(笑)、一応そこそこのモデルはできたので流れを書いていきます。

ちなみに前回と同じように、モデルを作る際には
AI Academyさんのデータ分析だったり、
https://aiacademy.jp/texts/#data_practice
Pythonで始める機械学習
https://www.oreilly.co.jp/books/9784873117980/
という本を参考にしました。

前回の記事

①データの確認

前回のタイタニックチュートリアルでは特徴量が10個以下だったのに対し、今回は100個近くもあります。もうこの時点で正直うえってなりましたが方針としてはまず欠損値を埋めていきます。

②欠損値を埋める

一つずつ欠損値があるデータを取り出して何を入れていくかの確認をするのはめんどくさいので、まず訓練データから予測値を抜いたものととテストデータを連結した後カテゴリー変数にはNを、数値型変数には0を入れます。

train_df = pd.read_csv(path+"train.csv", header=0)
test_df = pd.read_csv(path+"test.csv", header=0)

##後から訓練データかテストデータか判別できるように
train_df['WhatIsData'] = 'Train'
test_df['WhatIsData'] = 'Test'

##データの結合
drop_train_df = train_df.drop('SalePrice',axis=1)
allData = pd.concat([drop_train_df,test_df],axis=0)

na_col_list = allData.isnull().sum()[allData.isnull().sum()>0].sort_values(ascending=False).index.tolist()
for row in na_col_list:
    if allData[row].dtypes == "float64":
        allData[row][allData[row].isnull()] = 0
    else:
        allData[row][allData[row].isnull()] = "NA"

③ダミー変数化する

カテゴリー変数をダミー化します。普通にやると回帰でなんかエラーが起きてしまったので、全部のデータをint型にします。

##ダミー化する必要ないものをいったん取り除く
dropData = allData[allData.columns[allData.columns != "WhatIsData"]]

data_dummies = pd.get_dummies(dropData)
transdropData = pd.concat([dropData,data_dummies],axis=1)
##型変える
transdropData = transdropData.select_dtypes(include=["float64","int64","uint8"])
transdropData = transdropData.astype('int')

ここまでの作業でX_train、X_testの変換は終わりです。今回はXGboosting

④標準化する

今回は標準化を使用します。0から1の範囲ではなく標準化を使用する理由としては、豪邸などが存在するために、極端な値をとる可能性があると判断したためです。

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
columns = transdropData.columns.values
transdropData_scaled = pd.DataFrame(scaler.fit(transdropData).transform(transdropData),columns=columns)

ここまで訓練データとテストデータをまとめて処理していきましたが、これをまた分割してあげます。

⑤SalePriceのデータの変換

今回このSalePriceのデータを見ていくと、分布を見ると富裕層側のデータが偏っているため、
np.log()を使用することによって、データの形を標準正規分布に直してあげます。

y_train = np.log(train_df["SalePrice"])

ちなみにここまでの処理で参考にしたサイトがこちらです。
https://yolo-kiyoshi.com/2018/12/17/post-1003/

ここまでで前処理は一旦終わりです。

⑥モデルの構築

ここからはモデルを構築していきます。ランダムフォレストやSVCのSCRなども使用しましたが、今回は一番精度が良かったXGboosingを使用します。
GridSearchで
colsample_bytree:[0.5,0.6,0.7,0.8,0.9,1]
max_depth:[3,4,5]
learning_rate:[0.03,0.1,0.3]
という形でグリッドサーチしたところ、max_depth=3,learning_rate=0.1,colsample_bytree=0.6
の時1番スコアが良かったためこれでモデル構築をします。

##日時を指定してファイル名に入れるため
from datetime import datetime
time= datetime.now().strftime("%Y%m%d-%H%M%S")
time
import xgboost as xgb
model = xgb.XGBRegressor(max_depth=3,learning_rate=0.1,colsample_bytree=0.6)
model.fit(X_train,y_train)
predictions = np.exp(model.predict(X_test))

df_result = pd.DataFrame()
df_result['Id'] = test_df['Id']
df_result['SalePrice'] = predictions
df_result.to_csv('result.csv'+time,index=False)
df_result

結果は以下のようになりました
スコア→0.14661
ちなみに順位は上位80パーセントくらいです、とても低いのでここから改善点をググりながら探していきますw

⑦新しい特徴量の追加

今回はTotalSFというトータルの部屋の面積を追加します。今は1階2階などそれぞれの階ごとの面積しかありません。冷静に考えてもらえれば、これがむちゃくちゃ家の価格を左右するのは当たり前ですよねw
また、ランダムフォレストで特徴量エンジニアリングしてもらうと明らかになるのですが、TotalSFとOverallQual(完成したクオリティ)の二つがぶっちぎりに高い影響力を持つ変数となるので、この二つを掛け合わせた変数を新しく作ります。ちなみにallDataのところで新しい特徴量を追加するか最後のX_trainのところで追加するかが異なることは特に何も理由はありません、僕のプログラムがぐちゃぐちゃなだけです。

allData['TotalSF'] = allData['TotalBsmtSF'] + allData['1stFlrSF'] + allData['2ndFlrSF']
X_train["Interaction"] = X_train["TotalSF"]*X_train["OverallQual"]
X_test["Interaction"] = X_test["TotalSF"]*X_test["OverallQual"]

⑧外れ値を除去する

訓練データには二つほど外れた値があるので、そこを削除します。

Xmat = X_train
Xmat['SalePrice'] = y_train
Xmat = Xmat.drop(Xmat[(Xmat['TotalSF']>5) & (Xmat['SalePrice']<12.5)].index)
Xmat = Xmat.drop(Xmat[(Xmat['GrLivArea']>5) & (Xmat['SalePrice']<13)].index)

y_train = Xmat['SalePrice']
X_train = Xmat.drop(['SalePrice'], axis=1)

この7、8らへんは以下のページを参考にさせて頂いてます。というか結構パクってます、ごめんなさいw

⑨特徴量エンジニアリング

ここまで特徴量を全て使用していましたが、大事な特徴量は300くらいある中でもあまり多くないので、特徴量をランダムフォレストを使用して減らしていきます。
ちなみに上位37を選んだ理由は実際に作図してみて37番目より下はほとんど影響を与えていなかったからです。

from sklearn.ensemble import RandomForestRegressor
rf = RandomForestRegressor(n_estimators=80, max_features='auto')
rf.fit(X_train, y_train)
ranking = np.argsort(-rf.feature_importances_)
X_train = X_train.iloc[:,ranking[:37]]
X_test = X_test.iloc[:,ranking[:37]]

ここまでで最後にもう一度モデルを構築して提出します。

スコアは以下の通りでした
0.13275
スクリーンショット 2019-04-25 15.43.56.png

感想とか

実はこのコンペには25回ほど思考錯誤して出していて、この最高スコアは7回目あたりで出たやつです笑この他にもスタッキングを試したり、OverallQualの変数を1から10までをダミー変数化したりしてみましたが、どれもあまりスコアが良くならなかったため結局ここでは省きます。というか、勾配ブースティングするなら標準化しなくても良かったのでは感。次は実際のコンペの分析をやってみます。もう自分ではスコア上がらなさそうなので、couseraのkaggleコースとかも受けて見るのもアリかも。

今回用いたコード

もしかしたらぐちゃぐちゃに書いてしまっているので、少し違うかも
エラー出たらごめんなさい

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import os
import csv as csv

# /Users/hayatoyamaguchi/Public/kaggle/House Price/house-prices-advanced-regression-techniques


##前処理まで
path = "house-prices-advanced-regression-techniques/"
train_df = pd.read_csv(path+"train.csv", header=0)
test_df = pd.read_csv(path+"test.csv", header=0)
train_df['WhatIsData'] = 'Train'
test_df['WhatIsData'] = 'Test'
train_df.tail()

drop_train_df = train_df.drop('SalePrice',axis=1)
allData = pd.concat([drop_train_df,test_df],axis=0)
allData['TotalSF'] = allData['TotalBsmtSF'] + allData['1stFlrSF'] + allData['2ndFlrSF']
print(train_df.shape)
print(drop_train_df.shape)
print(test_df.shape)
print(allData.shape)
allData["LotFrontage"] = allData.groupby("Neighborhood")["LotFrontage"].transform(lambda x: x.fillna(x.median()))
na_col_list = allData.isnull().sum()[allData.isnull().sum()>0].sort_values(ascending=False).index.tolist()
for row in na_col_list:
    if allData[row].dtypes == "float64":
        allData[row][allData[row].isnull()] = 0
    else:
        allData[row][allData[row].isnull()] = "NA"

dropData = allData[allData.columns[allData.columns != "WhatIsData"]]

data_dummies = pd.get_dummies(dropData)
transdropData = pd.concat([dropData,data_dummies],axis=1)

transdropData = transdropData.select_dtypes(include=["float64","int64","uint8"])
transdropData = transdropData.astype('int')
a = transdropData.select_dtypes(include="object")
print(a.shape)
print(transdropData.shape)
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
columns = transdropData.columns.values
transdropData_scaled = pd.DataFrame(scaler.fit(transdropData).transform(transdropData),columns=columns)
print(transdropData_scaled.shape)
print(allData["WhatIsData"].shape)
# transAllData = pd.concat([transdropData_scaled,allData["WhatIsData"]],axis=1)

transdropData_scaled["WhatIsData"] = list(allData["WhatIsData"])
transAllData = transdropData_scaled

from sklearn import preprocessing
lab_enc = preprocessing.LabelEncoder()
encoded = lab_enc.fit_transform(train_df["SalePrice"])
type(np.log(train_df["SalePrice"][0]))

transAllData2 = transAllData.loc[:,~transAllData.columns.duplicated()]
print(transAllData.shape)
print(transAllData2.shape)

X_train = transAllData2[transAllData2['WhatIsData']=='Train'].drop(['WhatIsData','Id'], axis=1)
y_train = np.log(train_df["SalePrice"])
X_test = transAllData2[transAllData2['WhatIsData']=='Test'].drop(['WhatIsData','Id'], axis=1)


##実際のそれぞれの相関性をプロットする
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
from sklearn.ensemble import RandomForestRegressor
rf = RandomForestRegressor(n_estimators=80, max_features='auto')
rf.fit(X_train, y_train)
print('Training done using Random Forest')
from pylab import rcParams
rcParams['figure.figsize'] = 20,200


##特徴量エンジニアリングと要素の追加
ranking = np.argsort(-rf.feature_importances_)
X_train = X_train.iloc[:,ranking[:37]]
X_test = X_test.iloc[:,ranking[:37]]
X_train["Interaction"] = X_train["TotalSF"]*X_train["OverallQual"]
X_test["Interaction"] = X_test["TotalSF"]*X_test["OverallQual"]
X_train.head()


##外れ値の削除
Xmat = X_train
Xmat['SalePrice'] = y_train
Xmat = Xmat.drop(Xmat[(Xmat['TotalSF']>5) & (Xmat['SalePrice']<12.5)].index)
Xmat = Xmat.drop(Xmat[(Xmat['GrLivArea']>5) & (Xmat['SalePrice']<13)].index)

y_train = Xmat['SalePrice']
X_train = Xmat.drop(['SalePrice'], axis=1)


##グリッドサーチ、いろんな変数で試してみた
import xgboost as xgb
from sklearn.model_selection import GridSearchCV
param_grid = {"colsample_bytree":[0.5,0.6,0.7,0.8,0.9,1]}
# "min_child_weigh":[1,2,4,7,10]
# "colsample_bytree":[0.5,0.6,0.7,0.8,0.9,1]
# "max_depth":[3,4,5],"learning_rate":[0.03,0.1,0.3]
# "colsample_bytree":[0.5,0.6,0.7,0.8,0.9,1]

xgb = xgb.XGBRegressor()
grid_search = GridSearchCV(xgb, param_grid)
# , scoring = "neg_mean_squared_error"
grid_search.fit(X_train,y_train)
print(grid_search.best_score_)
print(grid_search.best_params_)

results = pd.DataFrame(grid_search.cv_results_)
results


##学習曲線の描画
from sklearn.model_selection import train_test_split
X_train_train,X_val,y_train_train,y_val = train_test_split(X_train,y_train, random_state=0)
import xgboost as xgb
epochs = [100,200,300,400,500,600,700,800,900,1000,1095]
train_scores = []
test_scores = []
for epoch in epochs:
    model = xgb.XGBRegressor(max_depth=3,learning_rate=0.1,colsample_bytree=0.6)
    X_train_epoch = X_train_train[:epoch]
    y_train_epoch = y_train_train[:epoch]
#     print(X_train_epoch.shape)
#     print(y_train_epoch.shape)
    model.fit(X_train_epoch,y_train_epoch)
#     print(forest.score(X_train_epoch,y_train_epoch))
#     print(forest.score(X_val,y_val))
    train_scores.append(model.score(X_train_epoch,y_train_epoch))
    test_scores.append(model.score(X_val,y_val))
import matplotlib.pyplot as plt
%matplotlib inline
x = epochs
y0 = train_scores
y1 = test_scores
fig = plt.figure()
plt.xlabel('epoch')
plt.plot(x, y0, label='train_scores')
plt.plot(x, y1, label='test_scores')
plt.legend()
plt.show()



##最終的な出力
from datetime import datetime
time= datetime.now().strftime("%Y%m%d-%H%M%S")
time
import xgboost as xgb
model = xgb.XGBRegressor(max_depth=3,learning_rate=0.1,colsample_bytree=0.6)
model.fit(X_train,y_train)
predictions = np.exp(model.predict(X_test))

df_result = pd.DataFrame()
df_result['Id'] = test_df['Id']
df_result['SalePrice'] = predictions
df_result.to_csv('result.csv'+time,index=False)
df_result

9
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
13