はじめに
今回はKaggleのチュートリアルであるHousingPriceに挑戦して、その解析結果をまとめました。
github上に全てのソースコードを公開しています。
*アルゴリズムのシートはかなり汚いので後々整理します。
また、機械学習をするにあたって以下のKernelsを参考にしました。
COMPREHENSIVE DATA EXPLORATION WITH PYTHON
データを前処理する際の手順をわかりやすくまとめています。一連のデータ前処理を身に着けるのにとても勉強になりました。
RegularizedLinearModels
こちらもデータの前処理を載せていますが、フューチャーエンジニアリング(新しい特徴量の生成)の考え方がとても参考になり、回帰モデルの精度が大幅に上がりました。
自分なりにまとめると、機械学習の一連の流れは
①全体の変数を把握して、変数をカテゴリ変数と連続変数に分ける。さらに、カテゴリ変数は名義尺度と順序尺度に分ける。(リストあるいは辞書に格納するのを忘れずに!)
②ヒートマップを表示して、目的変数(家の売値)と相関が高い従属変数を詳しく見る。
③欠損値・異常値の処理
④フューチャーエンジニアリング(新しい特徴量の生成)
⑤回帰問題のため、歪んだデータを正規分布に従うデータに変換(対数変換など)する。*機械学習では、必ずしも行う必要はありません。
⑥アルゴリズムの選択・ハイパーパラメータの調整
になります。
###データの前処理に取り組む前に・・・
Jupyterを使って機械学習をしますが、自分はJupyerのシート(新規ノートブック)を3つ作り、それぞれ以下のように分けました。
データの可視化用シート
データ前処理用シート
アルゴリズム用シート
シートを分ける理由はPCが重くなるのを避けるためです。実はMatplotlibの図はかなりメモリを消費するので、一つのシートに図を大量に表示するとめちゃくちゃ重くなります。ですので、シートは3つにわけましょう。
#データの可視化
ほぼCOMPREHENSIVE DATA EXPLORATION WITH PYTHONのコードをそのまま載せていて、またここでは大事な所だけ紹介します。
まずは目的変数であるSalePriceをプロットしてみましょう。
plt.style.use('ggplot')
plt.hist(train_df['SalePrice'],bins=50,color='blue')
plt.show()
だいたい13万~15万当たりの物件が集中しています。
そして値段が高くなるにつれて、物件の件数が少なくなっています。
高級住宅のデータがあまりないのでそれがデータの精度に影響を及ぼしそうです。
次に目的変数と相関が高い上位10個の独立変数をプロットします。
k = 10 #number of variables for heatmap
cols = corrmat.nlargest(k, 'SalePrice')['SalePrice'].index
cm = np.corrcoef(df_train[cols].values.T)
sns.set(font_scale=1.25)
hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 10}, yticklabels=cols.values, xticklabels=cols.values)
plt.show()
目的変数ともっとも相関が高いのはOverallQual(家の仕上がり、材料の評価)であり、次に高いのはGrLivArea(リビングの広さ)となっています。後はGarage(車庫)とYearBuilt(設立年数)が目的変数と相関がありそうです。
そのほかは疑似相関のような気がします。(例えばFullBathは面積が広いほど、多くのバスルームを作れるスペースが出来上がるので目的変数と相関があるように見える)
ペアプロットでさらに詳しく相関が高い10個の変数を見てみましょう!
sns.set()
cols = ['SalePrice', 'OverallQual', 'GrLivArea', 'GarageCars', 'TotalBsmtSF', 'FullBath', 'YearBuilt']
sns.pairplot(train_df[cols], size = 3.5)
plt.show();
画像が途中で切れていますが、GrLivAreaとTotalBsmtSFを見ると、傾向から大幅に外れているデータがあります。
残念ながら、「なぜ面積が高いのに、売値が低いのか」原因は分かりませんでしたが、結果論としてこれらのデータを削除したら機械学習の精度が上がったので今回は削除します。
train_df.sort_values(by = 'GrLivArea', ascending = False)[:2]
#ascending=False は'GrLivArea'の高いデータから順に並べ替えられる
train_df = train_df.drop(train_df[train_df['Id'] == 1299].index)
train_df = train_df.drop(train_df[train_df['Id'] == 524].index)
##欠損値の処理
異常値は先ほど処理をしたので、次は欠損値を処理します。
まずは欠損値がどれくらい発生しているのか、プロットしてみます。
total = train_df.isnull().sum().sort_values(ascending=False)
percent = (train_df.isnull().sum()/train_df.isnull().count()).sort_values(ascending=False)
missing_data = pd.concat([total, percent], axis=1, keys=['Total', 'Percent'])
missing_data.head(20)
結構な数のデータが欠損していますね。
Kernelを見ていると欠損が激しいデータを削除している方がいるのですが、今回の場合はあまりよくないと思います。(COMPREHENSIVE DATA EXPLORATION WITH PYTHONは特徴量を削除していますがRegularizedLinearModelsでは欠損値を埋めています)
ではまず、家の広さに関係している変数を選択して、欠損しているデータだけ抽出します。
b=['BsmtFinType1','BsmtUnfSF','TotalBsmtSF','BsmtExposure']
a=train_df[b]
a=a[a.isnull().any(axis=1)]
a.head(30)
これら欠損しているデータには傾向があり、BsmtUnfSFやTotalBsmtSFが0の場合、それに関係している変数(面積)はすべてデータ入力がされていません。
これはデータを入力する人が面倒くさいと思ったから欠損が発生してしまったのでしょう。
頼むから、マクロでNOや0を入れてくれよ(懇願)
とりあえず原因が分かったので、これら変数の欠損部分を0とNOで埋めていきます。(コードは割愛)
残りの欠損変数は
##カテゴリ変数の処理
カテゴリ変数の処理ですが、例えばOverallQualを各グループごとの平均値で集計すると5や6はあまり大差ないように見えます。*検定はとってない
ですので以下のように、いくつかのグループを束にすると機械学習の精度が良くなることがあります。
(10段階評価から5段階評価に変更しました。)
from scipy import stats
#pandasのデータフレームを複製したいときはコピーを必ずつける。=だけではだめ
temp_df=train_df.copy()
#5段階評価
cutnumber=list(range(0,11,2))
temp_df["SimplOverallQual"] = pd.cut (temp_df['OverallQual'],5)
cut_5=temp_df[['SimplOverallQual', 'SalePrice']].groupby(['SimplOverallQual'], as_index=False).mean ().sort_values(by='SimplOverallQual', ascending=True)
cut_5.plot.bar(x='SimplOverallQual')
こんな感じで残りの順序尺度もいくつか束にしていきます。(コードの詳細は自分のgithubか[RegularizedLinearModels]を参考にしてください。(https://www.kaggle.com/apapiu/regularized-linear-models))
##特徴量の生成
特徴量の生成ですが、
RegularizedLinearModelsのソースコードとほぼ同じですが、微妙に違うところもあります。
#フューチャーエンジニアリング
train_df["OverallGrade"] = train_df["OverallQual"] * train_df["OverallCond"]
# Overall quality of the garage
train_df["GarageGrade"] = train_df["GarageQual"] * train_df["GarageCond"]
# Overall quality of the exterior
train_df["ExterGrade"] = train_df["ExterQual"] * train_df["ExterCond"]
# Overall kitchen score
train_df["KitchenScore"] = train_df["KitchenAbvGr"] * train_df["KitchenQual"]
# Overall fireplace score
train_df["FireplaceScore"] = train_df["Fireplaces"] * train_df["FireplaceQu"]
# Overall garage score
train_df["GarageScore"] = train_df["GarageArea"] * train_df["GarageQual"]
# Overall pool score
train_df["PoolScore"] = train_df["PoolArea"] * train_df["PoolQC"]
# Simplified overall quality of the house
train_df["SimplOverallGrade"] = train_df["SimplOverallQual"] * train_df["SimplOverallCond"]
# Simplified overall quality of the exterior
#train_df["SimplExterGrade"] = train_df["SimplExterQual"] * train_df["SimplExterCond"]
# Simplified overall pool score
#train_df["SimplPoolScore"] = train_df["PoolArea"] * train_df["SimplPoolQC"]
# Simplified overall garage score
train_df["SimplGarageScore"] = train_df["GarageArea"] * train_df["SimplGarageQual"]
# Simplified overall fireplace score
train_df["SimplFireplaceScore"] = train_df["Fireplaces"] * train_df["SimplFireplaceQu"]
# Simplified overall kitchen score
#train_df["SimplKitchenScore"] = train_df["KitchenAbvGr"] * train_df["SimplKitchenQual"]
# Total number of bathrooms
train_df["TotalBath"] = train_df["BsmtFullBath"] + (0.5 * train_df["BsmtHalfBath"]) + \
train_df["FullBath"] + (0.5 * train_df["HalfBath"])
# Total SF for house (incl. basement)
train_df["AllSF"] = train_df["GrLivArea"] + train_df["TotalBsmtSF"]
# Total SF for 1st + 2nd floors
train_df["AllFlrsSF"] = train_df["1stFlrSF"] + train_df["2ndFlrSF"]
# Total SF for porch
train_df["AllPorchSF"] = train_df["OpenPorchSF"] + train_df["EnclosedPorch"] + \
train_df["3SsnPorch"] + train_df["ScreenPorch"]
# Has masonry veneer or not
train_df["HasMasVnr"] = train_df.MasVnrType.replace({"BrkCmn" : 1, "BrkFace" : 1, "CBlock" : 1,
"Stone" : 1, "None" : 0})
# House completed before sale or not
train_df["BoughtOffPlan"] = train_df.SaleCondition.replace({"Abnorml" : 0, "Alloca" : 0, "AdjLand" : 0,
"Family" : 0, "Normal" : 0, "Partial" : 1})
ここで、特徴量を新たに生成した後の、相関マップを見てみましょう。
新たに特徴量を生成したマップと比べると、AllSFが目的変数と相関がもっとも高くなっています。
ここで、目的変数と相関が高い上位9個の変数を2乗・3乗して新しい特徴量を作ります。
train_df["OverallQual-s2"] = train_df["OverallQual"] ** 2
train_df["OverallQual-s3"] = train_df["OverallQual"] ** 3
train_df["OverallQual-Sq"] = np.sqrt(train_df["OverallQual"])
train_df["AllSF-2"] = train_df["AllSF"] ** 2
train_df["AllSF-3"] = train_df["AllSF"] ** 3
train_df["AllSF-Sq"] = np.sqrt(train_df["AllSF"])
train_df["AllFlrsSF-2"] = train_df["AllFlrsSF"] ** 2
train_df["AllFlrsSF-3"] = train_df["AllFlrsSF"] ** 3
train_df["AllFlrsSF-Sq"] = np.sqrt(train_df["AllFlrsSF"])
train_df["GrLivArea-2"] = train_df["GrLivArea"] ** 2
train_df["GrLivArea-3"] = train_df["GrLivArea"] ** 3
train_df["GrLivArea-Sq"] = np.sqrt(train_df["GrLivArea"])
train_df["SimplOverallQual-s2"] = train_df["SimplOverallQual"] ** 2
train_df["SimplOverallQual-s3"] = train_df["SimplOverallQual"] ** 3
train_df["SimplOverallQual-Sq"] = np.sqrt(train_df["SimplOverallQual"])
train_df["ExterQual-2"] = train_df["ExterQual"] ** 2
train_df["ExterQual-3"] = train_df["ExterQual"] ** 3
train_df["ExterQual-Sq"] = np.sqrt(train_df["ExterQual"])
train_df["KitchenQual-2"] = train_df["KitchenQual"] ** 2
train_df["KitchenQual-3"] = train_df["KitchenQual"] ** 3
train_df["KitchenQual-Sq"] = np.sqrt(train_df["KitchenQual"])
train_df["SimpleNeighborhood-s2"] = train_df["SimpleNeighborhood"] ** 2
train_df["SimpleNeighborhood-s3"] = train_df["SimpleNeighborhood"] ** 3
train_df["SimpleNeighborhood-Sq"] = np.sqrt(train_df["SimpleNeighborhood"])
train_df["SimplBsmtQual-s2"] = train_df["SimplBsmtQual"] ** 2
train_df["SimplBsmtQual-s3"] = train_df["SimplBsmtQual"] ** 3
train_df["SimplBsmtQual-Sq"] = np.sqrt(train_df["SimplBsmtQual"])
2乗・3乗する理由はこちらの記事の「5.回帰分析を行うにあたっての注意点」を参考にしてください。
以上で、データの可視化と前処理は終わりです。細かいところは端折っているので、各自確認してください。
後半はアルゴリズムについて紹介します。